Created services and repositories

This commit is contained in:
2025-11-07 18:11:58 +01:00
parent 42efca48ee
commit 93858408e8
25 changed files with 954 additions and 565 deletions

View File

@@ -1,90 +1,101 @@
package com.project.back_end.DTO; package com.project.back_end.DTO;
import com.project.back_end.models.Appointment;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
public class AppointmentDTO { public class AppointmentDTO {
// 1. 'id' field:
// - Type: private Long
// - Description:
// - Represents the unique identifier for the appointment.
// - This is the primary key for identifying the appointment in the system.
// 2. 'doctorId' field: private final Long id;
// - Type: private Long private final Long doctorId;
// - Description: private final String doctorName;
// - Represents the ID of the doctor associated with the appointment. private final Long patientId;
// - This is a simplified field, capturing only the ID of the doctor (not the full Doctor object). private final String patientName;
private final String patientEmail;
// 3. 'doctorName' field: private final String patientPhone;
// - Type: private String private final String patientAddress;
// - Description: private final LocalDateTime appointmentTime;
// - Represents the name of the doctor associated with the appointment. private final LocalDate appointmentDate;
// - This is a simplified field for displaying the doctor's name. private final LocalTime appointmentTimeOnly;
private final LocalDateTime endTime;
// 4. 'patientId' field: private final int status;
// - Type: private Long
// - Description:
// - Represents the ID of the patient associated with the appointment.
// - This is a simplified field, capturing only the ID of the patient (not the full Patient object).
// 5. 'patientName' field:
// - Type: private String
// - Description:
// - Represents the name of the patient associated with the appointment.
// - This is a simplified field for displaying the patient's name.
// 6. 'patientEmail' field:
// - Type: private String
// - Description:
// - Represents the email of the patient associated with the appointment.
// - This is a simplified field for displaying the patient's email.
// 7. 'patientPhone' field:
// - Type: private String
// - Description:
// - Represents the phone number of the patient associated with the appointment.
// - This is a simplified field for displaying the patient's phone number.
// 8. 'patientAddress' field:
// - Type: private String
// - Description:
// - Represents the address of the patient associated with the appointment.
// - This is a simplified field for displaying the patient's address.
// 9. 'appointmentTime' field:
// - Type: private LocalDateTime
// - Description:
// - Represents the scheduled date and time of the appointment.
// - The time when the appointment is supposed to happen, stored as a LocalDateTime object.
// 10. 'status' field:
// - Type: private int
// - Description:
// - Represents the status of the appointment.
// - Status can indicate if the appointment is "Scheduled:0", "Completed:1", or other statuses (e.g., "Canceled") as needed.
// 11. 'appointmentDate' field (Custom Getter):
// - Type: private LocalDate
// - Description:
// - A derived field representing only the date part of the appointment (without the time).
// - Extracted from the 'appointmentTime' field.
// 12. 'appointmentTimeOnly' field (Custom Getter):
// - Type: private LocalTime
// - Description:
// - A derived field representing only the time part of the appointment (without the date).
// - Extracted from the 'appointmentTime' field.
// 13. 'endTime' field (Custom Getter):
// - Type: private LocalDateTime
// - Description:
// - A derived field representing the end time of the appointment.
// - Calculated by adding 1 hour to the 'appointmentTime' field.
// 14. Constructor:
// - The constructor accepts all the relevant fields for the AppointmentDTO, including simplified fields for the doctor and patient (ID, name, etc.).
// - It also calculates custom fields: 'appointmentDate', 'appointmentTimeOnly', and 'endTime' based on the 'appointmentTime' field.
// 15. Getters:
// - Standard getter methods are provided for all fields: id, doctorId, doctorName, patientId, patientName, patientEmail, patientPhone, patientAddress, appointmentTime, status, appointmentDate, appointmentTimeOnly, and endTime.
// - These methods allow access to the values of the fields in the AppointmentDTO object.
public AppointmentDTO(Long id, Long doctorId, String doctorName, Long patientId,
String patientName, String patientEmail, String patientPhone,
String patientAddress, LocalDateTime appointmentTime, int status) {
this.id = id;
this.doctorId = doctorId;
this.doctorName = doctorName;
this.patientId = patientId;
this.patientName = patientName;
this.patientEmail = patientEmail;
this.patientPhone = patientPhone;
this.patientAddress = patientAddress;
this.appointmentTime = appointmentTime;
this.appointmentDate = appointmentTime.toLocalDate();
this.appointmentTimeOnly = appointmentTime.toLocalTime();
this.endTime = appointmentTime.plusHours(1);
this.status = status;
}
public AppointmentDTO(Appointment appointment) {
this(appointment.getId(), appointment.getDoctor().getId(), appointment.getDoctorName(),
appointment.getPatient().getId(), appointment.getPatientName(), appointment.getPatient().getEmail(),
appointment.getPatient().getPhone(), appointment.getPatient().getAddress(),
appointment.getAppointmentTime(), appointment.getStatus().ordinal());
}
public Long getId() {
return id;
}
public Long getDoctorId() {
return doctorId;
}
public String getDoctorName() {
return doctorName;
}
public Long getPatientId() {
return patientId;
}
public String getPatientName() {
return patientName;
}
public String getPatientEmail() {
return patientEmail;
}
public String getPatientPhone() {
return patientPhone;
}
public String getPatientAddress() {
return patientAddress;
}
public LocalDateTime getAppointmentTime() {
return appointmentTime;
}
public LocalDate getAppointmentDate() {
return appointmentDate;
}
public LocalTime getAppointmentTimeOnly() {
return appointmentTimeOnly;
}
public LocalDateTime getEndTime() {
return endTime;
}
public int getStatus() {
return status;
}
} }

View File

