Harden assignment rules
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
package com.petshop.backend.config;
|
||||
|
||||
import com.petshop.backend.repository.CustomerPetRepository;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
@Component
|
||||
@Profile("local")
|
||||
public class LocalAppointmentCustomerSeedInitializer implements CommandLineRunner {
|
||||
|
||||
private final DataSource dataSource;
|
||||
private final CustomerPetRepository customerPetRepository;
|
||||
|
||||
public LocalAppointmentCustomerSeedInitializer(DataSource dataSource, CustomerPetRepository customerPetRepository) {
|
||||
this.dataSource = dataSource;
|
||||
this.customerPetRepository = customerPetRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(String... args) {
|
||||
if (customerPetRepository.count() > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
ResourceDatabasePopulator populator = new ResourceDatabasePopulator(false, false, "UTF-8",
|
||||
new ClassPathResource("dev/seed_demo_customer_pets.sql"));
|
||||
populator.execute(dataSource);
|
||||
}
|
||||
}
|
||||
@@ -71,21 +71,8 @@ public class AdoptionController {
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
|
||||
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
|
||||
public ResponseEntity<AdoptionResponse> createAdoption(@Valid @RequestBody AdoptionRequest request) {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
String role = authentication.getAuthorities().stream()
|
||||
.findFirst()
|
||||
.map(authority -> authority.getAuthority().replace("ROLE_", ""))
|
||||
.orElse(null);
|
||||
|
||||
if (role != null && role.equals("CUSTOMER")) {
|
||||
Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository);
|
||||
if (!request.getCustomerId().equals(customer.getCustomerId())) {
|
||||
throw new org.springframework.security.access.AccessDeniedException("You can only create adoptions for yourself");
|
||||
}
|
||||
}
|
||||
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(adoptionService.createAdoption(request));
|
||||
}
|
||||
|
||||
|
||||
@@ -57,6 +57,16 @@ public class DropdownController {
|
||||
);
|
||||
}
|
||||
|
||||
@GetMapping("/adoption-pets")
|
||||
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
|
||||
public ResponseEntity<List<DropdownOption>> getAdoptionPets() {
|
||||
return ResponseEntity.ok(
|
||||
petRepository.findAllByPetStatusIgnoreCaseOrderByPetNameAsc("Available").stream()
|
||||
.map(p -> new DropdownOption(p.getPetId(), p.getPetName()))
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
}
|
||||
|
||||
@GetMapping("/customers")
|
||||
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
|
||||
public ResponseEntity<List<DropdownOption>> getCustomers() {
|
||||
@@ -67,6 +77,16 @@ public class DropdownController {
|
||||
);
|
||||
}
|
||||
|
||||
@GetMapping("/appointment-customers")
|
||||
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
|
||||
public ResponseEntity<List<DropdownOption>> getAppointmentCustomers() {
|
||||
return ResponseEntity.ok(
|
||||
customerRepository.findAllWithPets().stream()
|
||||
.map(c -> new DropdownOption(c.getCustomerId(), c.getFirstName() + " " + c.getLastName()))
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
}
|
||||
|
||||
@GetMapping("/customers/{customerId}/pets")
|
||||
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
|
||||
public ResponseEntity<List<DropdownOption>> getCustomerPets(@PathVariable Long customerId) {
|
||||
@@ -174,8 +194,8 @@ public class DropdownController {
|
||||
return false;
|
||||
}
|
||||
return userRepository.findById(userId)
|
||||
.map(User::getRole)
|
||||
.filter(role -> role == User.Role.STAFF)
|
||||
.filter(user -> user.getRole() == User.Role.STAFF)
|
||||
.filter(user -> Boolean.TRUE.equals(user.getActive()))
|
||||
.isPresent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,4 +28,8 @@ public interface AdoptionRepository extends JpaRepository<Adoption, Long> {
|
||||
Page<Adoption> searchAdoptionsByCustomer(@Param("customerId") Long customerId, @Param("q") String query, Pageable pageable);
|
||||
|
||||
Optional<Adoption> findFirstByPet_IdAndAdoptionStatusOrderByAdoptionDateDesc(Long petId, String adoptionStatus);
|
||||
|
||||
boolean existsByPetPetIdAndAdoptionStatusIgnoreCaseAndAdoptionIdNot(Long petId, String adoptionStatus, Long adoptionId);
|
||||
|
||||
boolean existsByPetPetIdAndAdoptionStatusIgnoreCase(Long petId, String adoptionStatus);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,9 @@ public interface CustomerRepository extends JpaRepository<Customer, Long> {
|
||||
|
||||
Optional<Customer> findByUserId(Long userId);
|
||||
List<Customer> findAllByEmail(String email);
|
||||
|
||||
@Query("SELECT DISTINCT c FROM Customer c WHERE EXISTS (SELECT cp FROM CustomerPet cp WHERE cp.customer = c) ORDER BY c.firstName ASC, c.lastName ASC")
|
||||
List<Customer> findAllWithPets();
|
||||
|
||||
@Query("SELECT c FROM Customer c WHERE " +
|
||||
"LOWER(c.firstName) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
||||
|
||||
@@ -8,9 +8,13 @@ import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface PetRepository extends JpaRepository<Pet, Long> {
|
||||
|
||||
List<Pet> findAllByPetStatusIgnoreCaseOrderByPetNameAsc(String petStatus);
|
||||
|
||||
@Query("SELECT p FROM Pet p WHERE " +
|
||||
"(:q IS NULL OR LOWER(p.petName) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(p.petSpecies) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(p.petBreed) LIKE LOWER(CONCAT('%', :q, '%'))) AND " +
|
||||
"(:species IS NULL OR LOWER(p.petSpecies) = LOWER(:species)) AND " +
|
||||
|
||||
@@ -22,6 +22,12 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
@Service
|
||||
public class AdoptionService {
|
||||
|
||||
private static final String ADOPTION_STATUS_PENDING = "Pending";
|
||||
private static final String ADOPTION_STATUS_COMPLETED = "Completed";
|
||||
private static final String ADOPTION_STATUS_CANCELLED = "Cancelled";
|
||||
private static final String PET_STATUS_AVAILABLE = "Available";
|
||||
private static final String PET_STATUS_ADOPTED = "Adopted";
|
||||
|
||||
private final AdoptionRepository adoptionRepository;
|
||||
private final PetRepository petRepository;
|
||||
private final CustomerRepository customerRepository;
|
||||
@@ -75,15 +81,18 @@ public class AdoptionService {
|
||||
Customer customer = customerRepository.findById(request.getCustomerId())
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + request.getCustomerId()));
|
||||
Employee employee = resolveAdoptionEmployee(request.getEmployeeId());
|
||||
String adoptionStatus = normalizeAdoptionStatus(request.getAdoptionStatus());
|
||||
validatePetAvailability(pet, null);
|
||||
|
||||
Adoption adoption = new Adoption();
|
||||
adoption.setPet(pet);
|
||||
adoption.setCustomer(customer);
|
||||
adoption.setEmployee(employee);
|
||||
adoption.setAdoptionDate(request.getAdoptionDate());
|
||||
adoption.setAdoptionStatus(request.getAdoptionStatus());
|
||||
adoption.setAdoptionStatus(adoptionStatus);
|
||||
|
||||
adoption = adoptionRepository.save(adoption);
|
||||
syncPetStatus(pet, adoptionStatus, adoption.getAdoptionId());
|
||||
return mapToResponse(adoption);
|
||||
}
|
||||
|
||||
@@ -98,14 +107,17 @@ public class AdoptionService {
|
||||
Customer customer = customerRepository.findById(request.getCustomerId())
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + request.getCustomerId()));
|
||||
Employee employee = resolveAdoptionEmployee(request.getEmployeeId());
|
||||
String adoptionStatus = normalizeAdoptionStatus(request.getAdoptionStatus());
|
||||
validatePetAvailability(pet, adoption.getAdoptionId());
|
||||
|
||||
adoption.setPet(pet);
|
||||
adoption.setCustomer(customer);
|
||||
adoption.setEmployee(employee);
|
||||
adoption.setAdoptionDate(request.getAdoptionDate());
|
||||
adoption.setAdoptionStatus(request.getAdoptionStatus());
|
||||
adoption.setAdoptionStatus(adoptionStatus);
|
||||
|
||||
adoption = adoptionRepository.save(adoption);
|
||||
syncPetStatus(pet, adoptionStatus, adoption.getAdoptionId());
|
||||
return mapToResponse(adoption);
|
||||
}
|
||||
|
||||
@@ -161,8 +173,49 @@ public class AdoptionService {
|
||||
return false;
|
||||
}
|
||||
return userRepository.findById(userId)
|
||||
.map(User::getRole)
|
||||
.filter(role -> role == User.Role.STAFF)
|
||||
.filter(user -> user.getRole() == User.Role.STAFF)
|
||||
.filter(user -> Boolean.TRUE.equals(user.getActive()))
|
||||
.isPresent();
|
||||
}
|
||||
|
||||
private String normalizeAdoptionStatus(String adoptionStatus) {
|
||||
if (adoptionStatus == null) {
|
||||
throw new IllegalArgumentException("Adoption status is required");
|
||||
}
|
||||
String trimmedStatus = adoptionStatus.trim();
|
||||
if (ADOPTION_STATUS_PENDING.equalsIgnoreCase(trimmedStatus)) {
|
||||
return ADOPTION_STATUS_PENDING;
|
||||
}
|
||||
if (ADOPTION_STATUS_COMPLETED.equalsIgnoreCase(trimmedStatus)) {
|
||||
return ADOPTION_STATUS_COMPLETED;
|
||||
}
|
||||
if (ADOPTION_STATUS_CANCELLED.equalsIgnoreCase(trimmedStatus)) {
|
||||
return ADOPTION_STATUS_CANCELLED;
|
||||
}
|
||||
throw new IllegalArgumentException("Adoption status must be Pending, Completed, or Cancelled");
|
||||
}
|
||||
|
||||
private void validatePetAvailability(Pet pet, Long adoptionId) {
|
||||
boolean adoptedElsewhere = adoptionId == null
|
||||
? adoptionRepository.existsByPetPetIdAndAdoptionStatusIgnoreCase(pet.getPetId(), ADOPTION_STATUS_COMPLETED)
|
||||
: adoptionRepository.existsByPetPetIdAndAdoptionStatusIgnoreCaseAndAdoptionIdNot(pet.getPetId(), ADOPTION_STATUS_COMPLETED, adoptionId);
|
||||
if (adoptedElsewhere) {
|
||||
throw new IllegalArgumentException("Selected pet has already been adopted");
|
||||
}
|
||||
|
||||
if (!PET_STATUS_AVAILABLE.equalsIgnoreCase(pet.getPetStatus()) && adoptionId == null) {
|
||||
throw new IllegalArgumentException("Selected pet is not available for adoption");
|
||||
}
|
||||
}
|
||||
|
||||
private void syncPetStatus(Pet pet, String adoptionStatus, Long adoptionId) {
|
||||
boolean completedElsewhere = adoptionId != null
|
||||
&& adoptionRepository.existsByPetPetIdAndAdoptionStatusIgnoreCaseAndAdoptionIdNot(pet.getPetId(), ADOPTION_STATUS_COMPLETED, adoptionId);
|
||||
if (ADOPTION_STATUS_COMPLETED.equalsIgnoreCase(adoptionStatus) || completedElsewhere) {
|
||||
pet.setPetStatus(PET_STATUS_ADOPTED);
|
||||
} else {
|
||||
pet.setPetStatus(PET_STATUS_AVAILABLE);
|
||||
}
|
||||
petRepository.save(pet);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,8 +333,8 @@ public class AppointmentService {
|
||||
return false;
|
||||
}
|
||||
return userRepository.findById(userId)
|
||||
.map(User::getRole)
|
||||
.filter(role -> role == User.Role.STAFF)
|
||||
.filter(user -> user.getRole() == User.Role.STAFF)
|
||||
.filter(user -> Boolean.TRUE.equals(user.getActive()))
|
||||
.isPresent();
|
||||
}
|
||||
|
||||
|
||||
53
backend/src/main/resources/dev/seed_demo_customer_pets.sql
Normal file
53
backend/src/main/resources/dev/seed_demo_customer_pets.sql
Normal file
@@ -0,0 +1,53 @@
|
||||
INSERT INTO customer_pet (customer_id, pet_name, species, breed, image_url)
|
||||
SELECT c.customerId, 'Rocky', 'dog', 'Labrador', NULL
|
||||
FROM customer c
|
||||
WHERE c.email = 'alex@gmail.com'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM customer_pet cp
|
||||
WHERE cp.customer_id = c.customerId AND cp.pet_name = 'Rocky'
|
||||
);
|
||||
|
||||
INSERT INTO customer_pet (customer_id, pet_name, species, breed, image_url)
|
||||
SELECT c.customerId, 'Whiskers', 'cat', 'Persian', NULL
|
||||
FROM customer c
|
||||
WHERE c.email = 'emily@gmail.com'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM customer_pet cp
|
||||
WHERE cp.customer_id = c.customerId AND cp.pet_name = 'Whiskers'
|
||||
);
|
||||
|
||||
INSERT INTO customer_pet (customer_id, pet_name, species, breed, image_url)
|
||||
SELECT c.customerId, 'Daisy', 'dog', 'Beagle', NULL
|
||||
FROM customer c
|
||||
WHERE c.email = 'james@gmail.com'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM customer_pet cp
|
||||
WHERE cp.customer_id = c.customerId AND cp.pet_name = 'Daisy'
|
||||
);
|
||||
|
||||
INSERT INTO customer_pet (customer_id, pet_name, species, breed, image_url)
|
||||
SELECT c.customerId, 'Pepper', 'cat', 'Domestic Shorthair', NULL
|
||||
FROM customer c
|
||||
WHERE c.email = 'olivia@gmail.com'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM customer_pet cp
|
||||
WHERE cp.customer_id = c.customerId AND cp.pet_name = 'Pepper'
|
||||
);
|
||||
|
||||
INSERT INTO customer_pet (customer_id, pet_name, species, breed, image_url)
|
||||
SELECT c.customerId, 'Cooper', 'dog', 'Golden Retriever', NULL
|
||||
FROM customer c
|
||||
WHERE c.email = 'william@gmail.com'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM customer_pet cp
|
||||
WHERE cp.customer_id = c.customerId AND cp.pet_name = 'Cooper'
|
||||
);
|
||||
|
||||
INSERT INTO customer_pet (customer_id, pet_name, species, breed, image_url)
|
||||
SELECT c.customerId, 'Mittens', 'cat', 'Siamese', NULL
|
||||
FROM customer c
|
||||
WHERE c.email = 'sophia@gmail.com'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM customer_pet cp
|
||||
WHERE cp.customer_id = c.customerId AND cp.pet_name = 'Mittens'
|
||||
);
|
||||
Reference in New Issue
Block a user