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
+);