@@ -1,30 +1,42 @@
package com.project.back_end.DTO; package com.project.back_end.DTO;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class Login { public class Login {
// 1. 'email' field: private String identifier;
// - Type: private String private String password;
// - Description:
// - Represents the email address used for logging into the system.
// - The email field is expected to contain a valid email address for user authentication purposes.
// 2. 'password' field: public Login(String identifier, String password) {
// - Type: private String this.identifier = identifier;
// - Description: this.password = hashPassword(password);
// - Represents the password associated with the email address. }
// - The password field is used for verifying the user's identity during login.
// - It is generally hashed before being stored and compared during authentication.
// 3. Constructor: public String getIdentifier() {
// - No explicit constructor is defined for this class, as it relies on the default constructor provided by Java. return identifier;
// - This class can be initialized with setters or directly via reflection, as per the application's needs. }
// 4. Getters and Setters: public String getPassword() {
// - Standard getter and setter methods are provided for both 'email' and 'password' fields. return password;
// - The 'getEmail()' method allows access to the email value. }
// - The 'setEmail(String email)' method sets the email value.
// - The 'getPassword()' method allows access to the password value. public void setIdentifier(String identifier) {
// - The 'setPassword(String password)' method sets the password value. this.identifier = identifier;
}
public void setPassword(String password) {
this.password = password;
}
private static String hashPassword(String rawPassword) {
try {
return new String(MessageDigest.getInstance("SHA-256").digest(rawPassword.getBytes(StandardCharsets.UTF_8)));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
} }

View File

@@ -8,11 +8,14 @@ import jakarta.validation.constraints.NotNull;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.LocalTime; import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
@Entity @Entity
@Table(name = "appointments", uniqueConstraints = @UniqueConstraint(name = "doctor_patient_time_constraint", columnNames = {"doctor", "patient", "appointmentTime"})) @Table(name = "appointments", uniqueConstraints = @UniqueConstraint(name = "doctor_patient_time_constraint", columnNames = {"doctor", "patient", "appointmentTime"}))
public class Appointment { public class Appointment {
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm");
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; private Long id;
@@ -87,6 +90,11 @@ public class Appointment {
return appointmentTime.toLocalTime(); return appointmentTime.toLocalTime();
} }
@Transient
public String getAppointmentTimeAsString() {
return TIME_FORMATTER.format(getAppointmentTimeOnly()) + "-" + TIME_FORMATTER.format(getEndTime());
}
@Transient @Transient
public String getDoctorName() { public String getDoctorName() {
return doctor.getName(); return doctor.getName();

View File

@@ -11,6 +11,7 @@ import java.nio.charset.StandardCharsets;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.List; import java.util.List;
import java.util.Set;
@Entity @Entity
@Table(name = "doctors") @Table(name = "doctors")
@@ -43,9 +44,14 @@ public class Doctor {
@Pattern(regexp = "^[0-9]{10}$") @Pattern(regexp = "^[0-9]{10}$")
private String phone; private String phone;
@OneToMany(mappedBy = "doctor") @OneToMany(mappedBy = "doctor", fetch = FetchType.LAZY)
private List<Appointment> appointments; private List<Appointment> appointments;
@ElementCollection(fetch = FetchType.EAGER)
@CollectionTable(name = "doctors_available_times", uniqueConstraints = @UniqueConstraint(columnNames = {"element_value", "doctor_id"}))
@Column(name = "available_times")
private Set<String> availableTimes;
public Doctor() { public Doctor() {
} }
@@ -107,6 +113,14 @@ public class Doctor {
this.appointments = appointments; this.appointments = appointments;
} }
public Set<String> getAvailableTimes() {
return availableTimes;
}
public void setAvailableTimes(Set<String> availableTimes) {
this.availableTimes = availableTimes;
}
private static String hashPassword(String rawPassword) { private static String hashPassword(String rawPassword) {
try { try {
return new String(MessageDigest.getInstance("SHA-256").digest(rawPassword.getBytes(StandardCharsets.UTF_8))); return new String(MessageDigest.getInstance("SHA-256").digest(rawPassword.getBytes(StandardCharsets.UTF_8)));

View File

@@ -1,30 +1,32 @@
package com.project.back_end.mvc; package com.project.back_end.mvc;
import com.project.back_end.services.TokenService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@Controller
public class DashboardController { public class DashboardController {
// 1. Set Up the MVC Controller Class: private final TokenService tokenService;
// - Annotate the class with `@Controller` to indicate that it serves as an MVC controller returning view names (not JSON).
// - This class handles routing to admin and doctor dashboard pages based on token validation.
// 2. Autowire the Shared Service:
// - Inject the common `Service` class, which provides the token validation logic used to authorize access to dashboards.
// 3. Define the `adminDashboard` Method:
// - Handles HTTP GET requests to `/adminDashboard/{token}`.
// - Accepts an admin's token as a path variable.
// - Validates the token using the shared service for the `"admin"` role.
// - If the token is valid (i.e., no errors returned), forwards the user to the `"admin/adminDashboard"` view.
// - If invalid, redirects to the root URL, likely the login or home page.
// 4. Define the `doctorDashboard` Method:
// - Handles HTTP GET requests to `/doctorDashboard/{token}`.
// - Accepts a doctor's token as a path variable.
// - Validates the token using the shared service for the `"doctor"` role.
// - If the token is valid, forwards the user to the `"doctor/doctorDashboard"` view.
// - If the token is invalid, redirects to the root URL.
public DashboardController(TokenService tokenService) {
this.tokenService = tokenService;
}
@GetMapping("/adminDashboard/{token}")
public String adminDashboard(@PathVariable("token") String token) {
if (!tokenService.validateToken(token, "admin")) {
return "redirect:/";
}
return "redirect:admin/adminDashboard";
}
@GetMapping("/doctorDashboard/{token}")
public String doctorDashboard(@PathVariable("token") String token) {
if (!tokenService.validateToken(token, "doctor")) {
return "redirect:/";
}
return "redirect:admin/doctorDashboard";
}
} }

View File

@@ -1,30 +1,14 @@
package com.project.back_end.repo; package com.project.back_end.repo;
public interface AdminRepository { import com.project.back_end.models.Admin;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
// 1. Extend JpaRepository: import java.util.Optional;
// - The repository extends JpaRepository<Admin, Long>, which gives it basic CRUD functionality.
// - The methods such as save, delete, update, and find are inherited without the need for explicit implementation.
// - JpaRepository also includes pagination and sorting features.
// Example: public interface AdminRepository extends JpaRepository<Admin, Long> {} @Repository
public interface AdminRepository extends JpaRepository<Admin, Long> {
// 2. Custom Query Method: Optional<Admin> findByUsername(String username);
// - **findByUsername**:
// - This method allows you to find an Admin by their username.
// - Return type: Admin
// - Parameter: String username
// - It will return an Admin entity that matches the provided username.
// - If no Admin is found with the given username, it returns null.
// Example: public Admin findByUsername(String username);
// 3. Add @Repository annotation:
// - The @Repository annotation marks this interface as a Spring Data JPA repository.
// - While it is technically optional (since JpaRepository is a part of Spring Data), it's good practice to include it for clarity.
// - Spring Data JPA automatically implements the repository, providing the necessary CRUD functionality.
// Example: @Repository
// public interface AdminRepository extends JpaRepository<Admin, Long> { ... }
} }

View File

@@ -1,66 +1,68 @@
package com.project.back_end.repo; package com.project.back_end.repo;
public interface AppointmentRepository { import com.project.back_end.models.Appointment;
import jakarta.transaction.Transactional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
// 1. Extend JpaRepository: import java.time.LocalDateTime;
// - The repository extends JpaRepository<Appointment, Long>, which gives it basic CRUD functionality. import java.util.List;
// - The methods such as save, delete, update, and find are inherited without the need for explicit implementation.
// - JpaRepository also includes pagination and sorting features.
// Example: public interface AppointmentRepository extends JpaRepository<Appointment, Long> {} @Repository
public interface AppointmentRepository extends JpaRepository<Appointment, Long> {
// 2. Custom Query Methods: @Query("""
FROM Appointment a
LEFT JOIN FETCH Doctor d ON a.doctor = d
WHERE d.id = :doctorId
AND a.appointmentTime BETWEEN :start and :end
""")
List<Appointment> findByDoctorIdAndAppointmentTimeBetween(Long doctorId, LocalDateTime start, LocalDateTime end);
// - **findByDoctorIdAndAppointmentTimeBetween**: @Query("""
// - This method retrieves a list of appointments for a specific doctor within a given time range. FROM Appointment a
// - The doctors available times are eagerly fetched to avoid lazy loading. LEFT JOIN FETCH Doctor d ON a.doctor = d
// - Return type: List<Appointment> LEFT JOIN FETCH Patient p on a.patient = p
// - Parameters: Long doctorId, LocalDateTime start, LocalDateTime end WHERE d.id = :doctorId
// - It uses a LEFT JOIN to fetch the doctors available times along with the appointments. AND LOWER(p.name) LIKE CONCAT('%',LOWER(:patientName),'%')
AND a.appointmentTime BETWEEN :start and :end
""")
List<Appointment> findByDoctorIdAndPatient_NameContainingIgnoreCaseAndAppointmentTimeBetween(Long doctorId, String patientName, LocalDateTime start, LocalDateTime end);
// - **findByDoctorIdAndPatient_NameContainingIgnoreCaseAndAppointmentTimeBetween**: @Transactional
// - This method retrieves appointments for a specific doctor and patient name (ignoring case) within a given time range. @Modifying
// - It performs a LEFT JOIN to fetch both the doctor and patient details along with the appointment times. void deleteAllByDoctorId(Long doctorId);
// - Return type: List<Appointment>
// - Parameters: Long doctorId, String patientName, LocalDateTime start, LocalDateTime end
// - **deleteAllByDoctorId**: List<Appointment> findByPatientId(Long patientId);
// - This method deletes all appointments associated with a particular doctor.
// - It is marked as @Modifying and @Transactional, which makes it a modification query, ensuring that the operation is executed within a transaction.
// - Return type: void
// - Parameters: Long doctorId
// - **findByPatientId**: List<Appointment> findByPatient_IdAndStatusOrderByAppointmentTimeAsc(Long patientId, int status);
// - This method retrieves all appointments for a specific patient.
// - Return type: List<Appointment>
// - Parameters: Long patientId
// - **findByPatient_IdAndStatusOrderByAppointmentTimeAsc**: @Query("""
// - This method retrieves all appointments for a specific patient with a given status, ordered by the appointment time. FROM Appointment a
// - Return type: List<Appointment> LEFT JOIN FETCH Doctor d ON a.doctor = d
// - Parameters: Long patientId, int status LEFT JOIN FETCH Patient p on a.patient = p
WHERE LOWER(d.name) LIKE CONCAT('%', LOWER(:doctorName),'%')
AND p.id = :patientId
""")
List<Appointment> filterByDoctorNameAndPatientId(String doctorName, Long patientId);
// - **filterByDoctorNameAndPatientId**: @Query("""
// - This method retrieves appointments based on a doctors name (using a LIKE query) and the patients ID. FROM Appointment a
// - Return type: List<Appointment> LEFT JOIN FETCH Doctor d ON a.doctor = d
// - Parameters: String doctorName, Long patientId LEFT JOIN FETCH Patient p on a.patient = p
WHERE LOWER(d.name) LIKE CONCAT('%', LOWER(:doctorName),'%')
// - **filterByDoctorNameAndPatientIdAndStatus**: AND p.id = :patientId
// - This method retrieves appointments based on a doctors name (using a LIKE query), patients ID, and a specific appointment status. AND a.status = :status
// - Return type: List<Appointment> """)
// - Parameters: String doctorName, Long patientId, int status List<Appointment> filterByDoctorNameAndPatientIdAndStatus(String doctorName, Long patientId, int status);
// - **updateStatus**:
// - This method updates the status of a specific appointment based on its ID.
// - Return type: void
// - Parameters: int status, long id
// 3. @Modifying and @Transactional annotations:
// - The @Modifying annotation is used to indicate that the method performs a modification operation (like DELETE or UPDATE).
// - The @Transactional annotation ensures that the modification is done within a transaction, meaning that if any exception occurs, the changes will be rolled back.
// 4. @Repository annotation:
// - The @Repository annotation marks this interface as a Spring Data JPA repository.
// - Spring Data JPA automatically implements this repository, providing the necessary CRUD functionality and custom queries defined in the interface.
@Modifying
@Query("""
UPDATE Appointment a
SET a.status = :status
WHERE a.id = :id
""")
void updateStatus(int status, long id);
} }

View File

@@ -1,39 +1,31 @@
package com.project.back_end.repo; package com.project.back_end.repo;
public interface DoctorRepository { import com.project.back_end.models.Doctor;
// 1. Extend JpaRepository: import org.springframework.data.jpa.repository.JpaRepository;
// - The repository extends JpaRepository<Doctor, Long>, which gives it basic CRUD functionality. import org.springframework.data.jpa.repository.Query;
// - This allows the repository to perform operations like save, delete, update, and find without needing to implement these methods manually. import org.springframework.stereotype.Repository;
// - JpaRepository also includes features like pagination and sorting.
// Example: public interface DoctorRepository extends JpaRepository<Doctor, Long> {} import java.util.List;
import java.util.Optional;
// 2. Custom Query Methods: @Repository
public interface DoctorRepository extends JpaRepository<Doctor, Long> {
// - **findByEmail**: Optional<Doctor> findByEmail(String email);
// - This method retrieves a Doctor by their email.
// - Return type: Doctor
// - Parameters: String email
// - **findByNameLike**: @Query("""
// - This method retrieves a list of Doctors whose name contains the provided search string (case-sensitive). FROM Doctor d
// - The `CONCAT('%', :name, '%')` is used to create a pattern for partial matching. WHERE d.name LIKE CONCAT('%', :name ,'%')
// - Return type: List<Doctor> """)
// - Parameters: String name List<Doctor> findByNameLike(String name);
// - **findByNameContainingIgnoreCaseAndSpecialtyIgnoreCase**: @Query("""
// - This method retrieves a list of Doctors where the name contains the search string (case-insensitive) and the specialty matches exactly (case-insensitive). FROM Doctor d
// - It combines both fields for a more specific search. WHERE LOWER(d.name) LIKE CONCAT('%', LOWER(:name), '%')
// - Return type: List<Doctor> AND LOWER(d.specialty) = LOWER(:specialty)
// - Parameters: String name, String specialty """)
List<Doctor> findByNameContainingIgnoreCaseAndSpecialtyIgnoreCase(String name, String specialty);
// - **findBySpecialtyIgnoreCase**: List<Doctor> findBySpecialtyIgnoreCase(String specialty);
// - This method retrieves a list of Doctors with the specified specialty, ignoring case sensitivity.
// - Return type: List<Doctor>
// - Parameters: String specialty
// 3. @Repository annotation:
// - The @Repository annotation marks this interface as a Spring Data JPA repository.
// - Spring Data JPA automatically implements this repository, providing the necessary CRUD functionality and custom queries defined in the interface.
} }

View File

@@ -1,29 +1,16 @@
package com.project.back_end.repo; package com.project.back_end.repo;
public interface PatientRepository { import com.project.back_end.models.Patient;
// 1. Extend JpaRepository: import org.springframework.data.jpa.repository.JpaRepository;
// - The repository extends JpaRepository<Patient, Long>, which provides basic CRUD functionality. import org.springframework.stereotype.Repository;
// - This allows the repository to perform operations like save, delete, update, and find without needing to implement these methods manually.
// - JpaRepository also includes features like pagination and sorting.
// Example: public interface PatientRepository extends JpaRepository<Patient, Long> {} import java.util.Optional;
// 2. Custom Query Methods: @Repository
public interface PatientRepository extends JpaRepository<Patient, Long> {
// - **findByEmail**: Optional<Patient> findByEmail(String email);
// - This method retrieves a Patient by their email address.
// - Return type: Patient
// - Parameters: String email
// - **findByEmailOrPhone**:
// - This method retrieves a Patient by either their email or phone number, allowing flexibility for the search.
// - Return type: Patient
// - Parameters: String email, String phone
// 3. @Repository annotation:
// - The @Repository annotation marks this interface as a Spring Data JPA repository.
// - Spring Data JPA automatically implements this repository, providing the necessary CRUD functionality and custom queries defined in the interface.
Optional<Patient> findByEmailOrPhone(String email, String phone);
} }

View File

@@ -1,21 +1,12 @@
package com.project.back_end.repo; package com.project.back_end.repo;
public interface PrescriptionRepository { import com.project.back_end.models.Prescription;
// 1. Extend MongoRepository: import org.springframework.data.mongodb.repository.MongoRepository;
// - The repository extends MongoRepository<Prescription, String>, which provides basic CRUD functionality for MongoDB.
// - This allows the repository to perform operations like save, delete, update, and find without needing to implement these methods manually.
// - MongoRepository is tailored for working with MongoDB, unlike JpaRepository which is used for relational databases.
// Example: public interface PrescriptionRepository extends MongoRepository<Prescription, String> {} import java.util.List;
// 2. Custom Query Method: public interface PrescriptionRepository extends MongoRepository<Prescription, String> {
// - **findByAppointmentId**:
// - This method retrieves a list of prescriptions associated with a specific appointment.
// - Return type: List<Prescription>
// - Parameters: Long appointmentId
// - MongoRepository automatically derives the query from the method name, in this case, it will find prescriptions by the appointment ID.
List<Prescription> findByAppointmentId(Long appointmentId);
} }

View File

@@ -1,45 +1,101 @@
package com.project.back_end.services; package com.project.back_end.services;
import com.project.back_end.models.Appointment;
import com.project.back_end.models.Doctor;
import com.project.back_end.repo.AppointmentRepository;
import com.project.back_end.repo.DoctorRepository;
import com.project.back_end.repo.PatientRepository;
import jakarta.transaction.Transactional;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@Service
public class AppointmentService { public class AppointmentService {
// 1. **Add @Service Annotation**:
// - To indicate that this class is a service layer class for handling business logic.
// - The `@Service` annotation should be added before the class declaration to mark it as a Spring service component.
// - Instruction: Add `@Service` above the class definition.
// 2. **Constructor Injection for Dependencies**:
// - The `AppointmentService` class requires several dependencies like `AppointmentRepository`, `Service`, `TokenService`, `PatientRepository`, and `DoctorRepository`.
// - These dependencies should be injected through the constructor.
// - Instruction: Ensure constructor injection is used for proper dependency management in Spring.
// 3. **Add @Transactional Annotation for Methods that Modify Database**:
// - The methods that modify or update the database should be annotated with `@Transactional` to ensure atomicity and consistency of the operations.
// - Instruction: Add the `@Transactional` annotation above methods that interact with the database, especially those modifying data.
// 4. **Book Appointment Method**:
// - Responsible for saving the new appointment to the database.
// - If the save operation fails, it returns `0`; otherwise, it returns `1`.
// - Instruction: Ensure that the method handles any exceptions and returns an appropriate result code.
// 5. **Update Appointment Method**:
// - This method is used to update an existing appointment based on its ID.
// - It validates whether the patient ID matches, checks if the appointment is available for updating, and ensures that the doctor is available at the specified time.
// - If the update is successful, it saves the appointment; otherwise, it returns an appropriate error message.
// - Instruction: Ensure proper validation and error handling is included for appointment updates.
// 6. **Cancel Appointment Method**:
// - This method cancels an appointment by deleting it from the database.
// - It ensures the patient who owns the appointment is trying to cancel it and handles possible errors.
// - Instruction: Make sure that the method checks for the patient ID match before deleting the appointment.
// 7. **Get Appointments Method**:
// - This method retrieves a list of appointments for a specific doctor on a particular day, optionally filtered by the patient's name.
// - It uses `@Transactional` to ensure that database operations are consistent and handled in a single transaction.
// - Instruction: Ensure the correct use of transaction boundaries, especially when querying the database for appointments.
// 8. **Change Status Method**:
// - This method updates the status of an appointment by changing its value in the database.
// - It should be annotated with `@Transactional` to ensure the operation is executed in a single transaction.
// - Instruction: Add `@Transactional` before this method to ensure atomicity when updating appointment status.
private final AppointmentRepository appointmentRepository;
private final PatientRepository patientRepository;
private final DoctorRepository doctorRepository;
private final TokenService tokenService;
private final com.project.back_end.services.Service service;
public AppointmentService(AppointmentRepository appointmentRepository, PatientRepository patientRepository, DoctorRepository doctorRepository, TokenService tokenService, com.project.back_end.services.Service service) {
this.appointmentRepository = appointmentRepository;
this.patientRepository = patientRepository;
this.doctorRepository = doctorRepository;
this.tokenService = tokenService;
this.service = service;
}
@Transactional
public int bookAppointment(@Valid Appointment appointment) {
try {
appointmentRepository.save(appointment);
return 1;
} catch (Exception e) {
return 0;
}
}
@Transactional
public ResponseEntity<Map<String, String>> updateAppointment(@Valid Appointment appointment) {
if (appointmentRepository.findById(appointment.getId()).isEmpty()) {
return ResponseEntity.noContent().build();
}
if (service.validateAppointment(appointment) < 1) {
return ResponseEntity.badRequest().build();
}
try {
appointmentRepository.save(appointment);
return ResponseEntity.ok(Map.of("success", "true", "message", "Updated successfully!"));
} catch (Exception e) {
return ResponseEntity.ok(Map.of("success", "false", "message", e.getMessage()));
}
}
@Transactional
public ResponseEntity<Map<String,String>> cancelAppointment(@Valid Appointment appointment, String token) {
if (!tokenService.validateToken(token, "DOCTOR")) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
if (appointmentRepository.findById(appointment.getId()).isEmpty()) {
return ResponseEntity.noContent().build();
}
try {
appointmentRepository.delete(appointment);
return ResponseEntity.ok(Map.of("success", "true", "message", "Removed successfully!"));
} catch (Exception e) {
return ResponseEntity.ok(Map.of("success", "false", "message", e.getMessage()));
}
}
@Transactional
public Map<String, Object> getAppointment(String pname, LocalDate date, String token) {
String doctorEmail = tokenService.extractIdentifier(token);
Optional<Doctor> found = doctorRepository.findByEmail(doctorEmail);
if (found.isEmpty()) {
return Map.of("appointments", List.of());
}
Long doctorId = found.get().getId();
List<Appointment> appointments = appointmentRepository.findByDoctorIdAndAppointmentTimeBetween(
doctorId, date.atStartOfDay(), date.plusDays(1).atStartOfDay());
if (pname != null) {
appointments = appointments.stream().filter(a -> a.getPatientName().contains(pname)).toList();
}
return Map.of("appointments", appointments);
}
} }

View File

@@ -1,92 +1,202 @@
package com.project.back_end.services; package com.project.back_end.services;
import com.project.back_end.DTO.Login;
import com.project.back_end.models.Appointment;
import com.project.back_end.models.Doctor;
import com.project.back_end.repo.AppointmentRepository;
import com.project.back_end.repo.DoctorRepository;
import jakarta.transaction.Transactional;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@Service
public class DoctorService { public class DoctorService {
// 1. **Add @Service Annotation**: private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm");
// - This class should be annotated with `@Service` to indicate that it is a service layer class.
// - The `@Service` annotation marks this class as a Spring-managed bean for business logic.
// - Instruction: Add `@Service` above the class declaration.
// 2. **Constructor Injection for Dependencies**: private final DoctorRepository doctorRepository;
// - The `DoctorService` class depends on `DoctorRepository`, `AppointmentRepository`, and `TokenService`. private final AppointmentRepository appointmentRepository;
// - These dependencies should be injected via the constructor for proper dependency management. private final TokenService tokenService;
// - Instruction: Ensure constructor injection is used for injecting dependencies into the service.
// 3. **Add @Transactional Annotation for Methods that Modify or Fetch Database Data**: public DoctorService(DoctorRepository doctorRepository, AppointmentRepository appointmentRepository, TokenService tokenService) {
// - Methods like `getDoctorAvailability`, `getDoctors`, `findDoctorByName`, `filterDoctorsBy*` should be annotated with `@Transactional`. this.doctorRepository = doctorRepository;
// - The `@Transactional` annotation ensures that database operations are consistent and wrapped in a single transaction. this.appointmentRepository = appointmentRepository;
// - Instruction: Add the `@Transactional` annotation above the methods that perform database operations or queries. this.tokenService = tokenService;
}
// 4. **getDoctorAvailability Method**: @Transactional
// - Retrieves the available time slots for a specific doctor on a particular date and filters out already booked slots. public List<String> getDoctorAvailability(Long doctorId, LocalDate date) {
// - The method fetches all appointments for the doctor on the given date and calculates the availability by comparing against booked slots. Doctor doctor = doctorRepository.findById(doctorId).orElseThrow(() -> new IllegalArgumentException("Doctor not found"));
// - Instruction: Ensure that the time slots are properly formatted and the available slots are correctly filtered. return getAvailableTimes(doctor, date);
}
// 5. **saveDoctor Method**: @Transactional
// - Used to save a new doctor record in the database after checking if a doctor with the same email already exists. public int saveDoctor(@Valid Doctor doctor) {
// - If a doctor with the same email is found, it returns `-1` to indicate conflict; `1` for success, and `0` for internal errors. if (doctorRepository.findByEmail(doctor.getEmail()).isPresent()) {
// - Instruction: Ensure that the method correctly handles conflicts and exceptions when saving a doctor. return -1;
}
try {
doctorRepository.save(doctor);
return 1;
} catch (Exception e) {
return 0;
}
}
// 6. **updateDoctor Method**: @Transactional
// - Updates an existing doctor's details in the database. If the doctor doesn't exist, it returns `-1`. public int updateDoctor(@Valid Doctor doctor) {
// - Instruction: Make sure that the doctor exists before attempting to save the updated record and handle any errors properly. if (doctorRepository.findById(doctor.getId()).isEmpty()) {
return -1;
}
try {
doctorRepository.save(doctor);
return 1;
} catch (Exception e) {
return 0;
}
}
// 7. **getDoctors Method**: @Transactional
// - Fetches all doctors from the database. It is marked with `@Transactional` to ensure that the collection is properly loaded. public List<Doctor> getDoctors() {
// - Instruction: Ensure that the collection is eagerly loaded, especially if dealing with lazy-loaded relationships (e.g., available times). return doctorRepository.findAll();
}
// 8. **deleteDoctor Method**: @Transactional
// - Deletes a doctor from the system along with all appointments associated with that doctor. public int deleteDoctor(Doctor doctor) {
// - It first checks if the doctor exists. If not, it returns `-1`; otherwise, it deletes the doctor and their appointments. if (doctorRepository.findById(doctor.getId()).isEmpty()) {
// - Instruction: Ensure the doctor and their appointments are deleted properly, with error handling for internal issues. return -1;
}
try {
appointmentRepository.deleteAllByDoctorId(doctor.getId());
doctorRepository.delete(doctor);
return 1;
} catch (Exception e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return 0;
}
}
// 9. **validateDoctor Method**: @Transactional
// - Validates a doctor's login by checking if the email and password match an existing doctor record. public ResponseEntity<Map<String,String>> validateDoctor(Login login) {
// - It generates a token for the doctor if the login is successful, otherwise returns an error message. Optional<Doctor> found = doctorRepository.findByEmail(login.getIdentifier());
// - Instruction: Make sure to handle invalid login attempts and password mismatches properly with error responses. if (found.isEmpty()) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
}
// 10. **findDoctorByName Method**: Doctor doctor = found.get();
// - Finds doctors based on partial name matching and returns the list of doctors with their available times.
// - This method is annotated with `@Transactional` to ensure that the database query and data retrieval are properly managed within a transaction.
// - Instruction: Ensure that available times are eagerly loaded for the doctors.
if (!doctor.getPassword().equals(login.getPassword())) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
}
// 11. **filterDoctorsByNameSpecilityandTime Method**: String token = tokenService.generateToken(doctor.getEmail());
// - Filters doctors based on their name, specialty, and availability during a specific time (AM/PM). return ResponseEntity.ok(Map.of("token", token));
// - The method fetches doctors matching the name and specialty criteria, then filters them based on their availability during the specified time period. }
// - Instruction: Ensure proper filtering based on both the name and specialty as well as the specified time period.
// 12. **filterDoctorByTime Method**: @Transactional
// - Filters a list of doctors based on whether their available times match the specified time period (AM/PM). public List<Doctor> findDoctorByName(String name) {
// - This method processes a list of doctors and their available times to return those that fit the time criteria. return doctorRepository.findByNameLike(name);
// - Instruction: Ensure that the time filtering logic correctly handles both AM and PM time slots and edge cases. }
@Transactional
public Map<String, Object> filterDoctorsByNameSpecialityAndTime(String name, String specialty, String amOrPm) {
if (!"AM".equalsIgnoreCase(amOrPm) && !"PM".equalsIgnoreCase(amOrPm)) {
throw new IllegalArgumentException("AM/PM only accepted");
}
List<Doctor> doctors = doctorRepository.findByNameContainingIgnoreCaseAndSpecialtyIgnoreCase(name, specialty)
.stream()
.filter(d -> d.getAvailableTimes().stream().anyMatch(t -> matchesAMPM(t, amOrPm)))
.toList();
return Map.of("doctors", doctors);
}
// 13. **filterDoctorByNameAndTime Method**: @Transactional
// - Filters doctors based on their name and the specified time period (AM/PM). public Map<String, Object> filterDoctorByTime(String amOrPm) {
// - Fetches doctors based on partial name matching and filters the results to include only those available during the specified time period. if (!"AM".equalsIgnoreCase(amOrPm) && !"PM".equalsIgnoreCase(amOrPm)) {
// - Instruction: Ensure that the method correctly filters doctors based on the given name and time of day (AM/PM). throw new IllegalArgumentException("AM/PM only accepted");
}
List<Doctor> doctors = doctorRepository.findAll()
.stream()
.filter(d -> d.getAvailableTimes().stream().anyMatch(t -> matchesAMPM(t, amOrPm)))
.toList();
return Map.of("doctors", doctors);
}
// 14. **filterDoctorByNameAndSpecility Method**: @Transactional
// - Filters doctors by name and specialty. public Map<String, Object> filterDoctorByNameAndTime(String name, String amOrPm) {
// - It ensures that the resulting list of doctors matches both the name (case-insensitive) and the specified specialty. if (!"AM".equalsIgnoreCase(amOrPm) && !"PM".equalsIgnoreCase(amOrPm)) {
// - Instruction: Ensure that both name and specialty are considered when filtering doctors. throw new IllegalArgumentException("AM/PM only accepted");
}
List<Doctor> doctors = doctorRepository.findByNameLike(name)
.stream()
.filter(d -> d.getAvailableTimes().stream().anyMatch(t -> matchesAMPM(t, amOrPm)))
.toList();
return Map.of("doctors", doctors);
}
@Transactional
public Map<String, Object> filterDoctorByNameAndSpeciality(String name, String speciality) {
List<Doctor> doctors = doctorRepository.findByNameContainingIgnoreCaseAndSpecialtyIgnoreCase(name,speciality);
return Map.of("doctors", doctors);
}
// 15. **filterDoctorByTimeAndSpecility Method**: @Transactional
// - Filters doctors based on their specialty and availability during a specific time period (AM/PM). public Map<String, Object> filterDoctorByTimeAndSpeciality(String speciality, String amOrPm) {
// - Fetches doctors based on the specified specialty and filters them based on their available time slots for AM/PM. if (!"AM".equalsIgnoreCase(amOrPm) && !"PM".equalsIgnoreCase(amOrPm)) {
// - Instruction: Ensure the time filtering is accurately applied based on the given specialty and time period (AM/PM). throw new IllegalArgumentException("AM/PM only accepted");
}
List<Doctor> doctors = doctorRepository.findBySpecialtyIgnoreCase(speciality)
.stream()
.filter(d -> d.getAvailableTimes().stream().anyMatch(t -> matchesAMPM(t, amOrPm)))
.toList();
return Map.of("doctors", doctors);
}
// 16. **filterDoctorBySpecility Method**: @Transactional
// - Filters doctors based on their specialty. public Map<String, Object> filterDoctorBySpeciality(String speciality) {
// - This method fetches all doctors matching the specified specialty and returns them. List<Doctor> doctors = doctorRepository.findBySpecialtyIgnoreCase(speciality);
// - Instruction: Make sure the filtering logic works for case-insensitive specialty matching. return Map.of("doctors", doctors);
}
// 17. **filterDoctorsByTime Method**: @Transactional
// - Filters all doctors based on their availability during a specific time period (AM/PM). public List<Doctor> filterDoctorsByTime(String amOrPm) {
// - The method checks all doctors' available times and returns those available during the specified time period. if (!"AM".equalsIgnoreCase(amOrPm) && !"PM".equalsIgnoreCase(amOrPm)) {
// - Instruction: Ensure proper filtering logic to handle AM/PM time periods. throw new IllegalArgumentException("AM/PM only accepted");
}
return doctorRepository.findAll()
.stream()
.filter(d -> d.getAvailableTimes().stream().anyMatch(t -> matchesAMPM(t, amOrPm)))
.toList();
}
private static boolean matchesAMPM(String time, String amOrPm) {
LocalTime start = LocalTime.parse(time.substring(0, time.indexOf("-")));
return switch (amOrPm.toUpperCase()) {
case "AM" -> start.isBefore(LocalTime.NOON);
case "PM" -> start.equals(LocalTime.NOON) || start.isAfter(LocalTime.NOON);
default -> false;
};
}
private List<String> getAvailableTimes(Doctor doctor, LocalDate date) {
List<String> reservedTimes = appointmentRepository.findByDoctorIdAndAppointmentTimeBetween(doctor.getId(), date.atStartOfDay(), date.plusDays(1).atStartOfDay())
.stream()
.map(Appointment::getAppointmentTimeAsString)
.toList();
return doctor.getAvailableTimes()
.stream()
.filter(t -> !reservedTimes.contains(t))
.toList();
}
} }

