summaryrefslogtreecommitdiff
path: root/api/src
diff options
context:
space:
mode:
authorlucashemi <lucasxberger@gmail.com>2023-02-24 16:14:01 -0300
committerlucashemi <lucasxberger@gmail.com>2023-02-24 16:14:01 -0300
commit9f82c403098bb96acd8b7116f84416d3b4643b57 (patch)
treef69200376357e803dab229ff5cfea0c001e1803c /api/src
parent514f2e7194a875cfc53d7e1bccd922db2bbb3f3f (diff)
api-appointments
Diffstat (limited to 'api/src')
-rw-r--r--api/src/main/java/med/voll/api/controller/AppointmentController.java34
-rw-r--r--api/src/main/java/med/voll/api/controller/AuthenticationController.java3
-rw-r--r--api/src/main/java/med/voll/api/controller/DoctorController.java2
-rw-r--r--api/src/main/java/med/voll/api/controller/PatientController.java2
-rw-r--r--api/src/main/java/med/voll/api/domain/appointment/Appointment.java47
-rw-r--r--api/src/main/java/med/voll/api/domain/appointment/AppointmentDeletionData.java11
-rw-r--r--api/src/main/java/med/voll/api/domain/appointment/AppointmentListingData.java10
-rw-r--r--api/src/main/java/med/voll/api/domain/appointment/AppointmentRegistrationData.java17
-rw-r--r--api/src/main/java/med/voll/api/domain/appointment/AppointmentRepository.java18
-rw-r--r--api/src/main/java/med/voll/api/domain/appointment/AppointmentUpdateData.java19
-rw-r--r--api/src/main/java/med/voll/api/domain/appointment/AppointmentsSchedule.java75
-rw-r--r--api/src/main/java/med/voll/api/domain/appointment/ReasonForCancellation.java8
-rw-r--r--api/src/main/java/med/voll/api/domain/appointment/validations/cancellation/AppointmentCancellationValidator.java7
-rw-r--r--api/src/main/java/med/voll/api/domain/appointment/validations/cancellation/ValidatesTimeInAdvance.java27
-rw-r--r--api/src/main/java/med/voll/api/domain/appointment/validations/scheduling/AppointmentSchedulingValidator.java7
-rw-r--r--api/src/main/java/med/voll/api/domain/appointment/validations/scheduling/ValidatesActiveDoctor.java26
-rw-r--r--api/src/main/java/med/voll/api/domain/appointment/validations/scheduling/ValidatesActivePatient.java21
-rw-r--r--api/src/main/java/med/voll/api/domain/appointment/validations/scheduling/ValidatesDateAndTime.java21
-rw-r--r--api/src/main/java/med/voll/api/domain/appointment/validations/scheduling/ValidatesDoctorWithOtherAppointmentAtTheSameTime.java21
-rw-r--r--api/src/main/java/med/voll/api/domain/appointment/validations/scheduling/ValidatesPatientDoesntHaveAnotherAppointmentToday.java24
-rw-r--r--api/src/main/java/med/voll/api/domain/appointment/validations/scheduling/ValidatesTimeInAdvance.java21
-rw-r--r--api/src/main/java/med/voll/api/domain/doctor/DoctorRepository.java20
-rw-r--r--api/src/main/java/med/voll/api/domain/patient/PatientRepository.java7
-rw-r--r--api/src/main/java/med/voll/api/infra/exception/ErrorTreatment.java5
-rw-r--r--api/src/main/java/med/voll/api/infra/security/SecurityConfigurations.java1
-rw-r--r--api/src/main/java/med/voll/api/infra/security/TokenService.java2
-rw-r--r--api/src/main/java/med/voll/api/infra/springdoc/SpringDocConfigurations.java34
-rw-r--r--api/src/main/resources/application-prod.properties4
-rw-r--r--api/src/main/resources/application-test.properties1
-rw-r--r--api/src/main/resources/db/migration/V7__create-table-appointments.sql12
-rw-r--r--api/src/main/resources/db/migration/V8__alter-table-appointments-add-column-reason-for-cancellation.sql1
-rw-r--r--api/src/main/resources/db/migration/V9__alter-table-appointments-add-column-active.sql1
-rw-r--r--api/src/test/java/med/voll/api/ApiApplicationTests.java13
-rw-r--r--api/src/test/java/med/voll/api/controller/AppointmentControllerTest.java82
-rw-r--r--api/src/test/java/med/voll/api/controller/DoctorControllerTest.java77
-rw-r--r--api/src/test/java/med/voll/api/domain/doctor/DoctorRepositoryTest.java107
36 files changed, 773 insertions, 15 deletions
diff --git a/api/src/main/java/med/voll/api/controller/AppointmentController.java b/api/src/main/java/med/voll/api/controller/AppointmentController.java
new file mode 100644
index 0000000..e5a2ba4
--- /dev/null
+++ b/api/src/main/java/med/voll/api/controller/AppointmentController.java
@@ -0,0 +1,34 @@
+package med.voll.api.controller;
+
+import io.swagger.v3.oas.annotations.security.SecurityRequirement;
+import jakarta.validation.Valid;
+import med.voll.api.domain.appointment.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.util.UriComponentsBuilder;
+
+@RestController
+@RequestMapping("/appointments")
+@SecurityRequirement(name = "bearer-key")
+public class AppointmentController {
+ @Autowired
+ private AppointmentsSchedule appointmentsSchedule;
+
+ @PostMapping
+ @Transactional
+ public ResponseEntity register(@RequestBody @Valid AppointmentRegistrationData data, UriComponentsBuilder uriBuilder) {
+ var dto = appointmentsSchedule.schedule(data);
+ return ResponseEntity.ok(dto);
+ }
+
+ @DeleteMapping
+ @Transactional
+ public ResponseEntity cancel(@RequestBody @Valid AppointmentDeletionData data) {
+ appointmentsSchedule.cancel(data);
+ return ResponseEntity.noContent().build();
+ }
+
+
+}
diff --git a/api/src/main/java/med/voll/api/controller/AuthenticationController.java b/api/src/main/java/med/voll/api/controller/AuthenticationController.java
index b7dcf27..f86fc02 100644
--- a/api/src/main/java/med/voll/api/controller/AuthenticationController.java
+++ b/api/src/main/java/med/voll/api/controller/AuthenticationController.java
@@ -14,8 +14,9 @@ import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
-@Controller
+@RestController
@RequestMapping("/login")
public class AuthenticationController {
diff --git a/api/src/main/java/med/voll/api/controller/DoctorController.java b/api/src/main/java/med/voll/api/controller/DoctorController.java
index a262d34..f87da56 100644
--- a/api/src/main/java/med/voll/api/controller/DoctorController.java
+++ b/api/src/main/java/med/voll/api/controller/DoctorController.java
@@ -1,5 +1,6 @@
package med.voll.api.controller;
+import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import jakarta.validation.Valid;
import med.voll.api.domain.doctor.DoctorListingData;
import med.voll.api.domain.doctor.Doctor;
@@ -17,6 +18,7 @@ import org.springframework.web.util.UriComponentsBuilder;
@RestController
@RequestMapping("/doctors")
+@SecurityRequirement(name = "bearer-key")
public class DoctorController {
@Autowired
diff --git a/api/src/main/java/med/voll/api/controller/PatientController.java b/api/src/main/java/med/voll/api/controller/PatientController.java
index bd774a1..5675cfb 100644
--- a/api/src/main/java/med/voll/api/controller/PatientController.java
+++ b/api/src/main/java/med/voll/api/controller/PatientController.java
@@ -1,5 +1,6 @@
package med.voll.api.controller;
+import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import jakarta.validation.Valid;
import med.voll.api.domain.patient.PatientListingData;
import med.voll.api.domain.patient.Patient;
@@ -17,6 +18,7 @@ import org.springframework.web.util.UriComponentsBuilder;
@RestController
@RequestMapping("/patients")
+@SecurityRequirement(name = "bearer-key")
public class PatientController {
@Autowired
PatientRepository patientRepository;
diff --git a/api/src/main/java/med/voll/api/domain/appointment/Appointment.java b/api/src/main/java/med/voll/api/domain/appointment/Appointment.java
new file mode 100644
index 0000000..a48c15f
--- /dev/null
+++ b/api/src/main/java/med/voll/api/domain/appointment/Appointment.java
@@ -0,0 +1,47 @@
+package med.voll.api.domain.appointment;
+
+import jakarta.persistence.*;
+import lombok.AllArgsConstructor;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import med.voll.api.domain.doctor.Doctor;
+import med.voll.api.domain.patient.Patient;
+
+import java.time.LocalDateTime;
+
+@Table(name = "appointments")
+@Entity(name = "Appointment")
+@Getter
+@NoArgsConstructor
+@AllArgsConstructor
+@EqualsAndHashCode(of = "id")
+public class Appointment {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+ private LocalDateTime date;
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "doctor_id")
+ private Doctor doctor;
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "patient_id")
+ private Patient patient;
+ private Boolean active = true;
+
+ @Column(name = "reason_for_cancellation")
+ @Enumerated(EnumType.STRING)
+ private ReasonForCancellation reasonForCancellation;
+
+ public Appointment(Long id, LocalDateTime date, Doctor doctor, Patient patient) {
+ this.id = id;
+ this.date = date;
+ this.doctor = doctor;
+ this.patient = patient;
+ }
+
+ public void cancel(ReasonForCancellation reasonForCancellation) {
+ this.reasonForCancellation = reasonForCancellation;
+ this.active = false;
+ }
+}
diff --git a/api/src/main/java/med/voll/api/domain/appointment/AppointmentDeletionData.java b/api/src/main/java/med/voll/api/domain/appointment/AppointmentDeletionData.java
new file mode 100644
index 0000000..81dc28e
--- /dev/null
+++ b/api/src/main/java/med/voll/api/domain/appointment/AppointmentDeletionData.java
@@ -0,0 +1,11 @@
+package med.voll.api.domain.appointment;
+
+import jakarta.validation.constraints.NotNull;
+
+public record AppointmentDeletionData(
+ @NotNull
+ Long idAppointment,
+ @NotNull
+ ReasonForCancellation reasonForCancellation
+) {
+}
diff --git a/api/src/main/java/med/voll/api/domain/appointment/AppointmentListingData.java b/api/src/main/java/med/voll/api/domain/appointment/AppointmentListingData.java
new file mode 100644
index 0000000..4070a4c
--- /dev/null
+++ b/api/src/main/java/med/voll/api/domain/appointment/AppointmentListingData.java
@@ -0,0 +1,10 @@
+package med.voll.api.domain.appointment;
+
+import java.time.LocalDateTime;
+
+public record AppointmentListingData(Long id, LocalDateTime date, Long idDoctor, Long idPatient) {
+
+ public AppointmentListingData(Appointment appointment) {
+ this(appointment.getId(), appointment.getDate(), appointment.getDoctor().getId(), appointment.getPatient().getId());
+ }
+}
diff --git a/api/src/main/java/med/voll/api/domain/appointment/AppointmentRegistrationData.java b/api/src/main/java/med/voll/api/domain/appointment/AppointmentRegistrationData.java
new file mode 100644
index 0000000..fa62bde
--- /dev/null
+++ b/api/src/main/java/med/voll/api/domain/appointment/AppointmentRegistrationData.java
@@ -0,0 +1,17 @@
+package med.voll.api.domain.appointment;
+
+import jakarta.validation.constraints.Future;
+import jakarta.validation.constraints.NotNull;
+import med.voll.api.domain.doctor.Specialty;
+
+import java.time.LocalDateTime;
+
+public record AppointmentRegistrationData(
+ Long idDoctor,
+ @NotNull(message = "Patient is required")
+ Long idPatient,
+ @NotNull
+ @Future
+ LocalDateTime date,
+ Specialty specialty) {
+}
diff --git a/api/src/main/java/med/voll/api/domain/appointment/AppointmentRepository.java b/api/src/main/java/med/voll/api/domain/appointment/AppointmentRepository.java
new file mode 100644
index 0000000..a56e3c7
--- /dev/null
+++ b/api/src/main/java/med/voll/api/domain/appointment/AppointmentRepository.java
@@ -0,0 +1,18 @@
+package med.voll.api.domain.appointment;
+
+import med.voll.api.domain.doctor.Doctor;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import java.time.LocalDateTime;
+
+@Repository
+public interface AppointmentRepository extends JpaRepository<Appointment, Long> {
+ Page<Appointment> findAllByActiveTrue(Pageable pagination);
+
+ boolean existsByDoctorIdAndDateAndActiveIsTrue(Long idDoctor, LocalDateTime date);
+
+ boolean existsByPatientIdAndDateBetween(Long idPatient, LocalDateTime earliestTime, LocalDateTime latestTime);
+}
diff --git a/api/src/main/java/med/voll/api/domain/appointment/AppointmentUpdateData.java b/api/src/main/java/med/voll/api/domain/appointment/AppointmentUpdateData.java
new file mode 100644
index 0000000..c13712c
--- /dev/null
+++ b/api/src/main/java/med/voll/api/domain/appointment/AppointmentUpdateData.java
@@ -0,0 +1,19 @@
+package med.voll.api.domain.appointment;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+
+import java.time.LocalDateTime;
+
+public record AppointmentUpdateData(
+ @NotNull
+ Long id,
+ Integer year,
+ Integer month,
+ Integer day,
+ Integer hour,
+ Integer minute,
+ String doctor,
+ String patient
+) {
+}
diff --git a/api/src/main/java/med/voll/api/domain/appointment/AppointmentsSchedule.java b/api/src/main/java/med/voll/api/domain/appointment/AppointmentsSchedule.java
new file mode 100644
index 0000000..fd7d4e8
--- /dev/null
+++ b/api/src/main/java/med/voll/api/domain/appointment/AppointmentsSchedule.java
@@ -0,0 +1,75 @@
+package med.voll.api.domain.appointment;
+
+import jakarta.validation.ValidationException;
+import med.voll.api.domain.appointment.validations.cancellation.AppointmentCancellationValidator;
+import med.voll.api.domain.appointment.validations.scheduling.AppointmentSchedulingValidator;
+import med.voll.api.domain.doctor.Doctor;
+import med.voll.api.domain.doctor.DoctorRepository;
+import med.voll.api.domain.patient.PatientRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service
+public class AppointmentsSchedule {
+ @Autowired
+ private AppointmentRepository appointmentRepository;
+ @Autowired
+ private DoctorRepository doctorRepository;
+ @Autowired
+ private PatientRepository patientRepository;
+ @Autowired
+ private List<AppointmentSchedulingValidator> validators;
+
+ @Autowired
+ private List<AppointmentCancellationValidator> cancellationValidators;
+
+ public AppointmentListingData schedule(AppointmentRegistrationData data) {
+ if (!patientRepository.existsById(data.idPatient())) {
+ throw new ValidationException("Patient id doesn't exist");
+ }
+
+ if (data.idDoctor() != null && !doctorRepository.existsById(data.idDoctor())) {
+ throw new ValidationException("Doctor id doesn't exist");
+ }
+
+ validators.forEach(v -> v.validate(data));
+
+ var doctor = chooseDoctor(data);
+ if (doctor == null) {
+ throw new ValidationException("There are no available doctors on this date");
+ }
+ var patient = patientRepository.getReferenceById(data.idPatient());
+
+ var appointment = new Appointment(null, data.date(), doctor, patient, true, null);
+
+ appointmentRepository.save(appointment);
+
+ return new AppointmentListingData(appointment);
+ }
+
+ public void cancel(AppointmentDeletionData data) {
+ if (!appointmentRepository.existsById(data.idAppointment())) {
+ throw new ValidationException("Invalid appointment id");
+ }
+
+ cancellationValidators.forEach(v -> v.validate(data));
+
+ var appointment = appointmentRepository.getReferenceById(data.idAppointment());
+ appointment.cancel(data.reasonForCancellation());
+ }
+
+ private Doctor chooseDoctor(AppointmentRegistrationData data) {
+ if(data.idDoctor() != null) {
+ return doctorRepository.getReferenceById(data.idDoctor());
+ }
+
+ if (data.specialty() == null) {
+ throw new ValidationException("Specialty is mandatory when a doctor is not chosen");
+ }
+
+ return doctorRepository.chooseRandomDoctorAvailable(data.specialty(), data.date());
+ }
+
+}
diff --git a/api/src/main/java/med/voll/api/domain/appointment/ReasonForCancellation.java b/api/src/main/java/med/voll/api/domain/appointment/ReasonForCancellation.java
new file mode 100644
index 0000000..db6e98a
--- /dev/null
+++ b/api/src/main/java/med/voll/api/domain/appointment/ReasonForCancellation.java
@@ -0,0 +1,8 @@
+package med.voll.api.domain.appointment;
+
+public enum ReasonForCancellation {
+ PATIENT_GAVE_UP,
+ LONG_WAITING_TIME,
+ DOCTOR_CANCELED,
+ OTHER_REASON
+}
diff --git a/api/src/main/java/med/voll/api/domain/appointment/validations/cancellation/AppointmentCancellationValidator.java b/api/src/main/java/med/voll/api/domain/appointment/validations/cancellation/AppointmentCancellationValidator.java
new file mode 100644
index 0000000..6c9e0cc
--- /dev/null
+++ b/api/src/main/java/med/voll/api/domain/appointment/validations/cancellation/AppointmentCancellationValidator.java
@@ -0,0 +1,7 @@
+package med.voll.api.domain.appointment.validations.cancellation;
+
+import med.voll.api.domain.appointment.AppointmentDeletionData;
+
+public interface AppointmentCancellationValidator {
+ void validate(AppointmentDeletionData data);
+}
diff --git a/api/src/main/java/med/voll/api/domain/appointment/validations/cancellation/ValidatesTimeInAdvance.java b/api/src/main/java/med/voll/api/domain/appointment/validations/cancellation/ValidatesTimeInAdvance.java
new file mode 100644
index 0000000..03688ce
--- /dev/null
+++ b/api/src/main/java/med/voll/api/domain/appointment/validations/cancellation/ValidatesTimeInAdvance.java
@@ -0,0 +1,27 @@
+package med.voll.api.domain.appointment.validations.cancellation;
+
+import jakarta.validation.ValidationException;
+import med.voll.api.domain.appointment.AppointmentDeletionData;
+import med.voll.api.domain.appointment.AppointmentRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.time.Duration;
+import java.time.LocalDateTime;
+
+@Component("ValidatesTimeInAdvanceCancellation")
+public class ValidatesTimeInAdvance implements AppointmentCancellationValidator {
+ @Autowired
+ private AppointmentRepository appointmentRepository;
+
+ @Override
+ public void validate(AppointmentDeletionData data) {
+ var appointment = appointmentRepository.getReferenceById(data.idAppointment());
+ var now = LocalDateTime.now();
+ var differenceInHours = Duration.between(now, appointment.getDate()).toHours();
+
+ if (differenceInHours < 24) {
+ throw new ValidationException("Appointments can only be cancelled with at least 24 hours in advance!");
+ }
+ }
+}
diff --git a/api/src/main/java/med/voll/api/domain/appointment/validations/scheduling/AppointmentSchedulingValidator.java b/api/src/main/java/med/voll/api/domain/appointment/validations/scheduling/AppointmentSchedulingValidator.java
new file mode 100644
index 0000000..99f6128
--- /dev/null
+++ b/api/src/main/java/med/voll/api/domain/appointment/validations/scheduling/AppointmentSchedulingValidator.java
@@ -0,0 +1,7 @@
+package med.voll.api.domain.appointment.validations.scheduling;
+
+import med.voll.api.domain.appointment.AppointmentRegistrationData;
+
+public interface AppointmentSchedulingValidator {
+ void validate(AppointmentRegistrationData data);
+}
diff --git a/api/src/main/java/med/voll/api/domain/appointment/validations/scheduling/ValidatesActiveDoctor.java b/api/src/main/java/med/voll/api/domain/appointment/validations/scheduling/ValidatesActiveDoctor.java
new file mode 100644
index 0000000..59bb761
--- /dev/null
+++ b/api/src/main/java/med/voll/api/domain/appointment/validations/scheduling/ValidatesActiveDoctor.java
@@ -0,0 +1,26 @@
+package med.voll.api.domain.appointment.validations.scheduling;
+
+import jakarta.validation.ValidationException;
+import med.voll.api.domain.appointment.AppointmentRegistrationData;
+import med.voll.api.domain.doctor.DoctorRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class ValidatesActiveDoctor implements AppointmentSchedulingValidator {
+
+ @Autowired
+ private DoctorRepository doctorRepository;
+
+ public void validate(AppointmentRegistrationData data) {
+ // random doctor
+ if (data.idDoctor() == null) {
+ return;
+ }
+
+ var doctorIsActive = doctorRepository.findActiveById(data.idDoctor());
+ if (!doctorIsActive) {
+ throw new ValidationException("Appointment can't be scheduled with an inactive doctor");
+ }
+ }
+}
diff --git a/api/src/main/java/med/voll/api/domain/appointment/validations/scheduling/ValidatesActivePatient.java b/api/src/main/java/med/voll/api/domain/appointment/validations/scheduling/ValidatesActivePatient.java
new file mode 100644
index 0000000..439877f
--- /dev/null
+++ b/api/src/main/java/med/voll/api/domain/appointment/validations/scheduling/ValidatesActivePatient.java
@@ -0,0 +1,21 @@
+package med.voll.api.domain.appointment.validations.scheduling;
+
+import jakarta.validation.ValidationException;
+import med.voll.api.domain.appointment.AppointmentRegistrationData;
+import med.voll.api.domain.patient.PatientRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class ValidatesActivePatient implements AppointmentSchedulingValidator {
+
+ @Autowired
+ private PatientRepository patientRepository;
+
+ public void validate(AppointmentRegistrationData data) {
+ var patientIsActive = patientRepository.findActiveById(data.idPatient());
+ if (!patientIsActive) {
+ throw new ValidationException("Appointment can't be scheduled with an inactive patient");
+ }
+ }
+}
diff --git a/api/src/main/java/med/voll/api/domain/appointment/validations/scheduling/ValidatesDateAndTime.java b/api/src/main/java/med/voll/api/domain/appointment/validations/scheduling/ValidatesDateAndTime.java
new file mode 100644
index 0000000..beee6c6
--- /dev/null
+++ b/api/src/main/java/med/voll/api/domain/appointment/validations/scheduling/ValidatesDateAndTime.java
@@ -0,0 +1,21 @@
+package med.voll.api.domain.appointment.validations.scheduling;
+
+import jakarta.validation.ValidationException;
+import med.voll.api.domain.appointment.AppointmentRegistrationData;
+import org.springframework.stereotype.Component;
+
+import java.time.DayOfWeek;
+
+@Component
+public class ValidatesDateAndTime implements AppointmentSchedulingValidator {
+ public void validate(AppointmentRegistrationData data) {
+ var appointmentDate = data.date();
+ var sunday = appointmentDate.getDayOfWeek().equals(DayOfWeek.SUNDAY);
+ var beforeTheClinicOpens = appointmentDate.getHour() < 7;
+ var afterTheClinicCloses = appointmentDate.getHour() > 18;
+
+ if (sunday || beforeTheClinicOpens || afterTheClinicCloses) {
+ throw new ValidationException("Appointment outside opening hours or date");
+ }
+ }
+}
diff --git a/api/src/main/java/med/voll/api/domain/appointment/validations/scheduling/ValidatesDoctorWithOtherAppointmentAtTheSameTime.java b/api/src/main/java/med/voll/api/domain/appointment/validations/scheduling/ValidatesDoctorWithOtherAppointmentAtTheSameTime.java
new file mode 100644
index 0000000..fa94d81
--- /dev/null
+++ b/api/src/main/java/med/voll/api/domain/appointment/validations/scheduling/ValidatesDoctorWithOtherAppointmentAtTheSameTime.java
@@ -0,0 +1,21 @@
+package med.voll.api.domain.appointment.validations.scheduling;
+
+import jakarta.validation.ValidationException;
+import med.voll.api.domain.appointment.AppointmentRegistrationData;
+import med.voll.api.domain.appointment.AppointmentRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class ValidatesDoctorWithOtherAppointmentAtTheSameTime implements AppointmentSchedulingValidator {
+
+ @Autowired
+ private AppointmentRepository appointmentRepository;
+
+ public void validate(AppointmentRegistrationData data) {
+ var doctorHasAnotherAppointmentAtTheSameTime = appointmentRepository.existsByDoctorIdAndDateAndActiveIsTrue(data.idDoctor(), data.date());
+ if (doctorHasAnotherAppointmentAtTheSameTime) {
+ throw new ValidationException("This doctor already has an appointment at this time and date");
+ }
+ }
+}
diff --git a/api/src/main/java/med/voll/api/domain/appointment/validations/scheduling/ValidatesPatientDoesntHaveAnotherAppointmentToday.java b/api/src/main/java/med/voll/api/domain/appointment/validations/scheduling/ValidatesPatientDoesntHaveAnotherAppointmentToday.java
new file mode 100644
index 0000000..a412bfb
--- /dev/null
+++ b/api/src/main/java/med/voll/api/domain/appointment/validations/scheduling/ValidatesPatientDoesntHaveAnotherAppointmentToday.java
@@ -0,0 +1,24 @@
+package med.voll.api.domain.appointment.validations.scheduling;
+
+import jakarta.validation.ValidationException;
+import med.voll.api.domain.appointment.AppointmentRegistrationData;
+import med.voll.api.domain.appointment.AppointmentRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class ValidatesPatientDoesntHaveAnotherAppointmentToday implements AppointmentSchedulingValidator {
+
+ @Autowired
+ private AppointmentRepository appointmentRepository;
+
+ public void validate(AppointmentRegistrationData data) {
+ var earliestTime = data.date().withHour(7);
+ var latestTime = data.date().withHour(18);
+ var patientHasAnotherAppointmentToday = appointmentRepository.existsByPatientIdAndDateBetween(data.idPatient(), earliestTime, latestTime);
+
+ if (patientHasAnotherAppointmentToday) {
+ throw new ValidationException("Patient already has an scheduled appointment today");
+ }
+ }
+}
diff --git a/api/src/main/java/med/voll/api/domain/appointment/validations/scheduling/ValidatesTimeInAdvance.java b/api/src/main/java/med/voll/api/domain/appointment/validations/scheduling/ValidatesTimeInAdvance.java
new file mode 100644
index 0000000..442f4f9
--- /dev/null
+++ b/api/src/main/java/med/voll/api/domain/appointment/validations/scheduling/ValidatesTimeInAdvance.java
@@ -0,0 +1,21 @@
+package med.voll.api.domain.appointment.validations.scheduling;
+
+import jakarta.validation.ValidationException;
+import med.voll.api.domain.appointment.AppointmentRegistrationData;
+import org.springframework.stereotype.Component;
+
+import java.time.Duration;
+import java.time.LocalDateTime;
+
+@Component("ValidatesTimeInAdvanceScheduling")
+public class ValidatesTimeInAdvance implements AppointmentSchedulingValidator {
+ public void validate(AppointmentRegistrationData data) {
+ var appointmentDate = data.date();
+ var now = LocalDateTime.now();
+ var differenceInMinutes = Duration.between(now, appointmentDate).toMinutes();
+
+ if (differenceInMinutes < 30) {
+ throw new ValidationException("Appointment should be scheduled half an hour in advance");
+ }
+ }
+}
diff --git a/api/src/main/java/med/voll/api/domain/doctor/DoctorRepository.java b/api/src/main/java/med/voll/api/domain/doctor/DoctorRepository.java
index 1efd0af..8cd6c46 100644
--- a/api/src/main/java/med/voll/api/domain/doctor/DoctorRepository.java
+++ b/api/src/main/java/med/voll/api/domain/doctor/DoctorRepository.java
@@ -3,9 +3,29 @@ package med.voll.api.domain.doctor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
+import java.time.LocalDateTime;
+
@Repository
public interface DoctorRepository extends JpaRepository<Doctor, Long> {
Page<Doctor> findAllByActiveTrue(Pageable pagination);
+
+ //order by rand() limit 1
+ @Query("""
+ SELECT d from Doctor d where d.active = true
+ and d.specialty = :specialty
+ and d.id not in (
+ select a.doctor.id from Appointment a where a.date = :date
+ and a.active = true
+ )
+ """)
+ Doctor chooseRandomDoctorAvailable(Specialty specialty, LocalDateTime date);
+
+ @Query("""
+ select d.active from Doctor d
+ where d.id = :id
+ """)
+ Boolean findActiveById(Long id);
}
diff --git a/api/src/main/java/med/voll/api/domain/patient/PatientRepository.java b/api/src/main/java/med/voll/api/domain/patient/PatientRepository.java
index e550ac2..dbdcd87 100644
--- a/api/src/main/java/med/voll/api/domain/patient/PatientRepository.java
+++ b/api/src/main/java/med/voll/api/domain/patient/PatientRepository.java
@@ -3,9 +3,16 @@ package med.voll.api.domain.patient;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
@Repository
public interface PatientRepository extends JpaRepository<Patient, Long> {
Page<Patient> findAllByActiveTrue(Pageable pagination);
+
+ @Query("""
+ select p.active from Patient p
+ where p.id = :id
+ """)
+ boolean findActiveById(Long id);
}
diff --git a/api/src/main/java/med/voll/api/infra/exception/ErrorTreatment.java b/api/src/main/java/med/voll/api/infra/exception/ErrorTreatment.java
index 9e49fd3..f2d40cc 100644
--- a/api/src/main/java/med/voll/api/infra/exception/ErrorTreatment.java
+++ b/api/src/main/java/med/voll/api/infra/exception/ErrorTreatment.java
@@ -1,6 +1,7 @@
package med.voll.api.infra.exception;
import jakarta.persistence.EntityNotFoundException;
+import jakarta.validation.ValidationException;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
@@ -20,6 +21,10 @@ public class ErrorTreatment {
var errors = ex.getFieldErrors();
return ResponseEntity.badRequest().body(errors.stream().map(ValidationErrorData::new).toList());
}
+ @ExceptionHandler(ValidationException.class)
+ public ResponseEntity treatBusinessRule(ValidationException ex) {
+ return ResponseEntity.badRequest().body(ex.getMessage());
+ }
private record ValidationErrorData(String field, String message) {
public ValidationErrorData(FieldError error) {
diff --git a/api/src/main/java/med/voll/api/infra/security/SecurityConfigurations.java b/api/src/main/java/med/voll/api/infra/security/SecurityConfigurations.java
index 5055162..46038c6 100644
--- a/api/src/main/java/med/voll/api/infra/security/SecurityConfigurations.java
+++ b/api/src/main/java/med/voll/api/infra/security/SecurityConfigurations.java
@@ -27,6 +27,7 @@ public class SecurityConfigurations {
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeHttpRequests()
.requestMatchers(HttpMethod.POST, "/login").permitAll()
+ .requestMatchers("/v3/api-docs/**", "/swagger-ui.html", "/swagger-ui/**").permitAll()
.anyRequest().authenticated()
.and().addFilterBefore(securityFilter, UsernamePasswordAuthenticationFilter.class)
.build();
diff --git a/api/src/main/java/med/voll/api/infra/security/TokenService.java b/api/src/main/java/med/voll/api/infra/security/TokenService.java
index 63dda73..bf19a3d 100644
--- a/api/src/main/java/med/voll/api/infra/security/TokenService.java
+++ b/api/src/main/java/med/voll/api/infra/security/TokenService.java
@@ -45,6 +45,6 @@ public class TokenService {
}
private Instant expirationDate() {
- return LocalDateTime.now().plusHours(2).toInstant(ZoneOffset.of("-03:00"));
+ return LocalDateTime.now().plusHours(12).toInstant(ZoneOffset.of("-03:00"));
}
}
diff --git a/api/src/main/java/med/voll/api/infra/springdoc/SpringDocConfigurations.java b/api/src/main/java/med/voll/api/infra/springdoc/SpringDocConfigurations.java
new file mode 100644
index 0000000..c575160
--- /dev/null
+++ b/api/src/main/java/med/voll/api/infra/springdoc/SpringDocConfigurations.java
@@ -0,0 +1,34 @@
+package med.voll.api.infra.springdoc;
+
+import io.swagger.v3.oas.models.Components;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.info.Contact;
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.info.License;
+import io.swagger.v3.oas.models.security.SecurityScheme;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class SpringDocConfigurations {
+
+ @Bean
+ public OpenAPI customOpenAPI() {
+ return new OpenAPI()
+ .components(new Components()
+ .addSecuritySchemes("bearer-key",
+ new SecurityScheme()
+ .type(SecurityScheme.Type.HTTP)
+ .scheme("bearer")
+ .bearerFormat("JWT")))
+ .info(new Info()
+ .title("Voll.med API")
+ .description("Voll.med Rest API application, containing CRUD functionalities for doctors and patients, in addition to scheduling and canceling appointments")
+ .contact(new Contact()
+ .name("Backend Team")
+ .email("backend@voll.med"))
+ .license(new License()
+ .name("Apache 2.0")
+ .url("http://voll.med/api/license")));
+ }
+}
diff --git a/api/src/main/resources/application-prod.properties b/api/src/main/resources/application-prod.properties
new file mode 100644
index 0000000..49041d6
--- /dev/null
+++ b/api/src/main/resources/application-prod.properties
@@ -0,0 +1,4 @@
+# Database config
+spring.datasource.url=${DATASOURCE_URL}
+spring.datasource.username=${DATASOURCE_USERNAME}
+spring.datasource.password=${DATASOURCE_PASSWORD} \ No newline at end of file
diff --git a/api/src/main/resources/application-test.properties b/api/src/main/resources/application-test.properties
new file mode 100644
index 0000000..66d87f4
--- /dev/null
+++ b/api/src/main/resources/application-test.properties
@@ -0,0 +1 @@
+spring.datasource.url=jdbc:mariadb://localhost/voll_med_api_test \ No newline at end of file
diff --git a/api/src/main/resources/db/migration/V7__create-table-appointments.sql b/api/src/main/resources/db/migration/V7__create-table-appointments.sql
new file mode 100644
index 0000000..388810c
--- /dev/null
+++ b/api/src/main/resources/db/migration/V7__create-table-appointments.sql
@@ -0,0 +1,12 @@
+create table appointments
+(
+ id bigint not null auto_increment,
+ doctor_id bigint not null,
+ patient_id bigint not null,
+ date datetime not null,
+
+ primary key (id),
+ constraint fk_appointments_doctor_id foreign key(doctor_id) references doctors(id),
+ constraint fk_appointments_patient_id foreign key(patient_id) references patients(id)
+
+); \ No newline at end of file
diff --git a/api/src/main/resources/db/migration/V8__alter-table-appointments-add-column-reason-for-cancellation.sql b/api/src/main/resources/db/migration/V8__alter-table-appointments-add-column-reason-for-cancellation.sql
new file mode 100644
index 0000000..b1b3fde
--- /dev/null
+++ b/api/src/main/resources/db/migration/V8__alter-table-appointments-add-column-reason-for-cancellation.sql
@@ -0,0 +1 @@
+alter table appointments add column reason_for_cancellation varchar(100); \ No newline at end of file
diff --git a/api/src/main/resources/db/migration/V9__alter-table-appointments-add-column-active.sql b/api/src/main/resources/db/migration/V9__alter-table-appointments-add-column-active.sql
new file mode 100644
index 0000000..b02bda9
--- /dev/null
+++ b/api/src/main/resources/db/migration/V9__alter-table-appointments-add-column-active.sql
@@ -0,0 +1 @@
+alter table appointments add column active tinyint; \ No newline at end of file
diff --git a/api/src/test/java/med/voll/api/ApiApplicationTests.java b/api/src/test/java/med/voll/api/ApiApplicationTests.java
deleted file mode 100644
index eb360a5..0000000
--- a/api/src/test/java/med/voll/api/ApiApplicationTests.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package med.voll.api;
-
-import org.junit.jupiter.api.Test;
-import org.springframework.boot.test.context.SpringBootTest;
-
-@SpringBootTest
-class ApiApplicationTests {
-
- @Test
- void contextLoads() {
- }
-
-}
diff --git a/api/src/test/java/med/voll/api/controller/AppointmentControllerTest.java b/api/src/test/java/med/voll/api/controller/AppointmentControllerTest.java
new file mode 100644
index 0000000..6a0356c
--- /dev/null
+++ b/api/src/test/java/med/voll/api/controller/AppointmentControllerTest.java
@@ -0,0 +1,82 @@
+package med.voll.api.controller;
+
+import med.voll.api.domain.appointment.AppointmentListingData;
+import med.voll.api.domain.appointment.AppointmentRegistrationData;
+import med.voll.api.domain.appointment.AppointmentsSchedule;
+import med.voll.api.domain.doctor.Specialty;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.json.AutoConfigureJsonTesters;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.json.JacksonTester;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.web.servlet.MockMvc;
+
+import java.time.LocalDateTime;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+
+@SpringBootTest
+@AutoConfigureMockMvc
+@AutoConfigureJsonTesters
+class AppointmentControllerTest {
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ @Autowired
+ private JacksonTester<AppointmentRegistrationData> jacksonTesterRegistration;
+
+ @Autowired
+ private JacksonTester<AppointmentListingData> jacksonTesterListing;
+
+ @MockBean
+ private AppointmentsSchedule appointmentsSchedule;
+
+ @Test
+ @DisplayName("Should return http error 400 when receiving invalid data")
+ @WithMockUser
+ void registerTest1() throws Exception {
+ var response = mockMvc.perform(post("/appointments"))
+ .andReturn().getResponse();
+
+ assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value());
+ }
+
+ @Test
+ @DisplayName("Should return http error 200 when receiving valid data")
+ @WithMockUser
+ void registerTest2() throws Exception {
+ var date = LocalDateTime.now().plusHours(1);
+ var specialty = Specialty.CARDIOLOGY;
+
+ var listingData = new AppointmentListingData(null, date, 1l, 1l);
+
+ when(appointmentsSchedule.schedule(any())).thenReturn(listingData);
+
+ var response = mockMvc.perform(post("/appointments")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(jacksonTesterRegistration.write(
+ new AppointmentRegistrationData(1l, 1l, date, specialty)
+ ).getJson())
+ )
+ .andReturn().getResponse();
+
+ assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value());
+
+ var expectedJson = jacksonTesterListing.write(
+ listingData
+ ).getJson();
+
+ assertThat(response.getContentAsString()).isEqualTo(expectedJson);
+ }
+} \ No newline at end of file
diff --git a/api/src/test/java/med/voll/api/controller/DoctorControllerTest.java b/api/src/test/java/med/voll/api/controller/DoctorControllerTest.java
new file mode 100644
index 0000000..dbd3338
--- /dev/null
+++ b/api/src/test/java/med/voll/api/controller/DoctorControllerTest.java
@@ -0,0 +1,77 @@
+package med.voll.api.controller;
+
+import med.voll.api.domain.address.Address;
+import med.voll.api.domain.address.AddressData;
+import med.voll.api.domain.appointment.AppointmentListingData;
+import med.voll.api.domain.appointment.AppointmentRegistrationData;
+import med.voll.api.domain.doctor.*;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.json.AutoConfigureJsonTesters;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.json.JacksonTester;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.web.servlet.MockMvc;
+
+import java.time.LocalDateTime;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+
+@SpringBootTest
+@AutoConfigureMockMvc
+@AutoConfigureJsonTesters
+class DoctorControllerTest {
+
+ @Autowired
+ private MockMvc mockMvc;
+ @Autowired
+ private JacksonTester<DoctorRegistrationData> jacksonTesterRegistration;
+ @Autowired
+ private JacksonTester<DoctorDetailingData> jacksonTesterListing;
+ @MockBean
+ private DoctorRepository doctorRepository;
+
+ @Test
+ @DisplayName("Should return http error 400 when receiving invalid data")
+ @WithMockUser
+ void registerTest1() throws Exception {
+ var response = mockMvc.perform(post("/doctors"))
+ .andReturn().getResponse();
+
+ assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value());
+ }
+
+ @Test
+ @DisplayName("Should return http error 200 when receiving valid data")
+ @WithMockUser
+ void registerTest2() throws Exception {
+ var addressData = new AddressData("Route 66", "", "55555", "Los Angeles", "CA");
+ var registerData = new DoctorRegistrationData("doctor", "doctor@voll.med", "9999999999", Specialty.CARDIOLOGY, addressData);
+
+ when(doctorRepository.save(any())).thenReturn(new Doctor(registerData));
+
+ var response = mockMvc.perform(post("/doctors")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(jacksonTesterRegistration.write(registerData).getJson()))
+ .andReturn().getResponse();
+
+ assertThat(response.getStatus()).isEqualTo(HttpStatus.CREATED.value());
+
+ var detailingData = new DoctorDetailingData(null, registerData.name(), registerData.email(), registerData.phone(), registerData.specialty(), new Address(registerData.addressData()));
+
+ var expectedJson = jacksonTesterListing.write(
+ detailingData
+ ).getJson();
+
+ assertThat(response.getContentAsString()).isEqualTo(expectedJson);
+ }
+} \ No newline at end of file
diff --git a/api/src/test/java/med/voll/api/domain/doctor/DoctorRepositoryTest.java b/api/src/test/java/med/voll/api/domain/doctor/DoctorRepositoryTest.java
new file mode 100644
index 0000000..1ff80be
--- /dev/null
+++ b/api/src/test/java/med/voll/api/domain/doctor/DoctorRepositoryTest.java
@@ -0,0 +1,107 @@
+package med.voll.api.domain.doctor;
+
+import med.voll.api.domain.address.AddressData;
+import med.voll.api.domain.appointment.Appointment;
+import med.voll.api.domain.patient.Patient;
+import med.voll.api.domain.patient.PatientRegistrationData;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
+import org.springframework.test.context.ActiveProfiles;
+
+import java.time.DayOfWeek;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.temporal.TemporalAdjusters;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.*;
+
+@DataJpaTest
+@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
+@ActiveProfiles("test")
+class DoctorRepositoryTest {
+
+ @Autowired
+ private DoctorRepository doctorRepository;
+
+ @Autowired
+ private TestEntityManager testEntityManager;
+
+ @Test
+ @DisplayName("Should return null when there's no doctor available in the date")
+ void chooseRandomDoctorAvailableTest1() {
+ var nextMonday10AM = LocalDate.now()
+ .with(TemporalAdjusters.next(DayOfWeek.MONDAY))
+ .atTime(10, 0);
+
+ var doctor = registerDoctor("doctor", "doctor@voll.med", Specialty.CARDIOLOGY);
+ var patient = registerPatient("patient", "patient@voll.med", "00000000");
+ scheduleAppointment(nextMonday10AM, doctor, patient);
+
+ var availableDoctor = doctorRepository.chooseRandomDoctorAvailable(Specialty.CARDIOLOGY, nextMonday10AM);
+ assertThat(availableDoctor).isNull();
+ }
+
+ @Test
+ @DisplayName("Should return doctor when there's an doctor available in the date")
+ void chooseRandomDoctorAvailableTest2() {
+ var nextMonday10AM = LocalDate.now()
+ .with(TemporalAdjusters.next(DayOfWeek.MONDAY))
+ .atTime(10, 0);
+
+ var doctor = registerDoctor("doctor", "doctor@voll.med", Specialty.CARDIOLOGY);
+
+ var availableDoctor = doctorRepository.chooseRandomDoctorAvailable(Specialty.CARDIOLOGY, nextMonday10AM);
+ assertThat(availableDoctor).isEqualTo(doctor);
+ }
+
+ private void scheduleAppointment(LocalDateTime date, Doctor doctor, Patient patient) {
+ testEntityManager.persist(new Appointment(null, date, doctor, patient));
+ }
+
+ private Doctor registerDoctor(String name, String email, Specialty specialty) {
+ var doctor = new Doctor(doctorData(name, email, specialty));
+ testEntityManager.persist(doctor);
+ return doctor;
+ }
+
+ private Patient registerPatient(String name, String email, String ssn) {
+ var patient = new Patient(patientData(name, email, ssn));
+ testEntityManager.persist(patient);
+ return patient;
+ }
+
+ private DoctorRegistrationData doctorData(String name, String email, Specialty specialty) {
+ return new DoctorRegistrationData(
+ name,
+ email,
+ "999999999",
+ specialty,
+ addressData()
+ );
+ }
+
+ private PatientRegistrationData patientData(String name, String email, String ssn) {
+ return new PatientRegistrationData(
+ name,
+ email,
+ "999999999",
+ ssn,
+ addressData()
+ );
+ }
+
+ private AddressData addressData() {
+ return new AddressData(
+ "Route 66",
+ "",
+ "55555",
+ "Los Angeles",
+ "CA"
+ );
+ }
+} \ No newline at end of file