From 4e5db56cd63ad196ea03f0a50f53a57ab4411cb5 Mon Sep 17 00:00:00 2001 From: chetan deore Date: Fri, 18 Jul 2025 21:48:17 +0530 Subject: [PATCH] added vaccine apis and delete apis --- pom.xml | 13 ++++++ .../controller/OwnerHistoryController.java | 29 ++++++++++++ .../petclinic/api/service/OwnerService.java | 16 +++++++ .../exception/NotFoundException.java | 8 ++++ .../samples/petclinic/owner/Owner.java | 11 +++++ .../petclinic/owner/OwnerController.java | 28 ++++++++--- .../petclinic/owner/OwnerRepository.java | 2 + .../samples/petclinic/owner/Pet.java | 19 ++++++++ .../petclinic/vaccine/Vaccination.java | 23 ++++++++++ .../vaccine/VaccinationController.java | 21 +++++++++ .../vaccine/VaccinationRepository.java | 8 ++++ .../petclinic/vaccine/VaccinationService.java | 46 +++++++++++++++++++ .../vaccine/predicate/PetPredicates.java | 16 +++++++ .../vaccine/request/VaccinationRequest.java | 18 ++++++++ .../resources/application-postgres.properties | 12 +++-- src/main/resources/application.properties | 15 ++++-- src/main/resources/db/postgres/alter.sql | 16 +++++++ 17 files changed, 286 insertions(+), 15 deletions(-) create mode 100644 src/main/java/org/springframework/samples/petclinic/api/controller/OwnerHistoryController.java create mode 100644 src/main/java/org/springframework/samples/petclinic/api/service/OwnerService.java create mode 100644 src/main/java/org/springframework/samples/petclinic/exception/NotFoundException.java create mode 100644 src/main/java/org/springframework/samples/petclinic/vaccine/Vaccination.java create mode 100644 src/main/java/org/springframework/samples/petclinic/vaccine/VaccinationController.java create mode 100644 src/main/java/org/springframework/samples/petclinic/vaccine/VaccinationRepository.java create mode 100644 src/main/java/org/springframework/samples/petclinic/vaccine/VaccinationService.java create mode 100644 src/main/java/org/springframework/samples/petclinic/vaccine/predicate/PetPredicates.java create mode 100644 src/main/java/org/springframework/samples/petclinic/vaccine/request/VaccinationRequest.java create mode 100644 src/main/resources/db/postgres/alter.sql diff --git a/pom.xml b/pom.xml index 8576c22ba..95eaa2faf 100644 --- a/pom.xml +++ b/pom.xml @@ -145,6 +145,19 @@ jakarta.xml.bind jakarta.xml.bind-api + + + org.projectlombok + lombok + true + diff --git a/src/main/java/org/springframework/samples/petclinic/api/controller/OwnerHistoryController.java b/src/main/java/org/springframework/samples/petclinic/api/controller/OwnerHistoryController.java new file mode 100644 index 000000000..af9a10ccb --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/api/controller/OwnerHistoryController.java @@ -0,0 +1,29 @@ +package org.springframework.samples.petclinic.api.controller; + +import jakarta.validation.Valid; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.samples.petclinic.api.service.OwnerService; +import org.springframework.samples.petclinic.owner.Owner; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +@RestController("owner-controller") +@RestControllerAdvice +@Validated +@RequestMapping("/api/owner") +public class OwnerHistoryController { + + @Autowired + private OwnerService ownerService; + + @PostMapping + public void addOwner(@Valid @RequestBody Owner owner) { + ownerService.addOwner(owner); + } + + @GetMapping + public String getOwner() { + return "API working"; + } + +} diff --git a/src/main/java/org/springframework/samples/petclinic/api/service/OwnerService.java b/src/main/java/org/springframework/samples/petclinic/api/service/OwnerService.java new file mode 100644 index 000000000..bc0035482 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/api/service/OwnerService.java @@ -0,0 +1,16 @@ +package org.springframework.samples.petclinic.api.service; + +import org.springframework.samples.petclinic.owner.Owner; +import org.springframework.samples.petclinic.owner.OwnerRepository; +import org.springframework.stereotype.Service; + +@Service +public class OwnerService { + + private OwnerRepository ownerRepository; + + public void addOwner(Owner owner) { + ownerRepository.save(owner); + } + +} diff --git a/src/main/java/org/springframework/samples/petclinic/exception/NotFoundException.java b/src/main/java/org/springframework/samples/petclinic/exception/NotFoundException.java new file mode 100644 index 000000000..65e4c0918 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/exception/NotFoundException.java @@ -0,0 +1,8 @@ +package org.springframework.samples.petclinic.exception; + +public class NotFoundException extends RuntimeException{ + + public NotFoundException(String message){ + super(message); + } +} diff --git a/src/main/java/org/springframework/samples/petclinic/owner/Owner.java b/src/main/java/org/springframework/samples/petclinic/owner/Owner.java index 63e3acc7a..feac267f7 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/Owner.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/Owner.java @@ -18,6 +18,7 @@ package org.springframework.samples.petclinic.owner; import java.util.ArrayList; import java.util.List; +import lombok.Data; import org.springframework.core.style.ToStringCreator; import org.springframework.samples.petclinic.model.Person; import org.springframework.util.Assert; @@ -60,6 +61,8 @@ public class Owner extends Person { @Pattern(regexp = "\\d{10}", message = "{telephone.invalid}") private String telephone; + private Boolean active; + @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) @JoinColumn(name = "owner_id") @OrderBy("name") @@ -93,6 +96,14 @@ public class Owner extends Person { return this.pets; } + public Boolean isActive() { + return active; + } + + public void setActive(Boolean active) { + this.active = active; + } + public void addPet(Pet pet) { if (pet.isNew()) { getPets().add(pet); diff --git a/src/main/java/org/springframework/samples/petclinic/owner/OwnerController.java b/src/main/java/org/springframework/samples/petclinic/owner/OwnerController.java index 1348457ee..911da2a5b 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/OwnerController.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/OwnerController.java @@ -18,24 +18,23 @@ package org.springframework.samples.petclinic.owner; import java.util.List; import java.util.Optional; +import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; +import org.springframework.samples.petclinic.exception.NotFoundException; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.web.bind.WebDataBinder; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.InitBinder; -import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.ModelAndView; import jakarta.validation.Valid; import org.springframework.web.servlet.mvc.support.RedirectAttributes; +import static java.lang.String.format; + /** * @author Juergen Hoeller * @author Ken Krebs @@ -43,7 +42,9 @@ import org.springframework.web.servlet.mvc.support.RedirectAttributes; * @author Michael Isvy * @author Wick Dynex */ -@Controller +@RestController +@Slf4j +@RequestMapping class OwnerController { private static final String VIEWS_OWNER_CREATE_OR_UPDATE_FORM = "owners/createOrUpdateOwnerForm"; @@ -170,4 +171,17 @@ class OwnerController { return mav; } + @DeleteMapping("owners/{ownerId}") + public void deleteOwner(@PathVariable("ownerId") int id, + @RequestParam(name = "deletePets", required = false, defaultValue = "false") boolean deletePets){ + var owner = this.owners.findById(id) + .orElseThrow(() -> new NotFoundException(format("owner not found with id %s", id))); + owner.setActive(Boolean.FALSE); + if(deletePets){ + owner.getPets() + .forEach(pet -> pet.setActive(Boolean.FALSE)); + } + owners.save(owner); + log.info("Owner {} marked inactive{}", id, deletePets ? " along with pets" : ""); + } } diff --git a/src/main/java/org/springframework/samples/petclinic/owner/OwnerRepository.java b/src/main/java/org/springframework/samples/petclinic/owner/OwnerRepository.java index 9384b318e..801151b09 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/OwnerRepository.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/OwnerRepository.java @@ -62,4 +62,6 @@ public interface OwnerRepository extends JpaRepository { */ Optional findById(@Nonnull Integer id); + Optional findByIdAndActive(@Nonnull Integer id, Boolean active); + } diff --git a/src/main/java/org/springframework/samples/petclinic/owner/Pet.java b/src/main/java/org/springframework/samples/petclinic/owner/Pet.java index 1945f9b67..07dc97026 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/Pet.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/Pet.java @@ -17,6 +17,7 @@ package org.springframework.samples.petclinic.owner; import java.time.LocalDate; import java.util.Collection; +import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Set; @@ -32,6 +33,7 @@ import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; import jakarta.persistence.OrderBy; import jakarta.persistence.Table; +import org.springframework.samples.petclinic.vaccine.Vaccination; /** * Simple business object representing a pet. @@ -53,11 +55,16 @@ public class Pet extends NamedEntity { @JoinColumn(name = "type_id") private PetType type; + private Boolean active; + @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) @JoinColumn(name = "pet_id") @OrderBy("date ASC") private final Set visits = new LinkedHashSet<>(); + @OneToMany(mappedBy = "pet", cascade = CascadeType.ALL, fetch = FetchType.EAGER) + private final Set vaccinations = new LinkedHashSet<>(); + public void setBirthDate(LocalDate birthDate) { this.birthDate = birthDate; } @@ -82,4 +89,16 @@ public class Pet extends NamedEntity { getVisits().add(visit); } + public Boolean getActive() { + return active; + } + + public void setActive(Boolean active) { + this.active = active; + } + + public void addVaccination(Vaccination vaccination) { + vaccination.setPet(this); + this.vaccinations.add(vaccination); + } } diff --git a/src/main/java/org/springframework/samples/petclinic/vaccine/Vaccination.java b/src/main/java/org/springframework/samples/petclinic/vaccine/Vaccination.java new file mode 100644 index 000000000..1ce91a3d9 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/vaccine/Vaccination.java @@ -0,0 +1,23 @@ +package org.springframework.samples.petclinic.vaccine; + +import jakarta.persistence.*; +import lombok.Data; +import org.springframework.samples.petclinic.owner.Pet; + +import java.time.LocalDate; + +@Entity +@Data +public class Vaccination { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private String vaccineName; + private LocalDate vaccinationDate; + private String description; + private Boolean injected; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "pet_id", nullable = false) + private Pet pet; +} diff --git a/src/main/java/org/springframework/samples/petclinic/vaccine/VaccinationController.java b/src/main/java/org/springframework/samples/petclinic/vaccine/VaccinationController.java new file mode 100644 index 000000000..1e0396842 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/vaccine/VaccinationController.java @@ -0,0 +1,21 @@ +package org.springframework.samples.petclinic.vaccine; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.samples.petclinic.owner.OwnerRepository; +import org.springframework.samples.petclinic.owner.PetTypeRepository; +import org.springframework.samples.petclinic.vaccine.request.VaccinationRequest; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/vaccine") +public class VaccinationController { + + private VaccinationService vaccinationService; + + @PostMapping("/owner/{owner_id}/pet/{petId}") + public void addVaccine(@PathVariable Integer owner_id, @PathVariable Long petId, + @RequestBody VaccinationRequest request){ + + vaccinationService.addVaccineToPet(owner_id, petId, request); + } +} diff --git a/src/main/java/org/springframework/samples/petclinic/vaccine/VaccinationRepository.java b/src/main/java/org/springframework/samples/petclinic/vaccine/VaccinationRepository.java new file mode 100644 index 000000000..df1599eeb --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/vaccine/VaccinationRepository.java @@ -0,0 +1,8 @@ +package org.springframework.samples.petclinic.vaccine; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface VaccinationRepository extends JpaRepository { +} diff --git a/src/main/java/org/springframework/samples/petclinic/vaccine/VaccinationService.java b/src/main/java/org/springframework/samples/petclinic/vaccine/VaccinationService.java new file mode 100644 index 000000000..a13c33057 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/vaccine/VaccinationService.java @@ -0,0 +1,46 @@ +package org.springframework.samples.petclinic.vaccine; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.samples.petclinic.exception.NotFoundException; +import org.springframework.samples.petclinic.owner.OwnerRepository; +import org.springframework.samples.petclinic.owner.Pet; +import org.springframework.samples.petclinic.vaccine.predicate.PetPredicates; +import org.springframework.samples.petclinic.vaccine.request.VaccinationRequest; +import org.springframework.stereotype.Service; + +import java.util.HashSet; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import static java.lang.String.format; + +@Service +public class VaccinationService { + + @Autowired + private VaccinationRepository vaccinationRepository; + private OwnerRepository ownerRepository; + + + + public void addVaccineToPet(Integer ownerId, Long petId, VaccinationRequest request) { + var owner = ownerRepository.findByIdAndActive(ownerId, Boolean.TRUE) + .orElseThrow(()-> new NotFoundException(format("owner not found with id %s", ownerId))); + + var pet = owner.getPets().stream() + .filter(PetPredicates.isActive().and(PetPredicates.hasId(petId))) + .findFirst() + .orElseThrow(() -> new NotFoundException(format("Active pet not found with id %s", petId)));; + + Vaccination vaccination = new Vaccination(); + vaccination.setPet(pet); + vaccination.setVaccineName("Vacine1"); + vaccination.setDescription("Need to inject"); + vaccination.setVaccinationDate(request.getVaccinationDate()); + + pet.addVaccination(vaccination); + + vaccinationRepository.save(vaccination); + + } +} diff --git a/src/main/java/org/springframework/samples/petclinic/vaccine/predicate/PetPredicates.java b/src/main/java/org/springframework/samples/petclinic/vaccine/predicate/PetPredicates.java new file mode 100644 index 000000000..9fc02991c --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/vaccine/predicate/PetPredicates.java @@ -0,0 +1,16 @@ +package org.springframework.samples.petclinic.vaccine.predicate; + +import org.springframework.samples.petclinic.owner.Pet; + +import java.util.function.Predicate; + +public class PetPredicates { + + public static Predicate isActive() { + return pet -> Boolean.TRUE.equals(pet.getActive()); + } + + public static Predicate hasId(Long petId) { + return pet -> petId.equals(pet.getId()); + } +} diff --git a/src/main/java/org/springframework/samples/petclinic/vaccine/request/VaccinationRequest.java b/src/main/java/org/springframework/samples/petclinic/vaccine/request/VaccinationRequest.java new file mode 100644 index 000000000..7bfcd350c --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/vaccine/request/VaccinationRequest.java @@ -0,0 +1,18 @@ +package org.springframework.samples.petclinic.vaccine.request; + +import jakarta.annotation.Nonnull; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +import java.time.LocalDate; + +@Data +@Valid +public class VaccinationRequest { + @NotBlank + private String vaccineName; + private LocalDate vaccinationDate; + @NotBlank + private String description; +} diff --git a/src/main/resources/application-postgres.properties b/src/main/resources/application-postgres.properties index b265d7e5b..a94c3d968 100644 --- a/src/main/resources/application-postgres.properties +++ b/src/main/resources/application-postgres.properties @@ -1,7 +1,11 @@ # database init, supports postgres too -database=postgres -spring.datasource.url=${POSTGRES_URL:jdbc:postgresql://localhost/petclinic} -spring.datasource.username=${POSTGRES_USER:petclinic} -spring.datasource.password=${POSTGRES_PASS:petclinic} +database=petclinic +#spring.datasource.url=${POSTGRES_URL:jdbc:postgresql://localhost/petclinic} +spring.datasource.url=jdbc:postgresql://localhost:5432/petclinic +spring.datasource.username=postgres +spring.datasource.password=root + +#spring.datasource.username=${POSTGRES_USER:petclinic} +#spring.datasource.password=${POSTGRES_PASS:petclinic} # SQL is written to be idempotent so this is safe spring.sql.init.mode=always diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 6ed985654..49c5fb8d6 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,14 +1,19 @@ # database init, supports mysql too -database=h2 -spring.sql.init.schema-locations=classpath*:db/${database}/schema.sql -spring.sql.init.data-locations=classpath*:db/${database}/data.sql +database=postgres +spring.sql.init.schema-locations=classpath*:db/postgres/schema.sql,classpath*:db/postgres/alter.sql +spring.sql.init.data-locations=classpath*:db/postgres/data.sql + +spring.jpa.show-sql=true +spring.jpa.properties.hibernate.format_sql=true +logging.level.org.springframework.jdbc.datasource.init=DEBUG + # Web spring.thymeleaf.mode=HTML # JPA spring.jpa.hibernate.ddl-auto=none -spring.jpa.open-in-view=false +spring.jpa.open-in-view=true # Internationalization spring.messages.basename=messages/messages @@ -23,3 +28,5 @@ logging.level.org.springframework=INFO # Maximum time static resources should be cached spring.web.resources.cache.cachecontrol.max-age=12h + + diff --git a/src/main/resources/db/postgres/alter.sql b/src/main/resources/db/postgres/alter.sql new file mode 100644 index 000000000..6ab3e1517 --- /dev/null +++ b/src/main/resources/db/postgres/alter.sql @@ -0,0 +1,16 @@ +ALTER TABLE owners ADD COLUMN IF NOT EXISTS active BOOLEAN DEFAULT TRUE; + +ALTER TABLE pets ADD COLUMN IF NOT EXISTS active BOOLEAN DEFAULT TRUE; + +CREATE TABLE IF NOT EXISTS vaccinations ( + id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + vaccine_name TEXT NOT NULL, + vaccination_date DATE NOT NULL, + description TEXT, + injected BOOLEAN DEFAULT FALSE, + pet_id BIGINT NOT NULL, + CONSTRAINT fk_pet + FOREIGN KEY (pet_id) + REFERENCES pets(id) + ON DELETE CASCADE +);