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 a71c3777..eb91c6dc 100644 --- a/backend/src/main/java/com/petshop/backend/controller/AuthController.java +++ b/backend/src/main/java/com/petshop/backend/controller/AuthController.java @@ -309,17 +309,18 @@ public class AuthController { public ResponseEntity getAvatarFile() { User user = authHelper.getAuthenticatedUser(); - if (!avatarStorageService.hasAvatar(user)) { - return ResponseEntity.notFound().build(); + if (avatarStorageService.hasAvatar(user)) { + try { + Resource resource = avatarStorageService.loadAvatarResource(user); + MediaType mediaType = avatarStorageService.resolveMediaType(user); + return ResponseEntity.ok().contentType(mediaType).body(resource); + } catch (IllegalArgumentException ignored) { + } } - try { - Resource resource = avatarStorageService.loadAvatarResource(user); - MediaType mediaType = avatarStorageService.resolveMediaType(user); - return ResponseEntity.ok().contentType(mediaType).body(resource); - } catch (IllegalArgumentException ex) { - return ResponseEntity.notFound().build(); - } + return ResponseEntity.ok() + .contentType(MediaType.valueOf("image/svg+xml")) + .body(avatarStorageService.loadDefaultAvatarResource()); } @DeleteMapping("/me/avatar") diff --git a/backend/src/main/java/com/petshop/backend/controller/UserAvatarController.java b/backend/src/main/java/com/petshop/backend/controller/UserAvatarController.java index 602cfe54..ac7d146c 100644 --- a/backend/src/main/java/com/petshop/backend/controller/UserAvatarController.java +++ b/backend/src/main/java/com/petshop/backend/controller/UserAvatarController.java @@ -7,6 +7,7 @@ import com.petshop.backend.repository.UserRepository; import com.petshop.backend.service.AvatarStorageService; import com.petshop.backend.util.ImageValidationUtil; import org.springframework.core.io.Resource; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.DeleteMapping; @@ -42,18 +43,23 @@ public class UserAvatarController { @PreAuthorize("isAuthenticated()") public ResponseEntity getUserAvatarFile(@PathVariable Long userId) { User user = userRepository.findById(userId).orElse(null); - if (user == null || !avatarStorageService.hasAvatar(user)) { + if (user == null) { return ResponseEntity.notFound().build(); } - try { - Resource resource = avatarStorageService.loadAvatarResource(user); - return ResponseEntity.ok() - .contentType(avatarStorageService.resolveMediaType(user)) - .body(resource); - } catch (IllegalArgumentException ex) { - return ResponseEntity.notFound().build(); + if (avatarStorageService.hasAvatar(user)) { + try { + Resource resource = avatarStorageService.loadAvatarResource(user); + return ResponseEntity.ok() + .contentType(avatarStorageService.resolveMediaType(user)) + .body(resource); + } catch (IllegalArgumentException ignored) { + } } + + return ResponseEntity.ok() + .contentType(MediaType.valueOf("image/svg+xml")) + .body(avatarStorageService.loadDefaultAvatarResource()); } @PostMapping("/{userId}/avatar") 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 5ffcc16d..832dc12e 100644 --- a/backend/src/main/java/com/petshop/backend/service/AppointmentService.java +++ b/backend/src/main/java/com/petshop/backend/service/AppointmentService.java @@ -143,7 +143,7 @@ public class AppointmentService { appointment.setEmployee(employee); appointment.setAppointmentDate(request.getAppointmentDate()); appointment.setAppointmentTime(request.getAppointmentTime()); - appointment.setAppointmentStatus(request.getAppointmentStatus()); + appointment.setAppointmentStatus("Scheduled"); appointment.setPet(pet); appointment = appointmentRepository.save(appointment); @@ -304,11 +304,9 @@ public class AppointmentService { } private void validateAppointmentRequest(AppointmentRequest request) { - if ("Booked".equalsIgnoreCase(request.getAppointmentStatus())) { - LocalDateTime appointmentDateTime = LocalDateTime.of(request.getAppointmentDate(), request.getAppointmentTime()); - if (appointmentDateTime.isBefore(LocalDateTime.now())) { - throw new BusinessException("Booked appointments must be scheduled in the future"); - } + LocalDateTime appointmentDateTime = LocalDateTime.of(request.getAppointmentDate(), request.getAppointmentTime()); + if (appointmentDateTime.isBefore(LocalDateTime.now())) { + throw new BusinessException("Appointments must be scheduled in the future"); } } diff --git a/backend/src/main/java/com/petshop/backend/service/AvatarStorageService.java b/backend/src/main/java/com/petshop/backend/service/AvatarStorageService.java index 59442d4f..5ff61021 100644 --- a/backend/src/main/java/com/petshop/backend/service/AvatarStorageService.java +++ b/backend/src/main/java/com/petshop/backend/service/AvatarStorageService.java @@ -13,6 +13,7 @@ import org.springframework.web.multipart.MultipartFile; import jakarta.annotation.PostConstruct; import java.io.IOException; +import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -54,7 +55,11 @@ public class AvatarStorageService { public Resource loadAvatarResource(User user) { String filename = extractFilename(user.getAvatarUrl()); if (blobService.isEnabled()) { - return new ByteArrayResource(blobService.download(BLOB_CONTAINER, filename)); + try { + return new ByteArrayResource(blobService.download(BLOB_CONTAINER, filename)); + } catch (Exception ex) { + throw new IllegalArgumentException("Avatar file was not found"); + } } Path filePath = resolveStoredAvatarPath(user.getAvatarUrl()); if (!Files.exists(filePath) || !Files.isRegularFile(filePath)) { @@ -63,6 +68,18 @@ public class AvatarStorageService { return new PathResource(filePath); } + public Resource loadDefaultAvatarResource() { + InputStream is = getClass().getResourceAsStream("/static/default-avatar.svg"); + if (is == null) { + throw new IllegalStateException("Default avatar resource not found"); + } + try { + return new ByteArrayResource(is.readAllBytes()); + } catch (IOException e) { + throw new IllegalStateException("Failed to read default avatar", e); + } + } + public void deleteAvatar(User user) throws IOException { if (user.getAvatarUrl() == null || user.getAvatarUrl().isBlank()) return; if (blobService.isEnabled()) { @@ -80,7 +97,7 @@ public class AvatarStorageService { } public String toOwnerAvatarUrl(User user) { - return hasAvatar(user) ? "/api/v1/users/" + user.getId() + "/avatar/file" : null; + return "/api/v1/users/" + user.getId() + "/avatar/file"; } public String toStoredAvatarUrl(String avatarFilenamePath) { diff --git a/backend/src/main/java/com/petshop/backend/service/ChatService.java b/backend/src/main/java/com/petshop/backend/service/ChatService.java index a1442d41..bf519f23 100644 --- a/backend/src/main/java/com/petshop/backend/service/ChatService.java +++ b/backend/src/main/java/com/petshop/backend/service/ChatService.java @@ -146,6 +146,12 @@ public class ChatService { } } + boolean hasContent = request.getContent() != null && !request.getContent().isBlank(); + boolean hasAttachment = request.getAttachmentUrl() != null && !request.getAttachmentUrl().isBlank(); + if (!hasContent && !hasAttachment) { + throw new BusinessException("Message must have content or an attachment"); + } + ContentFilter.validate(request.getContent()); Message message = new Message(); 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 9625cfed..18ff269c 100644 --- a/backend/src/main/java/com/petshop/backend/service/CouponService.java +++ b/backend/src/main/java/com/petshop/backend/service/CouponService.java @@ -89,6 +89,10 @@ public class CouponService { } private void updateCouponFields(Coupon coupon, CouponRequest request) { + if ("PERCENTAGE".equalsIgnoreCase(request.getDiscountType()) + && request.getDiscountValue().compareTo(new BigDecimal("100")) >= 0) { + throw new BusinessException("Percentage discount must be less than 100"); + } coupon.setCouponCode(request.getCouponCode()); coupon.setDiscountType(request.getDiscountType()); coupon.setDiscountValue(request.getDiscountValue()); diff --git a/backend/src/main/resources/static/default-avatar.svg b/backend/src/main/resources/static/default-avatar.svg new file mode 100644 index 00000000..abacf491 --- /dev/null +++ b/backend/src/main/resources/static/default-avatar.svg @@ -0,0 +1 @@ +