diff --git a/pom.xml b/pom.xml index 683deaa..80ba2de 100644 --- a/pom.xml +++ b/pom.xml @@ -109,6 +109,13 @@ org.springframework.boot spring-boot-starter-cache + + + org.springframework.boot + spring-boot-starter-validation + 3.3.4 + + diff --git a/src/main/java/com/libraryman_api/book/BookController.java b/src/main/java/com/libraryman_api/book/BookController.java index 766b3b7..06b48fa 100644 --- a/src/main/java/com/libraryman_api/book/BookController.java +++ b/src/main/java/com/libraryman_api/book/BookController.java @@ -1,7 +1,7 @@ package com.libraryman_api.book; import com.libraryman_api.exception.ResourceNotFoundException; - +import jakarta.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; @@ -75,7 +75,7 @@ public ResponseEntity getBookById(@PathVariable int id) { */ @PostMapping @PreAuthorize("hasRole('LIBRARIAN') or hasRole('ADMIN')") - public BookDto addBook(@RequestBody BookDto bookDto) { + public BookDto addBook(@Valid @RequestBody BookDto bookDto) { return bookService.addBook(bookDto); } @@ -88,7 +88,7 @@ public BookDto addBook(@RequestBody BookDto bookDto) { */ @PutMapping("/{id}") @PreAuthorize("hasRole('LIBRARIAN') or hasRole('ADMIN')") - public BookDto updateBook(@PathVariable int id, @RequestBody BookDto bookDtoDetails) { + public BookDto updateBook(@PathVariable int id, @Valid @RequestBody BookDto bookDtoDetails) { return bookService.updateBook(id, bookDtoDetails); } diff --git a/src/main/java/com/libraryman_api/book/BookDto.java b/src/main/java/com/libraryman_api/book/BookDto.java index e6e480d..bc8450d 100644 --- a/src/main/java/com/libraryman_api/book/BookDto.java +++ b/src/main/java/com/libraryman_api/book/BookDto.java @@ -1,15 +1,36 @@ package com.libraryman_api.book; +import jakarta.validation.constraints.*; + public class BookDto { private int bookId; + @NotBlank(message = "Title is required ") + @Size(min = 1,max = 255,message = "Title must be between 1 and 255 characters") private String title; + + @NotBlank(message = "Author is required") + @Size(min = 1, max = 100, message = "Author name must be between 1 and 100 characters") private String author; + + @NotBlank(message = "isbn is required") + @Pattern(regexp = "^(978|979)-\\d{10}$", message = "Invalid ISBN format. Format must be '978-XXXXXXXXXX' or '979-XXXXXXXXXX'") private String isbn; + + @NotBlank(message = "Publisher is required") + @Size(min = 1, max = 100, message = "Publisher name must be between 1 and 100 characters") private String publisher; + + @Min(value = 1500, message = "Published year must be at least 1500") + @Max(value = 2100, message = "Published year cannot be more than 2100") private int publishedYear; + + @NotBlank(message = "Genre is required") + @Size(min = 1, max = 50, message = "Genre must be between 1 and 50 characters") private String genre; + + @Min(value = 0, message = "Copies available cannot be negative") private int copiesAvailable; public BookDto(int bookId, String title, String author, String isbn, String publisher, int publishedYear, String genre, int copiesAvailable) { diff --git a/src/main/java/com/libraryman_api/borrowing/BorrowingController.java b/src/main/java/com/libraryman_api/borrowing/BorrowingController.java index 2b69a68..5b11c55 100644 --- a/src/main/java/com/libraryman_api/borrowing/BorrowingController.java +++ b/src/main/java/com/libraryman_api/borrowing/BorrowingController.java @@ -1,6 +1,7 @@ package com.libraryman_api.borrowing; import com.libraryman_api.exception.ResourceNotFoundException; +import jakarta.validation.Valid; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; @@ -66,7 +67,7 @@ public Page getAllBorrowings(@PageableDefault(page = 0, size = 5, */ @PostMapping @PreAuthorize("hasRole('LIBRARIAN') or hasRole('ADMIN') or (hasRole('USER') and #borrowingsDto.member.memberId == authentication.principal.memberId)") - public BorrowingsDto borrowBook(@RequestBody BorrowingsDto borrowingsDto) { + public BorrowingsDto borrowBook(@Valid @RequestBody BorrowingsDto borrowingsDto) { return borrowingService.borrowBook(borrowingsDto); } diff --git a/src/main/java/com/libraryman_api/borrowing/BorrowingsDto.java b/src/main/java/com/libraryman_api/borrowing/BorrowingsDto.java index af9c859..9552800 100644 --- a/src/main/java/com/libraryman_api/borrowing/BorrowingsDto.java +++ b/src/main/java/com/libraryman_api/borrowing/BorrowingsDto.java @@ -3,15 +3,19 @@ import com.libraryman_api.book.BookDto; import com.libraryman_api.fine.Fines; import com.libraryman_api.member.dto.MembersDto; +import jakarta.validation.constraints.NotNull; import java.util.Date; public class BorrowingsDto { private int borrowingId; + @NotNull(message = "Book is required") private BookDto book; private Fines fine; + @NotNull(message = "Member is required") private MembersDto member; + private Date borrowDate; private Date dueDate; private Date returnDate; diff --git a/src/main/java/com/libraryman_api/exception/GlobalExceptionHandler.java b/src/main/java/com/libraryman_api/exception/GlobalExceptionHandler.java index 3ef1ca0..bcb7ec1 100644 --- a/src/main/java/com/libraryman_api/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/libraryman_api/exception/GlobalExceptionHandler.java @@ -2,11 +2,15 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.validation.FieldError; +import org.springframework.validation.ObjectError; +import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.context.request.WebRequest; import java.util.Date; +import java.util.*; /** * Global exception handler for the LibraryMan API. This class provides @@ -69,4 +73,18 @@ public ResponseEntity invalidPasswordException(InvalidPasswordException ex, W ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false)); return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST); } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity> MethodArgumentNotValidException(MethodArgumentNotValidException ex){ + List allErrors = ex.getBindingResult().getAllErrors(); + HashMap map = new HashMap<>(); + allErrors.forEach(objectError -> { + String message=objectError.getDefaultMessage(); + String field=((FieldError) objectError).getField(); + map.put(field,message); + }); + + return new ResponseEntity<>(map,HttpStatus.BAD_REQUEST); + } + } diff --git a/src/main/java/com/libraryman_api/member/MemberController.java b/src/main/java/com/libraryman_api/member/MemberController.java index a628d80..dee2857 100644 --- a/src/main/java/com/libraryman_api/member/MemberController.java +++ b/src/main/java/com/libraryman_api/member/MemberController.java @@ -4,6 +4,7 @@ import com.libraryman_api.member.dto.MembersDto; import com.libraryman_api.member.dto.UpdateMembersDto; import com.libraryman_api.member.dto.UpdatePasswordDto; +import jakarta.validation.Valid; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; @@ -86,7 +87,7 @@ public ResponseEntity getMemberById(@PathVariable int id) { */ @PutMapping("/{id}") @PreAuthorize("hasRole('LIBRARIAN') or hasRole('ADMIN') or (hasRole('USER') and #id == authentication.principal.memberId)") - public MembersDto updateMember(@PathVariable int id, @RequestBody UpdateMembersDto membersDtoDetails) { + public MembersDto updateMember(@PathVariable int id,@Valid @RequestBody UpdateMembersDto membersDtoDetails) { return memberService.updateMember(id, membersDtoDetails); } @@ -113,7 +114,7 @@ public void deleteMember(@PathVariable int id) { @PutMapping("/{id}/password") @PreAuthorize("#id == authentication.principal.memberId") public ResponseEntity updatePassword(@PathVariable int id, - @RequestBody UpdatePasswordDto updatePasswordDto) { + @Valid @RequestBody UpdatePasswordDto updatePasswordDto) { memberService.updatePassword(id, updatePasswordDto); return ResponseEntity.ok("Password updated successfully."); } diff --git a/src/main/java/com/libraryman_api/member/dto/MembersDto.java b/src/main/java/com/libraryman_api/member/dto/MembersDto.java index c08d655..2a222d1 100644 --- a/src/main/java/com/libraryman_api/member/dto/MembersDto.java +++ b/src/main/java/com/libraryman_api/member/dto/MembersDto.java @@ -1,17 +1,35 @@ package com.libraryman_api.member.dto; import com.libraryman_api.member.Role; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; import java.util.Date; public class MembersDto { private int memberId; + @NotBlank(message = "Name is required") + @Size(min = 2, max = 100, message = "Name must be between 2 and 100 characters") private String name; + @NotBlank(message = "Username is required") + @Size(min = 4, max = 50, message = "Username must be between 4 and 50 characters") private String username; + + @Pattern(regexp = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}$", message = "Please enter a valid email address (e.g., user@example.com)") + @NotBlank(message = "Email field cannot be empty. Please provide a valid email address.") private String email; + + @NotBlank(message = "Password is required") + @Size(min = 8, message = "Password must be at least 8 characters long") + @Pattern(regexp = "^(?=.*[0-9])(?=.*[a-zA-Z])(?=.*[@#$%^&+=]).*$", + message = "Password must contain at least one letter, one number, and one special character") private String password; + + private Role role; + private Date membershipDate; public MembersDto(int memberId, String name, String username, String email, String password, Role role, Date membershipDate) { diff --git a/src/main/java/com/libraryman_api/member/dto/UpdateMembersDto.java b/src/main/java/com/libraryman_api/member/dto/UpdateMembersDto.java index f76b715..8efb8bd 100644 --- a/src/main/java/com/libraryman_api/member/dto/UpdateMembersDto.java +++ b/src/main/java/com/libraryman_api/member/dto/UpdateMembersDto.java @@ -1,9 +1,18 @@ package com.libraryman_api.member.dto; -public class UpdateMembersDto { +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; +public class UpdateMembersDto { + @NotBlank(message = "Name is required") + @Size(min = 2, max = 100, message = "Name must be between 2 and 100 characters") private String name; + @NotBlank(message = "Username is required") + @Size(min = 4, max = 50, message = "Username must be between 4 and 50 characters") private String username; + @Pattern(regexp = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}$", message = "Please enter a valid email address (e.g., user@example.com)") + @NotBlank(message = "Email field cannot be empty. Please provide a valid email address.") private String email; public UpdateMembersDto(String name, String username, String email) { diff --git a/src/main/java/com/libraryman_api/member/dto/UpdatePasswordDto.java b/src/main/java/com/libraryman_api/member/dto/UpdatePasswordDto.java index 00a124f..f062a14 100644 --- a/src/main/java/com/libraryman_api/member/dto/UpdatePasswordDto.java +++ b/src/main/java/com/libraryman_api/member/dto/UpdatePasswordDto.java @@ -1,8 +1,16 @@ package com.libraryman_api.member.dto; -public class UpdatePasswordDto { +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; +public class UpdatePasswordDto { private String currentPassword; + + @NotBlank(message = "New Password is required") + @Size(min = 8, message = "Password must be at least 8 characters long") + @Pattern(regexp = "^(?=.*[0-9])(?=.*[a-zA-Z])(?=.*[@#$%^&+=]).*$", + message = "Password must contain at least one letter, one number, and one special character") private String newPassword; public UpdatePasswordDto(String currentPassword, String newPassword) {