From b17ca4fbbd480b30e366b3046131b18cdbd67f47 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Fri, 17 Apr 2026 13:31:00 -0600 Subject: [PATCH] unify error handling --- .../backend/controller/AuthController.java | 89 +++++-------------- .../backend/exception/ConflictException.java | 7 ++ .../exception/GlobalExceptionHandler.java | 17 ++++ .../backend/service/AdoptionService.java | 19 ++-- .../backend/service/AppointmentService.java | 16 ++-- .../backend/service/CouponService.java | 5 +- .../petshop/backend/service/PetService.java | 11 +-- .../backend/service/ProductService.java | 8 +- .../backend/util/AuthenticationHelper.java | 11 ++- 9 files changed, 85 insertions(+), 98 deletions(-) create mode 100644 backend/src/main/java/com/petshop/backend/exception/ConflictException.java diff --git a/backend/src/main/java/com/petshop/backend/controller/AuthController.java b/backend/src/main/java/com/petshop/backend/controller/AuthController.java index 58ac9d06..1b88551c 100644 --- a/backend/src/main/java/com/petshop/backend/controller/AuthController.java +++ b/backend/src/main/java/com/petshop/backend/controller/AuthController.java @@ -13,6 +13,9 @@ import com.petshop.backend.dto.auth.ResetPasswordResponse; import com.petshop.backend.dto.auth.UserInfoResponse; import com.petshop.backend.entity.StoreLocation; 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.security.JwtUtil; import com.petshop.backend.security.UserAuthCacheService; @@ -74,7 +77,7 @@ public class AuthController { } @PostMapping("/register") - public ResponseEntity register(@Valid @RequestBody RegisterRequest request) { + public ResponseEntity register(@Valid @RequestBody RegisterRequest request) { String username = trimToNull(request.getUsername()); String email = trimToNull(request.getEmail()); String firstName = trimToNull(request.getFirstName()); @@ -83,23 +86,17 @@ public class AuthController { if (userRepository.findByUsername(username).isPresent()) { log.warn("Registration rejected: username already exists ({})", username); - Map error = new HashMap<>(); - error.put("message", "Username already exists"); - return ResponseEntity.status(HttpStatus.CONFLICT).body(error); + throw new ConflictException("Username already exists"); } if (userRepository.findByEmail(email).isPresent()) { log.warn("Registration rejected: email already exists"); - Map error = new HashMap<>(); - error.put("message", "Email already exists"); - return ResponseEntity.status(HttpStatus.CONFLICT).body(error); + throw new ConflictException("Email already exists"); } if (phone != null && userRepository.findByPhone(phone).isPresent()) { log.warn("Registration rejected: phone already exists"); - Map error = new HashMap<>(); - error.put("message", "Phone already exists"); - return ResponseEntity.status(HttpStatus.CONFLICT).body(error); + throw new ConflictException("Phone already exists"); } User user = new User(); @@ -117,9 +114,7 @@ public class AuthController { try { savedUser = userRepository.save(user); } catch (DataIntegrityViolationException e) { - Map error = new HashMap<>(); - error.put("message", "Username, email, or phone already exists"); - return ResponseEntity.status(HttpStatus.CONFLICT).body(error); + throw new ConflictException("Username, email, or phone already exists"); } emailService.sendWelcome(savedUser); @@ -137,7 +132,7 @@ public class AuthController { } @PostMapping("/login") - public ResponseEntity login(@Valid @RequestBody LoginRequest request) { + public ResponseEntity login(@Valid @RequestBody LoginRequest request) { try { authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword()) @@ -159,21 +154,15 @@ public class AuthController { } catch (BadCredentialsException e) { log.warn("Login failed for username {}", request.getUsername()); - Map error = new HashMap<>(); - error.put("message", "Invalid username or password"); - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(error); + throw e; } catch (InternalAuthenticationServiceException e) { if (e.getCause() instanceof DisabledException disabledException) { log.warn("Login denied for disabled user {}", request.getUsername()); - Map error = new HashMap<>(); - error.put("message", disabledException.getMessage()); - return ResponseEntity.status(HttpStatus.FORBIDDEN).body(error); + throw disabledException; } throw e; } catch (DisabledException e) { - Map error = new HashMap<>(); - error.put("message", e.getMessage()); - return ResponseEntity.status(HttpStatus.FORBIDDEN).body(error); + throw e; } } @@ -196,7 +185,7 @@ public class AuthController { @Transactional @PutMapping("/me") - public ResponseEntity updateProfile(@Valid @RequestBody ProfileUpdateRequest request) { + public ResponseEntity updateProfile(@Valid @RequestBody ProfileUpdateRequest request) { Long userId = AuthenticationHelper.getAuthenticatedUserId(); User user = userRepository.findByIdForUpdate(userId) .orElseThrow(() -> new UsernameNotFoundException("User not found")); @@ -205,9 +194,7 @@ public class AuthController { String username = trimToNull(request.getUsername()); if (username != null && !username.equals(user.getUsername())) { if (userRepository.findByUsername(username).isPresent()) { - Map error = new HashMap<>(); - error.put("message", "Username already exists"); - return ResponseEntity.status(HttpStatus.CONFLICT).body(error); + throw new ConflictException("Username already exists"); } user.setUsername(username); invalidateToken = true; @@ -216,9 +203,7 @@ public class AuthController { String email = trimToNull(request.getEmail()); if (email != null && !email.equals(user.getEmail())) { if (userRepository.findByEmail(email).isPresent()) { - Map error = new HashMap<>(); - error.put("message", "Email already exists"); - return ResponseEntity.status(HttpStatus.CONFLICT).body(error); + throw new ConflictException("Email already exists"); } user.setEmail(email); } @@ -241,9 +226,7 @@ public class AuthController { if (phone != null && userRepository.findByPhone(phone) .filter(existing -> !existing.getId().equals(user.getId())) .isPresent()) { - Map error = new HashMap<>(); - error.put("message", "Phone already exists"); - return ResponseEntity.status(HttpStatus.CONFLICT).body(error); + throw new ConflictException("Phone already exists"); } user.setPhone(phone); } @@ -262,9 +245,7 @@ public class AuthController { try { updatedUser = userRepository.save(user); } catch (DataIntegrityViolationException e) { - Map error = new HashMap<>(); - error.put("message", "Username, email, or phone already exists"); - return ResponseEntity.status(HttpStatus.CONFLICT).body(error); + throw new ConflictException("Username, email, or phone already exists"); } userAuthCacheService.evict(updatedUser.getId()); return ResponseEntity.ok(toUserInfoResponse(updatedUser)); @@ -306,17 +287,6 @@ public class AuthController { 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) { String first = trimToNull(firstName); String last = trimToNull(lastName); @@ -329,30 +299,21 @@ public class AuthController { return first + " " + last; } - private record NameParts(String firstName, String lastName, String fullName) { - } - @PostMapping("/me/avatar") - public ResponseEntity uploadAvatar(@RequestParam("avatar") MultipartFile file) { + public ResponseEntity uploadAvatar(@RequestParam("avatar") MultipartFile file) { User user = getAuthenticatedUser(); if (file.isEmpty()) { - Map error = new HashMap<>(); - error.put("message", "Please select a file to upload"); - return ResponseEntity.badRequest().body(error); + throw new BusinessException("Please select a file to upload"); } if (file.getSize() > 5 * 1024 * 1024) { - Map error = new HashMap<>(); - error.put("message", "File size must not exceed 5MB"); - return ResponseEntity.badRequest().body(error); + throw new BusinessException("File size must not exceed 5MB"); } String contentType = file.getContentType(); if (contentType == null || (!contentType.equals("image/jpeg") && !contentType.equals("image/png") && !contentType.equals("image/gif"))) { - Map error = new HashMap<>(); - error.put("message", "Only JPG, PNG, and GIF images are allowed"); - return ResponseEntity.badRequest().body(error); + throw new BusinessException("Only JPG, PNG, and GIF images are allowed"); } try { @@ -364,9 +325,7 @@ public class AuthController { return ResponseEntity.ok(new AvatarUploadResponse(avatarStorageService.toOwnerAvatarUrl(user), "Avatar uploaded successfully")); } catch (IOException e) { - Map error = new HashMap<>(); - error.put("message", "Failed to upload avatar: " + e.getMessage()); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error); + throw new BusinessException("Failed to upload avatar: " + e.getMessage()); } } @@ -375,9 +334,7 @@ public class AuthController { User user = getAuthenticatedUser(); if (!avatarStorageService.hasAvatar(user)) { - Map error = new HashMap<>(); - error.put("message", "No avatar uploaded"); - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error); + throw new ResourceNotFoundException("No avatar uploaded"); } Map response = new HashMap<>(); diff --git a/backend/src/main/java/com/petshop/backend/exception/ConflictException.java b/backend/src/main/java/com/petshop/backend/exception/ConflictException.java new file mode 100644 index 00000000..a0112782 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/exception/ConflictException.java @@ -0,0 +1,7 @@ +package com.petshop.backend.exception; + +public class ConflictException extends RuntimeException { + public ConflictException(String message) { + super(message); + } +} diff --git a/backend/src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java b/backend/src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java index 7c2b475f..29b7f10d 100644 --- a/backend/src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java +++ b/backend/src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java @@ -5,6 +5,8 @@ import jakarta.servlet.http.HttpServletRequest; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.data.core.PropertyReferenceException; 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.ResponseEntity; import org.springframework.validation.FieldError; @@ -33,6 +35,11 @@ public class GlobalExceptionHandler { return buildErrorResponse(HttpStatus.BAD_REQUEST, ex.getMessage(), ex, request); } + @ExceptionHandler(ConflictException.class) + public ResponseEntity handleConflictException(ConflictException ex, HttpServletRequest request) { + return buildErrorResponse(HttpStatus.CONFLICT, ex.getMessage(), ex, request); + } + @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity> handleValidationExceptions(MethodArgumentNotValidException ex, HttpServletRequest request) { Map errors = new HashMap<>(); @@ -54,6 +61,16 @@ public class GlobalExceptionHandler { return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response); } + @ExceptionHandler(BadCredentialsException.class) + public ResponseEntity handleBadCredentials(BadCredentialsException ex, HttpServletRequest request) { + return buildErrorResponse(HttpStatus.UNAUTHORIZED, "Invalid username or password", ex, request); + } + + @ExceptionHandler(DisabledException.class) + public ResponseEntity handleDisabledException(DisabledException ex, HttpServletRequest request) { + return buildErrorResponse(HttpStatus.FORBIDDEN, ex.getMessage(), ex, request); + } + @ExceptionHandler(org.springframework.security.access.AccessDeniedException.class) public ResponseEntity handleAccessDeniedException(org.springframework.security.access.AccessDeniedException ex, HttpServletRequest request) { return buildErrorResponse(HttpStatus.FORBIDDEN, ex.getMessage(), ex, request); diff --git a/backend/src/main/java/com/petshop/backend/service/AdoptionService.java b/backend/src/main/java/com/petshop/backend/service/AdoptionService.java index 64eb1c11..8af4a82c 100644 --- a/backend/src/main/java/com/petshop/backend/service/AdoptionService.java +++ b/backend/src/main/java/com/petshop/backend/service/AdoptionService.java @@ -8,6 +8,7 @@ import com.petshop.backend.entity.Pet; import com.petshop.backend.entity.Sale; import com.petshop.backend.entity.StoreLocation; import com.petshop.backend.entity.User; +import com.petshop.backend.exception.BusinessException; import com.petshop.backend.exception.ResourceNotFoundException; import com.petshop.backend.event.AdoptionConfirmedEvent; import com.petshop.backend.event.SaleReceiptEvent; @@ -96,7 +97,7 @@ public class AdoptionService { String adoptionStatus = normalizeAdoptionStatus(request.getAdoptionStatus()); validatePetAvailability(pet, null, null); 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(); @@ -139,7 +140,7 @@ public class AdoptionService { Long currentPetId = adoption.getPet() != null ? adoption.getPet().getPetId() : null; validatePetAvailability(pet, adoption.getAdoptionId(), currentPetId); 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); @@ -168,7 +169,7 @@ public class AdoptionService { // Verify the pet is actually located at the claimed store 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 @@ -203,7 +204,7 @@ public class AdoptionService { } 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); @@ -280,7 +281,7 @@ public class AdoptionService { User employee = userRepository.findById(requestedEmployeeId) .orElseThrow(() -> new ResourceNotFoundException("Employee not found with id: " + requestedEmployeeId)); 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; } @@ -295,7 +296,7 @@ public class AdoptionService { private String normalizeAdoptionStatus(String adoptionStatus) { if (adoptionStatus == null) { - throw new IllegalArgumentException("Adoption status is required"); + throw new BusinessException("Adoption status is required"); } String trimmedStatus = adoptionStatus.trim(); if (ADOPTION_STATUS_PENDING.equalsIgnoreCase(trimmedStatus)) { @@ -310,7 +311,7 @@ public class AdoptionService { if (ADOPTION_STATUS_MISSED.equalsIgnoreCase(trimmedStatus)) { 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) { @@ -319,11 +320,11 @@ public class AdoptionService { ? adoptionRepository.existsByPet_IdAndAdoptionStatusIgnoreCase(pet.getPetId(), ADOPTION_STATUS_COMPLETED) : adoptionRepository.existsByPet_IdAndAdoptionStatusIgnoreCaseAndAdoptionIdNot(pet.getPetId(), ADOPTION_STATUS_COMPLETED, adoptionId); 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())) { - throw new IllegalArgumentException("Selected pet is not available for adoption"); + throw new BusinessException("Selected pet is not available for adoption"); } } diff --git a/backend/src/main/java/com/petshop/backend/service/AppointmentService.java b/backend/src/main/java/com/petshop/backend/service/AppointmentService.java index bc8ece99..1c0b2f1d 100644 --- a/backend/src/main/java/com/petshop/backend/service/AppointmentService.java +++ b/backend/src/main/java/com/petshop/backend/service/AppointmentService.java @@ -116,14 +116,14 @@ public class AppointmentService { // Customers must supply a pet that is Adopted and owned by them if (User.Role.CUSTOMER.equals(authenticatedUser.getRole())) { 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())) { - 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(); 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(); 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"); @@ -311,7 +311,7 @@ public class AppointmentService { if ("Booked".equalsIgnoreCase(request.getAppointmentStatus())) { LocalDateTime appointmentDateTime = LocalDateTime.of(request.getAppointmentDate(), request.getAppointmentTime()); 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() .anyMatch(u -> u.getId().equals(requestedEmployeeId)); 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; } @@ -377,7 +377,7 @@ public class AppointmentService { List existingAppointments = appointmentRepository .findByEmployeeIdAndAppointmentDate(employee.getId(), date); 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 existingAppointments = appointmentRepository .findByPetIdAndAppointmentDate(pet.getPetId(), date); 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"); } } diff --git a/backend/src/main/java/com/petshop/backend/service/CouponService.java b/backend/src/main/java/com/petshop/backend/service/CouponService.java index 92d67819..89cac69a 100644 --- a/backend/src/main/java/com/petshop/backend/service/CouponService.java +++ b/backend/src/main/java/com/petshop/backend/service/CouponService.java @@ -4,6 +4,7 @@ import com.petshop.backend.dto.common.BulkDeleteRequest; import com.petshop.backend.dto.common.CouponRequest; import com.petshop.backend.dto.common.CouponResponse; import com.petshop.backend.entity.Coupon; +import com.petshop.backend.exception.BusinessException; import com.petshop.backend.exception.ResourceNotFoundException; import com.petshop.backend.repository.CouponRepository; import org.springframework.data.domain.Page; @@ -39,7 +40,7 @@ public class CouponService { @Transactional public CouponResponse createCoupon(CouponRequest request) { 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(); @@ -55,7 +56,7 @@ public class CouponService { couponRepository.findByCouponCode(request.getCouponCode()).ifPresent(existing -> { if (!existing.getCouponId().equals(id)) { - throw new IllegalArgumentException("Coupon code already exists: " + request.getCouponCode()); + throw new BusinessException("Coupon code already exists: " + request.getCouponCode()); } }); diff --git a/backend/src/main/java/com/petshop/backend/service/PetService.java b/backend/src/main/java/com/petshop/backend/service/PetService.java index dd86eb4a..f901b797 100644 --- a/backend/src/main/java/com/petshop/backend/service/PetService.java +++ b/backend/src/main/java/com/petshop/backend/service/PetService.java @@ -9,6 +9,7 @@ import com.petshop.backend.entity.Adoption; import com.petshop.backend.entity.Pet; import com.petshop.backend.entity.StoreLocation; import com.petshop.backend.entity.User; +import com.petshop.backend.exception.BusinessException; import com.petshop.backend.exception.ResourceNotFoundException; import com.petshop.backend.security.AppPrincipal; import com.petshop.backend.repository.AdoptionRepository; @@ -125,7 +126,7 @@ public class PetService { boolean hasBooked = linkedAppointments.stream() .anyMatch(a -> "Booked".equalsIgnoreCase(a.getAppointmentStatus())); 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."); } // 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) { 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) { - 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(); 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); 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"); } } diff --git a/backend/src/main/java/com/petshop/backend/service/ProductService.java b/backend/src/main/java/com/petshop/backend/service/ProductService.java index 0b71d5b2..5234ef22 100644 --- a/backend/src/main/java/com/petshop/backend/service/ProductService.java +++ b/backend/src/main/java/com/petshop/backend/service/ProductService.java @@ -135,18 +135,18 @@ public class ProductService { private void validateImageFile(MultipartFile file) { 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) { - 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(); 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); 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"); } } diff --git a/backend/src/main/java/com/petshop/backend/util/AuthenticationHelper.java b/backend/src/main/java/com/petshop/backend/util/AuthenticationHelper.java index 66c7418f..c78e87d3 100644 --- a/backend/src/main/java/com/petshop/backend/util/AuthenticationHelper.java +++ b/backend/src/main/java/com/petshop/backend/util/AuthenticationHelper.java @@ -3,8 +3,11 @@ package com.petshop.backend.util; import com.petshop.backend.entity.User; import com.petshop.backend.repository.UserRepository; 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.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Component; @Component @@ -13,7 +16,7 @@ public class AuthenticationHelper { public static Authentication getAuthentication() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication == null || !authentication.isAuthenticated()) { - throw new RuntimeException("No authenticated user found"); + throw new AuthenticationCredentialsNotFoundException("No authenticated user found"); } return authentication; } @@ -23,7 +26,7 @@ public class AuthenticationHelper { if (principal instanceof AppPrincipal appPrincipal) { return appPrincipal; } - throw new RuntimeException("Authenticated principal is not supported"); + throw new AuthenticationServiceException("Authenticated principal is not supported"); } public static Long getAuthenticatedUserId() { @@ -36,11 +39,11 @@ public class AuthenticationHelper { if (principal instanceof AppPrincipal appPrincipal) { 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(); return userRepository.findByUsername(username) - .orElseThrow(() -> new RuntimeException("User not found: " + username)); + .orElseThrow(() -> new UsernameNotFoundException("User not found: " + username)); } }