PetController.java
package org.petify.shelter.controller;
import org.petify.shelter.dto.AdoptionRequest;
import org.petify.shelter.dto.AdoptionResponse;
import org.petify.shelter.dto.PetImageResponse;
import org.petify.shelter.dto.PetRequest;
import org.petify.shelter.dto.PetResponse;
import org.petify.shelter.dto.PetResponseWithImages;
import org.petify.shelter.dto.ShelterResponse;
import org.petify.shelter.enums.PetType;
import org.petify.shelter.service.AdoptionService;
import org.petify.shelter.service.FavoritePetService;
import org.petify.shelter.service.PetImageService;
import org.petify.shelter.service.PetService;
import org.petify.shelter.service.ShelterService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.List;
@RequiredArgsConstructor
@RestController
@RequestMapping("/pets")
public class PetController {
private final PetService petService;
private final AdoptionService adoptionService;
private final ShelterService shelterService;
private final FavoritePetService favoritePetService;
private final PetImageService petImageService;
@GetMapping()
public ResponseEntity<?> getAllPets(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "50") int size
) {
Pageable pageable = PageRequest.of(page, size);
return ResponseEntity.ok(petService.getPets(pageable));
}
// implements cursor-based pagination
@PreAuthorize("hasAnyRole('USER', 'VOLUNTEER', 'ADMIN')")
@GetMapping("/filter")
public ResponseEntity<?> getFilteredPets(
@RequestParam(required = false) Boolean vaccinated,
@RequestParam(required = false) Boolean urgent,
@RequestParam(required = false) Boolean sterilized,
@RequestParam(required = false) Boolean kidFriendly,
@RequestParam(required = false) Integer minAge,
@RequestParam(required = false) Integer maxAge,
@RequestParam(required = false) PetType type,
@RequestParam(required = false) Double userLat,
@RequestParam(required = false) Double userLng,
@RequestParam(required = false) Double radiusKm,
@RequestParam(required = false) Long cursor,
@RequestParam(defaultValue = "15") int limit,
@AuthenticationPrincipal Jwt jwt
) {
String username = jwt != null ? jwt.getSubject() : null;
return ResponseEntity.ok(
petService.getFilteredPetsWithCursor(vaccinated, urgent, sterilized, kidFriendly, minAge, maxAge,
type, userLat, userLng, radiusKm, cursor, limit, username)
);
}
@PreAuthorize("hasAnyRole('ADMIN', 'SHELTER')")
@PostMapping()
public ResponseEntity<?> addPet(@Valid @RequestPart PetRequest petRequest,
@RequestPart MultipartFile imageFile,
@AuthenticationPrincipal Jwt jwt) {
String username = jwt != null ? jwt.getSubject() : null;
ShelterResponse shelter = shelterService.getShelterByOwnerUsername(username);
if (!shelter.ownerUsername().equals(username)) {
throw new AccessDeniedException("You are not the owner of this shelter");
}
try {
PetResponse pet = petService.createPet(petRequest, shelter.id(), imageFile);
return new ResponseEntity<>(pet, HttpStatus.CREATED);
} catch (Exception e) {
return new ResponseEntity<>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@GetMapping("/{id}")
public ResponseEntity<?> getPetById(@PathVariable("id") Long id) {
return new ResponseEntity<>(petService.getPetById(id), HttpStatus.OK);
}
@GetMapping("/ids")
public ResponseEntity<List<Long>> getAllPetIds() {
List<Long> petIds = petService.getAllPetIds();
return ResponseEntity.ok(petIds);
}
@GetMapping("/all")
public ResponseEntity<List<PetResponseWithImages>> getAllPetsAsList() {
return ResponseEntity.ok(petService.getAllPets());
}
@GetMapping("/shelter/{shelterId}/ids")
@PreAuthorize("hasAnyRole('ADMIN', 'SHELTER')")
public ResponseEntity<List<Long>> getPetIdsByShelterId(@PathVariable Long shelterId) {
List<Long> petIds = petService.getPetIdsByShelterId(shelterId);
return ResponseEntity.ok(petIds);
}
@GetMapping("/{petId}/archived")
public ResponseEntity<Boolean> isPetArchived(@PathVariable Long petId) {
boolean archived = petService.isPetArchived(petId);
return ResponseEntity.ok(archived);
}
@PreAuthorize("hasAnyRole('ADMIN', 'SHELTER')")
@PutMapping("/{id}")
public ResponseEntity<PetResponse> updatePet(
@PathVariable Long id,
@Valid @RequestPart PetRequest petRequest,
@RequestPart(value = "imageFile", required = false) MultipartFile imageFile,
@AuthenticationPrincipal Jwt jwt) throws IOException {
verifyPetOwnership(id, jwt);
Long shelterId = petService.getPetById(id).shelterId();
PetResponse updatedPet = petService.updatePet(petRequest, id, shelterId, imageFile);
return ResponseEntity.ok(updatedPet);
}
@GetMapping("/{petId}/images")
public ResponseEntity<List<PetImageResponse>> getPetImages(
@PathVariable("petId") Long petId) {
List<PetImageResponse> images = petImageService.getImagesByPetId(petId);
return ResponseEntity.ok(images);
}
@PostMapping("/{petId}/images")
public ResponseEntity<?> uploadMultiplePetImages(
@PathVariable("petId") Long petId,
@AuthenticationPrincipal Jwt jwt,
@RequestParam("images") List<MultipartFile> files) throws IOException {
verifyPetOwnership(petId, jwt);
if (files.isEmpty()) {
return new ResponseEntity<>("No files send.", HttpStatus.BAD_REQUEST);
}
petImageService.addPetImages(petId, files);
return new ResponseEntity<>(HttpStatus.CREATED);
}
@PreAuthorize("hasAnyRole('ADMIN', 'SHELTER')")
@DeleteMapping("/{petId}/images/{imageId}")
public ResponseEntity<?> deleteImage(
@PathVariable("petId") Long petId,
@PathVariable("imageId") Long imageId,
@AuthenticationPrincipal Jwt jwt
) {
verifyPetOwnership(petId, jwt);
petImageService.deletePetImage(imageId);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
@PreAuthorize("hasAnyRole('ADMIN', 'SHELTER')")
@DeleteMapping("/{id}")
public ResponseEntity<?> deletePet(
@PathVariable("id") Long id,
@AuthenticationPrincipal Jwt jwt) {
verifyPetOwnership(id, jwt);
petService.deletePet(id);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
@PreAuthorize("hasAnyRole('ADMIN', 'SHELTER')")
@GetMapping("/{id}/adoptions")
public ResponseEntity<List<AdoptionResponse>> getPetAdoptionForms(@PathVariable Long id) {
List<AdoptionResponse> forms = adoptionService.getPetAdoptionForms(id);
return new ResponseEntity<>(forms, HttpStatus.OK);
}
@PreAuthorize("hasAnyRole('ADMIN', 'SHELTER')")
@PatchMapping("/{id}/archive")
public ResponseEntity<?> archivePet(
@PathVariable("id") Long id,
@AuthenticationPrincipal Jwt jwt) {
verifyPetOwnership(id, jwt);
PetResponse petResponse = petService.archivePet(id);
return new ResponseEntity<>(petResponse, HttpStatus.OK);
}
@PreAuthorize("hasAnyRole('USER', 'VOLUNTEER', 'ADMIN')")
@PostMapping("/{id}/adopt")
public ResponseEntity<AdoptionResponse> adoptPet(
@PathVariable("id") Long petId,
@Valid @RequestBody AdoptionRequest adoptionRequest,
@AuthenticationPrincipal Jwt jwt) {
String username = jwt != null ? jwt.getSubject() : null;
AdoptionResponse adoptionForm = adoptionService.createAdoptionForm(petId, username, adoptionRequest);
return new ResponseEntity<>(adoptionForm, HttpStatus.CREATED);
}
@PreAuthorize("hasAnyRole('USER', 'VOLUNTEER', 'ADMIN')")
@PostMapping("/{id}/like")
public ResponseEntity<?> likePet(
@PathVariable("id") Long petId,
@AuthenticationPrincipal Jwt jwt) {
String username = jwt != null ? jwt.getSubject() : null;
if (username == null) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
favoritePetService.like(username, petId);
return new ResponseEntity<>(HttpStatus.OK);
}
@PreAuthorize("hasAnyRole('USER', 'VOLUNTEER', 'ADMIN')")
@PostMapping("/{id}/dislike")
public ResponseEntity<?> dislikePet(
@PathVariable("id") Long petId,
@AuthenticationPrincipal Jwt jwt) {
String username = jwt != null ? jwt.getSubject() : null;
if (username == null) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
favoritePetService.dislike(username, petId);
return new ResponseEntity<>(HttpStatus.OK);
}
@PreAuthorize("hasAnyRole('USER', 'VOLUNTEER', 'ADMIN')")
@PostMapping("/{id}/support")
public ResponseEntity<?> supportPet(
@PathVariable("id") Long petId,
@AuthenticationPrincipal Jwt jwt) {
String username = jwt != null ? jwt.getSubject() : null;
if (username == null) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
favoritePetService.support(username, petId);
return new ResponseEntity<>(HttpStatus.OK);
}
@PreAuthorize("hasAnyRole('USER', 'VOLUNTEER', 'ADMIN')")
@GetMapping("/favorites")
public ResponseEntity<?> getFavoritePets(@AuthenticationPrincipal Jwt jwt) {
String username = jwt != null ? jwt.getSubject() : null;
List<PetResponseWithImages> favoritePets = favoritePetService.getFavoritePets(username);
return ResponseEntity.ok(favoritePets);
}
@PreAuthorize("hasAnyRole('USER', 'VOLUNTEER', 'ADMIN')")
@GetMapping("/supportedPets")
public ResponseEntity<?> getSupportedPets(@AuthenticationPrincipal Jwt jwt) {
String username = jwt != null ? jwt.getSubject() : null;
List<PetResponseWithImages> favoritePets = favoritePetService.getSupportedPets(username);
return ResponseEntity.ok(favoritePets);
}
@PreAuthorize("hasAnyRole('USER', 'VOLUNTEER', 'ADMIN')")
@GetMapping("/my-adoptions")
public ResponseEntity<List<AdoptionResponse>> getMyAdoptions(@AuthenticationPrincipal Jwt jwt) {
String username = jwt != null ? jwt.getSubject() : null;
if (username == null) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
List<AdoptionResponse> adoptions = adoptionService.getAdoptionsByUsername(username);
return ResponseEntity.ok(adoptions);
}
private void verifyPetOwnership(Long petId, @AuthenticationPrincipal Jwt jwt) {
String username = jwt != null ? jwt.getSubject() : null;
Long shelterId = petService.getPetById(petId).shelterId();
ShelterResponse shelter = shelterService.getShelterById(shelterId);
if (!shelter.ownerUsername().equals(username)) {
throw new AccessDeniedException("You are not the owner of this pet!");
}
}
}