From 3a7621678def1d03ed03b78b4251609826811501 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Tue, 14 Apr 2026 19:29:43 -0600 Subject: [PATCH 1/5] Fix backend issues --- .../com/petshop/backend/controller/RefundController.java | 4 ++-- .../com/petshop/backend/controller/SaleController.java | 2 +- .../backend/exception/GlobalExceptionHandler.java | 3 ++- .../petshop/backend/repository/ProductRepository.java | 4 ++++ .../java/com/petshop/backend/service/ProductService.java | 9 +++++++++ backend/src/main/resources/application.yml | 2 ++ 6 files changed, 20 insertions(+), 4 deletions(-) diff --git a/backend/src/main/java/com/petshop/backend/controller/RefundController.java b/backend/src/main/java/com/petshop/backend/controller/RefundController.java index 0001df1e..92fd22b3 100644 --- a/backend/src/main/java/com/petshop/backend/controller/RefundController.java +++ b/backend/src/main/java/com/petshop/backend/controller/RefundController.java @@ -30,7 +30,7 @@ public class RefundController { } @PostMapping - @PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')") + @PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF')") public ResponseEntity createRefund(@Valid @RequestBody RefundRequest request) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); String role = authentication.getAuthorities().stream() @@ -85,7 +85,7 @@ public class RefundController { } @PutMapping("/{id}") - @PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") + @PreAuthorize("hasRole('STAFF')") public ResponseEntity updateRefund(@PathVariable Long id, @Valid @RequestBody RefundUpdateRequest request) { return ResponseEntity.ok(refundService.updateRefundStatus(id, request.getStatus())); } diff --git a/backend/src/main/java/com/petshop/backend/controller/SaleController.java b/backend/src/main/java/com/petshop/backend/controller/SaleController.java index dffa63e4..cdb64c78 100644 --- a/backend/src/main/java/com/petshop/backend/controller/SaleController.java +++ b/backend/src/main/java/com/petshop/backend/controller/SaleController.java @@ -40,7 +40,7 @@ public class SaleController { } @PostMapping - @PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") + @PreAuthorize("hasRole('STAFF')") public ResponseEntity createSale(@Valid @RequestBody SaleRequest request) { return ResponseEntity.status(HttpStatus.CREATED).body(saleService.createSale(request)); } 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 1f9434a5..7c2b475f 100644 --- a/backend/src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java +++ b/backend/src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java @@ -42,9 +42,10 @@ public class GlobalExceptionHandler { errors.put(fieldName, errorMessage); }); + String firstMessage = errors.values().stream().findFirst().orElse("Validation failed"); Map response = new HashMap<>(); response.put("status", HttpStatus.BAD_REQUEST.value()); - response.put("message", "Validation failed"); + response.put("message", firstMessage); response.put("errors", errors); response.put("details", buildDetails(ex)); response.put("path", request.getRequestURI()); diff --git a/backend/src/main/java/com/petshop/backend/repository/ProductRepository.java b/backend/src/main/java/com/petshop/backend/repository/ProductRepository.java index 7122e6ea..140031d0 100644 --- a/backend/src/main/java/com/petshop/backend/repository/ProductRepository.java +++ b/backend/src/main/java/com/petshop/backend/repository/ProductRepository.java @@ -11,6 +11,10 @@ import org.springframework.stereotype.Repository; @Repository public interface ProductRepository extends JpaRepository { + boolean existsByProdNameIgnoreCase(String prodName); + + boolean existsByProdNameIgnoreCaseAndProdIdNot(String prodName, Long prodId); + @Query("SELECT p FROM Product p WHERE " + "(:q IS NULL OR LOWER(p.prodName) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(COALESCE(p.prodDesc, '')) LIKE LOWER(CONCAT('%', :q, '%'))) AND " + "(:categoryId IS NULL OR p.category.categoryId = :categoryId)") 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 d18890d5..38f30e84 100644 --- a/backend/src/main/java/com/petshop/backend/service/ProductService.java +++ b/backend/src/main/java/com/petshop/backend/service/ProductService.java @@ -5,6 +5,7 @@ import com.petshop.backend.dto.product.ProductRequest; import com.petshop.backend.dto.product.ProductResponse; import com.petshop.backend.entity.Category; import com.petshop.backend.entity.Product; +import com.petshop.backend.exception.BusinessException; import com.petshop.backend.exception.ResourceNotFoundException; import com.petshop.backend.repository.CategoryRepository; import com.petshop.backend.repository.ProductRepository; @@ -45,6 +46,10 @@ public class ProductService { @Transactional public ProductResponse createProduct(ProductRequest request) { + if (productRepository.existsByProdNameIgnoreCase(request.getProdName())) { + throw new BusinessException("A product with this name already exists"); + } + Category category = categoryRepository.findById(request.getCategoryId()) .orElseThrow(() -> new ResourceNotFoundException("Category not found with id: " + request.getCategoryId())); @@ -63,6 +68,10 @@ public class ProductService { Product product = productRepository.findById(id) .orElseThrow(() -> new ResourceNotFoundException("Product not found with id: " + id)); + if (productRepository.existsByProdNameIgnoreCaseAndProdIdNot(request.getProdName(), id)) { + throw new BusinessException("A product with this name already exists"); + } + Category category = categoryRepository.findById(request.getCategoryId()) .orElseThrow(() -> new ResourceNotFoundException("Category not found with id: " + request.getCategoryId())); diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index 4f424b93..f65764c0 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -68,6 +68,8 @@ openrouter: model: ${OPENROUTER_MODEL:openrouter/free} logging: + file: + name: ${LOG_FILE:log.txt} level: com.petshop: ${LOG_LEVEL:INFO} org.springframework.security: ${LOG_LEVEL_SECURITY:WARN} From 51a48e07ebfed8cf9176cbd0ce6e5bdcaf2cfa02 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Tue, 14 Apr 2026 19:37:43 -0600 Subject: [PATCH 2/5] Fix stuck pet status --- .../backend/service/AdoptionService.java | 33 ++++++++++++++++--- 1 file changed, 28 insertions(+), 5 deletions(-) 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 8885b3c8..3e777060 100644 --- a/backend/src/main/java/com/petshop/backend/service/AdoptionService.java +++ b/backend/src/main/java/com/petshop/backend/service/AdoptionService.java @@ -22,6 +22,7 @@ import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; +import java.util.List; @Service public class AdoptionService { @@ -159,15 +160,37 @@ public class AdoptionService { @Transactional public void deleteAdoption(Long id) { - if (!adoptionRepository.existsById(id)) { - throw new ResourceNotFoundException("Adoption not found with id: " + id); - } - adoptionRepository.deleteById(id); + Adoption adoption = adoptionRepository.findById(id) + .orElseThrow(() -> new ResourceNotFoundException("Adoption not found with id: " + id)); + Pet pet = adoption.getPet(); + String status = adoption.getAdoptionStatus(); + adoptionRepository.delete(adoption); + resetPetIfPending(pet, status); } @Transactional public void bulkDeleteAdoptions(BulkDeleteRequest request) { - adoptionRepository.deleteAllById(request.getIds()); + List adoptions = adoptionRepository.findAllById(request.getIds()); + adoptionRepository.deleteAll(adoptions); + for (Adoption adoption : adoptions) { + resetPetIfPending(adoption.getPet(), adoption.getAdoptionStatus()); + } + } + + private void resetPetIfPending(Pet pet, String deletedAdoptionStatus) { + if (!ADOPTION_STATUS_PENDING.equalsIgnoreCase(deletedAdoptionStatus)) { + return; + } + if (!PET_STATUS_PENDING.equalsIgnoreCase(pet.getPetStatus())) { + return; + } + boolean completedElsewhere = adoptionRepository.existsByPet_IdAndAdoptionStatusIgnoreCase( + pet.getPetId(), ADOPTION_STATUS_COMPLETED); + if (!completedElsewhere) { + pet.setPetStatus(PET_STATUS_AVAILABLE); + pet.setOwner(null); + petRepository.save(pet); + } } private String normalizeFilter(String value) { From 0c63963ddf14d566dac0453c821c48cb8316dddc Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Tue, 14 Apr 2026 19:46:25 -0600 Subject: [PATCH 3/5] Drop status column --- .../purchaseorder/PurchaseOrderResponse.java | 17 +++-------------- .../petshop/backend/entity/PurchaseOrder.java | 15 +-------------- .../backend/service/PurchaseOrderService.java | 1 - .../V4__drop_purchase_order_status.sql | 1 + 4 files changed, 5 insertions(+), 29 deletions(-) create mode 100644 backend/src/main/resources/db/migration/V4__drop_purchase_order_status.sql diff --git a/backend/src/main/java/com/petshop/backend/dto/purchaseorder/PurchaseOrderResponse.java b/backend/src/main/java/com/petshop/backend/dto/purchaseorder/PurchaseOrderResponse.java index fbf7d6d9..98f81914 100644 --- a/backend/src/main/java/com/petshop/backend/dto/purchaseorder/PurchaseOrderResponse.java +++ b/backend/src/main/java/com/petshop/backend/dto/purchaseorder/PurchaseOrderResponse.java @@ -11,21 +11,19 @@ public class PurchaseOrderResponse { private Long storeId; private String storeName; private LocalDate orderDate; - private String status; private LocalDateTime createdAt; private LocalDateTime updatedAt; public PurchaseOrderResponse() { } - public PurchaseOrderResponse(Long purchaseOrderId, Long supId, String supplierName, Long storeId, String storeName, LocalDate orderDate, String status, LocalDateTime createdAt, LocalDateTime updatedAt) { + public PurchaseOrderResponse(Long purchaseOrderId, Long supId, String supplierName, Long storeId, String storeName, LocalDate orderDate, LocalDateTime createdAt, LocalDateTime updatedAt) { this.purchaseOrderId = purchaseOrderId; this.supId = supId; this.supplierName = supplierName; this.storeId = storeId; this.storeName = storeName; this.orderDate = orderDate; - this.status = status; this.createdAt = createdAt; this.updatedAt = updatedAt; } @@ -78,14 +76,6 @@ public class PurchaseOrderResponse { this.orderDate = orderDate; } - public String getStatus() { - return status; - } - - public void setStatus(String status) { - this.status = status; - } - public LocalDateTime getCreatedAt() { return createdAt; } @@ -107,12 +97,12 @@ public class PurchaseOrderResponse { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; PurchaseOrderResponse that = (PurchaseOrderResponse) o; - return Objects.equals(purchaseOrderId, that.purchaseOrderId) && Objects.equals(supId, that.supId) && Objects.equals(supplierName, that.supplierName) && Objects.equals(storeId, that.storeId) && Objects.equals(storeName, that.storeName) && Objects.equals(orderDate, that.orderDate) && Objects.equals(status, that.status) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); + return Objects.equals(purchaseOrderId, that.purchaseOrderId) && Objects.equals(supId, that.supId) && Objects.equals(supplierName, that.supplierName) && Objects.equals(storeId, that.storeId) && Objects.equals(storeName, that.storeName) && Objects.equals(orderDate, that.orderDate) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); } @Override public int hashCode() { - return Objects.hash(purchaseOrderId, supId, supplierName, storeId, storeName, orderDate, status, createdAt, updatedAt); + return Objects.hash(purchaseOrderId, supId, supplierName, storeId, storeName, orderDate, createdAt, updatedAt); } @Override @@ -124,7 +114,6 @@ public class PurchaseOrderResponse { ", storeId=" + storeId + ", storeName='" + storeName + '\'' + ", orderDate=" + orderDate + - ", status='" + status + '\'' + ", createdAt=" + createdAt + ", updatedAt=" + updatedAt + '}'; diff --git a/backend/src/main/java/com/petshop/backend/entity/PurchaseOrder.java b/backend/src/main/java/com/petshop/backend/entity/PurchaseOrder.java index aafe3d89..fd50c120 100644 --- a/backend/src/main/java/com/petshop/backend/entity/PurchaseOrder.java +++ b/backend/src/main/java/com/petshop/backend/entity/PurchaseOrder.java @@ -27,9 +27,6 @@ public class PurchaseOrder { @Column(nullable = false) private LocalDate orderDate; - @Column(nullable = false, length = 50) - private String status; - @CreationTimestamp @Column(name = "created_at", updatable = false) private LocalDateTime createdAt; @@ -41,11 +38,10 @@ public class PurchaseOrder { public PurchaseOrder() { } - public PurchaseOrder(Long purchaseOrderId, Supplier supplier, LocalDate orderDate, String status, LocalDateTime createdAt, LocalDateTime updatedAt) { + public PurchaseOrder(Long purchaseOrderId, Supplier supplier, LocalDate orderDate, LocalDateTime createdAt, LocalDateTime updatedAt) { this.purchaseOrderId = purchaseOrderId; this.supplier = supplier; this.orderDate = orderDate; - this.status = status; this.createdAt = createdAt; this.updatedAt = updatedAt; } @@ -82,14 +78,6 @@ public class PurchaseOrder { this.orderDate = orderDate; } - public String getStatus() { - return status; - } - - public void setStatus(String status) { - this.status = status; - } - public LocalDateTime getCreatedAt() { return createdAt; } @@ -125,7 +113,6 @@ public class PurchaseOrder { "purchaseOrderId=" + purchaseOrderId + ", supplier=" + supplier + ", orderDate=" + orderDate + - ", status='" + status + '\'' + ", createdAt=" + createdAt + ", updatedAt=" + updatedAt + '}'; diff --git a/backend/src/main/java/com/petshop/backend/service/PurchaseOrderService.java b/backend/src/main/java/com/petshop/backend/service/PurchaseOrderService.java index 7c42cee9..f3099354 100644 --- a/backend/src/main/java/com/petshop/backend/service/PurchaseOrderService.java +++ b/backend/src/main/java/com/petshop/backend/service/PurchaseOrderService.java @@ -47,7 +47,6 @@ public class PurchaseOrderService { store != null ? store.getStoreId() : null, store != null ? store.getStoreName() : null, purchaseOrder.getOrderDate(), - purchaseOrder.getStatus(), purchaseOrder.getCreatedAt(), purchaseOrder.getUpdatedAt() ); diff --git a/backend/src/main/resources/db/migration/V4__drop_purchase_order_status.sql b/backend/src/main/resources/db/migration/V4__drop_purchase_order_status.sql new file mode 100644 index 00000000..bef741cf --- /dev/null +++ b/backend/src/main/resources/db/migration/V4__drop_purchase_order_status.sql @@ -0,0 +1 @@ +ALTER TABLE purchaseOrder DROP COLUMN status; From 933bd6b7fdec22319ad20e699bf8f8f2371e8322 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Tue, 14 Apr 2026 20:02:03 -0600 Subject: [PATCH 4/5] Add species filtering --- .../backend/controller/ServiceController.java | 3 ++- .../backend/repository/ServiceRepository.java | 8 ++++---- .../backend/service/AppointmentService.java | 15 +++++++++++++++ .../petshop/backend/service/ServiceService.java | 12 ++++-------- 4 files changed, 25 insertions(+), 13 deletions(-) diff --git a/backend/src/main/java/com/petshop/backend/controller/ServiceController.java b/backend/src/main/java/com/petshop/backend/controller/ServiceController.java index 4f6503dc..4789321a 100644 --- a/backend/src/main/java/com/petshop/backend/controller/ServiceController.java +++ b/backend/src/main/java/com/petshop/backend/controller/ServiceController.java @@ -25,8 +25,9 @@ public class ServiceController { @GetMapping public ResponseEntity> getAllServices( @RequestParam(required = false) String q, + @RequestParam(required = false) String species, Pageable pageable) { - return ResponseEntity.ok(serviceService.getAllServices(q, pageable)); + return ResponseEntity.ok(serviceService.getAllServices(q, species, pageable)); } @GetMapping("/{id}") diff --git a/backend/src/main/java/com/petshop/backend/repository/ServiceRepository.java b/backend/src/main/java/com/petshop/backend/repository/ServiceRepository.java index 7b057856..e6271dfd 100644 --- a/backend/src/main/java/com/petshop/backend/repository/ServiceRepository.java +++ b/backend/src/main/java/com/petshop/backend/repository/ServiceRepository.java @@ -11,8 +11,8 @@ import org.springframework.stereotype.Repository; @Repository public interface ServiceRepository extends JpaRepository { - @Query("SELECT s FROM Service s WHERE " + - "LOWER(s.serviceName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + - "LOWER(s.serviceDesc) LIKE LOWER(CONCAT('%', :q, '%'))") - Page searchServices(@Param("q") String query, Pageable pageable); + @Query("SELECT DISTINCT s FROM Service s LEFT JOIN s.species sp WHERE " + + "(:q IS NULL OR LOWER(s.serviceName) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(COALESCE(s.serviceDesc, '')) LIKE LOWER(CONCAT('%', :q, '%'))) AND " + + "(:species IS NULL OR LOWER(sp) = LOWER(:species))") + Page searchServices(@Param("q") String q, @Param("species") String species, Pageable pageable); } 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 45edd69f..b8b4ba60 100644 --- a/backend/src/main/java/com/petshop/backend/service/AppointmentService.java +++ b/backend/src/main/java/com/petshop/backend/service/AppointmentService.java @@ -8,6 +8,7 @@ import com.petshop.backend.entity.Appointment; 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.repository.AdoptionRepository; import com.petshop.backend.repository.AppointmentRepository; @@ -28,6 +29,7 @@ import java.time.LocalDateTime; import java.time.LocalTime; import java.util.ArrayList; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; @Service @@ -108,6 +110,7 @@ public class AppointmentService { User employee = resolveAppointmentEmployee(request.getEmployeeId(), store.getStoreId()); validateStoreAccess(store.getStoreId(), authenticatedUser); + validatePetServiceCompatibility(pet, service); validateAvailability(employee, service, request.getAppointmentDate(), request.getAppointmentTime(), null); Appointment appointment = new Appointment(); @@ -147,6 +150,7 @@ public class AppointmentService { User employee = resolveAppointmentEmployee(request.getEmployeeId(), store.getStoreId()); validateStoreAccess(store.getStoreId(), authenticatedUser); + validatePetServiceCompatibility(pet, service); validateAvailability(employee, service, request.getAppointmentDate(), request.getAppointmentTime(), id); appointment.setCustomer(customer); @@ -254,6 +258,17 @@ public class AppointmentService { return trimmed.isEmpty() ? null : trimmed; } + private void validatePetServiceCompatibility(Pet pet, com.petshop.backend.entity.Service service) { + if (pet == null) return; + Set allowed = service.getSpecies(); + if (allowed == null || allowed.isEmpty()) return; + boolean compatible = allowed.stream().anyMatch(s -> s.equalsIgnoreCase(pet.getPetSpecies())); + if (!compatible) { + throw new BusinessException( + "Service \"" + service.getServiceName() + "\" is not available for " + pet.getPetSpecies()); + } + } + private void validateAppointmentRequest(AppointmentRequest request) { if ("Booked".equalsIgnoreCase(request.getAppointmentStatus())) { LocalDateTime appointmentDateTime = LocalDateTime.of(request.getAppointmentDate(), request.getAppointmentTime()); diff --git a/backend/src/main/java/com/petshop/backend/service/ServiceService.java b/backend/src/main/java/com/petshop/backend/service/ServiceService.java index 65166f44..ccd7b829 100644 --- a/backend/src/main/java/com/petshop/backend/service/ServiceService.java +++ b/backend/src/main/java/com/petshop/backend/service/ServiceService.java @@ -20,14 +20,10 @@ public class ServiceService { } @Transactional(readOnly = true) - public Page getAllServices(String query, Pageable pageable) { - Page services; - if (query != null && !query.trim().isEmpty()) { - services = serviceRepository.searchServices(query, pageable); - } else { - services = serviceRepository.findAll(pageable); - } - return services.map(this::mapToResponse); + public Page getAllServices(String query, String species, Pageable pageable) { + String q = (query != null && !query.trim().isEmpty()) ? query.trim() : null; + String sp = (species != null && !species.trim().isEmpty()) ? species.trim() : null; + return serviceRepository.searchServices(q, sp, pageable).map(this::mapToResponse); } @Transactional(readOnly = true) From 923d3238086e9169c95855754ea9c18997a72245 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Tue, 14 Apr 2026 20:02:06 -0600 Subject: [PATCH 5/5] Block chat injection --- .../backend/controller/AiChatController.java | 2 ++ .../petshop/backend/service/ChatService.java | 3 ++ .../petshop/backend/util/ContentFilter.java | 30 +++++++++++++++++++ 3 files changed, 35 insertions(+) create mode 100644 backend/src/main/java/com/petshop/backend/util/ContentFilter.java diff --git a/backend/src/main/java/com/petshop/backend/controller/AiChatController.java b/backend/src/main/java/com/petshop/backend/controller/AiChatController.java index 34de297c..ba08b418 100644 --- a/backend/src/main/java/com/petshop/backend/controller/AiChatController.java +++ b/backend/src/main/java/com/petshop/backend/controller/AiChatController.java @@ -8,6 +8,7 @@ import com.petshop.backend.repository.PetRepository; import com.petshop.backend.repository.UserRepository; import com.petshop.backend.service.OpenRouterService; import com.petshop.backend.util.AuthenticationHelper; +import com.petshop.backend.util.ContentFilter; import jakarta.validation.Valid; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; @@ -49,6 +50,7 @@ public class AiChatController { if (request.getMessage() == null || request.getMessage().isBlank()) { return ResponseEntity.badRequest().body(AiChatResponse.fail("Message cannot be empty")); } + ContentFilter.validate(request.getMessage()); User user = getCurrentUser(); 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 97cd584a..0e80fc1a 100644 --- a/backend/src/main/java/com/petshop/backend/service/ChatService.java +++ b/backend/src/main/java/com/petshop/backend/service/ChatService.java @@ -9,6 +9,7 @@ import com.petshop.backend.entity.Conversation; import com.petshop.backend.entity.Message; import com.petshop.backend.entity.User; import com.petshop.backend.exception.ResourceNotFoundException; +import com.petshop.backend.util.ContentFilter; import com.petshop.backend.repository.ConversationRepository; import com.petshop.backend.repository.MessageRepository; import com.petshop.backend.repository.UserRepository; @@ -138,6 +139,8 @@ public class ChatService { } } + ContentFilter.validate(request.getContent()); + Message message = new Message(); message.setConversationId(conversationId); message.setSenderId(userId); diff --git a/backend/src/main/java/com/petshop/backend/util/ContentFilter.java b/backend/src/main/java/com/petshop/backend/util/ContentFilter.java new file mode 100644 index 00000000..0552b2d2 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/util/ContentFilter.java @@ -0,0 +1,30 @@ +package com.petshop.backend.util; + +import com.petshop.backend.exception.BusinessException; + +import java.util.Locale; +import java.util.Set; +import java.util.regex.Pattern; + +public class ContentFilter { + + private static final Pattern SCRIPT_PATTERN = Pattern.compile( + " PROFANITY = Set.of( + "profanityOne", "profanityTwo", "profanityThree" + ); + + public static void validate(String input) { + if (input == null || input.isBlank()) return; + if (SCRIPT_PATTERN.matcher(input).find()) { + throw new BusinessException("Message contains prohibited content"); + } + String lower = input.toLowerCase(Locale.ROOT); + for (String word : PROFANITY) { + if (lower.contains(word)) { + throw new BusinessException("Message contains prohibited language"); + } + } + } +}