View File

@@ -1,58 +1,148 @@
package com.project.back_end.services; package com.project.back_end.services;
import com.project.back_end.DTO.AppointmentDTO;
import com.project.back_end.models.Appointment;
import com.project.back_end.models.Patient;
import com.project.back_end.repo.AppointmentRepository;
import com.project.back_end.repo.PatientRepository;
import jakarta.transaction.Transactional;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@Service
public class PatientService { public class PatientService {
// 1. **Add @Service Annotation**:
// - The `@Service` annotation is used to mark this class as a Spring service component.
// - It will be managed by Spring's container and used for business logic related to patients and appointments.
// - Instruction: Ensure that the `@Service` annotation is applied above the class declaration.
// 2. **Constructor Injection for Dependencies**: private final PatientRepository patientRepository;
// - The `PatientService` class has dependencies on `PatientRepository`, `AppointmentRepository`, and `TokenService`. private final AppointmentRepository appointmentRepository;
// - These dependencies are injected via the constructor to maintain good practices of dependency injection and testing. private final TokenService tokenService;
// - Instruction: Ensure constructor injection is used for all the required dependencies.
// 3. **createPatient Method**: public PatientService(PatientRepository patientRepository, AppointmentRepository appointmentRepository, TokenService tokenService) {
// - Creates a new patient in the database. It saves the patient object using the `PatientRepository`. this.patientRepository = patientRepository;
// - If the patient is successfully saved, the method returns `1`; otherwise, it logs the error and returns `0`. this.appointmentRepository = appointmentRepository;
// - Instruction: Ensure that error handling is done properly and exceptions are caught and logged appropriately. this.tokenService = tokenService;
}
// 4. **getPatientAppointment Method**: @Transactional
// - Retrieves a list of appointments for a specific patient, based on their ID. public int createPatient(@Valid Patient patient) {
// - The appointments are then converted into `AppointmentDTO` objects for easier consumption by the API client. try {
// - This method is marked as `@Transactional` to ensure database consistency during the transaction. patientRepository.save(patient);
// - Instruction: Ensure that appointment data is properly converted into DTOs and the method handles errors gracefully. return 1;
} catch (Exception e) {
return 0;
}
}
// 5. **filterByCondition Method**: @Transactional
// - Filters appointments for a patient based on the condition (e.g., "past" or "future"). public ResponseEntity<Map<String, Object>> getPatientAppointment(Long patientId, String token) {
// - Retrieves appointments with a specific status (0 for future, 1 for past) for the patient. try {
// - Converts the appointments into `AppointmentDTO` and returns them in the response. if (!tokenService.validateToken(token, "PATIENT")) {
// - Instruction: Ensure the method correctly handles "past" and "future" conditions, and that invalid conditions are caught and returned as errors. return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
// 6. **filterByDoctor Method**: String patientEmail = tokenService.extractIdentifier(token);
// - Filters appointments for a patient based on the doctor's name. Optional<Patient> found = patientRepository.findByEmail(patientEmail);
// - It retrieves appointments where the doctors name matches the given value, and the patient ID matches the provided ID. if (found.isEmpty()) {
// - Instruction: Ensure that the method correctly filters by doctor's name and patient ID and handles any errors or invalid cases. return ResponseEntity.noContent().build();
}
// 7. **filterByDoctorAndCondition Method**: Patient patient = found.get();
// - Filters appointments based on both the doctor's name and the condition (past or future) for a specific patient. if (!patientId.equals(patient.getId())) {
// - This method combines filtering by doctor name and appointment status (past or future). return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
// - Converts the appointments into `AppointmentDTO` objects and returns them in the response. }
// - Instruction: Ensure that the filter handles both doctor name and condition properly, and catches errors for invalid input.
// 8. **getPatientDetails Method**:
// - Retrieves patient details using the `tokenService` to extract the patient's email from the provided token.
// - Once the email is extracted, it fetches the corresponding patient from the `patientRepository`.
// - It returns the patient's information in the response body.
// - Instruction: Make sure that the token extraction process works correctly and patient details are fetched properly based on the extracted email.
// 9. **Handling Exceptions and Errors**:
// - The service methods handle exceptions using try-catch blocks and log any issues that occur. If an error occurs during database operations, the service responds with appropriate HTTP status codes (e.g., `500 Internal Server Error`).
// - Instruction: Ensure that error handling is consistent across the service, with proper logging and meaningful error messages returned to the client.
// 10. **Use of DTOs (Data Transfer Objects)**:
// - The service uses `AppointmentDTO` to transfer appointment-related data between layers. This ensures that sensitive or unnecessary data (e.g., password or private patient information) is not exposed in the response.
// - Instruction: Ensure that DTOs are used appropriately to limit the exposure of internal data and only send the relevant fields to the client.
List<AppointmentDTO> appointments = appointmentRepository.findByPatientId(patientId)
.stream()
.map(AppointmentDTO::new)
.toList();
return ResponseEntity.ok(Map.of("appointments", appointments));
} catch (Exception e) {
return ResponseEntity.internalServerError().build();
}
}
@Transactional
public ResponseEntity<Map<String, Object>> filterByCondition(String condition, Long patientId) {
if (!"PAST".equalsIgnoreCase(condition) && !"PRESENT".equalsIgnoreCase(condition)) {
return ResponseEntity.badRequest().build();
}
try {
List<AppointmentDTO> appointments = appointmentRepository.findByPatientId(patientId)
.stream()
.filter(a -> matchesCondition(a, condition))
.map(AppointmentDTO::new)
.toList();
return ResponseEntity.ok(Map.of("appointments", appointments));
} catch (Exception e) {
return ResponseEntity.internalServerError().build();
}
}
@Transactional
public ResponseEntity<Map<String, Object>> filterByDoctor(String name, Long patientId) {
try {
List<AppointmentDTO> appointments = appointmentRepository.filterByDoctorNameAndPatientId(name, patientId)
.stream()
.map(AppointmentDTO::new)
.toList();
return ResponseEntity.ok(Map.of("appointments", appointments));
} catch (Exception e) {
return ResponseEntity.internalServerError().build();
}
}
@Transactional
public ResponseEntity<Map<String, Object>> filterByDoctorAndCondition(String condition, String name, long patientId) {
if (!"PAST".equalsIgnoreCase(condition) && !"PRESENT".equalsIgnoreCase(condition)) {
return ResponseEntity.badRequest().build();
}
try {
List<AppointmentDTO> appointments = appointmentRepository.filterByDoctorNameAndPatientId(name, patientId)
.stream()
.filter(a -> matchesCondition(a, condition))
.map(AppointmentDTO::new)
.toList();
return ResponseEntity.ok(Map.of("appointments", appointments));
} catch (Exception e) {
return ResponseEntity.internalServerError().build();
}
}
@Transactional
public ResponseEntity<Map<String, Object>> getPatientDetails(String token) {
try {
if (!tokenService.validateToken(token, "PATIENT")) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
String patientEmail = tokenService.extractIdentifier(token);
Optional<Patient> found = patientRepository.findByEmail(patientEmail);
if (found.isEmpty()) {
return ResponseEntity.noContent().build();
}
Patient patient = found.get();
return ResponseEntity.ok(Map.of("patient", patient));
} catch (Exception e) {
return ResponseEntity.internalServerError().build();
}
}
private static boolean matchesCondition(Appointment appointment, String condition) {
return switch (condition.toUpperCase()) {
case "PAST" -> appointment.getAppointmentTime().isBefore(LocalDateTime.now());
case "PRESENT" -> appointment.getAppointmentTime().isAfter(LocalDateTime.now());
default -> false;
};
}
} }

