Merge pull request #275 from RecentRunner/backend-fixes
Backend bug fixes
This commit is contained in:
@@ -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();
|
||||
|
||||
|
||||
@@ -25,8 +25,9 @@ public class ServiceController {
|
||||
@GetMapping
|
||||
public ResponseEntity<Page<ServiceResponse>> 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}")
|
||||
|
||||
@@ -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 +
|
||||
'}';
|
||||
|
||||
@@ -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 +
|
||||
'}';
|
||||
|
||||
@@ -11,8 +11,8 @@ import org.springframework.stereotype.Repository;
|
||||
@Repository
|
||||
public interface ServiceRepository extends JpaRepository<Service, Long> {
|
||||
|
||||
@Query("SELECT s FROM Service s WHERE " +
|
||||
"LOWER(s.serviceName) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
||||
"LOWER(s.serviceDesc) LIKE LOWER(CONCAT('%', :q, '%'))")
|
||||
Page<Service> 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<Service> searchServices(@Param("q") String q, @Param("species") String species, Pageable pageable);
|
||||
}
|
||||
|
||||
@@ -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<Adoption> 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) {
|
||||
|
||||
@@ -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<String> 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());
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()
|
||||
);
|
||||
|
||||
@@ -20,14 +20,10 @@ public class ServiceService {
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public Page<ServiceResponse> getAllServices(String query, Pageable pageable) {
|
||||
Page<com.petshop.backend.entity.Service> services;
|
||||
if (query != null && !query.trim().isEmpty()) {
|
||||
services = serviceRepository.searchServices(query, pageable);
|
||||
} else {
|
||||
services = serviceRepository.findAll(pageable);
|
||||
}
|
||||
return services.map(this::mapToResponse);
|
||||
public Page<ServiceResponse> 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)
|
||||
|
||||
@@ -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(
|
||||
"<script|javascript:|on\\w+\\s*=", Pattern.CASE_INSENSITIVE);
|
||||
|
||||
private static final Set<String> 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE purchaseOrder DROP COLUMN status;
|
||||
Reference in New Issue
Block a user