Skip to content

Commit

Permalink
feat: implement password update endpoint with DTO and exception handling
Browse files Browse the repository at this point in the history
  • Loading branch information
Guhapriya01 committed Oct 20, 2024
1 parent c94ddbd commit 3907744
Show file tree
Hide file tree
Showing 5 changed files with 201 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,42 +9,64 @@
import java.util.Date;

/**
* Global exception handler for the LibraryMan API.
* This class provides centralized exception handling across all controllers in the application.
* It handles specific exceptions and returns appropriate HTTP responses.
* Global exception handler for the LibraryMan API. This class provides
* centralized exception handling across all controllers in the application. It
* handles specific exceptions and returns appropriate HTTP responses.
*/
@ControllerAdvice
public class GlobalExceptionHandler {

/**
* Handles {@link ResourceNotFoundException} exceptions.
* This method is triggered when a {@code ResourceNotFoundException} is thrown in the application.
* It constructs an {@link ErrorDetails} object containing the exception details and returns
* a {@link ResponseEntity} with an HTTP status of {@code 404 Not Found}.
*
* @param ex the exception that was thrown.
* @param request the current web request in which the exception was thrown.
* @return a {@link ResponseEntity} containing the {@link ErrorDetails} and an HTTP status of {@code 404 Not Found}.
*/
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<?> resourceNotFoundException(ResourceNotFoundException ex, WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND);
}

/**
* Handles {@link InvalidSortFieldException} exceptions.
* This method is triggered when an {@code InvalidSortFieldException} is thrown in the application.
* It constructs an {@link ErrorDetails} object containing the exception details and returns
* a {@link ResponseEntity} with an HTTP status of {@code 400 Bad Request}.
*
* @param ex the exception that was thrown.
* @param request the current web request in which the exception was thrown.
* @return a {@link ResponseEntity} containing the {@link ErrorDetails} and an HTTP status of {@code 400 Bad Request}.
*/
@ExceptionHandler(InvalidSortFieldException.class)
public ResponseEntity<?> invalidSortFieldException(InvalidSortFieldException ex, WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST);
}
/**
* Handles {@link ResourceNotFoundException} exceptions. This method is
* triggered when a {@code ResourceNotFoundException} is thrown in the
* application. It constructs an {@link ErrorDetails} object containing the
* exception details and returns a {@link ResponseEntity} with an HTTP status of
* {@code 404 Not Found}.
*
* @param ex the exception that was thrown.
* @param request the current web request in which the exception was thrown.
* @return a {@link ResponseEntity} containing the {@link ErrorDetails} and an
* HTTP status of {@code 404 Not Found}.
*/
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<?> resourceNotFoundException(ResourceNotFoundException ex, WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND);
}

/**
* Handles {@link InvalidSortFieldException} exceptions. This method is
* triggered when an {@code InvalidSortFieldException} is thrown in the
* application. It constructs an {@link ErrorDetails} object containing the
* exception details and returns a {@link ResponseEntity} with an HTTP status of
* {@code 400 Bad Request}.
*
* @param ex the exception that was thrown.
* @param request the current web request in which the exception was thrown.
* @return a {@link ResponseEntity} containing the {@link ErrorDetails} and an
* HTTP status of {@code 400 Bad Request}.
*/
@ExceptionHandler(InvalidSortFieldException.class)
public ResponseEntity<?> invalidSortFieldException(InvalidSortFieldException ex, WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST);
}