View File

@@ -1,34 +1,45 @@
package com.project.back_end.services; package com.project.back_end.services;
import com.project.back_end.models.Prescription;
import com.project.back_end.repo.PrescriptionRepository;
import jakarta.transaction.Transactional;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
@Service
public class PrescriptionService { public class PrescriptionService {
// 1. **Add @Service Annotation**: private final PrescriptionRepository prescriptionRepository;
// - The `@Service` annotation marks this class as a Spring service component, allowing Spring's container to manage it.
// - This class contains the business logic related to managing prescriptions in the healthcare system.
// - Instruction: Ensure the `@Service` annotation is applied to mark this class as a Spring-managed service.
// 2. **Constructor Injection for Dependencies**:
// - The `PrescriptionService` class depends on the `PrescriptionRepository` to interact with the database.
// - It is injected through the constructor, ensuring proper dependency management and enabling testing.
// - Instruction: Constructor injection is a good practice, ensuring that all necessary dependencies are available at the time of service initialization.
// 3. **savePrescription Method**:
// - This method saves a new prescription to the database.
// - Before saving, it checks if a prescription already exists for the same appointment (using the appointment ID).
// - If a prescription exists, it returns a `400 Bad Request` with a message stating the prescription already exists.
// - If no prescription exists, it saves the new prescription and returns a `201 Created` status with a success message.
// - Instruction: Handle errors by providing appropriate status codes and messages, ensuring that multiple prescriptions for the same appointment are not saved.
// 4. **getPrescription Method**:
// - Retrieves a prescription associated with a specific appointment based on the `appointmentId`.
// - If a prescription is found, it returns it within a map wrapped in a `200 OK` status.
// - If there is an error while fetching the prescription, it logs the error and returns a `500 Internal Server Error` status with an error message.
// - Instruction: Ensure that this method handles edge cases, such as no prescriptions found for the given appointment, by returning meaningful responses.
// 5. **Exception Handling and Error Responses**:
// - Both methods (`savePrescription` and `getPrescription`) contain try-catch blocks to handle exceptions that may occur during database interaction.
// - If an error occurs, the method logs the error and returns an HTTP `500 Internal Server Error` response with a corresponding error message.
// - Instruction: Ensure that all potential exceptions are handled properly, and meaningful responses are returned to the client.
public PrescriptionService(PrescriptionRepository prescriptionRepository) {
this.prescriptionRepository = prescriptionRepository;
}
@Transactional
public ResponseEntity<Map<String, String>> savePrescription(@Valid Prescription prescription) {
try {
if (!prescriptionRepository.findByAppointmentId(prescription.getAppointment().getId()).isEmpty()) {
return ResponseEntity.badRequest().body(Map.of("message", "Prescription already exists"));
}
prescriptionRepository.save(prescription);
return ResponseEntity.status(HttpStatus.CREATED).body(Map.of("message", "Prescription saved"));
} catch (Exception e) {
return ResponseEntity.internalServerError().body(Map.of("message", "Internal Error"));
}
}
@Transactional
public ResponseEntity<Map<String, Object>> getPrescription(Long appointmentId) {
try {
List<Prescription> prescriptions = prescriptionRepository.findByAppointmentId(appointmentId);
return ResponseEntity.ok(Map.of("prescriptions", prescriptions));
} catch (Exception e) {
return ResponseEntity.internalServerError().body(Map.of("message", "Internal Error"));
}
}
} }

