diff --git a/backend/pom.xml b/backend/pom.xml
index da863de2..2f36d8d7 100644
--- a/backend/pom.xml
+++ b/backend/pom.xml
@@ -34,6 +34,10 @@
org.springframework.boot
spring-boot-starter-actuator
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
org.springframework.boot
spring-boot-starter-data-jpa
diff --git a/backend/src/main/java/org/raddatz/familienarchiv/controller/GlobalExceptionHandler.java b/backend/src/main/java/org/raddatz/familienarchiv/controller/GlobalExceptionHandler.java
index afeb052a..dff7d648 100644
--- a/backend/src/main/java/org/raddatz/familienarchiv/controller/GlobalExceptionHandler.java
+++ b/backend/src/main/java/org/raddatz/familienarchiv/controller/GlobalExceptionHandler.java
@@ -2,6 +2,7 @@ package org.raddatz.familienarchiv.controller;
import java.util.stream.Collectors;
+import jakarta.validation.ConstraintViolationException;
import org.raddatz.familienarchiv.exception.DomainException;
import org.raddatz.familienarchiv.exception.ErrorCode;
import org.springframework.http.ResponseEntity;
@@ -32,6 +33,14 @@ public class GlobalExceptionHandler {
return ResponseEntity.badRequest().body(new ErrorResponse(ErrorCode.VALIDATION_ERROR, message));
}
+ @ExceptionHandler(ConstraintViolationException.class)
+ public ResponseEntity handleConstraintViolation(ConstraintViolationException ex) {
+ String message = ex.getConstraintViolations().stream()
+ .map(v -> v.getPropertyPath() + ": " + v.getMessage())
+ .collect(Collectors.joining(", "));
+ return ResponseEntity.badRequest().body(new ErrorResponse(ErrorCode.VALIDATION_ERROR, message));
+ }
+
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public ResponseEntity handleTypeMismatch(MethodArgumentTypeMismatchException ex) {
String message = "Invalid value '" + ex.getValue() + "' for parameter '" + ex.getName() + "'";
diff --git a/backend/src/main/java/org/raddatz/familienarchiv/controller/NotificationController.java b/backend/src/main/java/org/raddatz/familienarchiv/controller/NotificationController.java
index 61bef6c6..ac85ae46 100644
--- a/backend/src/main/java/org/raddatz/familienarchiv/controller/NotificationController.java
+++ b/backend/src/main/java/org/raddatz/familienarchiv/controller/NotificationController.java
@@ -1,6 +1,9 @@
package org.raddatz.familienarchiv.controller;
+import jakarta.validation.constraints.Max;
+import jakarta.validation.constraints.Min;
import lombok.RequiredArgsConstructor;
+import io.swagger.v3.oas.annotations.Parameter;
import org.raddatz.familienarchiv.dto.NotificationDTO;
import org.raddatz.familienarchiv.dto.NotificationPreferenceDTO;
import org.raddatz.familienarchiv.model.AppUser;
@@ -16,6 +19,7 @@ import org.springframework.data.domain.Sort;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.core.Authentication;
+import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
@@ -25,6 +29,7 @@ import java.util.UUID;
@RestController
@RequiredArgsConstructor
+@Validated
public class NotificationController {
private final NotificationService notificationService;
@@ -44,9 +49,9 @@ public class NotificationController {
@GetMapping("/api/notifications")
public Page getNotifications(
@RequestParam(defaultValue = "0") int page,
- @RequestParam(defaultValue = "10") int size,
- @RequestParam(required = false) NotificationType type,
- @RequestParam(required = false) Boolean read,
+ @RequestParam(defaultValue = "10") @Min(1) @Max(100) int size,
+ @Parameter(description = "Filter by notification type") @RequestParam(required = false) NotificationType type,
+ @Parameter(description = "Filter by read status") @RequestParam(required = false) Boolean read,
Authentication authentication) {
AppUser user = resolveUser(authentication);
PageRequest pageable = PageRequest.of(page, size, Sort.by("createdAt").descending());