From 54142b5e3a4178b7fffa62546a3b2c57bcc7c618 Mon Sep 17 00:00:00 2001 From: Anish Date: Wed, 6 Nov 2024 17:01:09 +0530 Subject: [PATCH 1/5] api validation added to book and borrowing module --- pom.xml | 7 +++++++ .../libraryman_api/book/BookController.java | 5 +++-- .../java/com/libraryman_api/book/BookDto.java | 20 +++++++++++++++++++ .../borrowing/BorrowingController.java | 3 ++- .../borrowing/BorrowingsDto.java | 4 ++++ 5 files changed, 36 insertions(+), 3 deletions(-) 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 0e2660f..f74e4ea 100644 --- a/src/main/java/com/libraryman_api/book/BookController.java +++ b/src/main/java/com/libraryman_api/book/BookController.java @@ -1,6 +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; import org.springframework.data.domain.PageRequest; @@ -73,7 +74,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); } @@ -86,7 +87,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..2a5fe85 100644 --- a/src/main/java/com/libraryman_api/book/BookDto.java +++ b/src/main/java/com/libraryman_api/book/BookDto.java @@ -1,15 +1,35 @@ 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; + + @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; From 5cdb29e51e09675530eaafe24319112c0afc0e79 Mon Sep 17 00:00:00 2001 From: Anish Date: Wed, 6 Nov 2024 17:01:36 +0530 Subject: [PATCH 2/5] api validation added to member module --- .../member/MemberController.java | 3 ++- .../libraryman_api/member/dto/MembersDto.java | 18 ++++++++++++++++++ .../member/dto/UpdateMembersDto.java | 11 ++++++++++- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/libraryman_api/member/MemberController.java b/src/main/java/com/libraryman_api/member/MemberController.java index a628d80..0a22794 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); } 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..e3a4353 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; + + @NotBlank(message = "Role is required") 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) { From 2cee8a5caef4b48e31b972c97d68cddaddc57bd8 Mon Sep 17 00:00:00 2001 From: Anish Date: Wed, 6 Nov 2024 17:01:58 +0530 Subject: [PATCH 3/5] api validation exception handled --- .../exception/GlobalExceptionHandler.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) 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); + } + } From 02ea615b68f135eb4c922532e98b4a1eb007c971 Mon Sep 17 00:00:00 2001 From: Anish Date: Thu, 7 Nov 2024 16:43:53 +0530 Subject: [PATCH 4/5] added api validation to UpdatePasswordDto --- .../com/libraryman_api/member/MemberController.java | 2 +- .../libraryman_api/member/dto/UpdatePasswordDto.java | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/libraryman_api/member/MemberController.java b/src/main/java/com/libraryman_api/member/MemberController.java index 0a22794..dee2857 100644 --- a/src/main/java/com/libraryman_api/member/MemberController.java +++ b/src/main/java/com/libraryman_api/member/MemberController.java @@ -114,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/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) { From fa9e3029a6262cb4f532ba4163a676edea28231c Mon Sep 17 00:00:00 2001 From: Anish Date: Sat, 9 Nov 2024 12:38:57 +0530 Subject: [PATCH 5/5] 1.Added @NotBlank to the ISBN field in the Book DTO 2.Removed @NotBlank from the role field in the Members DTO --- src/main/java/com/libraryman_api/book/BookDto.java | 1 + src/main/java/com/libraryman_api/member/dto/MembersDto.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/libraryman_api/book/BookDto.java b/src/main/java/com/libraryman_api/book/BookDto.java index 2a5fe85..bc8450d 100644 --- a/src/main/java/com/libraryman_api/book/BookDto.java +++ b/src/main/java/com/libraryman_api/book/BookDto.java @@ -14,6 +14,7 @@ public class BookDto { @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; 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 e3a4353..2a222d1 100644 --- a/src/main/java/com/libraryman_api/member/dto/MembersDto.java +++ b/src/main/java/com/libraryman_api/member/dto/MembersDto.java @@ -27,7 +27,7 @@ public class MembersDto { message = "Password must contain at least one letter, one number, and one special character") private String password; - @NotBlank(message = "Role is required") + private Role role; private Date membershipDate;