View File

@@ -1,66 +1,154 @@
package com.project.back_end.services; package com.project.back_end.services;
import com.project.back_end.DTO.Login;
import com.project.back_end.models.Admin;
import com.project.back_end.models.Appointment;
import com.project.back_end.models.Patient;
import com.project.back_end.repo.AdminRepository;
import com.project.back_end.repo.DoctorRepository;
import com.project.back_end.repo.PatientRepository;
import jakarta.transaction.Transactional;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@org.springframework.stereotype.Service
public class Service { public class Service {
// 1. **@Service Annotation**
// The @Service annotation marks this class as a service component in Spring. This allows Spring to automatically detect it through component scanning
// and manage its lifecycle, enabling it to be injected into controllers or other services using @Autowired or constructor injection.
// 2. **Constructor Injection for Dependencies** private final TokenService tokenService;
// The constructor injects all required dependencies (TokenService, Repositories, and other Services). This approach promotes loose coupling, improves testability, private final AdminRepository adminRepository;
// and ensures that all required dependencies are provided at object creation time. private final DoctorRepository doctorRepository;
private final PatientRepository patientRepository;
// 3. **validateToken Method** private final DoctorService doctorService;
// This method checks if the provided JWT token is valid for a specific user. It uses the TokenService to perform the validation. private final PatientService patientService;
// If the token is invalid or expired, it returns a 401 Unauthorized response with an appropriate error message. This ensures security by preventing
// unauthorized access to protected resources.
// 4. **validateAdmin Method**
// This method validates the login credentials for an admin user.
// - It first searches the admin repository using the provided username.
// - If an admin is found, it checks if the password matches.
// - If the password is correct, it generates and returns a JWT token (using the admins username) with a 200 OK status.
// - If the password is incorrect, it returns a 401 Unauthorized status with an error message.
// - If no admin is found, it also returns a 401 Unauthorized.
// - If any unexpected error occurs during the process, a 500 Internal Server Error response is returned.
// This method ensures that only valid admin users can access secured parts of the system.
// 5. **filterDoctor Method**
// This method provides filtering functionality for doctors based on name, specialty, and available time slots.
// - It supports various combinations of the three filters.
// - If none of the filters are provided, it returns all available doctors.
// This flexible filtering mechanism allows the frontend or consumers of the API to search and narrow down doctors based on user criteria.
// 6. **validateAppointment Method**
// This method validates if the requested appointment time for a doctor is available.
// - It first checks if the doctor exists in the repository.
// - Then, it retrieves the list of available time slots for the doctor on the specified date.
// - It compares the requested appointment time with the start times of these slots.
// - If a match is found, it returns 1 (valid appointment time).
// - If no matching time slot is found, it returns 0 (invalid).
// - If the doctor doesnt exist, it returns -1.
// This logic prevents overlapping or invalid appointment bookings.
// 7. **validatePatient Method**
// This method checks whether a patient with the same email or phone number already exists in the system.
// - If a match is found, it returns false (indicating the patient is not valid for new registration).
// - If no match is found, it returns true.
// This helps enforce uniqueness constraints on patient records and prevent duplicate entries.
// 8. **validatePatientLogin Method**
// This method handles login validation for patient users.
// - It looks up the patient by email.
// - If found, it checks whether the provided password matches the stored one.
// - On successful validation, it generates a JWT token and returns it with a 200 OK status.
// - If the password is incorrect or the patient doesn't exist, it returns a 401 Unauthorized with a relevant error.
// - If an exception occurs, it returns a 500 Internal Server Error.
// This method ensures only legitimate patients can log in and access their data securely.
// 9. **filterPatient Method**
// This method filters a patient's appointment history based on condition and doctor name.
// - It extracts the email from the JWT token to identify the patient.
// - Depending on which filters (condition, doctor name) are provided, it delegates the filtering logic to PatientService.
// - If no filters are provided, it retrieves all appointments for the patient.
// This flexible method supports patient-specific querying and enhances user experience on the client side.
public Service(TokenService tokenService, AdminRepository adminRepository, DoctorRepository doctorRepository,
PatientRepository patientRepository, DoctorService doctorService, PatientService patientService) {
this.tokenService = tokenService;
this.adminRepository = adminRepository;
this.doctorRepository = doctorRepository;
this.patientRepository = patientRepository;
this.doctorService = doctorService;
this.patientService = patientService;
}
@Transactional
public ResponseEntity<Map<String, String>> validateToken(String token, String user) {
if (tokenService.validateToken(token, user)) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
return ResponseEntity.ok().body(Map.of("message", "User is valid"));
}
@Transactional
public ResponseEntity<Map<String, String>> validateAdmin(Admin receivedAdmin) {
try {
Optional<Admin> found = adminRepository.findByUsername(receivedAdmin.getUsername());
if (found.isEmpty()) {
return ResponseEntity.noContent().build();
}
Admin admin = found.get();
if (!receivedAdmin.getPassword().equals(admin.getPassword())) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
String token = tokenService.generateToken(receivedAdmin.getUsername());
return ResponseEntity.ok(Map.of("token", token));
} catch (Exception e) {
return ResponseEntity.internalServerError().build();
}
}
@Transactional
public Map<String, Object> filterDoctor(String name, String specialty, String time) {
if (name == null && specialty == null && time == null) {
return Map.of("doctors", doctorService.getDoctors());
} else if (name == null && specialty == null) {
return doctorService.filterDoctorByTime(time);
} else if (name == null && time == null) {
return doctorService.filterDoctorBySpeciality(specialty);
} else if (specialty == null && time == null) {
return Map.of("doctors", doctorService.findDoctorByName(name));
} else if (specialty == null) {
return doctorService.filterDoctorByNameAndTime(name, time);
} else if (time == null) {
return doctorService.filterDoctorByNameAndSpeciality(name, specialty);
} else if (name == null) {
return doctorService.filterDoctorByTimeAndSpeciality(specialty, time);
} else {
return doctorService.filterDoctorsByNameSpecialityAndTime(name, specialty, time);
}
}
@Transactional
public int validateAppointment(Appointment appointment) {
if (doctorRepository.findById(appointment.getDoctor().getId()).isEmpty()) {
return -1;
}
List<String> availableSlots = doctorService.getDoctorAvailability(appointment.getDoctor().getId(), appointment.getAppointmentDate());
if (!availableSlots.contains(appointment.getAppointmentTimeAsString())) {
return 0;
}
return 1;
}
@Transactional
public boolean validatePatient(Patient patient) {
return patientRepository.findByEmail(patient.getEmail()).isEmpty();
}
@Transactional
public ResponseEntity<Map<String, String>> validatePatientLogin(Login login) {
try {
Optional<Patient> found = patientRepository.findByEmail(login.getIdentifier());
if (found.isEmpty()) {
return ResponseEntity.noContent().build();
}
Patient patient = found.get();
if (!patient.getPassword().equalsIgnoreCase(login.getPassword())) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
String token = tokenService.generateToken(login.getIdentifier());
return ResponseEntity.ok(Map.of("token", token));
} catch (Exception e) {
return ResponseEntity.internalServerError().build();
}
}
@Transactional
public ResponseEntity<Map<String, Object>> filterPatient(String condition, String name, String token) {
try {
String patientEmail = tokenService.extractIdentifier(token);
Optional<Patient> found = patientRepository.findByEmail(patientEmail);
if (found.isEmpty()) {
return ResponseEntity.noContent().build();
}
Patient patient = found.get();
if (condition == null && name == null) {
return patientService.getPatientAppointment(patient.getId(), token);
} else if (condition == null) {
return patientService.filterByDoctor(name, patient.getId());
} else if ( name == null) {
return patientService.filterByCondition(condition, patient.getId());
} else {
return patientService.filterByDoctorAndCondition(condition, name, patient.getId());
}
} catch (Exception e) {
return ResponseEntity.internalServerError().build();
}
}
} }

