unify error handling
This commit is contained in:
@@ -13,6 +13,9 @@ import com.petshop.backend.dto.auth.ResetPasswordResponse;
|
|||||||
import com.petshop.backend.dto.auth.UserInfoResponse;
|
import com.petshop.backend.dto.auth.UserInfoResponse;
|
||||||
import com.petshop.backend.entity.StoreLocation;
|
import com.petshop.backend.entity.StoreLocation;
|
||||||
import com.petshop.backend.entity.User;
|
import com.petshop.backend.entity.User;
|
||||||
|
import com.petshop.backend.exception.BusinessException;
|
||||||
|
import com.petshop.backend.exception.ConflictException;
|
||||||
|
import com.petshop.backend.exception.ResourceNotFoundException;
|
||||||
import com.petshop.backend.repository.UserRepository;
|
import com.petshop.backend.repository.UserRepository;
|
||||||
import com.petshop.backend.security.JwtUtil;
|
import com.petshop.backend.security.JwtUtil;
|
||||||
import com.petshop.backend.security.UserAuthCacheService;
|
import com.petshop.backend.security.UserAuthCacheService;
|
||||||
@@ -74,7 +77,7 @@ public class AuthController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/register")
|
@PostMapping("/register")
|
||||||
public ResponseEntity<?> register(@Valid @RequestBody RegisterRequest request) {
|
public ResponseEntity<RegisterResponse> register(@Valid @RequestBody RegisterRequest request) {
|
||||||
String username = trimToNull(request.getUsername());
|
String username = trimToNull(request.getUsername());
|
||||||
String email = trimToNull(request.getEmail());
|
String email = trimToNull(request.getEmail());
|
||||||
String firstName = trimToNull(request.getFirstName());
|
String firstName = trimToNull(request.getFirstName());
|
||||||
@@ -83,23 +86,17 @@ public class AuthController {
|
|||||||
|
|
||||||
if (userRepository.findByUsername(username).isPresent()) {
|
if (userRepository.findByUsername(username).isPresent()) {
|
||||||
log.warn("Registration rejected: username already exists ({})", username);
|
log.warn("Registration rejected: username already exists ({})", username);
|
||||||
Map<String, String> error = new HashMap<>();
|
throw new ConflictException("Username already exists");
|
||||||
error.put("message", "Username already exists");
|
|
||||||
return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userRepository.findByEmail(email).isPresent()) {
|
if (userRepository.findByEmail(email).isPresent()) {
|
||||||
log.warn("Registration rejected: email already exists");
|
log.warn("Registration rejected: email already exists");
|
||||||
Map<String, String> error = new HashMap<>();
|
throw new ConflictException("Email already exists");
|
||||||
error.put("message", "Email already exists");
|
|
||||||
return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (phone != null && userRepository.findByPhone(phone).isPresent()) {
|
if (phone != null && userRepository.findByPhone(phone).isPresent()) {
|
||||||
log.warn("Registration rejected: phone already exists");
|
log.warn("Registration rejected: phone already exists");
|
||||||
Map<String, String> error = new HashMap<>();
|
throw new ConflictException("Phone already exists");
|
||||||
error.put("message", "Phone already exists");
|
|
||||||
return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
User user = new User();
|
User user = new User();
|
||||||
@@ -117,9 +114,7 @@ public class AuthController {
|
|||||||
try {
|
try {
|
||||||
savedUser = userRepository.save(user);
|
savedUser = userRepository.save(user);
|
||||||
} catch (DataIntegrityViolationException e) {
|
} catch (DataIntegrityViolationException e) {
|
||||||
Map<String, String> error = new HashMap<>();
|
throw new ConflictException("Username, email, or phone already exists");
|
||||||
error.put("message", "Username, email, or phone already exists");
|
|
||||||
return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
emailService.sendWelcome(savedUser);
|
emailService.sendWelcome(savedUser);
|
||||||
@@ -137,7 +132,7 @@ public class AuthController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/login")
|
@PostMapping("/login")
|
||||||
public ResponseEntity<?> login(@Valid @RequestBody LoginRequest request) {
|
public ResponseEntity<LoginResponse> login(@Valid @RequestBody LoginRequest request) {
|
||||||
try {
|
try {
|
||||||
authenticationManager.authenticate(
|
authenticationManager.authenticate(
|
||||||
new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword())
|
new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword())
|
||||||
@@ -159,21 +154,15 @@ public class AuthController {
|
|||||||
|
|
||||||
} catch (BadCredentialsException e) {
|
} catch (BadCredentialsException e) {
|
||||||
log.warn("Login failed for username {}", request.getUsername());
|
log.warn("Login failed for username {}", request.getUsername());
|
||||||
Map<String, String> error = new HashMap<>();
|
throw e;
|
||||||
error.put("message", "Invalid username or password");
|
|
||||||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(error);
|
|
||||||
} catch (InternalAuthenticationServiceException e) {
|
} catch (InternalAuthenticationServiceException e) {
|
||||||
if (e.getCause() instanceof DisabledException disabledException) {
|
if (e.getCause() instanceof DisabledException disabledException) {
|
||||||
log.warn("Login denied for disabled user {}", request.getUsername());
|
log.warn("Login denied for disabled user {}", request.getUsername());
|
||||||
Map<String, String> error = new HashMap<>();
|
throw disabledException;
|
||||||
error.put("message", disabledException.getMessage());
|
|
||||||
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(error);
|
|
||||||
}
|
}
|
||||||
throw e;
|
throw e;
|
||||||
} catch (DisabledException e) {
|
} catch (DisabledException e) {
|
||||||
Map<String, String> error = new HashMap<>();
|
throw e;
|
||||||
error.put("message", e.getMessage());
|
|
||||||
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(error);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,7 +185,7 @@ public class AuthController {
|
|||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@PutMapping("/me")
|
@PutMapping("/me")
|
||||||
public ResponseEntity<?> updateProfile(@Valid @RequestBody ProfileUpdateRequest request) {
|
public ResponseEntity<UserInfoResponse> updateProfile(@Valid @RequestBody ProfileUpdateRequest request) {
|
||||||
Long userId = AuthenticationHelper.getAuthenticatedUserId();
|
Long userId = AuthenticationHelper.getAuthenticatedUserId();
|
||||||
User user = userRepository.findByIdForUpdate(userId)
|
User user = userRepository.findByIdForUpdate(userId)
|
||||||
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
|
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
|
||||||
@@ -205,9 +194,7 @@ public class AuthController {
|
|||||||
String username = trimToNull(request.getUsername());
|
String username = trimToNull(request.getUsername());
|
||||||
if (username != null && !username.equals(user.getUsername())) {
|
if (username != null && !username.equals(user.getUsername())) {
|
||||||
if (userRepository.findByUsername(username).isPresent()) {
|
if (userRepository.findByUsername(username).isPresent()) {
|
||||||
Map<String, String> error = new HashMap<>();
|
throw new ConflictException("Username already exists");
|
||||||
error.put("message", "Username already exists");
|
|
||||||
return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
|
|
||||||
}
|
}
|
||||||
user.setUsername(username);
|
user.setUsername(username);
|
||||||
invalidateToken = true;
|
invalidateToken = true;
|
||||||
@@ -216,9 +203,7 @@ public class AuthController {
|
|||||||
String email = trimToNull(request.getEmail());
|
String email = trimToNull(request.getEmail());
|
||||||
if (email != null && !email.equals(user.getEmail())) {
|
if (email != null && !email.equals(user.getEmail())) {
|
||||||
if (userRepository.findByEmail(email).isPresent()) {
|
if (userRepository.findByEmail(email).isPresent()) {
|
||||||
Map<String, String> error = new HashMap<>();
|
throw new ConflictException("Email already exists");
|
||||||
error.put("message", "Email already exists");
|
|
||||||
return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
|
|
||||||
}
|
}
|
||||||
user.setEmail(email);
|
user.setEmail(email);
|
||||||
}
|
}
|
||||||
@@ -241,9 +226,7 @@ public class AuthController {
|
|||||||
if (phone != null && userRepository.findByPhone(phone)
|
if (phone != null && userRepository.findByPhone(phone)
|
||||||
.filter(existing -> !existing.getId().equals(user.getId()))
|
.filter(existing -> !existing.getId().equals(user.getId()))
|
||||||
.isPresent()) {
|
.isPresent()) {
|
||||||
Map<String, String> error = new HashMap<>();
|
throw new ConflictException("Phone already exists");
|
||||||
error.put("message", "Phone already exists");
|
|
||||||
return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
|
|
||||||
}
|
}
|
||||||
user.setPhone(phone);
|
user.setPhone(phone);
|
||||||
}
|
}
|
||||||
@@ -262,9 +245,7 @@ public class AuthController {
|
|||||||
try {
|
try {
|
||||||
updatedUser = userRepository.save(user);
|
updatedUser = userRepository.save(user);
|
||||||
} catch (DataIntegrityViolationException e) {
|
} catch (DataIntegrityViolationException e) {
|
||||||
Map<String, String> error = new HashMap<>();
|
throw new ConflictException("Username, email, or phone already exists");
|
||||||
error.put("message", "Username, email, or phone already exists");
|
|
||||||
return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
|
|
||||||
}
|
}
|
||||||
userAuthCacheService.evict(updatedUser.getId());
|
userAuthCacheService.evict(updatedUser.getId());
|
||||||
return ResponseEntity.ok(toUserInfoResponse(updatedUser));
|
return ResponseEntity.ok(toUserInfoResponse(updatedUser));
|
||||||
@@ -306,17 +287,6 @@ public class AuthController {
|
|||||||
return trimToNull(PhoneUtils.normalize(trimToNull(value)));
|
return trimToNull(PhoneUtils.normalize(trimToNull(value)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private NameParts splitFullName(String value) {
|
|
||||||
String normalized = trimToNull(value);
|
|
||||||
if (normalized == null) {
|
|
||||||
throw new IllegalArgumentException("Full name is required");
|
|
||||||
}
|
|
||||||
String[] parts = normalized.split("\\s+", 2);
|
|
||||||
String firstName = parts[0];
|
|
||||||
String lastName = parts.length > 1 ? parts[1] : "";
|
|
||||||
return new NameParts(firstName, lastName, joinFullName(firstName, lastName));
|
|
||||||
}
|
|
||||||
|
|
||||||
private String joinFullName(String firstName, String lastName) {
|
private String joinFullName(String firstName, String lastName) {
|
||||||
String first = trimToNull(firstName);
|
String first = trimToNull(firstName);
|
||||||
String last = trimToNull(lastName);
|
String last = trimToNull(lastName);
|
||||||
@@ -329,30 +299,21 @@ public class AuthController {
|
|||||||
return first + " " + last;
|
return first + " " + last;
|
||||||
}
|
}
|
||||||
|
|
||||||
private record NameParts(String firstName, String lastName, String fullName) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/me/avatar")
|
@PostMapping("/me/avatar")
|
||||||
public ResponseEntity<?> uploadAvatar(@RequestParam("avatar") MultipartFile file) {
|
public ResponseEntity<AvatarUploadResponse> uploadAvatar(@RequestParam("avatar") MultipartFile file) {
|
||||||
User user = getAuthenticatedUser();
|
User user = getAuthenticatedUser();
|
||||||
|
|
||||||
if (file.isEmpty()) {
|
if (file.isEmpty()) {
|
||||||
Map<String, String> error = new HashMap<>();
|
throw new BusinessException("Please select a file to upload");
|
||||||
error.put("message", "Please select a file to upload");
|
|
||||||
return ResponseEntity.badRequest().body(error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file.getSize() > 5 * 1024 * 1024) {
|
if (file.getSize() > 5 * 1024 * 1024) {
|
||||||
Map<String, String> error = new HashMap<>();
|
throw new BusinessException("File size must not exceed 5MB");
|
||||||
error.put("message", "File size must not exceed 5MB");
|
|
||||||
return ResponseEntity.badRequest().body(error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String contentType = file.getContentType();
|
String contentType = file.getContentType();
|
||||||
if (contentType == null || (!contentType.equals("image/jpeg") && !contentType.equals("image/png") && !contentType.equals("image/gif"))) {
|
if (contentType == null || (!contentType.equals("image/jpeg") && !contentType.equals("image/png") && !contentType.equals("image/gif"))) {
|
||||||
Map<String, String> error = new HashMap<>();
|
throw new BusinessException("Only JPG, PNG, and GIF images are allowed");
|
||||||
error.put("message", "Only JPG, PNG, and GIF images are allowed");
|
|
||||||
return ResponseEntity.badRequest().body(error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -364,9 +325,7 @@ public class AuthController {
|
|||||||
return ResponseEntity.ok(new AvatarUploadResponse(avatarStorageService.toOwnerAvatarUrl(user), "Avatar uploaded successfully"));
|
return ResponseEntity.ok(new AvatarUploadResponse(avatarStorageService.toOwnerAvatarUrl(user), "Avatar uploaded successfully"));
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Map<String, String> error = new HashMap<>();
|
throw new BusinessException("Failed to upload avatar: " + e.getMessage());
|
||||||
error.put("message", "Failed to upload avatar: " + e.getMessage());
|
|
||||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -375,9 +334,7 @@ public class AuthController {
|
|||||||
User user = getAuthenticatedUser();
|
User user = getAuthenticatedUser();
|
||||||
|
|
||||||
if (!avatarStorageService.hasAvatar(user)) {
|
if (!avatarStorageService.hasAvatar(user)) {
|
||||||
Map<String, String> error = new HashMap<>();
|
throw new ResourceNotFoundException("No avatar uploaded");
|
||||||
error.put("message", "No avatar uploaded");
|
|
||||||
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, String> response = new HashMap<>();
|
Map<String, String> response = new HashMap<>();
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.petshop.backend.exception;
|
||||||
|
|
||||||
|
public class ConflictException extends RuntimeException {
|
||||||
|
public ConflictException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,8 @@ import jakarta.servlet.http.HttpServletRequest;
|
|||||||
import org.springframework.dao.DataIntegrityViolationException;
|
import org.springframework.dao.DataIntegrityViolationException;
|
||||||
import org.springframework.data.core.PropertyReferenceException;
|
import org.springframework.data.core.PropertyReferenceException;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.security.authentication.BadCredentialsException;
|
||||||
|
import org.springframework.security.authentication.DisabledException;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.validation.FieldError;
|
import org.springframework.validation.FieldError;
|
||||||
@@ -33,6 +35,11 @@ public class GlobalExceptionHandler {
|
|||||||
return buildErrorResponse(HttpStatus.BAD_REQUEST, ex.getMessage(), ex, request);
|
return buildErrorResponse(HttpStatus.BAD_REQUEST, ex.getMessage(), ex, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(ConflictException.class)
|
||||||
|
public ResponseEntity<ApiErrorResponse> handleConflictException(ConflictException ex, HttpServletRequest request) {
|
||||||
|
return buildErrorResponse(HttpStatus.CONFLICT, ex.getMessage(), ex, request);
|
||||||
|
}
|
||||||
|
|
||||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||||
public ResponseEntity<Map<String, Object>> handleValidationExceptions(MethodArgumentNotValidException ex, HttpServletRequest request) {
|
public ResponseEntity<Map<String, Object>> handleValidationExceptions(MethodArgumentNotValidException ex, HttpServletRequest request) {
|
||||||
Map<String, String> errors = new HashMap<>();
|
Map<String, String> errors = new HashMap<>();
|
||||||
@@ -54,6 +61,16 @@ public class GlobalExceptionHandler {
|
|||||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
|
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(BadCredentialsException.class)
|
||||||
|
public ResponseEntity<ApiErrorResponse> handleBadCredentials(BadCredentialsException ex, HttpServletRequest request) {
|
||||||
|
return buildErrorResponse(HttpStatus.UNAUTHORIZED, "Invalid username or password", ex, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(DisabledException.class)
|
||||||
|
public ResponseEntity<ApiErrorResponse> handleDisabledException(DisabledException ex, HttpServletRequest request) {
|
||||||
|
return buildErrorResponse(HttpStatus.FORBIDDEN, ex.getMessage(), ex, request);
|
||||||
|
}
|
||||||
|
|
||||||
@ExceptionHandler(org.springframework.security.access.AccessDeniedException.class)
|
@ExceptionHandler(org.springframework.security.access.AccessDeniedException.class)
|
||||||
public ResponseEntity<ApiErrorResponse> handleAccessDeniedException(org.springframework.security.access.AccessDeniedException ex, HttpServletRequest request) {
|
public ResponseEntity<ApiErrorResponse> handleAccessDeniedException(org.springframework.security.access.AccessDeniedException ex, HttpServletRequest request) {
|
||||||
return buildErrorResponse(HttpStatus.FORBIDDEN, ex.getMessage(), ex, request);
|
return buildErrorResponse(HttpStatus.FORBIDDEN, ex.getMessage(), ex, request);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import com.petshop.backend.entity.Pet;
|
|||||||
import com.petshop.backend.entity.Sale;
|
import com.petshop.backend.entity.Sale;
|
||||||
import com.petshop.backend.entity.StoreLocation;
|
import com.petshop.backend.entity.StoreLocation;
|
||||||
import com.petshop.backend.entity.User;
|
import com.petshop.backend.entity.User;
|
||||||
|
import com.petshop.backend.exception.BusinessException;
|
||||||
import com.petshop.backend.exception.ResourceNotFoundException;
|
import com.petshop.backend.exception.ResourceNotFoundException;
|
||||||
import com.petshop.backend.event.AdoptionConfirmedEvent;
|
import com.petshop.backend.event.AdoptionConfirmedEvent;
|
||||||
import com.petshop.backend.event.SaleReceiptEvent;
|
import com.petshop.backend.event.SaleReceiptEvent;
|
||||||
@@ -96,7 +97,7 @@ public class AdoptionService {
|
|||||||
String adoptionStatus = normalizeAdoptionStatus(request.getAdoptionStatus());
|
String adoptionStatus = normalizeAdoptionStatus(request.getAdoptionStatus());
|
||||||
validatePetAvailability(pet, null, null);
|
validatePetAvailability(pet, null, null);
|
||||||
if (ADOPTION_STATUS_COMPLETED.equalsIgnoreCase(adoptionStatus) && request.getAdoptionDate() != null && request.getAdoptionDate().isAfter(LocalDate.now())) {
|
if (ADOPTION_STATUS_COMPLETED.equalsIgnoreCase(adoptionStatus) && request.getAdoptionDate() != null && request.getAdoptionDate().isAfter(LocalDate.now())) {
|
||||||
throw new IllegalArgumentException("Cannot mark a future-dated adoption as Completed");
|
throw new BusinessException("Cannot mark a future-dated adoption as Completed");
|
||||||
}
|
}
|
||||||
|
|
||||||
Adoption adoption = new Adoption();
|
Adoption adoption = new Adoption();
|
||||||
@@ -139,7 +140,7 @@ public class AdoptionService {
|
|||||||
Long currentPetId = adoption.getPet() != null ? adoption.getPet().getPetId() : null;
|
Long currentPetId = adoption.getPet() != null ? adoption.getPet().getPetId() : null;
|
||||||
validatePetAvailability(pet, adoption.getAdoptionId(), currentPetId);
|
validatePetAvailability(pet, adoption.getAdoptionId(), currentPetId);
|
||||||
if (ADOPTION_STATUS_COMPLETED.equalsIgnoreCase(adoptionStatus) && request.getAdoptionDate() != null && request.getAdoptionDate().isAfter(LocalDate.now())) {
|
if (ADOPTION_STATUS_COMPLETED.equalsIgnoreCase(adoptionStatus) && request.getAdoptionDate() != null && request.getAdoptionDate().isAfter(LocalDate.now())) {
|
||||||
throw new IllegalArgumentException("Cannot mark a future-dated adoption as Completed");
|
throw new BusinessException("Cannot mark a future-dated adoption as Completed");
|
||||||
}
|
}
|
||||||
|
|
||||||
adoption.setPet(pet);
|
adoption.setPet(pet);
|
||||||
@@ -168,7 +169,7 @@ public class AdoptionService {
|
|||||||
|
|
||||||
// Verify the pet is actually located at the claimed store
|
// Verify the pet is actually located at the claimed store
|
||||||
if (pet.getStore() == null || !pet.getStore().getStoreId().equals(sourceStoreId)) {
|
if (pet.getStore() == null || !pet.getStore().getStoreId().equals(sourceStoreId)) {
|
||||||
throw new IllegalArgumentException("The specified pet is not located at the selected store.");
|
throw new BusinessException("The specified pet is not located at the selected store.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the pet is available for adoption
|
// Verify the pet is available for adoption
|
||||||
@@ -203,7 +204,7 @@ public class AdoptionService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!ADOPTION_STATUS_PENDING.equalsIgnoreCase(adoption.getAdoptionStatus())) {
|
if (!ADOPTION_STATUS_PENDING.equalsIgnoreCase(adoption.getAdoptionStatus())) {
|
||||||
throw new IllegalArgumentException("Only pending adoptions can be cancelled");
|
throw new BusinessException("Only pending adoptions can be cancelled");
|
||||||
}
|
}
|
||||||
|
|
||||||
adoption.setAdoptionStatus(ADOPTION_STATUS_CANCELLED);
|
adoption.setAdoptionStatus(ADOPTION_STATUS_CANCELLED);
|
||||||
@@ -280,7 +281,7 @@ public class AdoptionService {
|
|||||||
User employee = userRepository.findById(requestedEmployeeId)
|
User employee = userRepository.findById(requestedEmployeeId)
|
||||||
.orElseThrow(() -> new ResourceNotFoundException("Employee not found with id: " + requestedEmployeeId));
|
.orElseThrow(() -> new ResourceNotFoundException("Employee not found with id: " + requestedEmployeeId));
|
||||||
if (!isAssignableUser(employee)) {
|
if (!isAssignableUser(employee)) {
|
||||||
throw new IllegalArgumentException("Selected employee is not assignable for adoption work");
|
throw new BusinessException("Selected employee is not assignable for adoption work");
|
||||||
}
|
}
|
||||||
return employee;
|
return employee;
|
||||||
}
|
}
|
||||||
@@ -295,7 +296,7 @@ public class AdoptionService {
|
|||||||
|
|
||||||
private String normalizeAdoptionStatus(String adoptionStatus) {
|
private String normalizeAdoptionStatus(String adoptionStatus) {
|
||||||
if (adoptionStatus == null) {
|
if (adoptionStatus == null) {
|
||||||
throw new IllegalArgumentException("Adoption status is required");
|
throw new BusinessException("Adoption status is required");
|
||||||
}
|
}
|
||||||
String trimmedStatus = adoptionStatus.trim();
|
String trimmedStatus = adoptionStatus.trim();
|
||||||
if (ADOPTION_STATUS_PENDING.equalsIgnoreCase(trimmedStatus)) {
|
if (ADOPTION_STATUS_PENDING.equalsIgnoreCase(trimmedStatus)) {
|
||||||
@@ -310,7 +311,7 @@ public class AdoptionService {
|
|||||||
if (ADOPTION_STATUS_MISSED.equalsIgnoreCase(trimmedStatus)) {
|
if (ADOPTION_STATUS_MISSED.equalsIgnoreCase(trimmedStatus)) {
|
||||||
return ADOPTION_STATUS_MISSED;
|
return ADOPTION_STATUS_MISSED;
|
||||||
}
|
}
|
||||||
throw new IllegalArgumentException("Adoption status must be Pending, Completed, Cancelled, or Missed");
|
throw new BusinessException("Adoption status must be Pending, Completed, Cancelled, or Missed");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void validatePetAvailability(Pet pet, Long adoptionId, Long currentPetId) {
|
private void validatePetAvailability(Pet pet, Long adoptionId, Long currentPetId) {
|
||||||
@@ -319,11 +320,11 @@ public class AdoptionService {
|
|||||||
? adoptionRepository.existsByPet_IdAndAdoptionStatusIgnoreCase(pet.getPetId(), ADOPTION_STATUS_COMPLETED)
|
? adoptionRepository.existsByPet_IdAndAdoptionStatusIgnoreCase(pet.getPetId(), ADOPTION_STATUS_COMPLETED)
|
||||||
: adoptionRepository.existsByPet_IdAndAdoptionStatusIgnoreCaseAndAdoptionIdNot(pet.getPetId(), ADOPTION_STATUS_COMPLETED, adoptionId);
|
: adoptionRepository.existsByPet_IdAndAdoptionStatusIgnoreCaseAndAdoptionIdNot(pet.getPetId(), ADOPTION_STATUS_COMPLETED, adoptionId);
|
||||||
if (adoptedElsewhere) {
|
if (adoptedElsewhere) {
|
||||||
throw new IllegalArgumentException("Selected pet has already been adopted");
|
throw new BusinessException("Selected pet has already been adopted");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!samePetAsCurrentAdoption && !PET_STATUS_AVAILABLE.equalsIgnoreCase(pet.getPetStatus())) {
|
if (!samePetAsCurrentAdoption && !PET_STATUS_AVAILABLE.equalsIgnoreCase(pet.getPetStatus())) {
|
||||||
throw new IllegalArgumentException("Selected pet is not available for adoption");
|
throw new BusinessException("Selected pet is not available for adoption");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -116,14 +116,14 @@ public class AppointmentService {
|
|||||||
// Customers must supply a pet that is Adopted and owned by them
|
// Customers must supply a pet that is Adopted and owned by them
|
||||||
if (User.Role.CUSTOMER.equals(authenticatedUser.getRole())) {
|
if (User.Role.CUSTOMER.equals(authenticatedUser.getRole())) {
|
||||||
if (pet == null) {
|
if (pet == null) {
|
||||||
throw new IllegalArgumentException("A pet must be selected for your appointment");
|
throw new BusinessException("A pet must be selected for your appointment");
|
||||||
}
|
}
|
||||||
if (pet.getOwner() == null || !pet.getOwner().getId().equals(authenticatedUser.getId())) {
|
if (pet.getOwner() == null || !pet.getOwner().getId().equals(authenticatedUser.getId())) {
|
||||||
throw new IllegalArgumentException("The selected pet does not belong to your account");
|
throw new BusinessException("The selected pet does not belong to your account");
|
||||||
}
|
}
|
||||||
String petStatus = pet.getPetStatus();
|
String petStatus = pet.getPetStatus();
|
||||||
if (!"Owned".equalsIgnoreCase(petStatus) && !"Adopted".equalsIgnoreCase(petStatus)) {
|
if (!"Owned".equalsIgnoreCase(petStatus) && !"Adopted".equalsIgnoreCase(petStatus)) {
|
||||||
throw new IllegalArgumentException("Only your own pets can be booked for appointments");
|
throw new BusinessException("Only your own pets can be booked for appointments");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,7 +198,7 @@ public class AppointmentService {
|
|||||||
|
|
||||||
String status = appointment.getAppointmentStatus();
|
String status = appointment.getAppointmentStatus();
|
||||||
if (!"Booked".equalsIgnoreCase(status) && !"Scheduled".equalsIgnoreCase(status)) {
|
if (!"Booked".equalsIgnoreCase(status) && !"Scheduled".equalsIgnoreCase(status)) {
|
||||||
throw new IllegalArgumentException("Only booked or scheduled appointments can be cancelled");
|
throw new BusinessException("Only booked or scheduled appointments can be cancelled");
|
||||||
}
|
}
|
||||||
|
|
||||||
appointment.setAppointmentStatus("Cancelled");
|
appointment.setAppointmentStatus("Cancelled");
|
||||||
@@ -311,7 +311,7 @@ public class AppointmentService {
|
|||||||
if ("Booked".equalsIgnoreCase(request.getAppointmentStatus())) {
|
if ("Booked".equalsIgnoreCase(request.getAppointmentStatus())) {
|
||||||
LocalDateTime appointmentDateTime = LocalDateTime.of(request.getAppointmentDate(), request.getAppointmentTime());
|
LocalDateTime appointmentDateTime = LocalDateTime.of(request.getAppointmentDate(), request.getAppointmentTime());
|
||||||
if (appointmentDateTime.isBefore(LocalDateTime.now())) {
|
if (appointmentDateTime.isBefore(LocalDateTime.now())) {
|
||||||
throw new IllegalArgumentException("Booked appointments must be scheduled in the future");
|
throw new BusinessException("Booked appointments must be scheduled in the future");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -363,7 +363,7 @@ public class AppointmentService {
|
|||||||
boolean assignedToStore = assignableUsers.stream()
|
boolean assignedToStore = assignableUsers.stream()
|
||||||
.anyMatch(u -> u.getId().equals(requestedEmployeeId));
|
.anyMatch(u -> u.getId().equals(requestedEmployeeId));
|
||||||
if (!assignedToStore) {
|
if (!assignedToStore) {
|
||||||
throw new IllegalArgumentException("Selected employee is not assignable for the selected store");
|
throw new BusinessException("Selected employee is not assignable for the selected store");
|
||||||
}
|
}
|
||||||
return employee;
|
return employee;
|
||||||
}
|
}
|
||||||
@@ -377,7 +377,7 @@ public class AppointmentService {
|
|||||||
List<Appointment> existingAppointments = appointmentRepository
|
List<Appointment> existingAppointments = appointmentRepository
|
||||||
.findByEmployeeIdAndAppointmentDate(employee.getId(), date);
|
.findByEmployeeIdAndAppointmentDate(employee.getId(), date);
|
||||||
if (!isSlotAvailable(existingAppointments, service, time, appointmentIdToIgnore)) {
|
if (!isSlotAvailable(existingAppointments, service, time, appointmentIdToIgnore)) {
|
||||||
throw new IllegalArgumentException("The selected employee is already booked for this time slot");
|
throw new BusinessException("The selected employee is already booked for this time slot");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,7 +401,7 @@ public class AppointmentService {
|
|||||||
List<Appointment> existingAppointments = appointmentRepository
|
List<Appointment> existingAppointments = appointmentRepository
|
||||||
.findByPetIdAndAppointmentDate(pet.getPetId(), date);
|
.findByPetIdAndAppointmentDate(pet.getPetId(), date);
|
||||||
if (!isSlotAvailable(existingAppointments, service, time, appointmentIdToIgnore)) {
|
if (!isSlotAvailable(existingAppointments, service, time, appointmentIdToIgnore)) {
|
||||||
throw new IllegalArgumentException("This pet already has an appointment during this time slot");
|
throw new BusinessException("This pet already has an appointment during this time slot");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.petshop.backend.dto.common.BulkDeleteRequest;
|
|||||||
import com.petshop.backend.dto.common.CouponRequest;
|
import com.petshop.backend.dto.common.CouponRequest;
|
||||||
import com.petshop.backend.dto.common.CouponResponse;
|
import com.petshop.backend.dto.common.CouponResponse;
|
||||||
import com.petshop.backend.entity.Coupon;
|
import com.petshop.backend.entity.Coupon;
|
||||||
|
import com.petshop.backend.exception.BusinessException;
|
||||||
import com.petshop.backend.exception.ResourceNotFoundException;
|
import com.petshop.backend.exception.ResourceNotFoundException;
|
||||||
import com.petshop.backend.repository.CouponRepository;
|
import com.petshop.backend.repository.CouponRepository;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
@@ -39,7 +40,7 @@ public class CouponService {
|
|||||||
@Transactional
|
@Transactional
|
||||||
public CouponResponse createCoupon(CouponRequest request) {
|
public CouponResponse createCoupon(CouponRequest request) {
|
||||||
if (couponRepository.findByCouponCode(request.getCouponCode()).isPresent()) {
|
if (couponRepository.findByCouponCode(request.getCouponCode()).isPresent()) {
|
||||||
throw new IllegalArgumentException("Coupon code already exists: " + request.getCouponCode());
|
throw new BusinessException("Coupon code already exists: " + request.getCouponCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
Coupon coupon = new Coupon();
|
Coupon coupon = new Coupon();
|
||||||
@@ -55,7 +56,7 @@ public class CouponService {
|
|||||||
|
|
||||||
couponRepository.findByCouponCode(request.getCouponCode()).ifPresent(existing -> {
|
couponRepository.findByCouponCode(request.getCouponCode()).ifPresent(existing -> {
|
||||||
if (!existing.getCouponId().equals(id)) {
|
if (!existing.getCouponId().equals(id)) {
|
||||||
throw new IllegalArgumentException("Coupon code already exists: " + request.getCouponCode());
|
throw new BusinessException("Coupon code already exists: " + request.getCouponCode());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import com.petshop.backend.entity.Adoption;
|
|||||||
import com.petshop.backend.entity.Pet;
|
import com.petshop.backend.entity.Pet;
|
||||||
import com.petshop.backend.entity.StoreLocation;
|
import com.petshop.backend.entity.StoreLocation;
|
||||||
import com.petshop.backend.entity.User;
|
import com.petshop.backend.entity.User;
|
||||||
|
import com.petshop.backend.exception.BusinessException;
|
||||||
import com.petshop.backend.exception.ResourceNotFoundException;
|
import com.petshop.backend.exception.ResourceNotFoundException;
|
||||||
import com.petshop.backend.security.AppPrincipal;
|
import com.petshop.backend.security.AppPrincipal;
|
||||||
import com.petshop.backend.repository.AdoptionRepository;
|
import com.petshop.backend.repository.AdoptionRepository;
|
||||||
@@ -125,7 +126,7 @@ public class PetService {
|
|||||||
boolean hasBooked = linkedAppointments.stream()
|
boolean hasBooked = linkedAppointments.stream()
|
||||||
.anyMatch(a -> "Booked".equalsIgnoreCase(a.getAppointmentStatus()));
|
.anyMatch(a -> "Booked".equalsIgnoreCase(a.getAppointmentStatus()));
|
||||||
if (hasBooked) {
|
if (hasBooked) {
|
||||||
throw new IllegalArgumentException(
|
throw new BusinessException(
|
||||||
"Your pet has a booked appointment. Please cancel the appointment before removing your pet from our database.");
|
"Your pet has a booked appointment. Please cancel the appointment before removing your pet from our database.");
|
||||||
}
|
}
|
||||||
// Nullify the pet reference on non-booked appointments to avoid FK constraint violations
|
// Nullify the pet reference on non-booked appointments to avoid FK constraint violations
|
||||||
@@ -280,18 +281,18 @@ public class PetService {
|
|||||||
|
|
||||||
private void validateImageFile(MultipartFile file) {
|
private void validateImageFile(MultipartFile file) {
|
||||||
if (file == null || file.isEmpty()) {
|
if (file == null || file.isEmpty()) {
|
||||||
throw new IllegalArgumentException("Please select an image to upload");
|
throw new BusinessException("Please select an image to upload");
|
||||||
}
|
}
|
||||||
if (file.getSize() > 5 * 1024 * 1024) {
|
if (file.getSize() > 5 * 1024 * 1024) {
|
||||||
throw new IllegalArgumentException("Image file size must be less than 5MB");
|
throw new BusinessException("Image file size must be less than 5MB");
|
||||||
}
|
}
|
||||||
String contentType = file.getContentType();
|
String contentType = file.getContentType();
|
||||||
if (contentType == null) {
|
if (contentType == null) {
|
||||||
throw new IllegalArgumentException("Only JPG, PNG, and GIF images are allowed");
|
throw new BusinessException("Only JPG, PNG, and GIF images are allowed");
|
||||||
}
|
}
|
||||||
String normalized = contentType.toLowerCase(Locale.ROOT);
|
String normalized = contentType.toLowerCase(Locale.ROOT);
|
||||||
if (!normalized.equals("image/jpeg") && !normalized.equals("image/png") && !normalized.equals("image/gif")) {
|
if (!normalized.equals("image/jpeg") && !normalized.equals("image/png") && !normalized.equals("image/gif")) {
|
||||||
throw new IllegalArgumentException("Only JPG, PNG, and GIF images are allowed");
|
throw new BusinessException("Only JPG, PNG, and GIF images are allowed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -135,18 +135,18 @@ public class ProductService {
|
|||||||
|
|
||||||
private void validateImageFile(MultipartFile file) {
|
private void validateImageFile(MultipartFile file) {
|
||||||
if (file == null || file.isEmpty()) {
|
if (file == null || file.isEmpty()) {
|
||||||
throw new IllegalArgumentException("Please select an image to upload");
|
throw new BusinessException("Please select an image to upload");
|
||||||
}
|
}
|
||||||
if (file.getSize() > 5 * 1024 * 1024) {
|
if (file.getSize() > 5 * 1024 * 1024) {
|
||||||
throw new IllegalArgumentException("Image file size must be less than 5MB");
|
throw new BusinessException("Image file size must be less than 5MB");
|
||||||
}
|
}
|
||||||
String contentType = file.getContentType();
|
String contentType = file.getContentType();
|
||||||
if (contentType == null) {
|
if (contentType == null) {
|
||||||
throw new IllegalArgumentException("Only JPG, PNG, and GIF images are allowed");
|
throw new BusinessException("Only JPG, PNG, and GIF images are allowed");
|
||||||
}
|
}
|
||||||
String normalized = contentType.toLowerCase(Locale.ROOT);
|
String normalized = contentType.toLowerCase(Locale.ROOT);
|
||||||
if (!normalized.equals("image/jpeg") && !normalized.equals("image/png") && !normalized.equals("image/gif")) {
|
if (!normalized.equals("image/jpeg") && !normalized.equals("image/png") && !normalized.equals("image/gif")) {
|
||||||
throw new IllegalArgumentException("Only JPG, PNG, and GIF images are allowed");
|
throw new BusinessException("Only JPG, PNG, and GIF images are allowed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,11 @@ package com.petshop.backend.util;
|
|||||||
import com.petshop.backend.entity.User;
|
import com.petshop.backend.entity.User;
|
||||||
import com.petshop.backend.repository.UserRepository;
|
import com.petshop.backend.repository.UserRepository;
|
||||||
import com.petshop.backend.security.AppPrincipal;
|
import com.petshop.backend.security.AppPrincipal;
|
||||||
|
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
|
||||||
|
import org.springframework.security.authentication.AuthenticationServiceException;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@@ -13,7 +16,7 @@ public class AuthenticationHelper {
|
|||||||
public static Authentication getAuthentication() {
|
public static Authentication getAuthentication() {
|
||||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||||
if (authentication == null || !authentication.isAuthenticated()) {
|
if (authentication == null || !authentication.isAuthenticated()) {
|
||||||
throw new RuntimeException("No authenticated user found");
|
throw new AuthenticationCredentialsNotFoundException("No authenticated user found");
|
||||||
}
|
}
|
||||||
return authentication;
|
return authentication;
|
||||||
}
|
}
|
||||||
@@ -23,7 +26,7 @@ public class AuthenticationHelper {
|
|||||||
if (principal instanceof AppPrincipal appPrincipal) {
|
if (principal instanceof AppPrincipal appPrincipal) {
|
||||||
return appPrincipal;
|
return appPrincipal;
|
||||||
}
|
}
|
||||||
throw new RuntimeException("Authenticated principal is not supported");
|
throw new AuthenticationServiceException("Authenticated principal is not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Long getAuthenticatedUserId() {
|
public static Long getAuthenticatedUserId() {
|
||||||
@@ -36,11 +39,11 @@ public class AuthenticationHelper {
|
|||||||
|
|
||||||
if (principal instanceof AppPrincipal appPrincipal) {
|
if (principal instanceof AppPrincipal appPrincipal) {
|
||||||
return userRepository.findById(appPrincipal.getUserId())
|
return userRepository.findById(appPrincipal.getUserId())
|
||||||
.orElseThrow(() -> new RuntimeException("User not found: " + appPrincipal.getUserId()));
|
.orElseThrow(() -> new UsernameNotFoundException("User not found: " + appPrincipal.getUserId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
String username = authentication.getName();
|
String username = authentication.getName();
|
||||||
return userRepository.findByUsername(username)
|
return userRepository.findByUsername(username)
|
||||||
.orElseThrow(() -> new RuntimeException("User not found: " + username));
|
.orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user