diff --git a/eeg_backend/src/main/java/at/mueller/eeg/backend/common/api/NotificationController.java b/eeg_backend/src/main/java/at/mueller/eeg/backend/common/api/NotificationController.java index eeccc4f..d66b0f8 100644 --- a/eeg_backend/src/main/java/at/mueller/eeg/backend/common/api/NotificationController.java +++ b/eeg_backend/src/main/java/at/mueller/eeg/backend/common/api/NotificationController.java @@ -32,8 +32,8 @@ public class NotificationController { @PutMapping("/{id}/read") @PreAuthorize("hasRole('MEMBER') or hasRole('ADMIN')") - public ResponseEntity markAsRead(@PathVariable UUID id) { - notificationService.markAsRead(id); + public ResponseEntity markAsRead(@PathVariable UUID id, @CurrentUserId String userId) { + notificationService.markAsRead(id, UUID.fromString(userId)); return ResponseEntity.ok().build(); } diff --git a/eeg_backend/src/main/java/at/mueller/eeg/backend/common/exception/GlobalExceptionHandler.java b/eeg_backend/src/main/java/at/mueller/eeg/backend/common/exception/GlobalExceptionHandler.java index f39299d..5926cd8 100644 --- a/eeg_backend/src/main/java/at/mueller/eeg/backend/common/exception/GlobalExceptionHandler.java +++ b/eeg_backend/src/main/java/at/mueller/eeg/backend/common/exception/GlobalExceptionHandler.java @@ -7,9 +7,12 @@ import org.springframework.http.ResponseEntity; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.DisabledException; +import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +import java.util.stream.Collectors; + @RestControllerAdvice public class GlobalExceptionHandler { @@ -52,11 +55,22 @@ public class GlobalExceptionHandler { .body(new ErrorResponse(ex.getMessage(), HttpStatus.CONFLICT.value())); } + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleValidation(MethodArgumentNotValidException ex) { + String message = ex.getBindingResult().getFieldErrors().stream() + .map(fe -> fe.getField() + ": " + fe.getDefaultMessage()) + .collect(Collectors.joining(", ")); + log.warn("Validation error: {}", message); + return ResponseEntity + .status(HttpStatus.BAD_REQUEST) + .body(new ErrorResponse(message, HttpStatus.BAD_REQUEST.value())); + } + @ExceptionHandler(AtNumberAlreadyExistsException.class) public ResponseEntity handleAtNumberAlreadyExists(AtNumberAlreadyExistsException ex) { return ResponseEntity .status(HttpStatus.CONFLICT) - .body(new ErrorResponse("Zählerpunkt existiert bereits", HttpStatus.CONFLICT.value())); + .body(new ErrorResponse(ex.getMessage(), HttpStatus.CONFLICT.value())); } @ExceptionHandler(Exception.class) diff --git a/eeg_backend/src/main/java/at/mueller/eeg/backend/common/service/NotificationService.java b/eeg_backend/src/main/java/at/mueller/eeg/backend/common/service/NotificationService.java index 52c4416..a4e55a6 100644 --- a/eeg_backend/src/main/java/at/mueller/eeg/backend/common/service/NotificationService.java +++ b/eeg_backend/src/main/java/at/mueller/eeg/backend/common/service/NotificationService.java @@ -4,6 +4,7 @@ import at.mueller.eeg.backend.common.domain.Notification; import at.mueller.eeg.backend.common.domain.NotificationType; import at.mueller.eeg.backend.common.repository.NotificationRepository; import lombok.RequiredArgsConstructor; +import org.springframework.security.access.AccessDeniedException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -37,11 +38,14 @@ public class NotificationService { } @Transactional - public void markAsRead(UUID id) { - notificationRepository.findById(id).ifPresent(notification -> { - notification.setRead(true); - notificationRepository.save(notification); - }); + public void markAsRead(UUID id, UUID userId) { + Notification notification = notificationRepository.findById(id) + .orElseThrow(() -> new IllegalArgumentException("Notification nicht gefunden: " + id)); + if (!notification.getUserId().equals(userId)) { + throw new AccessDeniedException("Keine Berechtigung, diese Notification als gelesen zu markieren."); + } + notification.setRead(true); + notificationRepository.save(notification); } @Transactional diff --git a/eeg_backend/src/main/java/at/mueller/eeg/backend/community/api/EnergyCommunityAdminController.java b/eeg_backend/src/main/java/at/mueller/eeg/backend/community/api/EnergyCommunityAdminController.java index aeb569f..8591688 100644 --- a/eeg_backend/src/main/java/at/mueller/eeg/backend/community/api/EnergyCommunityAdminController.java +++ b/eeg_backend/src/main/java/at/mueller/eeg/backend/community/api/EnergyCommunityAdminController.java @@ -10,6 +10,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import java.util.List; @@ -17,6 +18,7 @@ import java.util.UUID; @RestController @RequestMapping("/api/admin/energy-communities") +@PreAuthorize("hasRole('ADMIN')") @RequiredArgsConstructor public class EnergyCommunityAdminController { diff --git a/eeg_backend/src/main/java/at/mueller/eeg/backend/community/domain/MeteringPoint.java b/eeg_backend/src/main/java/at/mueller/eeg/backend/community/domain/MeteringPoint.java index 699776e..9f26fac 100644 --- a/eeg_backend/src/main/java/at/mueller/eeg/backend/community/domain/MeteringPoint.java +++ b/eeg_backend/src/main/java/at/mueller/eeg/backend/community/domain/MeteringPoint.java @@ -48,7 +48,7 @@ public class MeteringPoint { @Column(nullable = false) private MakoState makoState = MakoState.NEW; - @OneToMany(mappedBy = "meteringPoint", cascade = CascadeType.ALL) + @OneToMany(mappedBy = "meteringPoint", cascade = CascadeType.ALL, orphanRemoval = true) private List memberships; public void fireTrigger(MakoTrigger trigger) { diff --git a/eeg_backend/src/main/java/at/mueller/eeg/backend/community/service/EnergyCommunityService.java b/eeg_backend/src/main/java/at/mueller/eeg/backend/community/service/EnergyCommunityService.java index 67e6bde..ad35499 100644 --- a/eeg_backend/src/main/java/at/mueller/eeg/backend/community/service/EnergyCommunityService.java +++ b/eeg_backend/src/main/java/at/mueller/eeg/backend/community/service/EnergyCommunityService.java @@ -6,10 +6,8 @@ import at.mueller.eeg.backend.community.mapper.EnergyCommunityMapper; import at.mueller.eeg.backend.community.repository.EnergyCommunityRepository; import at.mueller.eeg.backend.community.repository.MembershipRepository; import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.server.ResponseStatusException; import java.util.List; import java.util.Map; @@ -53,7 +51,7 @@ public class EnergyCommunityService { @Transactional public EnergyCommunityDto update(UUID id, EnergyCommunityDto dto) { EnergyCommunity existingEntity = energyCommunityRepository.findById(id) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Energiegemeinschaft nicht gefunden")); + .orElseThrow(() -> new IllegalArgumentException("Energiegemeinschaft nicht gefunden: " + id)); energyCommunityMapper.toEntity(dto, existingEntity); EnergyCommunity updatedEntity = energyCommunityRepository.save(existingEntity); Map memberCountMap = getMemberCountsForIds(List.of(id)); @@ -67,7 +65,7 @@ public class EnergyCommunityService { throw new IllegalArgumentException("Energy Community mit ID " + id + " existiert nicht."); } if (membershipRepository.existsByEnergyCommunityId(id)) { - throw new ResponseStatusException(HttpStatus.CONFLICT, "Die Energiegemeinschaft kann nicht gelöscht werden, da noch Zählpunkte zugeordnet sind."); + throw new IllegalStateException("Die Energiegemeinschaft kann nicht gelöscht werden, da noch Zählpunkte zugeordnet sind."); } energyCommunityRepository.deleteById(id); } diff --git a/eeg_backend/src/main/java/at/mueller/eeg/backend/iam/service/AdminIamService.java b/eeg_backend/src/main/java/at/mueller/eeg/backend/iam/service/AdminIamService.java index c359e7a..60264b5 100644 --- a/eeg_backend/src/main/java/at/mueller/eeg/backend/iam/service/AdminIamService.java +++ b/eeg_backend/src/main/java/at/mueller/eeg/backend/iam/service/AdminIamService.java @@ -7,10 +7,8 @@ import at.mueller.eeg.backend.iam.domain.User; import at.mueller.eeg.backend.iam.repository.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.context.ApplicationEventPublisher; -import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.server.ResponseStatusException; import java.util.List; import java.util.UUID; @@ -53,10 +51,10 @@ public class AdminIamService { @Transactional public void rejectUser(UUID userId) { User user = userRepository.findById(userId) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "User nicht gefunden")); + .orElseThrow(() -> new IllegalArgumentException("User nicht gefunden")); if (user.getStatus() != RegistrationStatus.PENDING) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Nur User im Status PENDING können abgelehnt werden."); + throw new IllegalStateException("Nur User im Status PENDING können abgelehnt werden."); } user.setStatus(RegistrationStatus.REJECTED); diff --git a/eeg_backend/src/main/java/at/mueller/eeg/backend/tariff/service/TariffService.java b/eeg_backend/src/main/java/at/mueller/eeg/backend/tariff/service/TariffService.java index ffe9556..56e2202 100644 --- a/eeg_backend/src/main/java/at/mueller/eeg/backend/tariff/service/TariffService.java +++ b/eeg_backend/src/main/java/at/mueller/eeg/backend/tariff/service/TariffService.java @@ -84,6 +84,8 @@ public class TariffService { "Es existiert bereits ein Tarif zwischen diesen beiden Zählpunkten."); }); + validateUserTariffRequest(request.energyCommunityId(), request); + MeteringPoint sourcePoint = meteringPointRepository.findById(request.sourceMeteringPointId()) .orElseThrow(() -> new IllegalArgumentException( "Quell-Zählpunkt nicht gefunden: " + request.sourceMeteringPointId())); @@ -91,36 +93,8 @@ public class TariffService { .orElseThrow(() -> new IllegalArgumentException( "Ziel-Zählpunkt nicht gefunden: " + request.targetMeteringPointId())); - if (sourcePoint.getMakoState() != MakoState.ACTIVE) { - throw new IllegalStateException("Quell-Zählpunkt ist nicht aktiv."); - } - if (targetPoint.getMakoState() != MakoState.ACTIVE) { - throw new IllegalStateException("Ziel-Zählpunkt ist nicht aktiv."); - } - - CommunityTariff communityTariff = communityTariffRepository.findByEnergyCommunityId( - request.energyCommunityId()) - .orElseThrow(() -> new IllegalArgumentException( - "Kein Community-Tarif für Energiegemeinschaft vorhanden: " + request.energyCommunityId())); - - if (request.pricePerKwhCents().compareTo(communityTariff.getMaxPricePerKwhCents()) > 0) { - throw new IllegalStateException( - "Preis darf den Maximalpreis des Community-Tarifs nicht überschreiten. " + - "Maximalpreis: " + communityTariff.getMaxPricePerKwhCents() + " Cent/kWh"); - } - - UUID sourceUserId = sourcePoint.getUserId(); - UUID targetUserId = targetPoint.getUserId(); - - if (!membershipRepository.isActiveMemberOfCommunity(sourceUserId, request.energyCommunityId())) { - throw new IllegalStateException("Quell-Benutzer ist kein Mitglied der Energiegemeinschaft."); - } - if (!membershipRepository.isActiveMemberOfCommunity(targetUserId, request.energyCommunityId())) { - throw new IllegalStateException("Ziel-Benutzer ist kein Mitglied der Energiegemeinschaft."); - } - if (!tariffInviteRepository.existsByProducerUserIdAndConsumerUserIdAndEnergyCommunityIdAndStatus( - sourceUserId, targetUserId, request.energyCommunityId(), InviteStatus.ACCEPTED)) { + sourcePoint.getUserId(), targetPoint.getUserId(), request.energyCommunityId(), InviteStatus.ACCEPTED)) { throw new IllegalStateException( "Zwischen Produzent und Konsument muss eine angenommene Einladung bestehen."); } @@ -139,7 +113,7 @@ public class TariffService { request.targetMeteringPointId(), request.pricePerKwhCents()); eventPublisher.publishEvent(new UserTariffChangedEvent( - request.energyCommunityId(), sourceUserId, targetUserId, "CREATED")); + request.energyCommunityId(), sourcePoint.getUserId(), targetPoint.getUserId(), "CREATED")); return toUserTariffResponse(saved); } @@ -161,40 +135,10 @@ public class TariffService { throw new IllegalStateException("Quell- und Ziel-Zählpunkt müssen unterschiedlich sein."); } - MeteringPoint newSourcePoint = meteringPointRepository.findById(request.sourceMeteringPointId()) - .orElseThrow(() -> new IllegalArgumentException( - "Quell-Zählpunkt nicht gefunden: " + request.sourceMeteringPointId())); + validateUserTariffRequest(request.energyCommunityId(), request); + MeteringPoint newTargetPoint = meteringPointRepository.findById(request.targetMeteringPointId()) - .orElseThrow(() -> new IllegalArgumentException( - "Ziel-Zählpunkt nicht gefunden: " + request.targetMeteringPointId())); - - if (newSourcePoint.getMakoState() != MakoState.ACTIVE) { - throw new IllegalStateException("Quell-Zählpunkt ist nicht aktiv."); - } - if (newTargetPoint.getMakoState() != MakoState.ACTIVE) { - throw new IllegalStateException("Ziel-Zählpunkt ist nicht aktiv."); - } - - CommunityTariff communityTariff = communityTariffRepository.findByEnergyCommunityId( - request.energyCommunityId()) - .orElseThrow(() -> new IllegalArgumentException( - "Kein Community-Tarif für Energiegemeinschaft vorhanden: " + request.energyCommunityId())); - - if (request.pricePerKwhCents().compareTo(communityTariff.getMaxPricePerKwhCents()) > 0) { - throw new IllegalStateException( - "Preis darf den Maximalpreis des Community-Tarifs nicht überschreiten. " + - "Maximalpreis: " + communityTariff.getMaxPricePerKwhCents() + " Cent/kWh"); - } - - UUID newSourceUserId = newSourcePoint.getUserId(); - UUID newTargetUserId = newTargetPoint.getUserId(); - - if (!membershipRepository.isActiveMemberOfCommunity(newSourceUserId, request.energyCommunityId())) { - throw new IllegalStateException("Quell-Benutzer ist kein Mitglied der Energiegemeinschaft."); - } - if (!membershipRepository.isActiveMemberOfCommunity(newTargetUserId, request.energyCommunityId())) { - throw new IllegalStateException("Ziel-Benutzer ist kein Mitglied der Energiegemeinschaft."); - } + .orElseThrow(() -> new IllegalArgumentException("Ziel-Zählpunkt nicht gefunden.")); tariff.setEnergyCommunityId(request.energyCommunityId()); tariff.setSourceMeteringPointId(request.sourceMeteringPointId()); @@ -208,7 +152,7 @@ public class TariffService { tariffId, request.pricePerKwhCents()); eventPublisher.publishEvent(new UserTariffChangedEvent( - request.energyCommunityId(), newSourceUserId, newTargetUserId, "UPDATED")); + request.energyCommunityId(), sourcePoint.getUserId(), newTargetPoint.getUserId(), "UPDATED")); return toUserTariffResponse(saved); } @@ -269,4 +213,40 @@ public class TariffService { tariff.getCreatedAt() ); } + + private void validateUserTariffRequest(UUID communityId, UserTariffRequest request) { + MeteringPoint sourcePoint = meteringPointRepository.findById(request.sourceMeteringPointId()) + .orElseThrow(() -> new IllegalArgumentException( + "Quell-Zählpunkt nicht gefunden: " + request.sourceMeteringPointId())); + MeteringPoint targetPoint = meteringPointRepository.findById(request.targetMeteringPointId()) + .orElseThrow(() -> new IllegalArgumentException( + "Ziel-Zählpunkt nicht gefunden: " + request.targetMeteringPointId())); + + if (sourcePoint.getMakoState() != MakoState.ACTIVE) { + throw new IllegalStateException("Quell-Zählpunkt ist nicht aktiv."); + } + if (targetPoint.getMakoState() != MakoState.ACTIVE) { + throw new IllegalStateException("Ziel-Zählpunkt ist nicht aktiv."); + } + + CommunityTariff communityTariff = communityTariffRepository.findByEnergyCommunityId(communityId) + .orElseThrow(() -> new IllegalArgumentException( + "Kein Community-Tarif für Energiegemeinschaft vorhanden: " + communityId)); + + if (request.pricePerKwhCents().compareTo(communityTariff.getMaxPricePerKwhCents()) > 0) { + throw new IllegalStateException( + "Preis darf den Maximalpreis des Community-Tarifs nicht überschreiten. " + + "Maximalpreis: " + communityTariff.getMaxPricePerKwhCents() + " Cent/kWh"); + } + + UUID sourceUserId = sourcePoint.getUserId(); + UUID targetUserId = targetPoint.getUserId(); + + if (!membershipRepository.isActiveMemberOfCommunity(sourceUserId, communityId)) { + throw new IllegalStateException("Quell-Benutzer ist kein Mitglied der Energiegemeinschaft."); + } + if (!membershipRepository.isActiveMemberOfCommunity(targetUserId, communityId)) { + throw new IllegalStateException("Ziel-Benutzer ist kein Mitglied der Energiegemeinschaft."); + } + } } diff --git a/eeg_backend/src/main/resources/application-prod.yml b/eeg_backend/src/main/resources/application-prod.yml index 3f29369..39885ab 100644 --- a/eeg_backend/src/main/resources/application-prod.yml +++ b/eeg_backend/src/main/resources/application-prod.yml @@ -6,7 +6,7 @@ spring: password: ${DATABASE_PASSWORD:} jpa: hibernate: - ddl-auto: ${JPA_DDL_AUTO:update} + ddl-auto: ${JPA_DDL_AUTO:validate} show-sql: false properties: hibernate: diff --git a/eeg_backend/src/test/java/at/mueller/eeg/backend/common/service/NotificationServiceTest.java b/eeg_backend/src/test/java/at/mueller/eeg/backend/common/service/NotificationServiceTest.java index 4182caf..d238f0b 100644 --- a/eeg_backend/src/test/java/at/mueller/eeg/backend/common/service/NotificationServiceTest.java +++ b/eeg_backend/src/test/java/at/mueller/eeg/backend/common/service/NotificationServiceTest.java @@ -9,6 +9,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.access.AccessDeniedException; import java.util.List; import java.util.Optional; @@ -82,12 +83,32 @@ class NotificationServiceTest { .thenReturn(Optional.of(testNotification)); when(notificationRepository.save(any())).thenReturn(testNotification); - notificationService.markAsRead(testNotification.getId()); + notificationService.markAsRead(testNotification.getId(), testUserId); assertTrue(testNotification.isRead()); verify(notificationRepository).save(testNotification); } + @Test + void markAsRead_throwsIfNotOwner() { + UUID otherUserId = UUID.randomUUID(); + when(notificationRepository.findById(testNotification.getId())) + .thenReturn(Optional.of(testNotification)); + + assertThrows(AccessDeniedException.class, + () -> notificationService.markAsRead(testNotification.getId(), otherUserId)); + verify(notificationRepository, never()).save(any()); + } + + @Test + void markAsRead_throwsIfNotFound() { + UUID unknownId = UUID.randomUUID(); + when(notificationRepository.findById(unknownId)).thenReturn(Optional.empty()); + + assertThrows(IllegalArgumentException.class, + () -> notificationService.markAsRead(unknownId, testUserId)); + } + @Test void markAllAsRead_callsRepository() { notificationService.markAllAsRead(testUserId); diff --git a/eeg_backend/src/test/java/at/mueller/eeg/backend/community/service/EnergyCommunityServiceTest.java b/eeg_backend/src/test/java/at/mueller/eeg/backend/community/service/EnergyCommunityServiceTest.java index e42729c..f456d52 100644 --- a/eeg_backend/src/test/java/at/mueller/eeg/backend/community/service/EnergyCommunityServiceTest.java +++ b/eeg_backend/src/test/java/at/mueller/eeg/backend/community/service/EnergyCommunityServiceTest.java @@ -12,7 +12,6 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.web.server.ResponseStatusException; import java.util.ArrayList; import java.util.List; @@ -137,7 +136,7 @@ class EnergyCommunityServiceTest { when(energyCommunityRepository.findById(unknownId)).thenReturn(Optional.empty()); - assertThrows(ResponseStatusException.class, () -> energyCommunityService.update(unknownId, dto)); + assertThrows(IllegalArgumentException.class, () -> energyCommunityService.update(unknownId, dto)); } @Test @@ -155,7 +154,7 @@ class EnergyCommunityServiceTest { when(energyCommunityRepository.existsById(testCommunityId)).thenReturn(true); when(membershipRepository.existsByEnergyCommunityId(testCommunityId)).thenReturn(true); - assertThrows(ResponseStatusException.class, () -> energyCommunityService.delete(testCommunityId)); + assertThrows(IllegalStateException.class, () -> energyCommunityService.delete(testCommunityId)); verify(energyCommunityRepository, never()).deleteById(any()); } diff --git a/eeg_backend/src/test/java/at/mueller/eeg/backend/iam/service/AdminIamServiceTest.java b/eeg_backend/src/test/java/at/mueller/eeg/backend/iam/service/AdminIamServiceTest.java index e65bdbb..584589d 100644 --- a/eeg_backend/src/test/java/at/mueller/eeg/backend/iam/service/AdminIamServiceTest.java +++ b/eeg_backend/src/test/java/at/mueller/eeg/backend/iam/service/AdminIamServiceTest.java @@ -12,7 +12,6 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.context.ApplicationEventPublisher; -import org.springframework.web.server.ResponseStatusException; import java.util.List; import java.util.Optional; @@ -113,7 +112,7 @@ class AdminIamServiceTest { testUser.setStatus(RegistrationStatus.APPROVED); when(userRepository.findById(testUser.getId())).thenReturn(Optional.of(testUser)); - assertThrows(ResponseStatusException.class, () -> adminIamService.rejectUser(testUser.getId())); + assertThrows(IllegalStateException.class, () -> adminIamService.rejectUser(testUser.getId())); verify(userRepository, never()).save(any()); } @@ -122,7 +121,7 @@ class AdminIamServiceTest { UUID unknownId = UUID.randomUUID(); when(userRepository.findById(unknownId)).thenReturn(Optional.empty()); - assertThrows(ResponseStatusException.class, () -> adminIamService.rejectUser(unknownId)); + assertThrows(IllegalArgumentException.class, () -> adminIamService.rejectUser(unknownId)); } @Test diff --git a/eeg_frontend/src/app/layout/dashboard-layout/dashboard-layout.spec.ts b/eeg_frontend/src/app/layout/dashboard-layout/dashboard-layout.spec.ts index b39229a..ef46deb 100644 --- a/eeg_frontend/src/app/layout/dashboard-layout/dashboard-layout.spec.ts +++ b/eeg_frontend/src/app/layout/dashboard-layout/dashboard-layout.spec.ts @@ -3,6 +3,7 @@ import {ActivatedRoute, Router} from '@angular/router'; import {of} from 'rxjs'; import {DashboardLayout} from './dashboard-layout'; import {AuthService} from '../../services/auth'; +import {NotificationService} from '../../services/notification'; describe('DashboardLayout', () => { let component: DashboardLayout; @@ -14,7 +15,8 @@ describe('DashboardLayout', () => { providers: [ {provide: ActivatedRoute, useValue: {snapshot: {queryParamMap: {get: () => null}}}}, {provide: Router, useValue: {events: of(), navigate: () => {}}}, - {provide: AuthService, useValue: {currentUserRole: () => 'MEMBER', logout: () => {}}} + {provide: AuthService, useValue: {currentUserRole: () => 'MEMBER', logout: () => {}}}, + {provide: NotificationService, useValue: {getNotifications: () => of([]), getUnreadCount: () => of(0), unreadCount: () => 0}} ] }).compileComponents(); diff --git a/eeg_frontend/src/app/pages/admin-user-approval/admin-user-approval.spec.ts b/eeg_frontend/src/app/pages/admin-user-approval/admin-user-approval.spec.ts index 7508d58..aafd346 100644 --- a/eeg_frontend/src/app/pages/admin-user-approval/admin-user-approval.spec.ts +++ b/eeg_frontend/src/app/pages/admin-user-approval/admin-user-approval.spec.ts @@ -89,7 +89,7 @@ describe('AdminUserApprovalComponent', () => { component.switchTab('all'); - const allReq = httpMock.expectOne((r) => r.url.includes('/api/admin/users')); + const allReq = httpMock.expectOne((r) => r.url.includes('/api/admin/users') && !r.url.includes('/pending')); expect(allReq.request.method).toBe('GET'); allReq.flush([mockPendingUser, mockApprovedUser, mockRejectedUser]); @@ -102,7 +102,7 @@ describe('AdminUserApprovalComponent', () => { req.flush([]); component.switchTab('all'); - const allReq = httpMock.expectOne((r) => r.url.includes('/api/admin/users')); + const allReq = httpMock.expectOne((r) => r.url.includes('/api/admin/users') && !r.url.includes('/pending')); allReq.flush([]); expect(component.activeTab()).toBe('all'); @@ -116,7 +116,7 @@ describe('AdminUserApprovalComponent', () => { pendingReq.flush([]); component.switchTab('all'); - const allReq = httpMock.expectOne((r) => r.url.includes('/api/admin/users')); + const allReq = httpMock.expectOne((r) => r.url.includes('/api/admin/users') && !r.url.includes('/pending')); allReq.flush([mockApprovedUser]); fixture.detectChanges(); @@ -125,7 +125,7 @@ describe('AdminUserApprovalComponent', () => { component.switchTab('all'); expect(component.activeTab()).toBe('all'); - const allReqs = httpMock.match((r) => r.url.includes('/api/admin/users')); + const allReqs = httpMock.match((r) => r.url.includes('/api/admin/users') && !r.url.includes('/pending')); expect(allReqs.length).toBe(0); }); @@ -146,7 +146,7 @@ describe('AdminUserApprovalComponent', () => { pendingReq.flush([]); component.switchTab('all'); - const allReq = httpMock.expectOne((r) => r.url.includes('/api/admin/users')); + const allReq = httpMock.expectOne((r) => r.url.includes('/api/admin/users') && !r.url.includes('/pending')); allReq.flush('Error', { status: 500, statusText: 'Server Error' }); expect(component.errorMessage()).toBe('Fehler beim Laden aller Benutzer.'); @@ -349,7 +349,7 @@ describe('AdminUserApprovalComponent', () => { pendingReq.flush([]); component.switchTab('all'); - const allReq = httpMock.expectOne((r) => r.url.includes('/api/admin/users')); + const allReq = httpMock.expectOne((r) => r.url.includes('/api/admin/users') && !r.url.includes('/pending')); allReq.flush([mockApprovedUser]); fixture.detectChanges(); @@ -368,7 +368,7 @@ describe('AdminUserApprovalComponent', () => { pendingReq.flush([]); component.switchTab('all'); - const allReq = httpMock.expectOne((r) => r.url.includes('/api/admin/users')); + const allReq = httpMock.expectOne((r) => r.url.includes('/api/admin/users') && !r.url.includes('/pending')); allReq.flush([]); fixture.detectChanges(); @@ -382,7 +382,7 @@ describe('AdminUserApprovalComponent', () => { pendingReq.flush([]); component.switchTab('all'); - const allReq = httpMock.expectOne((r) => r.url.includes('/api/admin/users')); + const allReq = httpMock.expectOne((r) => r.url.includes('/api/admin/users') && !r.url.includes('/pending')); allReq.flush([mockApprovedUser]); fixture.detectChanges(); @@ -397,7 +397,7 @@ describe('AdminUserApprovalComponent', () => { pendingReq.flush([]); component.switchTab('all'); - const allReq = httpMock.expectOne((r) => r.url.includes('/api/admin/users')); + const allReq = httpMock.expectOne((r) => r.url.includes('/api/admin/users') && !r.url.includes('/pending')); allReq.flush([mockRejectedUser]); fixture.detectChanges(); @@ -422,7 +422,7 @@ describe('AdminUserApprovalComponent', () => { pendingReq.flush([]); component.switchTab('all'); - const allReq = httpMock.expectOne((r) => r.url.includes('/api/admin/users')); + const allReq = httpMock.expectOne((r) => r.url.includes('/api/admin/users') && !r.url.includes('/pending')); allReq.flush([mockApprovedUser]); fixture.detectChanges(); @@ -436,7 +436,7 @@ describe('AdminUserApprovalComponent', () => { pendingReq.flush([]); component.switchTab('all'); - const allReq = httpMock.expectOne((r) => r.url.includes('/api/admin/users')); + const allReq = httpMock.expectOne((r) => r.url.includes('/api/admin/users') && !r.url.includes('/pending')); allReq.flush([mockApprovedUser, mockPendingUser]); fixture.detectChanges(); diff --git a/eeg_frontend/src/app/pages/user-tariff/user-tariff.ts b/eeg_frontend/src/app/pages/user-tariff/user-tariff.ts index 3a722ef..767ad11 100644 --- a/eeg_frontend/src/app/pages/user-tariff/user-tariff.ts +++ b/eeg_frontend/src/app/pages/user-tariff/user-tariff.ts @@ -5,6 +5,7 @@ import {UserTariffControllerService, TariffControllerService, UserTariffRequest, import {MeteringPointService, MeteringPoint} from '../../services/metering-point'; import {MembershipService, MembershipResponse} from '../../services/membership'; import {ToastService} from '../../services/toast'; +import {AuthService} from '../../services/auth'; @Component({ selector: 'app-user-tariff', @@ -19,6 +20,7 @@ export class UserTariffComponent implements OnInit, OnDestroy { private meteringPointService = inject(MeteringPointService); private membershipService = inject(MembershipService); private toastService = inject(ToastService); + private authService = inject(AuthService); private fb = inject(FormBuilder); memberships = signal([]); @@ -187,7 +189,13 @@ export class UserTariffComponent implements OnInit, OnDestroy { onDelete(tariff: UserTariffResponse) { if (!confirm('Möchtest du diesen Tarif wirklich löschen?')) return; - this.userTariffService.deleteUserTariff(tariff.id!).subscribe({ + const userId = this.authService.getCurrentUserId(); + if (!userId) { + this.toastService.error('Benutzer nicht identifiziert.'); + return; + } + + this.userTariffService.deleteUserTariff(tariff.id!, userId).subscribe({ next: () => { this.toastService.success('Tarif erfolgreich gelöscht.'); this.loadUserTariffs(this.selectedCommunityId());