View File

@@ -1,43 +1,70 @@
package com.project.back_end.services; package com.project.back_end.services;
import com.project.back_end.repo.AdminRepository;
import com.project.back_end.repo.DoctorRepository;
import com.project.back_end.repo.PatientRepository;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Date;
@Component
public class TokenService { public class TokenService {
// 1. **@Component Annotation**
// The @Component annotation marks this class as a Spring component, meaning Spring will manage it as a bean within its application context.
// This allows the class to be injected into other Spring-managed components (like services or controllers) where it's needed.
// 2. **Constructor Injection for Dependencies** private final AdminRepository adminRepository;
// The constructor injects dependencies for `AdminRepository`, `DoctorRepository`, and `PatientRepository`, private final DoctorRepository doctorRepository;
// allowing the service to interact with the database and validate users based on their role (admin, doctor, or patient). private final PatientRepository patientRepository;
// Constructor injection ensures that the class is initialized with all required dependencies, promoting immutability and making the class testable.
// 3. **getSigningKey Method**
// This method retrieves the HMAC SHA key used to sign JWT tokens.
// It uses the `jwt.secret` value, which is provided from an external source (like application properties).
// The `Keys.hmacShaKeyFor()` method converts the secret key string into a valid `SecretKey` for signing and verification of JWTs.
// 4. **generateToken Method**
// This method generates a JWT token for a user based on their email.
// - The `subject` of the token is set to the user's email, which is used as an identifier.
// - The `issuedAt` is set to the current date and time.
// - The `expiration` is set to 7 days from the issue date, ensuring the token expires after one week.
// - The token is signed using the signing key generated by `getSigningKey()`, making it secure and tamper-proof.
// The method returns the JWT token as a string.
// 5. **extractEmail Method**
// This method extracts the user's email (subject) from the provided JWT token.
// - The token is first verified using the signing key to ensure it hasnt been tampered with.
// - After verification, the token is parsed, and the subject (which represents the email) is extracted.
// This method allows the application to retrieve the user's identity (email) from the token for further use.
// 6. **validateToken Method**
// This method validates whether a provided JWT token is valid for a specific user role (admin, doctor, or patient).
// - It first extracts the email from the token using the `extractEmail()` method.
// - Depending on the role (`admin`, `doctor`, or `patient`), it checks the corresponding repository (AdminRepository, DoctorRepository, or PatientRepository)
// to see if a user with the extracted email exists.
// - If a match is found for the specified user role, it returns true, indicating the token is valid.
// - If the role or user does not exist, it returns false, indicating the token is invalid.
// - The method gracefully handles any errors by returning false if the token is invalid or an exception occurs.
// This ensures secure access control based on the user's role and their existence in the system.
private final SecretKey signingKey;
private final JwtParser jwtParser;
public TokenService(AdminRepository adminRepository, DoctorRepository doctorRepository, PatientRepository patientRepository, @Value("${jwt.secret}") String jwtSecret) {
this.adminRepository = adminRepository;
this.doctorRepository = doctorRepository;
this.patientRepository = patientRepository;
this.signingKey = Keys.hmacShaKeyFor(jwtSecret.getBytes());
this.jwtParser = Jwts.parser().verifyWith(signingKey).build();
}
public String generateToken(String identifier) {
LocalDateTime now = LocalDateTime.now();
LocalDateTime exp = now.plusDays(7);
return Jwts.builder()
.subject(identifier)
.issuedAt(Date.from(now.toInstant(ZoneOffset.UTC)))
.expiration(Date.from(exp.toInstant(ZoneOffset.UTC)))
.signWith(signingKey)
.compact();
}
public String extractIdentifier(String token) {
try {
return jwtParser.parseSignedClaims(token).getPayload().getSubject();
} catch (Exception e) {
return null;
}
}
public boolean validateToken(String token, String role) {
try {
String identifier = extractIdentifier(token);
if (identifier == null) {
return false;
}
return switch (role.toUpperCase()) {
case "ADMIN" -> adminRepository.findByUsername(identifier).isPresent();
case "DOCTOR" -> doctorRepository.findByEmail(identifier).isPresent();
case "PATIENT" -> patientRepository.findByEmail(identifier).isPresent();
default -> false;
};
} catch (Exception e) {
return false;
}
}
} }

