summaryrefslogtreecommitdiff
path: root/api/src/main/java
diff options
context:
space:
mode:
authorlucashemi <lucasxberger@gmail.com>2023-02-08 14:57:43 -0300
committerlucashemi <lucasxberger@gmail.com>2023-02-08 14:57:43 -0300
commit514f2e7194a875cfc53d7e1bccd922db2bbb3f3f (patch)
tree6b62dada02b9d02e624c40b7f3d4704537cbcb80 /api/src/main/java
readme
Diffstat (limited to 'api/src/main/java')
-rw-r--r--api/src/main/java/med/voll/api/ApiApplication.java13
-rw-r--r--api/src/main/java/med/voll/api/controller/AuthenticationController.java37
-rw-r--r--api/src/main/java/med/voll/api/controller/DoctorController.java66
-rw-r--r--api/src/main/java/med/voll/api/controller/PatientController.java65
-rw-r--r--api/src/main/java/med/voll/api/domain/address/Address.java45
-rw-r--r--api/src/main/java/med/voll/api/domain/address/AddressData.java18
-rw-r--r--api/src/main/java/med/voll/api/domain/doctor/Doctor.java58
-rw-r--r--api/src/main/java/med/voll/api/domain/doctor/DoctorDetailingData.java9
-rw-r--r--api/src/main/java/med/voll/api/domain/doctor/DoctorListingData.java8
-rw-r--r--api/src/main/java/med/voll/api/domain/doctor/DoctorRegistrationData.java23
-rw-r--r--api/src/main/java/med/voll/api/domain/doctor/DoctorRepository.java11
-rw-r--r--api/src/main/java/med/voll/api/domain/doctor/DoctorUpdateData.java13
-rw-r--r--api/src/main/java/med/voll/api/domain/doctor/Specialty.java19
-rw-r--r--api/src/main/java/med/voll/api/domain/patient/Patient.java53
-rw-r--r--api/src/main/java/med/voll/api/domain/patient/PatientDetailingData.java9
-rw-r--r--api/src/main/java/med/voll/api/domain/patient/PatientListingData.java9
-rw-r--r--api/src/main/java/med/voll/api/domain/patient/PatientRegistrationData.java24
-rw-r--r--api/src/main/java/med/voll/api/domain/patient/PatientRepository.java11
-rw-r--r--api/src/main/java/med/voll/api/domain/patient/PatientUpdateData.java12
-rw-r--r--api/src/main/java/med/voll/api/domain/user/AuthenticationData.java6
-rw-r--r--api/src/main/java/med/voll/api/domain/user/AuthenticationService.java18
-rw-r--r--api/src/main/java/med/voll/api/domain/user/User.java63
-rw-r--r--api/src/main/java/med/voll/api/domain/user/UserRepository.java8
-rw-r--r--api/src/main/java/med/voll/api/infra/exception/ErrorTreatment.java29
-rw-r--r--api/src/main/java/med/voll/api/infra/security/SecurityConfigurations.java44
-rw-r--r--api/src/main/java/med/voll/api/infra/security/SecurityFilter.java48
-rw-r--r--api/src/main/java/med/voll/api/infra/security/TokenJWTData.java4
-rw-r--r--api/src/main/java/med/voll/api/infra/security/TokenService.java50
28 files changed, 773 insertions, 0 deletions
diff --git a/api/src/main/java/med/voll/api/ApiApplication.java b/api/src/main/java/med/voll/api/ApiApplication.java
new file mode 100644
index 0000000..e08ef48
--- /dev/null
+++ b/api/src/main/java/med/voll/api/ApiApplication.java
@@ -0,0 +1,13 @@
+package med.voll.api;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class ApiApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(ApiApplication.class, args);
+ }
+
+}
diff --git a/api/src/main/java/med/voll/api/controller/AuthenticationController.java b/api/src/main/java/med/voll/api/controller/AuthenticationController.java
new file mode 100644
index 0000000..b7dcf27
--- /dev/null
+++ b/api/src/main/java/med/voll/api/controller/AuthenticationController.java
@@ -0,0 +1,37 @@
+package med.voll.api.controller;
+
+import jakarta.validation.Valid;
+import med.voll.api.domain.user.AuthenticationData;
+import med.voll.api.domain.user.User;
+import med.voll.api.infra.security.TokenJWTData;
+import med.voll.api.infra.security.TokenService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+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;
+
+@Controller
+@RequestMapping("/login")
+public class AuthenticationController {
+
+ @Autowired
+ private AuthenticationManager authenticationManager;
+
+ @Autowired
+ private TokenService tokenService;
+
+ @PostMapping
+ public ResponseEntity performLogin(@RequestBody @Valid AuthenticationData data) {
+ var authenticationToken = new UsernamePasswordAuthenticationToken(data.login(), data.password());
+ Authentication authentication = authenticationManager.authenticate(authenticationToken);
+
+ var tokenJWT = tokenService.generateToken((User) authentication.getPrincipal());
+
+ return ResponseEntity.ok(new TokenJWTData(tokenJWT));
+ }
+}
diff --git a/api/src/main/java/med/voll/api/controller/DoctorController.java b/api/src/main/java/med/voll/api/controller/DoctorController.java
new file mode 100644
index 0000000..a262d34
--- /dev/null
+++ b/api/src/main/java/med/voll/api/controller/DoctorController.java
@@ -0,0 +1,66 @@
+package med.voll.api.controller;
+
+import jakarta.validation.Valid;
+import med.voll.api.domain.doctor.DoctorListingData;
+import med.voll.api.domain.doctor.Doctor;
+import med.voll.api.domain.doctor.DoctorRepository;
+import med.voll.api.domain.doctor.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.web.PageableDefault;
+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("/doctors")
+public class DoctorController {
+
+ @Autowired
+ private DoctorRepository doctorRepository;
+
+ @PostMapping
+ @Transactional
+ public ResponseEntity register(@RequestBody @Valid DoctorRegistrationData data, UriComponentsBuilder uriBuilder) {
+ var doctor = new Doctor(data);
+ doctorRepository.save(doctor);
+
+ var uri = uriBuilder.path("/doctors/{id}").buildAndExpand(doctor.getId()).toUri();
+
+ return ResponseEntity.created(uri).body(new DoctorDetailingData(doctor));
+ }
+
+ @GetMapping
+ public ResponseEntity<Page<DoctorListingData>> list(@PageableDefault(sort = {"name"}, direction = Sort.Direction.ASC) Pageable pagination) {
+ var page = doctorRepository.findAllByActiveTrue(pagination).map(DoctorListingData::new);
+ return ResponseEntity.ok(page);
+ }
+
+ @PutMapping
+ @Transactional
+ public ResponseEntity update(@RequestBody @Valid DoctorUpdateData data) {
+ Doctor doctor = doctorRepository.getReferenceById(data.id());
+ doctor.updateInformation(data);
+
+ return ResponseEntity.ok(new DoctorDetailingData(doctor));
+ }
+
+ @DeleteMapping("/{id}")
+ @Transactional
+ public ResponseEntity delete(@PathVariable Long id) {
+ Doctor doctor = doctorRepository.getReferenceById(id);
+ doctor.delete();
+
+ return ResponseEntity.noContent().build();
+ }
+
+ @GetMapping("/{id}")
+ public ResponseEntity detail(@PathVariable Long id) {
+ Doctor doctor = doctorRepository.getReferenceById(id);
+
+ return ResponseEntity.ok(new DoctorDetailingData(doctor));
+ }
+}
diff --git a/api/src/main/java/med/voll/api/controller/PatientController.java b/api/src/main/java/med/voll/api/controller/PatientController.java
new file mode 100644
index 0000000..bd774a1
--- /dev/null
+++ b/api/src/main/java/med/voll/api/controller/PatientController.java
@@ -0,0 +1,65 @@
+package med.voll.api.controller;
+
+import jakarta.validation.Valid;
+import med.voll.api.domain.patient.PatientListingData;
+import med.voll.api.domain.patient.Patient;
+import med.voll.api.domain.patient.PatientRepository;
+import med.voll.api.domain.patient.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.web.PageableDefault;
+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("/patients")
+public class PatientController {
+ @Autowired
+ PatientRepository patientRepository;
+
+ @PostMapping
+ @Transactional
+ public ResponseEntity register(@RequestBody @Valid PatientRegistrationData data, UriComponentsBuilder uriBuilder) {
+ var patient = new Patient(data);
+ patientRepository.save(patient);
+
+ var uri = uriBuilder.path("/patients/{id}").buildAndExpand(patient.getId()).toUri();
+
+ return ResponseEntity.created(uri).body(new PatientDetailingData(patient));
+ }
+
+ @GetMapping
+ public ResponseEntity<Page<PatientListingData>> list(@PageableDefault(sort = {"name"}, direction = Sort.Direction.ASC) Pageable pagination) {
+ var page = patientRepository.findAllByActiveTrue(pagination).map(PatientListingData::new);
+ return ResponseEntity.ok(page);
+ }
+
+ @PutMapping
+ @Transactional
+ public ResponseEntity update(@RequestBody @Valid PatientUpdateData data) {
+ Patient patient = patientRepository.getReferenceById(data.id());
+ patient.updateInformation(data);
+
+ return ResponseEntity.ok(new PatientDetailingData(patient));
+ }
+
+ @DeleteMapping("/{id}")
+ @Transactional
+ public ResponseEntity delete(@PathVariable Long id) {
+ Patient patient = patientRepository.getReferenceById(id);
+ patient.delete();
+
+ return ResponseEntity.noContent().build();
+ }
+
+ @GetMapping("/{id}")
+ public ResponseEntity detail(@PathVariable Long id) {
+ Patient patient = patientRepository.getReferenceById(id);
+
+ return ResponseEntity.ok(new PatientDetailingData(patient));
+ }
+}
diff --git a/api/src/main/java/med/voll/api/domain/address/Address.java b/api/src/main/java/med/voll/api/domain/address/Address.java
new file mode 100644
index 0000000..b348d1f
--- /dev/null
+++ b/api/src/main/java/med/voll/api/domain/address/Address.java
@@ -0,0 +1,45 @@
+package med.voll.api.domain.address;
+
+import jakarta.persistence.Embeddable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Embeddable
+@Getter
+@NoArgsConstructor
+@AllArgsConstructor
+public class Address {
+
+ private String addressLine1;
+ private String addressLine2;
+ private String postalCode;
+ private String city;
+ private String state;
+
+ public Address(AddressData data) {
+ this.addressLine1 = data.addressLine1();
+ this.addressLine2 = data.addressLine2();
+ this.postalCode = data.postalCode();
+ this.city = data.city();
+ this.state = data.state();
+ }
+
+ public void updateInformation(AddressData data) {
+ if (data.addressLine1() != null) {
+ this.addressLine1 = data.addressLine1();
+ }
+ if (data.addressLine2() != null) {
+ this.addressLine2 = data.addressLine2();
+ }
+ if (data.postalCode() != null) {
+ this.postalCode = data.postalCode();
+ }
+ if (data.city() != null) {
+ this.city = data.city();
+ }
+ if (data.state() != null) {
+ this.state = data.state();
+ }
+ }
+}
diff --git a/api/src/main/java/med/voll/api/domain/address/AddressData.java b/api/src/main/java/med/voll/api/domain/address/AddressData.java
new file mode 100644
index 0000000..386219b
--- /dev/null
+++ b/api/src/main/java/med/voll/api/domain/address/AddressData.java
@@ -0,0 +1,18 @@
+package med.voll.api.domain.address;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Pattern;
+
+public record AddressData(
+ @NotBlank(message = "Address1 is required")
+ String addressLine1,
+ String addressLine2,
+ @NotBlank(message = "Zip code is required")
+ @Pattern(regexp = "\\d{5}", message = "Zip code must have 9 digits")
+ String postalCode,
+ @NotBlank(message = "City is required")
+ String city,
+ @NotBlank(message = "State is required")
+ String state) {
+
+}
diff --git a/api/src/main/java/med/voll/api/domain/doctor/Doctor.java b/api/src/main/java/med/voll/api/domain/doctor/Doctor.java
new file mode 100644
index 0000000..9c3f5f5
--- /dev/null
+++ b/api/src/main/java/med/voll/api/domain/doctor/Doctor.java
@@ -0,0 +1,58 @@
+package med.voll.api.domain.doctor;
+
+import jakarta.persistence.*;
+import lombok.AllArgsConstructor;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import med.voll.api.domain.address.Address;
+
+@Table(name = "doctors")
+@Entity(name = "Doctor")
+@Getter
+@NoArgsConstructor
+@AllArgsConstructor
+@EqualsAndHashCode(of = "id")
+public class Doctor {
+
+ @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+ private String name;
+ private String email;
+ private String phone;
+ private Boolean active;
+
+ @Enumerated(EnumType.STRING)
+ private Specialty specialty;
+
+ @Embedded
+ private Address address;
+
+ public Doctor(DoctorRegistrationData data) {
+ this.active = true;
+ this.name = data.name();
+ this.email = data.email();
+ this.phone = data.phone();
+ this.specialty = data.specialty();
+ this.address = new Address(data.addressData());
+ }
+
+ public void updateInformation(DoctorUpdateData data) {
+ if (data.name() != null) {
+ this.name = data.name();
+ }
+ if (data.phone() != null) {
+ this.phone = data.phone();
+ }
+ if (data.specialty() != null) {
+ this.specialty = data.specialty();
+ }
+ if (data.addressData() != null) {
+ this.address.updateInformation(data.addressData());
+ }
+ }
+
+ public void delete() {
+ this.active = false;
+ }
+}
diff --git a/api/src/main/java/med/voll/api/domain/doctor/DoctorDetailingData.java b/api/src/main/java/med/voll/api/domain/doctor/DoctorDetailingData.java
new file mode 100644
index 0000000..fff6d9c
--- /dev/null
+++ b/api/src/main/java/med/voll/api/domain/doctor/DoctorDetailingData.java
@@ -0,0 +1,9 @@
+package med.voll.api.domain.doctor;
+
+import med.voll.api.domain.address.Address;
+
+public record DoctorDetailingData(Long id, String name, String email, String phone, Specialty specialty, Address address) {
+ public DoctorDetailingData(Doctor doctor) {
+ this(doctor.getId(), doctor.getName(), doctor.getEmail(), doctor.getPhone(), doctor.getSpecialty(), doctor.getAddress());
+ }
+}
diff --git a/api/src/main/java/med/voll/api/domain/doctor/DoctorListingData.java b/api/src/main/java/med/voll/api/domain/doctor/DoctorListingData.java
new file mode 100644
index 0000000..ec0ea33
--- /dev/null
+++ b/api/src/main/java/med/voll/api/domain/doctor/DoctorListingData.java
@@ -0,0 +1,8 @@
+package med.voll.api.domain.doctor;
+
+public record DoctorListingData(Long id, String name, String email, Specialty specialty) {
+
+ public DoctorListingData(Doctor doctor) {
+ this(doctor.getId(), doctor.getName(), doctor.getEmail(), doctor.getSpecialty());
+ }
+}
diff --git a/api/src/main/java/med/voll/api/domain/doctor/DoctorRegistrationData.java b/api/src/main/java/med/voll/api/domain/doctor/DoctorRegistrationData.java
new file mode 100644
index 0000000..4ceaa2d
--- /dev/null
+++ b/api/src/main/java/med/voll/api/domain/doctor/DoctorRegistrationData.java
@@ -0,0 +1,23 @@
+package med.voll.api.domain.doctor;
+
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Pattern;
+import med.voll.api.domain.address.AddressData;
+
+public record DoctorRegistrationData(
+ @NotBlank(message = "Name is required")
+ String name,
+ @Email(message = "Invalid email format")
+ @NotBlank(message = "Email is required")
+ String email,
+ @Pattern(regexp = "\\d{10}", message = "Telephone number format is invalid")
+ @NotBlank(message = "Telephone number is required")
+ String phone,
+ @NotNull(message = "Specialty is required")
+ Specialty specialty,
+ @NotNull(message = "Address data is required")
+ @Valid AddressData addressData) {
+}
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
new file mode 100644
index 0000000..1efd0af
--- /dev/null
+++ b/api/src/main/java/med/voll/api/domain/doctor/DoctorRepository.java
@@ -0,0 +1,11 @@
+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.stereotype.Repository;
+
+@Repository
+public interface DoctorRepository extends JpaRepository<Doctor, Long> {
+ Page<Doctor> findAllByActiveTrue(Pageable pagination);
+}
diff --git a/api/src/main/java/med/voll/api/domain/doctor/DoctorUpdateData.java b/api/src/main/java/med/voll/api/domain/doctor/DoctorUpdateData.java
new file mode 100644
index 0000000..d3b9747
--- /dev/null
+++ b/api/src/main/java/med/voll/api/domain/doctor/DoctorUpdateData.java
@@ -0,0 +1,13 @@
+package med.voll.api.domain.doctor;
+
+import jakarta.validation.constraints.NotNull;
+import med.voll.api.domain.address.AddressData;
+
+public record DoctorUpdateData(
+ @NotNull
+ Long id,
+ String name,
+ String phone,
+ Specialty specialty,
+ AddressData addressData) {
+}
diff --git a/api/src/main/java/med/voll/api/domain/doctor/Specialty.java b/api/src/main/java/med/voll/api/domain/doctor/Specialty.java
new file mode 100644
index 0000000..7580089
--- /dev/null
+++ b/api/src/main/java/med/voll/api/domain/doctor/Specialty.java
@@ -0,0 +1,19 @@
+package med.voll.api.domain.doctor;
+
+public enum Specialty {
+
+ ANESTHESIOLOGY,
+ CARDIOLOGY,
+ DERMATOLOGY,
+ GYNECOLOGY,
+ IMMUNOLOGY,
+ NEUROLOGY,
+ OPHTHALMOLOGY,
+ ORTHOPEDICS,
+ PATHOLOGY,
+ PEDIATRICS,
+ PSYCHIATRY,
+ RADIOLOGY,
+ SURGERY,
+ UROLOGY
+}
diff --git a/api/src/main/java/med/voll/api/domain/patient/Patient.java b/api/src/main/java/med/voll/api/domain/patient/Patient.java
new file mode 100644
index 0000000..1e7562f
--- /dev/null
+++ b/api/src/main/java/med/voll/api/domain/patient/Patient.java
@@ -0,0 +1,53 @@
+package med.voll.api.domain.patient;
+
+import jakarta.persistence.*;
+import lombok.AllArgsConstructor;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import med.voll.api.domain.address.Address;
+
+@Table(name = "patients")
+@Entity(name = "Patient")
+@Getter
+@NoArgsConstructor
+@AllArgsConstructor
+@EqualsAndHashCode(of = "id")
+public class Patient {
+
+ @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+ private String name;
+ private String email;
+ private String phone;
+ private String ssn;
+ private Boolean active;
+
+ @Embedded
+ private Address address;
+
+ public Patient(PatientRegistrationData data) {
+ this.active = true;
+ this.name = data.name();
+ this.email = data.email();
+ this.ssn = data.ssn();
+ this.phone = data.phone();
+ this.address = new Address(data.addressData());
+ }
+
+ public void updateInformation(PatientUpdateData data) {
+ if (data.name() != null) {
+ this.name = data.name();
+ }
+ if (data.phone() != null) {
+ this.phone = data.phone();
+ }
+ if (data.addressData() != null) {
+ this.address.updateInformation(data.addressData());
+ }
+ }
+
+ public void delete() {
+ this.active = false;
+ }
+}
diff --git a/api/src/main/java/med/voll/api/domain/patient/PatientDetailingData.java b/api/src/main/java/med/voll/api/domain/patient/PatientDetailingData.java
new file mode 100644
index 0000000..76156be
--- /dev/null
+++ b/api/src/main/java/med/voll/api/domain/patient/PatientDetailingData.java
@@ -0,0 +1,9 @@
+package med.voll.api.domain.patient;
+
+import med.voll.api.domain.address.Address;
+
+public record PatientDetailingData(Long id, String name, String email, String phone, String ssn, Address address) {
+ public PatientDetailingData(Patient patient) {
+ this(patient.getId(), patient.getName(), patient.getEmail(), patient.getPhone(), patient.getSsn(), patient.getAddress());
+ }
+}
diff --git a/api/src/main/java/med/voll/api/domain/patient/PatientListingData.java b/api/src/main/java/med/voll/api/domain/patient/PatientListingData.java
new file mode 100644
index 0000000..1d4a15d
--- /dev/null
+++ b/api/src/main/java/med/voll/api/domain/patient/PatientListingData.java
@@ -0,0 +1,9 @@
+package med.voll.api.domain.patient;
+
+public record PatientListingData(Long id, String name, String phone) {
+
+ public PatientListingData(Patient patient) {
+ this(patient.getId(), patient.getName(), patient.getPhone());
+ }
+
+}
diff --git a/api/src/main/java/med/voll/api/domain/patient/PatientRegistrationData.java b/api/src/main/java/med/voll/api/domain/patient/PatientRegistrationData.java
new file mode 100644
index 0000000..ae1687d
--- /dev/null
+++ b/api/src/main/java/med/voll/api/domain/patient/PatientRegistrationData.java
@@ -0,0 +1,24 @@
+package med.voll.api.domain.patient;
+
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Pattern;
+import med.voll.api.domain.address.AddressData;
+
+public record PatientRegistrationData(
+ @NotBlank(message = "Name is required")
+ String name,
+ @NotBlank(message = "Email is required")
+ @Email(message = "Invalid email format")
+ String email,
+ @NotBlank(message = "Telephone number is required")
+ @Pattern(regexp = "\\d{10}", message = "Telephone number format is invalid")
+ String phone,
+ @NotBlank(message = "SSN is required")
+ String ssn,
+ @NotNull(message = "Address is required")
+ @Valid
+ AddressData addressData) {
+}
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
new file mode 100644
index 0000000..e550ac2
--- /dev/null
+++ b/api/src/main/java/med/voll/api/domain/patient/PatientRepository.java
@@ -0,0 +1,11 @@
+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.stereotype.Repository;
+
+@Repository
+public interface PatientRepository extends JpaRepository<Patient, Long> {
+ Page<Patient> findAllByActiveTrue(Pageable pagination);
+}
diff --git a/api/src/main/java/med/voll/api/domain/patient/PatientUpdateData.java b/api/src/main/java/med/voll/api/domain/patient/PatientUpdateData.java
new file mode 100644
index 0000000..55bf145
--- /dev/null
+++ b/api/src/main/java/med/voll/api/domain/patient/PatientUpdateData.java
@@ -0,0 +1,12 @@
+package med.voll.api.domain.patient;
+
+import jakarta.validation.constraints.NotNull;
+import med.voll.api.domain.address.AddressData;
+
+public record PatientUpdateData(
+ @NotNull
+ Long id,
+ String name,
+ String phone,
+ AddressData addressData) {
+}
diff --git a/api/src/main/java/med/voll/api/domain/user/AuthenticationData.java b/api/src/main/java/med/voll/api/domain/user/AuthenticationData.java
new file mode 100644
index 0000000..ce753b9
--- /dev/null
+++ b/api/src/main/java/med/voll/api/domain/user/AuthenticationData.java
@@ -0,0 +1,6 @@
+package med.voll.api.domain.user;
+
+import jakarta.validation.constraints.NotBlank;
+
+public record AuthenticationData(@NotBlank(message = "Email is required") String login, @NotBlank(message = "Password is required") String password) {
+}
diff --git a/api/src/main/java/med/voll/api/domain/user/AuthenticationService.java b/api/src/main/java/med/voll/api/domain/user/AuthenticationService.java
new file mode 100644
index 0000000..b089a9f
--- /dev/null
+++ b/api/src/main/java/med/voll/api/domain/user/AuthenticationService.java
@@ -0,0 +1,18 @@
+package med.voll.api.domain.user;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Service;
+
+@Service
+public class AuthenticationService implements UserDetailsService {
+ @Autowired
+ private UserRepository userRepository;
+
+ @Override
+ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+ return userRepository.findByLogin(username);
+ }
+}
diff --git a/api/src/main/java/med/voll/api/domain/user/User.java b/api/src/main/java/med/voll/api/domain/user/User.java
new file mode 100644
index 0000000..23d74e0
--- /dev/null
+++ b/api/src/main/java/med/voll/api/domain/user/User.java
@@ -0,0 +1,63 @@
+package med.voll.api.domain.user;
+
+import jakarta.persistence.*;
+import lombok.AllArgsConstructor;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+import java.util.Collection;
+import java.util.List;
+
+@Table(name = "users")
+@Entity(name = "User")
+@Getter
+@NoArgsConstructor
+@AllArgsConstructor
+@EqualsAndHashCode(of = "id")
+public class User implements UserDetails {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+ private String login;
+ private String password;
+
+ @Override
+ public Collection<? extends GrantedAuthority> getAuthorities() {
+ return List.of(new SimpleGrantedAuthority("ROLE_USER"));
+ }
+
+ @Override
+ public String getPassword() {
+ return this.password;
+ }
+
+ @Override
+ public String getUsername() {
+ return this.login;
+ }
+
+ @Override
+ public boolean isAccountNonExpired() {
+ return true;
+ }
+
+ @Override
+ public boolean isAccountNonLocked() {
+ return true;
+ }
+
+ @Override
+ public boolean isCredentialsNonExpired() {
+ return true;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return true;
+ }
+}
diff --git a/api/src/main/java/med/voll/api/domain/user/UserRepository.java b/api/src/main/java/med/voll/api/domain/user/UserRepository.java
new file mode 100644
index 0000000..20ce55d
--- /dev/null
+++ b/api/src/main/java/med/voll/api/domain/user/UserRepository.java
@@ -0,0 +1,8 @@
+package med.voll.api.domain.user;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.security.core.userdetails.UserDetails;
+
+public interface UserRepository extends JpaRepository<User, Long> {
+ UserDetails findByLogin(String login);
+}
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
new file mode 100644
index 0000000..9e49fd3
--- /dev/null
+++ b/api/src/main/java/med/voll/api/infra/exception/ErrorTreatment.java
@@ -0,0 +1,29 @@
+package med.voll.api.infra.exception;
+
+import jakarta.persistence.EntityNotFoundException;
+import org.springframework.http.ResponseEntity;
+import org.springframework.validation.FieldError;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+@RestControllerAdvice
+public class ErrorTreatment {
+
+ @ExceptionHandler(EntityNotFoundException.class)
+ public ResponseEntity treatError404() {
+ return ResponseEntity.notFound().build();
+ }
+
+ @ExceptionHandler(MethodArgumentNotValidException.class)
+ public ResponseEntity treatError400(MethodArgumentNotValidException ex) {
+ var errors = ex.getFieldErrors();
+ return ResponseEntity.badRequest().body(errors.stream().map(ValidationErrorData::new).toList());
+ }
+
+ private record ValidationErrorData(String field, String message) {
+ public ValidationErrorData(FieldError error) {
+ this(error.getField(), error.getDefaultMessage());
+ }
+ }
+}
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
new file mode 100644
index 0000000..5055162
--- /dev/null
+++ b/api/src/main/java/med/voll/api/infra/security/SecurityConfigurations.java
@@ -0,0 +1,44 @@
+package med.voll.api.infra.security;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpMethod;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+
+@Configuration
+@EnableWebSecurity
+public class SecurityConfigurations {
+
+ @Autowired
+ private SecurityFilter securityFilter;
+
+ @Bean
+ public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
+ return httpSecurity.csrf().disable()
+ .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
+ .and().authorizeHttpRequests()
+ .requestMatchers(HttpMethod.POST, "/login").permitAll()
+ .anyRequest().authenticated()
+ .and().addFilterBefore(securityFilter, UsernamePasswordAuthenticationFilter.class)
+ .build();
+ }
+
+ @Bean
+ public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
+ return authenticationConfiguration.getAuthenticationManager();
+ }
+
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ return new BCryptPasswordEncoder();
+ }
+}
diff --git a/api/src/main/java/med/voll/api/infra/security/SecurityFilter.java b/api/src/main/java/med/voll/api/infra/security/SecurityFilter.java
new file mode 100644
index 0000000..42f3df5
--- /dev/null
+++ b/api/src/main/java/med/voll/api/infra/security/SecurityFilter.java
@@ -0,0 +1,48 @@
+package med.voll.api.infra.security;
+
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import med.voll.api.domain.user.UserRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import java.io.IOException;
+
+@Component
+public class SecurityFilter extends OncePerRequestFilter {
+
+ @Autowired
+ private TokenService tokenService;
+
+ @Autowired
+ private UserRepository userRepository;
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
+ var tokenJWT = retrieveToken(request);
+
+ if (tokenJWT != null) {
+ var subject = tokenService.getSubject(tokenJWT);
+ var user = userRepository.findByLogin(subject);
+
+ var authentication = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
+ SecurityContextHolder.getContext().setAuthentication(authentication);
+ }
+
+ filterChain.doFilter(request, response);
+ }
+
+ private String retrieveToken(HttpServletRequest request) {
+ var authorizationHeader = request.getHeader("Authorization");
+ if (authorizationHeader != null) {
+ return authorizationHeader.replace("Bearer ", "");
+ }
+
+ return null;
+ }
+}
diff --git a/api/src/main/java/med/voll/api/infra/security/TokenJWTData.java b/api/src/main/java/med/voll/api/infra/security/TokenJWTData.java
new file mode 100644
index 0000000..2be8aa8
--- /dev/null
+++ b/api/src/main/java/med/voll/api/infra/security/TokenJWTData.java
@@ -0,0 +1,4 @@
+package med.voll.api.infra.security;
+
+public record TokenJWTData(String token) {
+}
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
new file mode 100644
index 0000000..63dda73
--- /dev/null
+++ b/api/src/main/java/med/voll/api/infra/security/TokenService.java
@@ -0,0 +1,50 @@
+package med.voll.api.infra.security;
+
+import com.auth0.jwt.JWT;
+import com.auth0.jwt.algorithms.Algorithm;
+import com.auth0.jwt.exceptions.JWTCreationException;
+import com.auth0.jwt.exceptions.JWTVerificationException;
+import med.voll.api.domain.user.User;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+
+@Service
+public class TokenService {
+
+ @Value("${api.security.token.secret}")
+ private String secret;
+
+ public String generateToken(User user) {
+ try {
+ var algorithm = Algorithm.HMAC256(secret);
+ return JWT.create()
+ .withIssuer("API Voll.med")
+ .withSubject(user.getLogin())
+ .withExpiresAt(expirationDate())
+ .sign(algorithm);
+ } catch (JWTCreationException exception){
+ throw new RuntimeException("Error when generating token JWT", exception);
+ }
+ }
+
+ public String getSubject(String tokenJWT) {
+ try {
+ var algorithm = Algorithm.HMAC256(secret);
+ return JWT.require(algorithm)
+ .withIssuer("API Voll.med")
+ .build()
+ .verify(tokenJWT)
+ .getSubject();
+ } catch (JWTVerificationException exception){
+ throw new RuntimeException("Invalid or expired TokenJWT!");
+ }
+ }
+
+ private Instant expirationDate() {
+ return LocalDateTime.now().plusHours(2).toInstant(ZoneOffset.of("-03:00"));
+ }
+}