/**
* Handles {@link InvalidPasswordException} exceptions. This method is triggered
* when an {@code InvalidPasswordException} is thrown in the application. It
* constructs an {@link ErrorDetails} object containing the exception details
* and returns a {@link ResponseEntity} with an HTTP status of
* {@code 400 Bad Request}.
*
* @param ex the exception that was thrown.
* @param request the current web request in which the exception was thrown.
* @return a {@link ResponseEntity} containing the {@link ErrorDetails} and an
* HTTP status of {@code 400 Bad Request}.
*/
@ExceptionHandler(InvalidPasswordException.class)
public ResponseEntity<?> invalidPasswordException(InvalidPasswordException ex, WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.libraryman_api.exception;

import java.io.Serial;

/**
* Custom exception class to handle scenarios where an invalid password is provided
* in the Library Management System. This exception is thrown when a password update
* operation fails due to invalid password criteria.
*/
public class InvalidPasswordException extends RuntimeException {

/**
* The {@code serialVersionUID} is a unique identifier for each version of a serializable class.
* It is used during the deserialization process to verify that the sender and receiver of a
* serialized object have loaded classes for that object that are compatible with each other.
*
* The {@code serialVersionUID} field is important for ensuring that a serialized class
* (especially when transmitted over a network or saved to disk) can be successfully deserialized,
* even if the class definition changes in later versions. If the {@code serialVersionUID} does not
* match during deserialization, an {@code InvalidClassException} is thrown.
*
* This field is optional, but it is good practice to explicitly declare it to prevent
* automatic generation, which could lead to compatibility issues when the class structure changes.
*
* The {@code @Serial} annotation is used here to indicate that this field is related to
* serialization. This annotation is available starting from Java 14 and helps improve clarity
* regarding the purpose of this field.
*/
@Serial
private static final long serialVersionUID = 1L;

/**
* Constructs a new {@code InvalidPasswordException} with the specified detail message.
*
* @param message the detail message explaining the reason for the exception
*/
public InvalidPasswordException(String message) {
super(message);
}

/**
* Constructs a new {@code InvalidPasswordException} with the specified detail message and cause.
*
* @param message the detail message explaining the reason for the exception
* @param cause the cause of the exception (which is saved for later retrieval by the {@link #getCause()} method)
*/
public InvalidPasswordException(String message, Throwable cause) {
super(message, cause);
}
}
17 changes: 17 additions & 0 deletions src/main/java/com/libraryman_api/member/MemberController.java
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,21 @@ public MembersDto updateMember(@PathVariable int id, @RequestBody UpdateMembersD
public void deleteMember(@PathVariable int id) {
memberService.deleteMember(id);
}

/**
* Updates the password for a library member.
* If the member is not found or the update fails, an appropriate exception will be thrown.
*
* @param id the ID of the member whose password is to be updated
* @param updatePasswordDto the {@link UpdatePasswordDto} object containing the password details
* @return a {@link ResponseEntity} containing a success message indicating the password was updated successfully
*/
@PutMapping("/{id}/password")
@PreAuthorize("#id == authentication.principal.memberId")
public ResponseEntity<?> updatePassword(@PathVariable int id,
@RequestBody UpdatePasswordDto updatePasswordDto) {
memberService.updatePassword(id, updatePasswordDto);
return ResponseEntity.ok("Password updated successfully.");
}

}
39 changes: 38 additions & 1 deletion src/main/java/com/libraryman_api/member/MemberService.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@
import org.springframework.data.mapping.PropertyReferenceException;
import org.springframework.stereotype.Service;

import com.libraryman_api.exception.InvalidPasswordException;
import com.libraryman_api.exception.InvalidSortFieldException;
import com.libraryman_api.exception.ResourceNotFoundException;
import com.libraryman_api.notification.NotificationService;
import com.libraryman_api.security.config.PasswordEncoder;



Expand All @@ -35,16 +37,18 @@ public class MemberService {

private final MemberRepository memberRepository;
private final NotificationService notificationService;
private final PasswordEncoder passwordEncoder;

/**
* Constructs a new {@code MemberService} with the specified repositories and services.
*
* @param memberRepository the repository for managing member records
* @param notificationService the service for sending notifications related to member activities
*/
public MemberService(MemberRepository memberRepository, NotificationService notificationService) {
public MemberService(MemberRepository memberRepository, NotificationService notificationService, PasswordEncoder passwordEncoder) {
this.memberRepository = memberRepository;
this.notificationService = notificationService;
this.passwordEncoder = passwordEncoder;
}

/**
Expand Down Expand Up @@ -143,6 +147,39 @@ public void deleteMember(int memberId) {
memberRepository.delete(member);
}

/**
* Updates the password for a library member.
*
* <p>This method verifies the current password provided by the member, checks if the
* new password is different, and then updates the member's password in the database.
* If the current password is incorrect or the new password is the same as the current
* password, an {@link InvalidPasswordException} is thrown.</p>
*
* @param memberId the ID of the member whose password is to be updated
* @param updatePasswordDto the {@link UpdatePasswordDto} object containing the password details
* @throws ResourceNotFoundException if the member with the specified ID is not found
* @throws InvalidPasswordException if the current password is incorrect or the new password is the same as the current password
*/
public void updatePassword(int memberId, UpdatePasswordDto updatePasswordDto) {
Members member = memberRepository.findById(memberId)
.orElseThrow(() -> new ResourceNotFoundException("Member not found"));

// Check the current password
String currentAuthPassword = member.getPassword();

if (!passwordEncoder.bCryptPasswordEncoder().matches(updatePasswordDto.getCurrentPassword(), currentAuthPassword)) {
throw new InvalidPasswordException("Current password is incorrect");
}

// Check if new password is different from old password
if (updatePasswordDto.getCurrentPassword().equals(updatePasswordDto.getNewPassword())) {
throw new InvalidPasswordException("New password must be different from the old password");
}

member.setPassword(passwordEncoder.bCryptPasswordEncoder().encode(updatePasswordDto.getNewPassword()));
memberRepository.save(member);
}

/**
* Converts a MembersDto object to a Members entity.
*
Expand Down
40 changes: 40 additions & 0 deletions src/main/java/com/libraryman_api/member/UpdatePasswordDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.libraryman_api.member;

public class UpdatePasswordDto {

private String currentPassword;

private String newPassword;

public UpdatePasswordDto(String currentPassword, String newPassword) {
this.currentPassword = currentPassword;
this.newPassword = newPassword;
}

public UpdatePasswordDto() {
}

public String getCurrentPassword() {
return currentPassword;
}

public void setCurrentPassword(String currentPassword) {
this.currentPassword = currentPassword;
}

public String getNewPassword() {
return newPassword;
}

public void setNewPassword(String newPassword) {
this.newPassword = newPassword;
}

@Override
public String toString() {
return "UpdatePasswordDto{" +
"currentPassword='" + currentPassword + '\'' +
", newPassword='" + newPassword + '\'' +
'}';
}
}

0 comments on commit 3907744

Please sign in to comment.