View File

@@ -24,9 +24,7 @@ h2 {
} }
.main-content { .main-content {
flex-grow: 1;
padding: 40px; padding: 40px;
display: flex;
text-align: center; text-align: center;
background-image: url("index.png"); background-image: url("index.png");
background-size: cover; background-size: cover;

View File

@@ -17,9 +17,9 @@
<div id="header"></div> <div id="header"></div>
<main class="main-content"> <main class="main-content">
<h2>Select Your Role:</h2> <h2>Select Your Role:</h2>
<button>Admin</button> <button id="adminLogin">Admin</button>
<button>Doctor</button> <button id="doctorLogin">Doctor</button>
<button>Patient</button> <button id="patientLogin" onclick="window.location.href='/pages/loggedPatientDashboard.html'">Patient</button>
</main> </main>
<div id="footer"></div> <div id="footer"></div>
</div> </div>

View File

@@ -1,6 +1,6 @@
import openModal from "../components/modals.js"; import { openModal } from "../components/modals.js";
import { getDoctors, filterDoctors, saveDoctor } from "./services/doctorServices.js"; import { getDoctors, filterDoctors, saveDoctor } from "./services/doctorServices.js";
import createDoctorCard from "./components/doctorCard.js"; import { createDoctorCard } from "./components/doctorCard.js";
window.onload = function () { window.onload = function () {
document.getElementById("searchBar").addEventListener("input", filterDoctorsOnChange); document.getElementById("searchBar").addEventListener("input", filterDoctorsOnChange);

View File

@@ -1,6 +1,6 @@
import deleteDoctor from "../services/doctorService.js"; import { deleteDoctor } from "../services/doctorServices.js";
import getPatientData from "../services/patientServices.js"; import { getPatientData } from "../services/patientServices.js";
import showBookingOverlay from "../loggedPatient.js"; import { showBookingOverlay } from "../loggedPatient.js";
export function createDoctorCard(doctor) { export function createDoctorCard(doctor) {
const card = document.createElement("div"); const card = document.createElement("div");

View File

@@ -1,4 +1,4 @@
export function renderFooter() { function renderFooter() {
const footer = document.getElementById("footer"); const footer = document.getElementById("footer");
footer.innerHTML = ` footer.innerHTML = `
<footer class="footer"> <footer class="footer">

View File

@@ -1,4 +1,4 @@
export function renderHeader() { function renderHeader() {
const headerDiv = document.getElementById("header"); const headerDiv = document.getElementById("header");
if (window.location.pathname.endsWith("/")) { if (window.location.pathname.endsWith("/")) {
@@ -53,26 +53,26 @@ export function renderHeader() {
attachHeaderButtonListeners(); attachHeaderButtonListeners();
} }
export function attachHeaderButtonListeners() { function attachHeaderButtonListeners() {
const login = document.getElementById(patientLogin); const login = document.getElementById("patientLogin");
if (login) { if (login) {
login.addEventListener('click', () => { openModal('patientLogin'); }); login.addEventListener('click', () => { openModal('patientLogin'); });
} }
const signup = document.getElementById(patientSignup); const signup = document.getElementById("patientSignup");
if (signup) { if (signup) {
signup.addEventListener('click', () => { openModal('patientSignup'); }); signup.addEventListener('click', () => { openModal('patientSignup'); });
} }
} }
export function logout() { function logout() {
localStorage.removeItem("userRole"); localStorage.removeItem("userRole");
localStorage.removeItem("TOKEN"); localStorage.removeItem("TOKEN");
window.location.href = "/"; window.location.href = "/";
} }
export function logoutPatient() { function logoutPatient() {
localStorage.removeItem("userRole"); localStorage.removeItem("userRole");
selectRole('patient') selectRole('patient');
window.location.href='/pages/loggedPatientDashboard.html window.location.href='/pages/loggedPatientDashboard.html';
} }

View File

@@ -1,5 +1,5 @@
import getAllAppointments from "./services/appointmentRecordService.js"; import { getAllAppointments } from "./services/appointmentRecordService.js";
import createPatientRow from "./components/patientRows.js"; import { createPatientRow } from "./components/patientRows.js";
const patientTable = document.getElementById("patientTableBody"); const patientTable = document.getElementById("patientTableBody");
const token = localStorage.getItem("TOKEN"); const token = localStorage.getItem("TOKEN");

View File

@@ -1,4 +1,4 @@
import openModal from "../components/modals.js"; import { openModal } from "../components/modals.js";
import { API_BASE_URL } from "../config/config.js" import { API_BASE_URL } from "../config/config.js"
const ADMIN_API = API_BASE_URL + '/admin'; const ADMIN_API = API_BASE_URL + '/admin';
@@ -21,37 +21,43 @@ window.onload = function () {
} }
} }
export async function adminLoginHandler() { window.adminLoginHandler = async function() {
const username = document.getElementById('username').value; const username = document.getElementById('username').value;
const password = document.getElementById('password').value; const password = document.getElementById('password').value;
const admin = { username, password }; const admin = { username, password };
try { try {
await fetch(ADMIN_API, { const response = await fetch(ADMIN_API, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(admin) body: JSON.stringify(admin)
}) });
.then(response => response.json()) const result = await response.json();
.then(json => localStorage.setItem("TOKEN", json)) if (!response.ok) {
.then(item -> selectRole('admin')) throw new Error(result.message);
}
localStorage.setItem("TOKEN", result)
selectRole('admin');
} catch (error) { } catch (error) {
alert("Invalid credentials!"); alert("Invalid credentials!");
} }
} }
export async function doctorLoginHandler() { window.doctorLoginHandler = async function() {
const email = document.getElementById('email').value; const email = document.getElementById('email').value;
const password = document.getElementById('password').value; const password = document.getElementById('password').value;
const doctor = { email, password }; const doctor = { email, password };
try { try {
await fetch(DOCTOR_API, { const response = await fetch(DOCTOR_API, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(doctor) body: JSON.stringify(doctor)
}) })
.then(response => response.json()) const result = await response.json();
.then(json => localStorage.setItem("TOKEN", json)) if (!response.ok) {
.then(item -> selectRole('doctor')) throw new Error(result.message);
}
localStorage.setItem("TOKEN", result)
selectRole('doctor');
} catch (error) { } catch (error) {
alert("Invalid credentials!"); alert("Invalid credentials!");
} }