add my pets api

This commit is contained in:
2026-04-07 09:15:01 -06:00
parent 4d244cc1c5
commit 0cb2ecff02
5 changed files with 260 additions and 0 deletions

View File

@@ -0,0 +1,76 @@
package com.petshop.backend.controller;
import com.petshop.backend.dto.pet.MyPetRequest;
import com.petshop.backend.dto.pet.MyPetResponse;
import com.petshop.backend.entity.User;
import com.petshop.backend.repository.UserRepository;
import com.petshop.backend.service.PetService;
import com.petshop.backend.util.AuthenticationHelper;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
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.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/v1/my-pets")
@PreAuthorize("isAuthenticated()")
public class MyPetController {
private final PetService petService;
private final UserRepository userRepository;
public MyPetController(PetService petService, UserRepository userRepository) {
this.petService = petService;
this.userRepository = userRepository;
}
@GetMapping
public ResponseEntity<List<MyPetResponse>> getMyPets() {
return ResponseEntity.ok(petService.getMyPets(currentUserId()));
}
@PostMapping
public ResponseEntity<MyPetResponse> createMyPet(@Valid @RequestBody MyPetRequest request) {
return ResponseEntity.ok(petService.createMyPet(currentUserId(), request));
}
@PutMapping("/{id}")
public ResponseEntity<MyPetResponse> updateMyPet(@PathVariable Long id, @Valid @RequestBody MyPetRequest request) {
return ResponseEntity.ok(petService.updateMyPet(currentUserId(), id, request));
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteMyPet(@PathVariable Long id) {
petService.deleteMyPet(currentUserId(), id);
return ResponseEntity.noContent().build();
}
@PostMapping("/{id}/image")
public ResponseEntity<?> uploadMyPetImage(@PathVariable Long id, @RequestParam("image") MultipartFile image) {
try {
return ResponseEntity.ok(petService.uploadMyPetImage(currentUserId(), id, image));
} catch (IllegalArgumentException ex) {
return ResponseEntity.badRequest().body(Map.of("message", ex.getMessage()));
} catch (IOException ex) {
return ResponseEntity.badRequest().body(Map.of("message", "Failed to upload pet image: " + ex.getMessage()));
}
}
private Long currentUserId() {
User user = AuthenticationHelper.getAuthenticatedUser(userRepository);
return user.getId();
}
}

View File

@@ -0,0 +1,42 @@
package com.petshop.backend.dto.pet;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
public class MyPetRequest {
@NotBlank(message = "Pet name is required")
@Size(max = 50, message = "Pet name must not exceed 50 characters")
private String petName;
@NotBlank(message = "Species is required")
@Size(max = 50, message = "Species must not exceed 50 characters")
private String species;
@Size(max = 50, message = "Breed must not exceed 50 characters")
private String breed;
public String getPetName() {
return petName;
}
public void setPetName(String petName) {
this.petName = petName;
}
public String getSpecies() {
return species;
}
public void setSpecies(String species) {
this.species = species;
}
public String getBreed() {
return breed;
}
public void setBreed(String breed) {
this.breed = breed;
}
}

View File

@@ -0,0 +1,61 @@
package com.petshop.backend.dto.pet;
public class MyPetResponse {
private Long customerPetId;
private String petName;
private String species;
private String breed;
private String imageUrl;
public MyPetResponse() {
}
public MyPetResponse(Long customerPetId, String petName, String species, String breed, String imageUrl) {
this.customerPetId = customerPetId;
this.petName = petName;
this.species = species;
this.breed = breed;
this.imageUrl = imageUrl;
}
public Long getCustomerPetId() {
return customerPetId;
}
public void setCustomerPetId(Long customerPetId) {
this.customerPetId = customerPetId;
}
public String getPetName() {
return petName;
}
public void setPetName(String petName) {
this.petName = petName;
}
public String getSpecies() {
return species;
}
public void setSpecies(String species) {
this.species = species;
}
public String getBreed() {
return breed;
}
public void setBreed(String breed) {
this.breed = breed;
}
public String getImageUrl() {
return imageUrl;
}
public void setImageUrl(String imageUrl) {
this.imageUrl = imageUrl;
}
}

View File

@@ -9,11 +9,14 @@ import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface PetRepository extends JpaRepository<Pet, Long> {
List<Pet> findAllByPetStatusIgnoreCaseOrderByPetNameAsc(String petStatus);
List<Pet> findAllByOwner_IdOrderByPetNameAsc(Long ownerId);
Optional<Pet> findByIdAndOwner_Id(Long id, Long ownerId);
@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(COALESCE(p.petBreed, '')) LIKE LOWER(CONCAT('%', :q, '%'))) AND " +

View File

@@ -1,6 +1,8 @@
package com.petshop.backend.service;
import com.petshop.backend.dto.common.BulkDeleteRequest;
import com.petshop.backend.dto.pet.MyPetRequest;
import com.petshop.backend.dto.pet.MyPetResponse;
import com.petshop.backend.dto.pet.PetRequest;
import com.petshop.backend.dto.pet.PetResponse;
import com.petshop.backend.entity.Adoption;
@@ -25,6 +27,7 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.List;
import java.util.Locale;
@Service
@@ -82,6 +85,50 @@ public class PetService {
return mapToResponse(pet);
}
@Transactional(readOnly = true)
public List<MyPetResponse> getMyPets(Long ownerUserId) {
return petRepository.findAllByOwner_IdOrderByPetNameAsc(ownerUserId).stream()
.map(this::mapToMyPetResponse)
.toList();
}
@Transactional
public MyPetResponse createMyPet(Long ownerUserId, MyPetRequest request) {
User owner = userRepository.findById(ownerUserId)
.orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + ownerUserId));
Pet pet = new Pet();
pet.setOwner(owner);
pet.setStore(null);
pet.setPetStatus("Owned");
applyMyPetRequest(pet, request);
return mapToMyPetResponse(petRepository.save(pet));
}
@Transactional
public MyPetResponse updateMyPet(Long ownerUserId, Long petId, MyPetRequest request) {
Pet pet = findOwnedPet(ownerUserId, petId);
pet.setPetStatus("Owned");
pet.setStore(null);
applyMyPetRequest(pet, request);
return mapToMyPetResponse(petRepository.save(pet));
}
@Transactional
public void deleteMyPet(Long ownerUserId, Long petId) {
Pet pet = findOwnedPet(ownerUserId, petId);
deleteStoredImageIfPresent(pet.getImageUrl());
petRepository.delete(pet);
}
@Transactional
public MyPetResponse uploadMyPetImage(Long ownerUserId, Long petId, MultipartFile file) throws IOException {
validateImageFile(file);
Pet pet = findOwnedPet(ownerUserId, petId);
deleteStoredImageIfPresent(pet.getImageUrl());
pet.setImageUrl(catalogImageStorageService.storePetImage(file));
return mapToMyPetResponse(petRepository.save(pet));
}
@Transactional
public PetResponse createPet(PetRequest request) {
Pet pet = new Pet();
@@ -225,6 +272,11 @@ public class PetService {
}
}
private Pet findOwnedPet(Long ownerUserId, Long petId) {
return petRepository.findByIdAndOwner_Id(petId, ownerUserId)
.orElseThrow(() -> new ResourceNotFoundException("Pet not found with id: " + petId));
}
private void deleteStoredImageIfPresent(String storedImagePath) {
if (storedImagePath == null || storedImagePath.isBlank()) {
return;
@@ -276,6 +328,32 @@ public class PetService {
);
}
private MyPetResponse mapToMyPetResponse(Pet pet) {
return new MyPetResponse(
pet.getPetId(),
pet.getPetName(),
pet.getPetSpecies(),
pet.getPetBreed(),
pet.getImageUrl() != null && !pet.getImageUrl().isBlank() ? "/api/v1/pets/" + pet.getPetId() + "/image" : null
);
}
private void applyMyPetRequest(Pet pet, MyPetRequest request) {
pet.setPetName(request.getPetName().trim());
pet.setPetSpecies(request.getSpecies().trim());
pet.setPetBreed(normalizeOptional(request.getBreed()));
pet.setPetAge(null);
pet.setPetPrice(null);
}
private String normalizeOptional(String value) {
if (value == null) {
return null;
}
String trimmed = value.trim();
return trimmed.isEmpty() ? null : trimmed;
}
private void applyOwnerAndStore(Pet pet, PetRequest request) {
if ("owned".equalsIgnoreCase(request.getPetStatus())) {
if (request.getCustomerId() != null) {