Appointments, account stuff, adopt a pet changes
This commit is contained in:
@@ -9,6 +9,7 @@ import com.petshop.backend.dto.auth.RegisterResponse;
|
||||
import com.petshop.backend.dto.auth.UserInfoResponse;
|
||||
import com.petshop.backend.entity.EmployeeStore;
|
||||
import com.petshop.backend.entity.User;
|
||||
import com.petshop.backend.repository.CustomerRepository;
|
||||
import com.petshop.backend.repository.EmployeeRepository;
|
||||
import com.petshop.backend.repository.EmployeeStoreRepository;
|
||||
import com.petshop.backend.repository.UserRepository;
|
||||
@@ -47,8 +48,9 @@ public class AuthController {
|
||||
private final EmployeeRepository employeeRepository;
|
||||
private final EmployeeStoreRepository employeeStoreRepository;
|
||||
private final AvatarStorageService avatarStorageService;
|
||||
private final CustomerRepository customerRepository;
|
||||
|
||||
public AuthController(AuthenticationManager authenticationManager, UserRepository userRepository, JwtUtil jwtUtil, PasswordEncoder passwordEncoder, UserBusinessLinkageService userBusinessLinkageService, EmployeeRepository employeeRepository, EmployeeStoreRepository employeeStoreRepository, AvatarStorageService avatarStorageService) {
|
||||
public AuthController(AuthenticationManager authenticationManager, UserRepository userRepository, JwtUtil jwtUtil, PasswordEncoder passwordEncoder, UserBusinessLinkageService userBusinessLinkageService, EmployeeRepository employeeRepository, EmployeeStoreRepository employeeStoreRepository, AvatarStorageService avatarStorageService, CustomerRepository customerRepository) {
|
||||
this.authenticationManager = authenticationManager;
|
||||
this.userRepository = userRepository;
|
||||
this.jwtUtil = jwtUtil;
|
||||
@@ -57,6 +59,7 @@ public class AuthController {
|
||||
this.employeeRepository = employeeRepository;
|
||||
this.employeeStoreRepository = employeeStoreRepository;
|
||||
this.avatarStorageService = avatarStorageService;
|
||||
this.customerRepository = customerRepository;
|
||||
}
|
||||
|
||||
@PostMapping("/register")
|
||||
@@ -147,6 +150,7 @@ public class AuthController {
|
||||
User user = getAuthenticatedUser();
|
||||
|
||||
EmployeeStore employeeStore = resolveEmployeeStore(user);
|
||||
Long customerId = resolveCustomerId(user);
|
||||
|
||||
return ResponseEntity.ok(new UserInfoResponse(
|
||||
user.getId(),
|
||||
@@ -156,6 +160,7 @@ public class AuthController {
|
||||
user.getPhone(),
|
||||
avatarStorageService.toOwnerAvatarUrl(user),
|
||||
user.getRole().name(),
|
||||
customerId,
|
||||
employeeStore != null ? employeeStore.getStore().getStoreId() : null,
|
||||
employeeStore != null ? employeeStore.getStore().getStoreName() : null
|
||||
));
|
||||
@@ -216,6 +221,7 @@ public class AuthController {
|
||||
userBusinessLinkageService.syncLinkedRecords(updatedUser);
|
||||
|
||||
EmployeeStore employeeStore = resolveEmployeeStore(updatedUser);
|
||||
Long customerId = resolveCustomerId(updatedUser);
|
||||
|
||||
return ResponseEntity.ok(new UserInfoResponse(
|
||||
updatedUser.getId(),
|
||||
@@ -225,6 +231,7 @@ public class AuthController {
|
||||
updatedUser.getPhone(),
|
||||
avatarStorageService.toOwnerAvatarUrl(updatedUser),
|
||||
updatedUser.getRole().name(),
|
||||
customerId,
|
||||
employeeStore != null ? employeeStore.getStore().getStoreId() : null,
|
||||
employeeStore != null ? employeeStore.getStore().getStoreName() : null
|
||||
));
|
||||
@@ -240,6 +247,12 @@ public class AuthController {
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private Long resolveCustomerId(User user) {
|
||||
return customerRepository.findByUserId(user.getId())
|
||||
.map(c -> c.getCustomerId())
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private String trimToNull(String value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
package com.petshop.backend.controller;
|
||||
|
||||
import com.petshop.backend.dto.customerpet.CustomerPetRequest;
|
||||
import com.petshop.backend.dto.customerpet.CustomerPetResponse;
|
||||
import com.petshop.backend.service.CatalogImageStorageService;
|
||||
import com.petshop.backend.service.CustomerPetService;
|
||||
import com.petshop.backend.entity.CustomerPet;
|
||||
import com.petshop.backend.repository.CustomerPetRepository;
|
||||
import com.petshop.backend.repository.CustomerRepository;
|
||||
import com.petshop.backend.repository.UserRepository;
|
||||
import com.petshop.backend.entity.Customer;
|
||||
import com.petshop.backend.util.AuthenticationHelper;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/my-pets")
|
||||
@PreAuthorize("hasRole('CUSTOMER')")
|
||||
public class CustomerPetController {
|
||||
|
||||
private final CustomerPetService customerPetService;
|
||||
private final CustomerPetRepository customerPetRepository;
|
||||
private final CustomerRepository customerRepository;
|
||||
private final UserRepository userRepository;
|
||||
private final CatalogImageStorageService catalogImageStorageService;
|
||||
|
||||
public CustomerPetController(CustomerPetService customerPetService,
|
||||
CustomerPetRepository customerPetRepository,
|
||||
CustomerRepository customerRepository,
|
||||
UserRepository userRepository,
|
||||
CatalogImageStorageService catalogImageStorageService) {
|
||||
this.customerPetService = customerPetService;
|
||||
this.customerPetRepository = customerPetRepository;
|
||||
this.customerRepository = customerRepository;
|
||||
this.userRepository = userRepository;
|
||||
this.catalogImageStorageService = catalogImageStorageService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ResponseEntity<List<CustomerPetResponse>> getMyPets() {
|
||||
|
||||
return ResponseEntity.ok(customerPetService.getMyPets());
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<CustomerPetResponse> createPet(@Valid @RequestBody CustomerPetRequest request) {
|
||||
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(customerPetService.createPet(request));
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ResponseEntity<CustomerPetResponse> updatePet(@PathVariable Long id, @Valid @RequestBody CustomerPetRequest request) {
|
||||
|
||||
return ResponseEntity.ok(customerPetService.updatePet(id, request));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ResponseEntity<Void> deletePet(@PathVariable Long id) {
|
||||
customerPetService.deletePet(id);
|
||||
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/image")
|
||||
public ResponseEntity<?> uploadImage(@PathVariable Long id, @RequestParam("image") MultipartFile image) {
|
||||
try {
|
||||
|
||||
return ResponseEntity.ok(customerPetService.uploadImage(id, image));
|
||||
}
|
||||
|
||||
catch (IllegalArgumentException ex) {
|
||||
Map<String, String> error = new HashMap<>();
|
||||
error.put("message", ex.getMessage());
|
||||
|
||||
return ResponseEntity.badRequest().body(error);
|
||||
}
|
||||
|
||||
catch (IOException ex) {
|
||||
Map<String, String> error = new HashMap<>();
|
||||
error.put("message", "Failed to upload image: " + ex.getMessage());
|
||||
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/{id}/image")
|
||||
public ResponseEntity<Resource> getImage(@PathVariable Long id) {
|
||||
Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository);
|
||||
CustomerPet pet = customerPetRepository.findByCustomerPetIdAndCustomerCustomerId(id, customer.getCustomerId()).orElse(null);
|
||||
|
||||
if (pet == null || pet.getImageUrl() == null || pet.getImageUrl().isBlank()) {
|
||||
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
Resource resource = catalogImageStorageService.loadPetImage(pet.getImageUrl());
|
||||
MediaType mediaType = catalogImageStorageService.resolveMediaType(resource);
|
||||
|
||||
return ResponseEntity.ok().contentType(mediaType).body(resource);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}/image")
|
||||
public ResponseEntity<CustomerPetResponse> deleteImage(@PathVariable Long id) {
|
||||
|
||||
return ResponseEntity.ok(customerPetService.deleteImage(id));
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.petshop.backend.dto.appointment;
|
||||
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
@@ -26,9 +25,10 @@ public class AppointmentRequest {
|
||||
@NotNull(message = "Appointment status is required")
|
||||
private String appointmentStatus;
|
||||
|
||||
@NotEmpty(message = "At least one pet must be specified")
|
||||
private List<Long> petIds;
|
||||
|
||||
private List<Long> customerPetIds;
|
||||
|
||||
public Long getCustomerId() {
|
||||
return customerId;
|
||||
}
|
||||
@@ -85,6 +85,14 @@ public class AppointmentRequest {
|
||||
this.petIds = petIds;
|
||||
}
|
||||
|
||||
public List<Long> getCustomerPetIds() {
|
||||
return customerPetIds;
|
||||
}
|
||||
|
||||
public void setCustomerPetIds(List<Long> customerPetIds) {
|
||||
this.customerPetIds = customerPetIds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
@@ -96,12 +104,13 @@ public class AppointmentRequest {
|
||||
Objects.equals(appointmentDate, that.appointmentDate) &&
|
||||
Objects.equals(appointmentTime, that.appointmentTime) &&
|
||||
Objects.equals(appointmentStatus, that.appointmentStatus) &&
|
||||
Objects.equals(petIds, that.petIds);
|
||||
Objects.equals(petIds, that.petIds) &&
|
||||
Objects.equals(customerPetIds, that.customerPetIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(customerId, storeId, serviceId, appointmentDate, appointmentTime, appointmentStatus, petIds);
|
||||
return Objects.hash(customerId, storeId, serviceId, appointmentDate, appointmentTime, appointmentStatus, petIds, customerPetIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -114,6 +123,7 @@ public class AppointmentRequest {
|
||||
", appointmentTime=" + appointmentTime +
|
||||
", appointmentStatus='" + appointmentStatus + '\'' +
|
||||
", petIds=" + petIds +
|
||||
", customerPetIds=" + customerPetIds +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ public class AppointmentResponse {
|
||||
private String appointmentStatus;
|
||||
private List<String> petNames;
|
||||
private List<Long> petIds;
|
||||
private List<String> customerPetNames;
|
||||
private List<Long> customerPetIds;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@@ -138,6 +140,24 @@ public class AppointmentResponse {
|
||||
this.petIds = petIds;
|
||||
}
|
||||
|
||||
public List<String> getCustomerPetNames() {
|
||||
|
||||
return customerPetNames;
|
||||
}
|
||||
|
||||
public void setCustomerPetNames(List<String> customerPetNames) {
|
||||
this.customerPetNames = customerPetNames;
|
||||
}
|
||||
|
||||
public List<Long> getCustomerPetIds() {
|
||||
|
||||
return customerPetIds;
|
||||
}
|
||||
|
||||
public void setCustomerPetIds(List<Long> customerPetIds) {
|
||||
this.customerPetIds = customerPetIds;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
@@ -10,13 +10,14 @@ public class UserInfoResponse {
|
||||
private String phone;
|
||||
private String avatarUrl;
|
||||
private String role;
|
||||
private Long customerId;
|
||||
private Long storeId;
|
||||
private String storeName;
|
||||
|
||||
public UserInfoResponse() {
|
||||
}
|
||||
|
||||
public UserInfoResponse(Long id, String username, String email, String fullName, String phone, String avatarUrl, String role, Long storeId, String storeName) {
|
||||
public UserInfoResponse(Long id, String username, String email, String fullName, String phone, String avatarUrl, String role, Long customerId, Long storeId, String storeName) {
|
||||
this.id = id;
|
||||
this.username = username;
|
||||
this.email = email;
|
||||
@@ -24,6 +25,7 @@ public class UserInfoResponse {
|
||||
this.phone = phone;
|
||||
this.avatarUrl = avatarUrl;
|
||||
this.role = role;
|
||||
this.customerId = customerId;
|
||||
this.storeId = storeId;
|
||||
this.storeName = storeName;
|
||||
}
|
||||
@@ -84,6 +86,15 @@ public class UserInfoResponse {
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
public Long getCustomerId() {
|
||||
|
||||
return customerId;
|
||||
}
|
||||
|
||||
public void setCustomerId(Long customerId) {
|
||||
this.customerId = customerId;
|
||||
}
|
||||
|
||||
public Long getStoreId() {
|
||||
return storeId;
|
||||
}
|
||||
@@ -112,13 +123,14 @@ public class UserInfoResponse {
|
||||
Objects.equals(phone, that.phone) &&
|
||||
Objects.equals(avatarUrl, that.avatarUrl) &&
|
||||
Objects.equals(role, that.role) &&
|
||||
Objects.equals(customerId, that.customerId) &&
|
||||
Objects.equals(storeId, that.storeId) &&
|
||||
Objects.equals(storeName, that.storeName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id, username, email, fullName, phone, avatarUrl, role, storeId, storeName);
|
||||
return Objects.hash(id, username, email, fullName, phone, avatarUrl, role, customerId, storeId, storeName);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -131,6 +143,7 @@ public class UserInfoResponse {
|
||||
", phone='" + phone + '\'' +
|
||||
", avatarUrl='" + avatarUrl + '\'' +
|
||||
", role='" + role + '\'' +
|
||||
", customerId=" + customerId +
|
||||
", storeId=" + storeId +
|
||||
", storeName='" + storeName + '\'' +
|
||||
'}';
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.petshop.backend.dto.customerpet;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class CustomerPetRequest {
|
||||
|
||||
@NotBlank(message = "Pet name is required")
|
||||
private String petName;
|
||||
|
||||
@NotBlank(message = "Species is required")
|
||||
private String species;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
CustomerPetRequest that = (CustomerPetRequest) o;
|
||||
|
||||
return Objects.equals(petName, that.petName) && Objects.equals(species, that.species) && Objects.equals(breed, that.breed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
|
||||
return Objects.hash(petName, species, breed);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package com.petshop.backend.dto.customerpet;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Objects;
|
||||
|
||||
public class CustomerPetResponse {
|
||||
|
||||
private Long customerPetId;
|
||||
private Long customerId;
|
||||
private String petName;
|
||||
private String species;
|
||||
private String breed;
|
||||
private String imageUrl;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
public CustomerPetResponse() {
|
||||
}
|
||||
|
||||
public CustomerPetResponse(Long customerPetId, Long customerId, String petName, String species, String breed, String imageUrl, LocalDateTime createdAt, LocalDateTime updatedAt) {
|
||||
this.customerPetId = customerPetId;
|
||||
this.customerId = customerId;
|
||||
this.petName = petName;
|
||||
this.species = species;
|
||||
this.breed = breed;
|
||||
this.imageUrl = imageUrl;
|
||||
this.createdAt = createdAt;
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
|
||||
public Long getCustomerPetId() {
|
||||
|
||||
return customerPetId;
|
||||
}
|
||||
|
||||
public void setCustomerPetId(Long customerPetId) {
|
||||
this.customerPetId = customerPetId;
|
||||
}
|
||||
|
||||
public Long getCustomerId() {
|
||||
|
||||
return customerId;
|
||||
}
|
||||
|
||||
public void setCustomerId(Long customerId) {
|
||||
this.customerId = customerId;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreatedAt() {
|
||||
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(LocalDateTime createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public LocalDateTime getUpdatedAt() {
|
||||
|
||||
return updatedAt;
|
||||
}
|
||||
|
||||
public void setUpdatedAt(LocalDateTime updatedAt) {
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CustomerPetResponse that = (CustomerPetResponse) o;
|
||||
|
||||
return Objects.equals(customerPetId, that.customerPetId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
|
||||
return Objects.hash(customerPetId);
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,14 @@ public class Appointment {
|
||||
)
|
||||
private Set<Pet> pets = new HashSet<>();
|
||||
|
||||
@ManyToMany
|
||||
@JoinTable(
|
||||
name = "appointment_customer_pet",
|
||||
joinColumns = @JoinColumn(name = "appointment_id"),
|
||||
inverseJoinColumns = @JoinColumn(name = "customer_pet_id")
|
||||
)
|
||||
private Set<CustomerPet> customerPets = new HashSet<>();
|
||||
|
||||
@CreationTimestamp
|
||||
@Column(name = "created_at", updatable = false)
|
||||
private LocalDateTime createdAt;
|
||||
@@ -136,6 +144,15 @@ public class Appointment {
|
||||
this.pets = pets;
|
||||
}
|
||||
|
||||
public Set<CustomerPet> getCustomerPets() {
|
||||
|
||||
return customerPets;
|
||||
}
|
||||
|
||||
public void setCustomerPets(Set<CustomerPet> customerPets) {
|
||||
this.customerPets = customerPets;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
package com.petshop.backend.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import org.hibernate.annotations.CreationTimestamp;
|
||||
import org.hibernate.annotations.UpdateTimestamp;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Objects;
|
||||
|
||||
@Entity
|
||||
@Table(name = "customer_pet")
|
||||
public class CustomerPet {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "customer_pet_id")
|
||||
private Long customerPetId;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "customer_id", nullable = false)
|
||||
private Customer customer;
|
||||
|
||||
@Column(name = "pet_name", nullable = false, length = 50)
|
||||
private String petName;
|
||||
|
||||
@Column(nullable = false, length = 50)
|
||||
private String species;
|
||||
|
||||
@Column(length = 50)
|
||||
private String breed;
|
||||
|
||||
@Column(name = "image_url", length = 255)
|
||||
private String imageUrl;
|
||||
|
||||
@CreationTimestamp
|
||||
@Column(name = "created_at", updatable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@UpdateTimestamp
|
||||
@Column(name = "updated_at")
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
public CustomerPet() {
|
||||
}
|
||||
|
||||
public Long getCustomerPetId() {
|
||||
|
||||
return customerPetId;
|
||||
}
|
||||
|
||||
public void setCustomerPetId(Long customerPetId) {
|
||||
this.customerPetId = customerPetId;
|
||||
}
|
||||
|
||||
public Customer getCustomer() {
|
||||
|
||||
return customer;
|
||||
}
|
||||
|
||||
public void setCustomer(Customer customer) {
|
||||
this.customer = customer;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreatedAt() {
|
||||
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(LocalDateTime createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public LocalDateTime getUpdatedAt() {
|
||||
|
||||
return updatedAt;
|
||||
}
|
||||
|
||||
public void setUpdatedAt(LocalDateTime updatedAt) {
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CustomerPet that = (CustomerPet) o;
|
||||
return Objects.equals(customerPetId, that.customerPetId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
|
||||
return Objects.hash(customerPetId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.petshop.backend.repository;
|
||||
|
||||
import com.petshop.backend.entity.CustomerPet;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public interface CustomerPetRepository extends JpaRepository<CustomerPet, Long> {
|
||||
|
||||
List<CustomerPet> findByCustomerCustomerIdOrderByCreatedAtDesc(Long customerId);
|
||||
|
||||
Optional<CustomerPet> findByCustomerPetIdAndCustomerCustomerId(Long customerPetId, Long customerId);
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import com.petshop.backend.dto.appointment.AppointmentResponse;
|
||||
import com.petshop.backend.dto.common.BulkDeleteRequest;
|
||||
import com.petshop.backend.entity.Appointment;
|
||||
import com.petshop.backend.entity.Customer;
|
||||
import com.petshop.backend.entity.CustomerPet;
|
||||
import com.petshop.backend.entity.Employee;
|
||||
import com.petshop.backend.entity.EmployeeStore;
|
||||
import com.petshop.backend.entity.Pet;
|
||||
@@ -12,6 +13,7 @@ import com.petshop.backend.entity.StoreLocation;
|
||||
import com.petshop.backend.entity.User;
|
||||
import com.petshop.backend.exception.ResourceNotFoundException;
|
||||
import com.petshop.backend.repository.AppointmentRepository;
|
||||
import com.petshop.backend.repository.CustomerPetRepository;
|
||||
import com.petshop.backend.repository.CustomerRepository;
|
||||
import com.petshop.backend.repository.EmployeeRepository;
|
||||
import com.petshop.backend.repository.EmployeeStoreRepository;
|
||||
@@ -40,6 +42,7 @@ public class AppointmentService {
|
||||
|
||||
private final AppointmentRepository appointmentRepository;
|
||||
private final CustomerRepository customerRepository;
|
||||
private final CustomerPetRepository customerPetRepository;
|
||||
private final ServiceRepository serviceRepository;
|
||||
private final PetRepository petRepository;
|
||||
private final StoreRepository storeRepository;
|
||||
@@ -47,9 +50,10 @@ public class AppointmentService {
|
||||
private final EmployeeRepository employeeRepository;
|
||||
private final EmployeeStoreRepository employeeStoreRepository;
|
||||
|
||||
public AppointmentService(AppointmentRepository appointmentRepository, CustomerRepository customerRepository, ServiceRepository serviceRepository, PetRepository petRepository, StoreRepository storeRepository, UserRepository userRepository, EmployeeRepository employeeRepository, EmployeeStoreRepository employeeStoreRepository) {
|
||||
public AppointmentService(AppointmentRepository appointmentRepository, CustomerRepository customerRepository, CustomerPetRepository customerPetRepository, ServiceRepository serviceRepository, PetRepository petRepository, StoreRepository storeRepository, UserRepository userRepository, EmployeeRepository employeeRepository, EmployeeStoreRepository employeeStoreRepository) {
|
||||
this.appointmentRepository = appointmentRepository;
|
||||
this.customerRepository = customerRepository;
|
||||
this.customerPetRepository = customerPetRepository;
|
||||
this.serviceRepository = serviceRepository;
|
||||
this.petRepository = petRepository;
|
||||
this.storeRepository = storeRepository;
|
||||
@@ -107,7 +111,16 @@ public class AppointmentService {
|
||||
validateStoreAccess(store.getStoreId());
|
||||
validateAvailability(store, service, request.getAppointmentDate(), request.getAppointmentTime(), null);
|
||||
|
||||
Set<Pet> pets = fetchPets(request.getPetIds());
|
||||
boolean hasPetIds = request.getPetIds() != null && !request.getPetIds().isEmpty();
|
||||
boolean hasCustomerPetIds = request.getCustomerPetIds() != null && !request.getCustomerPetIds().isEmpty();
|
||||
|
||||
if (!hasPetIds && !hasCustomerPetIds) {
|
||||
|
||||
throw new IllegalArgumentException("Please specify at least one pet.");
|
||||
}
|
||||
|
||||
Set<Pet> pets = hasPetIds ? fetchPets(request.getPetIds()) : new HashSet<>();
|
||||
Set<CustomerPet> customerPets = hasCustomerPetIds ? fetchCustomerPets(request.getCustomerPetIds()) : new HashSet<>();
|
||||
|
||||
Appointment appointment = new Appointment();
|
||||
appointment.setCustomer(customer);
|
||||
@@ -117,6 +130,7 @@ public class AppointmentService {
|
||||
appointment.setAppointmentTime(request.getAppointmentTime());
|
||||
appointment.setAppointmentStatus(request.getAppointmentStatus());
|
||||
appointment.setPets(pets);
|
||||
appointment.setCustomerPets(customerPets);
|
||||
|
||||
appointment = appointmentRepository.save(appointment);
|
||||
return mapToResponse(appointment);
|
||||
@@ -141,7 +155,16 @@ public class AppointmentService {
|
||||
validateStoreAccess(store.getStoreId());
|
||||
validateAvailability(store, service, request.getAppointmentDate(), request.getAppointmentTime(), id);
|
||||
|
||||
Set<Pet> pets = fetchPets(request.getPetIds());
|
||||
boolean hasPetIds = request.getPetIds() != null && !request.getPetIds().isEmpty();
|
||||
boolean hasCustomerPetIds = request.getCustomerPetIds() != null && !request.getCustomerPetIds().isEmpty();
|
||||
|
||||
if (!hasPetIds && !hasCustomerPetIds) {
|
||||
|
||||
throw new IllegalArgumentException("Please specify at least one pet.");
|
||||
}
|
||||
|
||||
Set<Pet> pets = hasPetIds ? fetchPets(request.getPetIds()) : new HashSet<>();
|
||||
Set<CustomerPet> customerPets = hasCustomerPetIds ? fetchCustomerPets(request.getCustomerPetIds()) : new HashSet<>();
|
||||
|
||||
appointment.setCustomer(customer);
|
||||
appointment.setStore(store);
|
||||
@@ -150,6 +173,7 @@ public class AppointmentService {
|
||||
appointment.setAppointmentTime(request.getAppointmentTime());
|
||||
appointment.setAppointmentStatus(request.getAppointmentStatus());
|
||||
appointment.setPets(pets);
|
||||
appointment.setCustomerPets(customerPets);
|
||||
|
||||
appointment = appointmentRepository.save(appointment);
|
||||
return mapToResponse(appointment);
|
||||
@@ -213,6 +237,17 @@ public class AppointmentService {
|
||||
return pets;
|
||||
}
|
||||
|
||||
private Set<CustomerPet> fetchCustomerPets(List<Long> customerPetIds) {
|
||||
Set<CustomerPet> customerPets = new HashSet<>();
|
||||
for (Long customerPetId : customerPetIds) {
|
||||
CustomerPet customerPet = customerPetRepository.findById(customerPetId)
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Customer pet not found with id: " + customerPetId));
|
||||
customerPets.add(customerPet);
|
||||
}
|
||||
|
||||
return customerPets;
|
||||
}
|
||||
|
||||
private AppointmentResponse mapToResponse(Appointment appointment) {
|
||||
List<String> petNames = appointment.getPets().stream()
|
||||
.map(Pet::getPetName)
|
||||
@@ -222,22 +257,33 @@ public class AppointmentService {
|
||||
.map(Pet::getPetId)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return new AppointmentResponse(
|
||||
appointment.getAppointmentId(),
|
||||
appointment.getCustomer().getCustomerId(),
|
||||
appointment.getCustomer().getFirstName() + " " + appointment.getCustomer().getLastName(),
|
||||
appointment.getStore().getStoreId(),
|
||||
appointment.getStore().getStoreName(),
|
||||
appointment.getService().getServiceId(),
|
||||
appointment.getService().getServiceName(),
|
||||
appointment.getAppointmentDate(),
|
||||
appointment.getAppointmentTime(),
|
||||
appointment.getAppointmentStatus(),
|
||||
petNames,
|
||||
petIds,
|
||||
appointment.getCreatedAt(),
|
||||
appointment.getUpdatedAt()
|
||||
);
|
||||
List<String> customerPetNames = appointment.getCustomerPets().stream()
|
||||
.map(CustomerPet::getPetName)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<Long> customerPetIds = appointment.getCustomerPets().stream()
|
||||
.map(CustomerPet::getCustomerPetId)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
AppointmentResponse response = new AppointmentResponse();
|
||||
response.setAppointmentId(appointment.getAppointmentId());
|
||||
response.setCustomerId(appointment.getCustomer().getCustomerId());
|
||||
response.setCustomerName(appointment.getCustomer().getFirstName() + " " + appointment.getCustomer().getLastName());
|
||||
response.setStoreId(appointment.getStore().getStoreId());
|
||||
response.setStoreName(appointment.getStore().getStoreName());
|
||||
response.setServiceId(appointment.getService().getServiceId());
|
||||
response.setServiceName(appointment.getService().getServiceName());
|
||||
response.setAppointmentDate(appointment.getAppointmentDate());
|
||||
response.setAppointmentTime(appointment.getAppointmentTime());
|
||||
response.setAppointmentStatus(appointment.getAppointmentStatus());
|
||||
response.setPetNames(petNames);
|
||||
response.setPetIds(petIds);
|
||||
response.setCustomerPetNames(customerPetNames);
|
||||
response.setCustomerPetIds(customerPetIds);
|
||||
response.setCreatedAt(appointment.getCreatedAt());
|
||||
response.setUpdatedAt(appointment.getUpdatedAt());
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private void validateAvailability(StoreLocation store, com.petshop.backend.entity.Service service, LocalDate date, LocalTime time, Long appointmentIdToIgnore) {
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
package com.petshop.backend.service;
|
||||
|
||||
import com.petshop.backend.dto.customerpet.CustomerPetRequest;
|
||||
import com.petshop.backend.dto.customerpet.CustomerPetResponse;
|
||||
import com.petshop.backend.entity.Customer;
|
||||
import com.petshop.backend.entity.CustomerPet;
|
||||
import com.petshop.backend.exception.ResourceNotFoundException;
|
||||
import com.petshop.backend.repository.CustomerPetRepository;
|
||||
import com.petshop.backend.repository.CustomerRepository;
|
||||
import com.petshop.backend.repository.UserRepository;
|
||||
import com.petshop.backend.util.AuthenticationHelper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class CustomerPetService {
|
||||
|
||||
private final CustomerPetRepository customerPetRepository;
|
||||
private final CustomerRepository customerRepository;
|
||||
private final UserRepository userRepository;
|
||||
private final CatalogImageStorageService catalogImageStorageService;
|
||||
|
||||
public CustomerPetService(CustomerPetRepository customerPetRepository,
|
||||
CustomerRepository customerRepository,
|
||||
UserRepository userRepository,
|
||||
CatalogImageStorageService catalogImageStorageService) {
|
||||
this.customerPetRepository = customerPetRepository;
|
||||
this.customerRepository = customerRepository;
|
||||
this.userRepository = userRepository;
|
||||
this.catalogImageStorageService = catalogImageStorageService;
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public List<CustomerPetResponse> getMyPets() {
|
||||
Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository);
|
||||
|
||||
return customerPetRepository.findByCustomerCustomerIdOrderByCreatedAtDesc(customer.getCustomerId())
|
||||
.stream()
|
||||
.map(this::mapToResponse)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public CustomerPetResponse createPet(CustomerPetRequest request) {
|
||||
Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository);
|
||||
|
||||
CustomerPet pet = new CustomerPet();
|
||||
pet.setCustomer(customer);
|
||||
pet.setPetName(request.getPetName());
|
||||
pet.setSpecies(request.getSpecies());
|
||||
pet.setBreed(request.getBreed());
|
||||
|
||||
pet = customerPetRepository.save(pet);
|
||||
|
||||
return mapToResponse(pet);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public CustomerPetResponse updatePet(Long id, CustomerPetRequest request) {
|
||||
Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository);
|
||||
CustomerPet pet = customerPetRepository.findByCustomerPetIdAndCustomerCustomerId(id, customer.getCustomerId())
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Pet not found with id: " + id));
|
||||
|
||||
pet.setPetName(request.getPetName());
|
||||
pet.setSpecies(request.getSpecies());
|
||||
pet.setBreed(request.getBreed());
|
||||
|
||||
pet = customerPetRepository.save(pet);
|
||||
|
||||
return mapToResponse(pet);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void deletePet(Long id) {
|
||||
Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository);
|
||||
CustomerPet pet = customerPetRepository.findByCustomerPetIdAndCustomerCustomerId(id, customer.getCustomerId()).orElseThrow(() -> new ResourceNotFoundException("Pet not found with id: " + id));
|
||||
deleteStoredImageIfPresent(pet.getImageUrl());
|
||||
|
||||
customerPetRepository.delete(pet);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public CustomerPetResponse uploadImage(Long id, MultipartFile file) throws IOException {
|
||||
validateImageFile(file);
|
||||
Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository);
|
||||
CustomerPet pet = customerPetRepository.findByCustomerPetIdAndCustomerCustomerId(id, customer.getCustomerId()).orElseThrow(() -> new ResourceNotFoundException("Pet not found with id: " + id));
|
||||
deleteStoredImageIfPresent(pet.getImageUrl());
|
||||
pet.setImageUrl(catalogImageStorageService.storePetImage(file));
|
||||
|
||||
return mapToResponse(customerPetRepository.save(pet));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public CustomerPetResponse deleteImage(Long id) {
|
||||
Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository);
|
||||
CustomerPet pet = customerPetRepository.findByCustomerPetIdAndCustomerCustomerId(id, customer.getCustomerId()).orElseThrow(() -> new ResourceNotFoundException("Pet not found with id: " + id));
|
||||
deleteStoredImageIfPresent(pet.getImageUrl());
|
||||
pet.setImageUrl(null);
|
||||
|
||||
return mapToResponse(customerPetRepository.save(pet));
|
||||
}
|
||||
|
||||
private CustomerPetResponse mapToResponse(CustomerPet pet) {
|
||||
return new CustomerPetResponse(
|
||||
pet.getCustomerPetId(),
|
||||
pet.getCustomer().getCustomerId(),
|
||||
pet.getPetName(),
|
||||
pet.getSpecies(),
|
||||
pet.getBreed(),
|
||||
pet.getImageUrl() != null && !pet.getImageUrl().isBlank()
|
||||
? "/api/v1/my-pets/" + pet.getCustomerPetId() + "/image"
|
||||
: null,
|
||||
pet.getCreatedAt(),
|
||||
pet.getUpdatedAt()
|
||||
);
|
||||
}
|
||||
|
||||
private void validateImageFile(MultipartFile file) {
|
||||
if (file == null || file.isEmpty()) {
|
||||
|
||||
throw new IllegalArgumentException("Please select an image to upload");
|
||||
}
|
||||
|
||||
if (file.getSize() > 5 * 1024 * 1024) {
|
||||
|
||||
throw new IllegalArgumentException("Image file size must be less than 5MB");
|
||||
}
|
||||
|
||||
String contentType = file.getContentType();
|
||||
|
||||
if (contentType == null) {
|
||||
|
||||
throw new IllegalArgumentException("Only JPG, PNG, and GIF images are allowed");
|
||||
}
|
||||
|
||||
String normalized = contentType.toLowerCase(Locale.ROOT);
|
||||
|
||||
if (!normalized.equals("image/jpeg") && !normalized.equals("image/png") && !normalized.equals("image/gif")) {
|
||||
|
||||
throw new IllegalArgumentException("Only JPG, PNG, and GIF images are allowed");
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteStoredImageIfPresent(String storedImagePath) {
|
||||
if (storedImagePath == null || storedImagePath.isBlank()) {
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
catalogImageStorageService.deletePetImage(storedImagePath);
|
||||
}
|
||||
|
||||
catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
INSERT INTO service (serviceName, serviceDesc, serviceDuration, servicePrice)
|
||||
VALUES ('Pet Adoption', 'Schedule a visit to meet and adopt an available pet', 30, 0.00);
|
||||
@@ -0,0 +1,7 @@
|
||||
CREATE TABLE IF NOT EXISTS appointment_customer_pet (
|
||||
appointment_id BIGINT NOT NULL,
|
||||
customer_pet_id BIGINT NOT NULL,
|
||||
PRIMARY KEY (appointment_id, customer_pet_id),
|
||||
FOREIGN KEY (appointment_id) REFERENCES appointment(appointmentId),
|
||||
FOREIGN KEY (customer_pet_id) REFERENCES customer_pet(customer_pet_id)
|
||||
);
|
||||
11
backend/src/main/resources/db/migration/V9__customer_pet.sql
Normal file
11
backend/src/main/resources/db/migration/V9__customer_pet.sql
Normal file
@@ -0,0 +1,11 @@
|
||||
CREATE TABLE IF NOT EXISTS customer_pet (
|
||||
customer_pet_id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
customer_id BIGINT NOT NULL,
|
||||
pet_name VARCHAR(50) NOT NULL,
|
||||
species VARCHAR(50) NOT NULL,
|
||||
breed VARCHAR(50) NULL,
|
||||
image_url VARCHAR(255) NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (customer_id) REFERENCES customer(customerId)
|
||||
);
|
||||
Reference in New Issue
Block a user