Fully working app

This commit is contained in:
2025-11-09 17:33:37 +01:00
parent f1879640c0
commit 66609ab25d
42 changed files with 710 additions and 620 deletions

View File

@@ -1,17 +1,24 @@
package com.project.back_end.DTO;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import com.fasterxml.jackson.annotation.JsonAlias;
import com.project.back_end.utils.PasswordUtil;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
public class Login {
@NotNull
@Size(min = 3)
@JsonAlias({"email","username"})
private String identifier;
@NotNull
@Size(min = 3)
private String password;
public Login(String identifier, String password) {
this.identifier = identifier;
this.password = hashPassword(password);
this.password = PasswordUtil.hashPassword(password);
}
public String getIdentifier() {
@@ -21,22 +28,4 @@ public class Login {
public String getPassword() {
return password;
}
public void setIdentifier(String identifier) {
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

@@ -1,27 +1,30 @@
package com.project.back_end.controllers;
import com.project.back_end.DTO.Login;
import com.project.back_end.models.Admin;
import com.project.back_end.services.Service;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
@RequestMapping("${api.path}" + "admin")
public class AdminController {
// 1. Set Up the Controller Class:
// - Annotate the class with `@RestController` to indicate that it's a REST controller, used to handle web requests and return JSON responses.
// - Use `@RequestMapping("${api.path}admin")` to define a base path for all endpoints in this controller.
// - This allows the use of an external property (`api.path`) for flexible configuration of endpoint paths.
private final Service service;
public AdminController(Service service) {
this.service = service;
}
// 2. Autowire Service Dependency:
// - Use constructor injection to autowire the `Service` class.
// - The service handles core logic related to admin validation and token checking.
// - This promotes cleaner code and separation of concerns between the controller and business logic layer.
// 3. Define the `adminLogin` Method:
// - Handles HTTP POST requests for admin login functionality.
// - Accepts an `Admin` object in the request body, which contains login credentials.
// - Delegates authentication logic to the `validateAdmin` method in the service layer.
// - Returns a `ResponseEntity` with a `Map` containing login status or messages.
}
@PostMapping
public ResponseEntity<Map<String, String>> adminLogin(@Valid @RequestBody Login login) {
return service.validateAdminLogin(login);
}
}

View File

@@ -1,48 +1,78 @@
package com.project.back_end.controllers;
import com.project.back_end.models.Appointment;
import com.project.back_end.services.AppointmentService;
import com.project.back_end.services.Service;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDate;
import java.util.Map;
import static org.springframework.http.ResponseEntity.*;
@RestController
@RequestMapping("/appointments")
public class AppointmentController {
// 1. Set Up the Controller Class:
// - Annotate the class with `@RestController` to define it as a REST API controller.
// - Use `@RequestMapping("/appointments")` to set a base path for all appointment-related endpoints.
// - This centralizes all routes that deal with booking, updating, retrieving, and canceling appointments.
private final AppointmentService appointmentService;
private final Service service;
public AppointmentController(AppointmentService appointmentService, Service service) {
this.appointmentService = appointmentService;
this.service = service;
}
// 2. Autowire Dependencies:
// - Inject `AppointmentService` for handling the business logic specific to appointments.
// - Inject the general `Service` class, which provides shared functionality like token validation and appointment checks.
@GetMapping("/{date}/{patientName}/{token}")
public ResponseEntity<Map<String, Object>> getAppointments(@PathVariable LocalDate date, @PathVariable String patientName, @PathVariable String token) {
ResponseEntity<Map<String, Object>> validation = service.validateToken(token, "doctor");
if (!validation.getStatusCode().is2xxSuccessful()) {
return validation;
}
return ResponseEntity.ok(appointmentService.getAppointment(patientName.equals("null") ? null : patientName, date, token));
}
@PostMapping("/{token}")
public ResponseEntity<Map<String, Object>> bookAppointment(@Valid @RequestBody Appointment appointment, @PathVariable String token) {
ResponseEntity<Map<String, Object>> validation = service.validateToken(token, "patient");
if (!validation.getStatusCode().is2xxSuccessful()) {
return validation;
}
int validationResult = service.validateAppointment(appointment);
return switch (validationResult) {
case -1 -> badRequest().body(Map.of("success", false,"message", "Doctor not found"));
case 0 -> badRequest().body(Map.of("success", false, "message", "Time has already been reserved"));
case 1 -> {
int saveResult = appointmentService.bookAppointment(appointment);
if (saveResult == 0) {
yield internalServerError().body(Map.of("success", false,"message", "Failed to save appointment!"));
} else if (saveResult == 1){
yield status(HttpStatus.CREATED).body(Map.of("success", true,"message", "Appointment booked!"));
} else {
yield internalServerError().body(Map.of("success", false,"message", "Internal Server error"));
}
}
default -> internalServerError().body(Map.of("success", false,"message", "Internal Server error"));
};
}
// 3. Define the `getAppointments` Method:
// - Handles HTTP GET requests to fetch appointments based on date and patient name.
// - Takes the appointment date, patient name, and token as path variables.
// - First validates the token for role `"doctor"` using the `Service`.
// - If the token is valid, returns appointments for the given patient on the specified date.
// - If the token is invalid or expired, responds with the appropriate message and status code.
@PutMapping("/{token}")
public ResponseEntity<Map<String, Object>> updateAppointment(@Valid @RequestBody Appointment appointment, @PathVariable String token) {
ResponseEntity<Map<String, Object>> validation = service.validateToken(token, "patient");
if (!validation.getStatusCode().is2xxSuccessful()) {
return validation;
}
return appointmentService.updateAppointment(appointment);
}
// 4. Define the `bookAppointment` Method:
// - Handles HTTP POST requests to create a new appointment.
// - Accepts a validated `Appointment` object in the request body and a token as a path variable.
// - Validates the token for the `"patient"` role.
// - Uses service logic to validate the appointment data (e.g., check for doctor availability and time conflicts).
// - Returns success if booked, or appropriate error messages if the doctor ID is invalid or the slot is already taken.
// 5. Define the `updateAppointment` Method:
// - Handles HTTP PUT requests to modify an existing appointment.
// - Accepts a validated `Appointment` object and a token as input.
// - Validates the token for `"patient"` role.
// - Delegates the update logic to the `AppointmentService`.
// - Returns an appropriate success or failure response based on the update result.
// 6. Define the `cancelAppointment` Method:
// - Handles HTTP DELETE requests to cancel a specific appointment.
// - Accepts the appointment ID and a token as path variables.
// - Validates the token for `"patient"` role to ensure the user is authorized to cancel the appointment.
// - Calls `AppointmentService` to handle the cancellation process and returns the result.
}
@DeleteMapping("/{id}/{token}")
public ResponseEntity<Map<String, Object>> cancelAppointment(@PathVariable long id, @PathVariable String token) {
ResponseEntity<Map<String, Object>> validation = service.validateToken(token, "patient");
if (!validation.getStatusCode().is2xxSuccessful()) {
return validation;
}
return appointmentService.cancelAppointment(id, token);
}
}

View File

@@ -1,61 +1,103 @@
package com.project.back_end.controllers;
import com.project.back_end.DTO.Login;
import com.project.back_end.models.Doctor;
import com.project.back_end.services.DoctorService;
import com.project.back_end.services.Service;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDate;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("${api.path}doctor")
public class DoctorController {
// 1. Set Up the Controller Class:
// - Annotate the class with `@RestController` to define it as a REST controller that serves JSON responses.
// - Use `@RequestMapping("${api.path}doctor")` to prefix all endpoints with a configurable API path followed by "doctor".
// - This class manages doctor-related functionalities such as registration, login, updates, and availability.
private final DoctorService doctorService;
private final Service service;
public DoctorController(DoctorService doctorService, Service service) {
this.doctorService = doctorService;
this.service = service;
}
// 2. Autowire Dependencies:
// - Inject `DoctorService` for handling the core logic related to doctors (e.g., CRUD operations, authentication).
// - Inject the shared `Service` class for general-purpose features like token validation and filtering.
@GetMapping("/availability/{user}/{doctorId}/{date}/{token}")
public ResponseEntity<Map<String, Object>> getDoctorAvailability(@PathVariable String user, @PathVariable long doctorId, @PathVariable LocalDate date, @PathVariable String token) {
ResponseEntity<Map<String, Object>> validation = service.validateToken(token, user);
if (!validation.getStatusCode().is2xxSuccessful()) {
return validation;
}
List<String> doctorAvailability = doctorService.getDoctorAvailability(doctorId, date);
return ResponseEntity.ok(Map.of("availability", doctorAvailability));
}
// 3. Define the `getDoctorAvailability` Method:
// - Handles HTTP GET requests to check a specific doctors availability on a given date.
// - Requires `user` type, `doctorId`, `date`, and `token` as path variables.
// - First validates the token against the user type.
// - If the token is invalid, returns an error response; otherwise, returns the availability status for the doctor.
@GetMapping
public ResponseEntity<Map<String, Object>> getDoctor(
@RequestParam(required = false) String name,
@RequestParam(required = false) String time,
@RequestParam(required = false) String specialty) {
return ResponseEntity.ok(service.filterDoctor(name, specialty, time));
}
@PostMapping("/{token}")
public ResponseEntity<Map<String, Object>> saveDoctor(@Valid @RequestBody Doctor doctor, @PathVariable String token) {
ResponseEntity<Map<String, Object>> validation = service.validateToken(token, "admin");
if (!validation.getStatusCode().is2xxSuccessful()) {
return validation;
}
int result = doctorService.saveDoctor(doctor);
return switch (result) {
case -1 -> ResponseEntity.badRequest().body(Map.of("message", "Doctor email already exists"));
case 0 -> ResponseEntity.internalServerError().body(Map.of("message", "Failed to save doctor"));
case 1 -> ResponseEntity.status(HttpStatus.CREATED).body(Map.of("message", "Doctor saved successfully"));
default -> ResponseEntity.internalServerError().build();
};
}
// 4. Define the `getDoctor` Method:
// - Handles HTTP GET requests to retrieve a list of all doctors.
// - Returns the list within a response map under the key `"doctors"` with HTTP 200 OK status.
@PostMapping("/login")
public ResponseEntity<Map<String, Object>> doctorLogin(@Valid @RequestBody Login login) {
return doctorService.validateDoctor(login);
}
@PutMapping("/{token}")
public ResponseEntity<Map<String, Object>> updateDoctor(@Valid @RequestBody Doctor doctor, @PathVariable String token) {
ResponseEntity<Map<String, Object>> validation = service.validateToken(token, "admin");
if (!validation.getStatusCode().is2xxSuccessful()) {
return validation;
}
// 5. Define the `saveDoctor` Method:
// - Handles HTTP POST requests to register a new doctor.
// - Accepts a validated `Doctor` object in the request body and a token for authorization.
// - Validates the token for the `"admin"` role before proceeding.
// - If the doctor already exists, returns a conflict response; otherwise, adds the doctor and returns a success message.
int result = doctorService.updateDoctor(doctor);
return switch (result) {
case -1 -> ResponseEntity.badRequest().body(Map.of("message", "Doctor not found"));
case 0 -> ResponseEntity.internalServerError().body(Map.of("message", "Failed to save doctor"));
case 1 -> ResponseEntity.ok(Map.of("message", "Doctor updated successfully"));
default -> ResponseEntity.internalServerError().build();
};
}
@DeleteMapping("/{id}/{token}")
public ResponseEntity<Map<String, Object>> deleteDoctor(@PathVariable long id, @PathVariable String token) {
ResponseEntity<Map<String, Object>> validation = service.validateToken(token, "admin");
if (!validation.getStatusCode().is2xxSuccessful()) {
return validation;
}
int result = doctorService.deleteDoctor(id);
return switch (result) {
case -1 -> ResponseEntity.badRequest().body(Map.of("message", "Doctor not found"));
case 0 -> ResponseEntity.internalServerError().body(Map.of("message", "Failed to delete doctor"));
case 1 -> ResponseEntity.ok(Map.of("message", "Doctor deleted successfully"));
default -> ResponseEntity.internalServerError().build();
};
}
// 6. Define the `doctorLogin` Method:
// - Handles HTTP POST requests for doctor login.
// - Accepts a validated `Login` DTO containing credentials.
// - Delegates authentication to the `DoctorService` and returns login status and token information.
// 7. Define the `updateDoctor` Method:
// - Handles HTTP PUT requests to update an existing doctor's information.
// - Accepts a validated `Doctor` object and a token for authorization.
// - Token must belong to an `"admin"`.
// - If the doctor exists, updates the record and returns success; otherwise, returns not found or error messages.
// 8. Define the `deleteDoctor` Method:
// - Handles HTTP DELETE requests to remove a doctor by ID.
// - Requires both doctor ID and an admin token as path variables.
// - If the doctor exists, deletes the record and returns a success message; otherwise, responds with a not found or error message.
// 9. Define the `filter` Method:
// - Handles HTTP GET requests to filter doctors based on name, time, and specialty.
// - Accepts `name`, `time`, and `speciality` as path variables.
// - Calls the shared `Service` to perform filtering logic and returns matching doctors in the response.
}
@GetMapping("/filter/{name}/{time}/{specialty}")
public ResponseEntity<Map<String, Object>> filter(@PathVariable String name, @PathVariable String time, @PathVariable String specialty) {
return ResponseEntity.ok(service.filterDoctor(name, specialty, time));
}
}

View File

@@ -1,52 +1,80 @@
package com.project.back_end.controllers;
import com.project.back_end.DTO.Login;
import com.project.back_end.models.Patient;
import com.project.back_end.services.PatientService;
import com.project.back_end.services.Service;
import com.project.back_end.services.TokenService;
import jakarta.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/patient")
public class PatientController {
// 1. Set Up the Controller Class:
// - Annotate the class with `@RestController` to define it as a REST API controller for patient-related operations.
// - Use `@RequestMapping("/patient")` to prefix all endpoints with `/patient`, grouping all patient functionalities under a common route.
private static final Logger log = LoggerFactory.getLogger(PatientController.class);
private final PatientService patientService;
private final Service service;
private final TokenService tokenService;
public PatientController(PatientService patientService, Service service, TokenService tokenService) {
this.patientService = patientService;
this.service = service;
this.tokenService = tokenService;
}
// 2. Autowire Dependencies:
// - Inject `PatientService` to handle patient-specific logic such as creation, retrieval, and appointments.
// - Inject the shared `Service` class for tasks like token validation and login authentication.
@GetMapping("/{token}")
public ResponseEntity<Map<String, Object>> getPatient(@PathVariable String token) {
return patientService.getPatientDetails(token);
}
@PostMapping
public ResponseEntity<Map<String, Object>> createPatient(@Valid @RequestBody Patient patient) {
try {
boolean validated = service.validatePatient(patient);
if (!validated) {
return ResponseEntity.ok(Map.of("message", "Patient with email id or phone no already exist"));
}
int result = patientService.createPatient(patient);
return switch (result) {
case 0 -> ResponseEntity.internalServerError().body(Map.of("success", false, "message", "Internal server error"));
case 1 -> ResponseEntity.status(HttpStatus.CREATED).body(Map.of("success", false, "message", "Signup successful"));
default -> ResponseEntity.internalServerError().body(Map.of("success", false, "message", "Unknown result"));
};
} catch (Exception e) {
log.error("Failed to save patient", e);
return ResponseEntity.internalServerError().body(Map.of("success", false, "message", "Internal server error"));
}
}
// 3. Define the `getPatient` Method:
// - Handles HTTP GET requests to retrieve patient details using a token.
// - Validates the token for the `"patient"` role using the shared service.
// - If the token is valid, returns patient information; otherwise, returns an appropriate error message.
@PostMapping("/login")
public ResponseEntity<Map<String, Object>> login(@Valid @RequestBody Login login) {
return service.validatePatientLogin(login);
}
@GetMapping("/{id}/{token}")
public ResponseEntity<Map<String, Object>> getPatientAppointment(@PathVariable long id, @PathVariable String token) {
return patientService.getPatientAppointment(id, token);
}
// 4. Define the `createPatient` Method:
// - Handles HTTP POST requests for patient registration.
// - Accepts a validated `Patient` object in the request body.
// - First checks if the patient already exists using the shared service.
// - If validation passes, attempts to create the patient and returns success or error messages based on the outcome.
// 5. Define the `login` Method:
// - Handles HTTP POST requests for patient login.
// - Accepts a `Login` DTO containing email/username and password.
// - Delegates authentication to the `validatePatientLogin` method in the shared service.
// - Returns a response with a token or an error message depending on login success.
// 6. Define the `getPatientAppointment` Method:
// - Handles HTTP GET requests to fetch appointment details for a specific patient.
// - Requires the patient ID, token, and user role as path variables.
// - Validates the token using the shared service.
// - If valid, retrieves the patient's appointment data from `PatientService`; otherwise, returns a validation error.
// 7. Define the `filterPatientAppointment` Method:
// - Handles HTTP GET requests to filter a patient's appointments based on specific conditions.
// - Accepts filtering parameters: `condition`, `name`, and a token.
// - Token must be valid for a `"patient"` role.
// - If valid, delegates filtering logic to the shared service and returns the filtered result.
}
@GetMapping("/{id}/{user}/{token}")
public ResponseEntity<Map<String, Object>> getPatientAppointmentUser(@PathVariable long id, @PathVariable String user, @PathVariable String token) {
boolean validated = tokenService.validateToken(token, user);
if (!validated) {
return ResponseEntity.badRequest().body(Map.of("success", false, "message", "Invalid token"));
}
return patientService.getPatientAppointment(id, token);
}
@GetMapping("/filter/{condition}/{name}/{token}")
public ResponseEntity<Map<String, Object>> filterPatientAppointment(@PathVariable String condition, @PathVariable String name, @PathVariable String token) {
return service.filterPatient(condition.equals("null") ? null : condition, name.equals("null") ? null : name, token);
}
}

View File

@@ -1,33 +1,41 @@
package com.project.back_end.controllers;
import com.project.back_end.models.Prescription;
import com.project.back_end.services.PrescriptionService;
import com.project.back_end.services.Service;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("${api.path}prescription")
public class PrescriptionController {
// 1. Set Up the Controller Class:
// - Annotate the class with `@RestController` to define it as a REST API controller.
// - Use `@RequestMapping("${api.path}prescription")` to set the base path for all prescription-related endpoints.
// - This controller manages creating and retrieving prescriptions tied to appointments.
private final PrescriptionService prescriptionService;
private final Service service;
// 2. Autowire Dependencies:
// - Inject `PrescriptionService` to handle logic related to saving and fetching prescriptions.
// - Inject the shared `Service` class for token validation and role-based access control.
// - Inject `AppointmentService` to update appointment status after a prescription is issued.
public PrescriptionController(PrescriptionService prescriptionService, Service service) {
this.prescriptionService = prescriptionService;
this.service = service;
}
@PostMapping("/{token}")
public ResponseEntity<Map<String, Object>> savePrescription(@Valid @RequestBody Prescription prescription, @PathVariable String token) {
ResponseEntity<Map<String, Object>> validation = service.validateToken(token, "doctor");
if (!validation.getStatusCode().is2xxSuccessful()) {
return validation;
}
return prescriptionService.savePrescription(prescription);
}
// 3. Define the `savePrescription` Method:
// - Handles HTTP POST requests to save a new prescription for a given appointment.
// - Accepts a validated `Prescription` object in the request body and a doctors token as a path variable.
// - Validates the token for the `"doctor"` role.
// - If the token is valid, updates the status of the corresponding appointment to reflect that a prescription has been added.
// - Delegates the saving logic to `PrescriptionService` and returns a response indicating success or failure.
// 4. Define the `getPrescription` Method:
// - Handles HTTP GET requests to retrieve a prescription by its associated appointment ID.
// - Accepts the appointment ID and a doctors token as path variables.
// - Validates the token for the `"doctor"` role using the shared service.
// - If the token is valid, fetches the prescription using the `PrescriptionService`.
// - Returns the prescription details or an appropriate error message if validation fails.
}
@GetMapping("/{appointmentId}/{token}")
public ResponseEntity<Map<String, Object>> getPrescription(@PathVariable long appointmentId, @PathVariable String token) {
ResponseEntity<Map<String, Object>> validation = service.validateToken(token, "doctor");
if (!validation.getStatusCode().is2xxSuccessful()) {
return validation;
}
return prescriptionService.getPrescription(appointmentId);
}
}

View File

@@ -1,14 +1,11 @@
package com.project.back_end.models;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.project.back_end.utils.PasswordUtil;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@Entity
@Table(name = "admins")
public class Admin {
@@ -23,12 +20,14 @@ public class Admin {
@NotNull
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
@Size(min = 256, max = 256)
@Size(min = 3, max = 256)
private String password;
public Admin() {}
public Admin(String username, String password) {
this.username = username;
this.password = hashPassword(password);
this.password = PasswordUtil.hashPassword(password);
}
public String getUsername() {
@@ -44,14 +43,6 @@ public class Admin {
}
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);
}
this.password = PasswordUtil.hashPassword(password);
}
}

View File

@@ -22,12 +22,10 @@ public class Appointment {
@NotNull
@ManyToOne
@JsonIgnore
private Doctor doctor;
@NotNull
@ManyToOne
@JsonIgnore
private Patient patient;
@NotNull

View File

@@ -1,16 +1,13 @@
package com.project.back_end.models;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.project.back_end.utils.PasswordUtil;
import jakarta.persistence.*;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.Set;
@Entity
@@ -21,32 +18,28 @@ public class Doctor {
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotNull
@NotNull (message = "Name must not be empty")
@Size(min = 3, max = 100)
@Column(unique=true)
private String name;
@NotNull
@Email
@NotNull(message = "Email must not be empty")
@Email()
@Column(unique=true)
private String email;
@NotNull
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
@Size(min = 256,max = 256)
@Size(min = 4,max = 256, message = "Password must be at least for characters")
private String password;
@NotNull
@NotNull (message = "Speciality must not be empty")
@Size(min = 3, max = 50)
private String specialty;
@NotNull
@NotNull (message = "Phone must not be empty")
@Pattern(regexp = "^[0-9]{10}$")
private String phone;
@OneToMany(mappedBy = "doctor", fetch = FetchType.LAZY)
private List<Appointment> appointments;
@ElementCollection(fetch = FetchType.EAGER)
@CollectionTable(name = "doctors_available_times", uniqueConstraints = @UniqueConstraint(columnNames = {"element_value", "doctor_id"}))
@Column(name = "available_times")
@@ -58,7 +51,7 @@ public class Doctor {
public Doctor(String name, String email, String password) {
this.name = name;
this.email = email;
this.password = hashPassword(password);
this.password = PasswordUtil.hashPassword(password);
}
public Long getId() {
@@ -77,10 +70,6 @@ public class Doctor {
this.specialty = specialty;
}
public void setPassword(String password) {
this.password = hashPassword(password);
}
public String getEmail() {
return email;
}
@@ -105,12 +94,8 @@ public class Doctor {
this.phone = phone;
}
public List<Appointment> getAppointments() {
return appointments;
}
public void setAppointments(List<Appointment> appointments) {
this.appointments = appointments;
public void setPassword(String password) {
this.password = PasswordUtil.hashPassword(password);
}
public Set<String> getAvailableTimes() {
@@ -120,12 +105,4 @@ public class Doctor {
public void setAvailableTimes(Set<String> availableTimes) {
this.availableTimes = availableTimes;
}
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

@@ -1,15 +1,13 @@
package com.project.back_end.models;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.project.back_end.utils.PasswordUtil;
import jakarta.persistence.*;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.List;
@Entity
@@ -22,7 +20,7 @@ public class Patient {
@NotNull
@Size(min = 3, max = 100)
@Column(unique=true)
@Column
private String name;
@NotNull
@@ -32,20 +30,17 @@ public class Patient {
@NotNull
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
@Size(min = 256,max = 256)
@Size(min = 4, max = 256, message = "Password must be at least 4 characters")
private String password;
@NotNull
@Pattern(regexp = "^[0-9]{10}$")
@Pattern(regexp = "^[0-9]{10}$", message = "Phone number must be 10 numbers")
private String phone;
@NotNull
@Size(max = 255)
private String address;
@OneToMany(mappedBy = "patient")
private List<Appointment> appointments;
public Patient(){}
public Patient(String name, String email, String password, String phone, String address) {
@@ -53,11 +48,7 @@ public class Patient {
this.email = email;
this.phone = phone;
this.address = address;
this.password = hashPassword(password);
}
public void setPassword(String password) {
this.password = hashPassword(password);
this.password = PasswordUtil.hashPassword(password);
}
public String getPassword() {
@@ -80,8 +71,8 @@ public class Patient {
this.address = address;
}
public void setAppointments(List<Appointment> appointments) {
this.appointments = appointments;
public void setPassword(String password) {
this.password = PasswordUtil.hashPassword(password);
}
public Long getId() {
@@ -103,16 +94,4 @@ public class Patient {
public String getAddress() {
return address;
}
public List<Appointment> getAppointments() {
return appointments;
}
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

@@ -1,32 +1,30 @@
package com.project.back_end.models;
import jakarta.persistence.Column;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import org.springframework.data.mongodb.core.mapping.Document;
import static java.util.Objects.requireNonNull;
@Document(collation = "prescriptions")
@Document(collection = "prescriptions")
public class Prescription {
@Id
private String id;
@NotNull
@Size(min = 3, max = 100)
@Size(min = 3, max = 100, message = "Patient name must be at least 3 characters")
private String patientName;
@ManyToOne
private Appointment appointment;
@NotNull(message = "Appointment id must be set")
private Long appointmentId;
@NotNull
@Size(min = 3, max = 100)
@NotNull(message = "Medication cannot be null")
@Size(min = 3, max = 100, message = "Medication has to be more than 3 characters")
private String medication;
@NotNull
@NotNull(message = "Dosage cannot be null")
private String dosage;
@Size(max = 200)
@@ -34,8 +32,8 @@ public class Prescription {
public Prescription() {}
public Prescription(Patient patient, Appointment appointment, String medication, String dosage, String doctorNotes) {
this.appointment = appointment;
public Prescription(Patient patient, Long appointmentId, String medication, String dosage, String doctorNotes) {
this.appointmentId = appointmentId;
this.medication = medication;
this.dosage = dosage;
this.doctorNotes = doctorNotes;
@@ -70,12 +68,12 @@ public class Prescription {
this.doctorNotes = doctorNotes;
}
public Appointment getAppointment() {
return appointment;
public Long getAppointmentId() {
return appointmentId;
}
public void setAppointment(Appointment appointment) {
this.appointment = appointment;
public void setAppointmentId(Long appointmentId) {
this.appointmentId = appointmentId;
}
public String getPatientName() {

View File

@@ -1,7 +1,10 @@
package com.project.back_end.mvc;
import com.project.back_end.services.Service;
import com.project.back_end.services.TokenService;
import org.hibernate.annotations.View;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@@ -19,7 +22,7 @@ public class DashboardController {
if (!tokenService.validateToken(token, "admin")) {
return "redirect:/";
}
return "redirect:admin/adminDashboard";
return "admin/adminDashboard";
}
@GetMapping("/doctorDashboard/{token}")
@@ -27,6 +30,6 @@ public class DashboardController {
if (!tokenService.validateToken(token, "doctor")) {
return "redirect:/";
}
return "redirect:admin/doctorDashboard";
return "doctor/doctorDashboard";
}
}

View File

@@ -16,6 +16,7 @@ public interface AppointmentRepository extends JpaRepository<Appointment, Long>
@Query("""
FROM Appointment a
LEFT JOIN FETCH Doctor d ON a.doctor = d
LEFT JOIN FETCH Patient p on a.patient = p
WHERE d.id = :doctorId
AND a.appointmentTime BETWEEN :start and :end
""")

View File

@@ -4,9 +4,11 @@ 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 jakarta.validation.constraints.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
@@ -14,88 +16,109 @@ import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import static java.util.Objects.requireNonNull;
@Service
public class AppointmentService {
private static final Logger log = LoggerFactory.getLogger(AppointmentService.class);
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) {
public AppointmentService(AppointmentRepository appointmentRepository,
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) {
public int bookAppointment(@Valid @NotNull Appointment appointment) {
try {
requireNonNull(appointment, "Appointment cannot be null");
appointmentRepository.save(appointment);
return 1;
} catch (Exception e) {
log.error(e.getMessage(), 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();
}
public ResponseEntity<Map<String, Object>> updateAppointment(@Valid @NotNull Appointment appointment) {
try {
requireNonNull(appointment, "Appointment cannot be null");
if (appointmentRepository.findById(appointment.getId()).isEmpty()) {
return ResponseEntity.noContent().build();
}
if (service.validateAppointment(appointment) < 1) {
return ResponseEntity.badRequest().build();
}
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()));
log.error(e.getMessage(), e);
return ResponseEntity.ok(Map.of("success", "false", "message", "Internal error"));
}
}
@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();
}
public ResponseEntity<Map<String,Object>> cancelAppointment(long id, @NotNull String token) {
try {
appointmentRepository.delete(appointment);
requireNonNull(token, "Token cannot be null");
if (!tokenService.validateToken(token, "DOCTOR")) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
Optional<Appointment> found = appointmentRepository.findById(id);
if (found.isEmpty()) {
return ResponseEntity.badRequest().body(Map.of("success", "false", "message", "Appointment not found"));
}
appointmentRepository.delete(found.get());
return ResponseEntity.ok(Map.of("success", "true", "message", "Removed successfully!"));
} catch (Exception e) {
return ResponseEntity.ok(Map.of("success", "false", "message", e.getMessage()));
log.error(e.getMessage(), e);
return ResponseEntity.internalServerError().body(Map.of("success", "false", "message", "Internal error"));
}
}
@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());
}
public Map<String, Object> getAppointment(String pname, @NotNull LocalDate date, @NotNull String token) {
try {
requireNonNull(date, "Date cannot be null");
requireNonNull(token, "Token cannot be null");
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();
}
Long doctorId = found.get().getId();
return Map.of("appointments", appointments);
List<Appointment> appointments;
if (pname !=null) {
appointments = appointmentRepository.findByDoctorIdAndPatient_NameContainingIgnoreCaseAndAppointmentTimeBetween(
doctorId, pname, date.atStartOfDay(), date.plusDays(1).atStartOfDay());
} else {
appointments = appointmentRepository.findByDoctorIdAndAppointmentTimeBetween(
doctorId, date.atStartOfDay(), date.plusDays(1).atStartOfDay());
}
return Map.of("success", "true", "appointments", appointments);
} catch (Exception e) {
log.error(e.getMessage(), e);
return Map.of("success", "false", "message", "Internal error");
}
}
}

View File

@@ -7,6 +7,9 @@ import com.project.back_end.repo.AppointmentRepository;
import com.project.back_end.repo.DoctorRepository;
import jakarta.transaction.Transactional;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
@@ -14,15 +17,16 @@ 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;
import static java.util.Objects.requireNonNull;
@Service
public class DoctorService {
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm");
private static final Logger log = LoggerFactory.getLogger(DoctorService.class);
private final DoctorRepository doctorRepository;
private final AppointmentRepository appointmentRepository;
@@ -35,33 +39,38 @@ public class DoctorService {
}
@Transactional
public List<String> getDoctorAvailability(Long doctorId, LocalDate date) {
public List<String> getDoctorAvailability(long doctorId, @NotNull LocalDate date) {
requireNonNull(date, "Date cannot be null");
Doctor doctor = doctorRepository.findById(doctorId).orElseThrow(() -> new IllegalArgumentException("Doctor not found"));
return getAvailableTimes(doctor, date);
}
@Transactional
public int saveDoctor(@Valid Doctor doctor) {
if (doctorRepository.findByEmail(doctor.getEmail()).isPresent()) {
return -1;
}
public int saveDoctor(@Valid @NotNull Doctor doctor) {
try {
requireNonNull(doctor, "Doctor cannot be null");
if (doctorRepository.findByEmail(doctor.getEmail()).isPresent()) {
return -1;
}
doctorRepository.save(doctor);
return 1;
} catch (Exception e) {
log.error(e.getMessage(), e);
return 0;
}
}
@Transactional
public int updateDoctor(@Valid Doctor doctor) {
if (doctorRepository.findById(doctor.getId()).isEmpty()) {
return -1;
}
public int updateDoctor(@Valid @NotNull Doctor doctor) {
try {
requireNonNull(doctor, "Doctor cannot be null");
if (doctorRepository.findById(doctor.getId()).isEmpty()) {
return -1;
}
doctorRepository.save(doctor);
return 1;
} catch (Exception e) {
log.error(e.getMessage(), e);
return 0;
}
}
@@ -72,13 +81,14 @@ public class DoctorService {
}
@Transactional
public int deleteDoctor(Doctor doctor) {
if (doctorRepository.findById(doctor.getId()).isEmpty()) {
return -1;
}
public int deleteDoctor(long id) {
try {
appointmentRepository.deleteAllByDoctorId(doctor.getId());
doctorRepository.delete(doctor);
Optional<Doctor> found = doctorRepository.findById(id);
if (found.isEmpty()) {
return -1;
}
appointmentRepository.deleteAllByDoctorId(id);
doctorRepository.delete(found.get());
return 1;
} catch (Exception e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
@@ -87,7 +97,7 @@ public class DoctorService {
}
@Transactional
public ResponseEntity<Map<String,String>> validateDoctor(Login login) {
public ResponseEntity<Map<String,Object>> validateDoctor(@Valid @NotNull Login login) {
Optional<Doctor> found = doctorRepository.findByEmail(login.getIdentifier());
if (found.isEmpty()) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
@@ -104,12 +114,16 @@ public class DoctorService {
}
@Transactional
public List<Doctor> findDoctorByName(String name) {
return doctorRepository.findByNameLike(name);
public List<Doctor> findDoctorByName(@NotNull String name) {
return doctorRepository.findByNameLike(requireNonNull(name, "Name cannot be null"));
}
@Transactional
public Map<String, Object> filterDoctorsByNameSpecialityAndTime(String name, String specialty, String amOrPm) {
public Map<String, Object> filterDoctorsByNameSpecialityAndTime(@NotNull String name, @NotNull String specialty, @NotNull String amOrPm) {
requireNonNull(name, "Name cannot be null");
requireNonNull(specialty, "Speciality cannot be null");
requireNonNull(amOrPm, "Time cannot be null");
if (!"AM".equalsIgnoreCase(amOrPm) && !"PM".equalsIgnoreCase(amOrPm)) {
throw new IllegalArgumentException("AM/PM only accepted");
}
@@ -121,10 +135,12 @@ public class DoctorService {
}
@Transactional
public Map<String, Object> filterDoctorByTime(String amOrPm) {
public Map<String, Object> filterDoctorByTime(@NotNull String amOrPm) {
requireNonNull(amOrPm, "Time cannot be null");
if (!"AM".equalsIgnoreCase(amOrPm) && !"PM".equalsIgnoreCase(amOrPm)) {
throw new IllegalArgumentException("AM/PM only accepted");
}
List<Doctor> doctors = doctorRepository.findAll()
.stream()
.filter(d -> d.getAvailableTimes().stream().anyMatch(t -> matchesAMPM(t, amOrPm)))
@@ -133,10 +149,14 @@ public class DoctorService {
}
@Transactional
public Map<String, Object> filterDoctorByNameAndTime(String name, String amOrPm) {
public Map<String, Object> filterDoctorByNameAndTime(@NotNull String name, @NotNull String amOrPm) {
requireNonNull(name, "Name cannot be null");
requireNonNull(amOrPm, "Time cannot be null");
if (!"AM".equalsIgnoreCase(amOrPm) && !"PM".equalsIgnoreCase(amOrPm)) {
throw new IllegalArgumentException("AM/PM only accepted");
}
List<Doctor> doctors = doctorRepository.findByNameLike(name)
.stream()
.filter(d -> d.getAvailableTimes().stream().anyMatch(t -> matchesAMPM(t, amOrPm)))
@@ -145,13 +165,19 @@ public class DoctorService {
}
@Transactional
public Map<String, Object> filterDoctorByNameAndSpeciality(String name, String speciality) {
public Map<String, Object> filterDoctorByNameAndSpeciality(@NotNull String name, @NotNull String speciality) {
requireNonNull(name, "Name cannot be null");
requireNonNull(speciality, "Speciality cannot be null");
List<Doctor> doctors = doctorRepository.findByNameContainingIgnoreCaseAndSpecialtyIgnoreCase(name,speciality);
return Map.of("doctors", doctors);
}
@Transactional
public Map<String, Object> filterDoctorByTimeAndSpeciality(String speciality, String amOrPm) {
public Map<String, Object> filterDoctorByTimeAndSpeciality(@NotNull String speciality, @NotNull String amOrPm) {
requireNonNull(speciality, "Speciality cannot be null");
requireNonNull(amOrPm, "Time cannot be null");
if (!"AM".equalsIgnoreCase(amOrPm) && !"PM".equalsIgnoreCase(amOrPm)) {
throw new IllegalArgumentException("AM/PM only accepted");
}
@@ -163,22 +189,12 @@ public class DoctorService {
}
@Transactional
public Map<String, Object> filterDoctorBySpeciality(String speciality) {
public Map<String, Object> filterDoctorBySpeciality(@NotNull String speciality) {
requireNonNull(speciality, "Speciality cannot be null");
List<Doctor> doctors = doctorRepository.findBySpecialtyIgnoreCase(speciality);
return Map.of("doctors", doctors);
}
@Transactional
public List<Doctor> filterDoctorsByTime(String amOrPm) {
if (!"AM".equalsIgnoreCase(amOrPm) && !"PM".equalsIgnoreCase(amOrPm)) {
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()) {

View File

@@ -7,6 +7,8 @@ import com.project.back_end.repo.AppointmentRepository;
import com.project.back_end.repo.PatientRepository;
import jakarta.transaction.Transactional;
import jakarta.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
@@ -19,6 +21,7 @@ import java.util.Optional;
@Service
public class PatientService {
private static final Logger log = LoggerFactory.getLogger(PatientService.class);
private final PatientRepository patientRepository;
private final AppointmentRepository appointmentRepository;
private final TokenService tokenService;
@@ -35,6 +38,7 @@ public class PatientService {
patientRepository.save(patient);
return 1;
} catch (Exception e) {
log.error("Failed to create patient", e);
return 0;
}
}
@@ -42,7 +46,7 @@ public class PatientService {
@Transactional
public ResponseEntity<Map<String, Object>> getPatientAppointment(Long patientId, String token) {
try {
if (!tokenService.validateToken(token, "PATIENT")) {
if (!tokenService.validateToken(token, "patient")) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
@@ -57,8 +61,6 @@ public class PatientService {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
List<AppointmentDTO> appointments = appointmentRepository.findByPatientId(patientId)
.stream()
.map(AppointmentDTO::new)
@@ -71,8 +73,8 @@ public class PatientService {
@Transactional
public ResponseEntity<Map<String, Object>> filterByCondition(String condition, Long patientId) {
if (!"PAST".equalsIgnoreCase(condition) && !"PRESENT".equalsIgnoreCase(condition)) {
return ResponseEntity.badRequest().build();
if (!"past".equalsIgnoreCase(condition) && !"future".equalsIgnoreCase(condition)) {
return ResponseEntity.badRequest().body(Map.of("success", false, "message","Condition must be past or future"));
}
try {
List<AppointmentDTO> appointments = appointmentRepository.findByPatientId(patientId)
@@ -101,7 +103,7 @@ public class PatientService {
@Transactional
public ResponseEntity<Map<String, Object>> filterByDoctorAndCondition(String condition, String name, long patientId) {
if (!"PAST".equalsIgnoreCase(condition) && !"PRESENT".equalsIgnoreCase(condition)) {
if (!"past".equalsIgnoreCase(condition) && !"future".equalsIgnoreCase(condition)) {
return ResponseEntity.badRequest().build();
}
try {
@@ -119,7 +121,7 @@ public class PatientService {
@Transactional
public ResponseEntity<Map<String, Object>> getPatientDetails(String token) {
try {
if (!tokenService.validateToken(token, "PATIENT")) {
if (!tokenService.validateToken(token, "patient")) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
@@ -138,9 +140,9 @@ public class PatientService {
}
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());
return switch (condition.toLowerCase()) {
case "past" -> appointment.getAppointmentTime().isBefore(LocalDateTime.now());
case "future" -> appointment.getAppointmentTime().isAfter(LocalDateTime.now());
default -> false;
};
}

View File

@@ -4,6 +4,8 @@ import com.project.back_end.models.Prescription;
import com.project.back_end.repo.PrescriptionRepository;
import jakarta.transaction.Transactional;
import jakarta.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
@@ -14,6 +16,7 @@ import java.util.Map;
@Service
public class PrescriptionService {
private static final Logger log = LoggerFactory.getLogger(PrescriptionService.class);
private final PrescriptionRepository prescriptionRepository;
public PrescriptionService(PrescriptionRepository prescriptionRepository) {
@@ -21,14 +24,15 @@ public class PrescriptionService {
}
@Transactional
public ResponseEntity<Map<String, String>> savePrescription(@Valid Prescription prescription) {
public ResponseEntity<Map<String, Object>> savePrescription(@Valid Prescription prescription) {
try {
if (!prescriptionRepository.findByAppointmentId(prescription.getAppointment().getId()).isEmpty()) {
if (!prescriptionRepository.findByAppointmentId(prescription.getAppointmentId()).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) {
log.error("Failed to save prescription", e);
return ResponseEntity.internalServerError().body(Map.of("message", "Internal Error"));
}
}
@@ -39,6 +43,7 @@ public class PrescriptionService {
List<Prescription> prescriptions = prescriptionRepository.findByAppointmentId(appointmentId);
return ResponseEntity.ok(Map.of("prescriptions", prescriptions));
} catch (Exception e) {
log.error("Failed to get prescription", e);
return ResponseEntity.internalServerError().body(Map.of("message", "Internal Error"));
}
}

View File

@@ -37,28 +37,30 @@ public class Service {
}
@Transactional
public ResponseEntity<Map<String, String>> validateToken(String token, String user) {
if (tokenService.validateToken(token, user)) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
public ResponseEntity<Map<String, Object>> validateToken(String token, String user) {
if (!tokenService.validateToken(token, user)) {
return ResponseEntity
.status(HttpStatus.UNAUTHORIZED)
.body(Map.of("success", false, "message", "User is not authorized!"));
}
return ResponseEntity.ok().body(Map.of("message", "User is valid"));
return ResponseEntity.ok().body(Map.of("success", false, "message", "User is valid"));
}
@Transactional
public ResponseEntity<Map<String, String>> validateAdmin(Admin receivedAdmin) {
public ResponseEntity<Map<String, String>> validateAdminLogin(Login login) {
try {
Optional<Admin> found = adminRepository.findByUsername(receivedAdmin.getUsername());
Optional<Admin> found = adminRepository.findByUsername(login.getIdentifier());
if (found.isEmpty()) {
return ResponseEntity.noContent().build();
}
Admin admin = found.get();
if (!receivedAdmin.getPassword().equals(admin.getPassword())) {
if (!login.getPassword().equals(admin.getPassword())) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
String token = tokenService.generateToken(receivedAdmin.getUsername());
String token = tokenService.generateToken(login.getIdentifier());
return ResponseEntity.ok(Map.of("token", token));
} catch (Exception e) {
@@ -105,7 +107,7 @@ public class Service {
}
@Transactional
public ResponseEntity<Map<String, String>> validatePatientLogin(Login login) {
public ResponseEntity<Map<String, Object>> validatePatientLogin(Login login) {
try {
Optional<Patient> found = patientRepository.findByEmail(login.getIdentifier());
if (found.isEmpty()) {
@@ -148,7 +150,7 @@ public class Service {
}
} catch (Exception e) {
return ResponseEntity.internalServerError().build();
return ResponseEntity.internalServerError().body(Map.of("success", false, "message", "Internal Server Error"));
}
}
}

View File

@@ -6,6 +6,8 @@ import com.project.back_end.repo.PatientRepository;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@@ -17,6 +19,7 @@ import java.util.Date;
@Component
public class TokenService {
private static final Logger log = LoggerFactory.getLogger(TokenService.class);
private final AdminRepository adminRepository;
private final DoctorRepository doctorRepository;
private final PatientRepository patientRepository;
@@ -47,7 +50,8 @@ public class TokenService {
try {
return jwtParser.parseSignedClaims(token).getPayload().getSubject();
} catch (Exception e) {
return null;
log.error("Failed to parse token claims", e);
return null;
}
}
@@ -55,15 +59,20 @@ public class TokenService {
try {
String identifier = extractIdentifier(token);
if (identifier == null) {
log.error("Failed to extract identifier from token {}", token);
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;
default -> {
log.error("Role {} unknown", role);
yield false;
}
};
} catch (Exception e) {
log.error("Failed to validate token.", e);
return false;
}
}

View File

@@ -0,0 +1,18 @@
package com.project.back_end.utils;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HexFormat;
public class PasswordUtil {
private PasswordUtil(){}
public static String hashPassword(String rawPassword) {
try {
return HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256").digest(rawPassword.getBytes()));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -25,9 +25,8 @@ h2 {
.main-content {
flex-grow: 1;
padding: 40px;
display: flex;
text-align: center;
background-image: url("index.png");
background-image: url("/assets/images/defineRole/index.png");
background-size: cover;
background-position: center;
background-repeat: no-repeat;

View File

@@ -18,9 +18,8 @@ html, body {
.main-content {
flex-grow: 1;
padding: 40px;
display: flex;
text-align: center;
background-image: url("index.png");
background-image: url("/assets/images/defineRole/index.png");
background-size: cover;
background-position: center;
background-repeat: no-repeat;

View File

@@ -24,9 +24,10 @@ h2 {
}
.main-content {
flex-grow: 1;
padding: 40px;
text-align: center;
background-image: url("index.png");
background-image: url("/assets/images/defineRole/index.png");
background-size: cover;
background-position: center;
background-repeat: no-repeat;

View File

@@ -11,7 +11,7 @@
<script src="./js/components/footer.js" defer></script>
<link rel="icon" type="image/png" href="./assets/images/logo/logo.png" />
</head>
<body>
<body onload="renderContent()">
<div class="container">
<div class="wrapper">
<div id="header"></div>
@@ -19,14 +19,16 @@
<h2>Select Your Role:</h2>
<button id="adminLogin">Admin</button>
<button id="doctorLogin">Doctor</button>
<button id="patientLogin" onclick="window.location.href='/pages/loggedPatientDashboard.html'">Patient</button>
<button id="patientLogin">Patient</button>
</main>
<div id="footer"></div>
</div>
</div>
<div id="modal" class="modal">
<button id="closeModal">Close</button>
<div id="modal-body"></div>
<div class="modal-content">
<span class="close" id="closeModal">&times;</span>
<div id="modal-body"></div>
</div>
</div>
<script type="module" src="js/services/index.js" defer></script>

View File

@@ -1,17 +1,12 @@
import { openModal } from "../components/modals.js";
import { openModal } from "./components/modals.js";
import { getDoctors, filterDoctors, saveDoctor } from "./services/doctorServices.js";
import { createDoctorCard } from "./components/doctorCard.js";
window.onload = function () {
document.getElementById("searchBar").addEventListener("input", filterDoctorsOnChange);
document.getElementById("filterTime").addEventListener("change", filterDoctorsOnChange);
document.getElementById("filterSpecialty").addEventListener("change", filterDoctorsOnChange);
document.getElementById('addDocBtn').addEventListener('click', () => { openModal('addDoctor'); });
loadDoctorCards();
};
export async function loadDoctorCards() {
async function loadDoctorCards() {
try {
const doctors = await getDoctors();
renderDoctorCards(doctors);
@@ -20,37 +15,30 @@ export async function loadDoctorCards() {
}
}
export async function filterDoctorsOnChange() {
const name = document.getElementById("searchBar").value;
const time = document.getElementById("filterTime").value;
const specialty = document.getElementById("filterSpecialty").value;
const doctors = filterDoctors(name, time, specialty);
renderDoctorCards(doctors);
}
export async function renderDoctorCards(doctors) {
const contentDiv = document.getElementById("content");
contentDiv.innerHTML = "";
for (doctor : doctors) {
const card = createDoctorCard(doctor);
contentDiv.appendChild(card);
}
}
export async function adminAddDoctor() {
window.adminAddDoctor = async function() {
const doctor = {
"name": document.getElementById("doctorName").value,
"email": document.getElementById("doctorEmail").value,
"password": document.getElementById("doctorPassword").value,
"speciality": document.getElementById("specialization").value,
"specialty": document.getElementById("specialization").value,
"phone": document.getElementById("doctorPhone").value,
"availability": document.querySelectorAll('input[name=availability]:checked').map(e => e.value)
"availableTimes": Array.from( document.querySelectorAll('input[name=availability]:checked').values()).map(input => input.value)
}
const token = localStorage.getItem("TOKEN");
const token = localStorage.getItem("token");
if (token == 'undefined') {
throw Error("No Authentication token found!");
}
saveDoctor(doctor, token);
try {
const { success, message } = await saveDoctor(doctor, token);
if (success) {
alert(message);
document.getElementById("modal").style.display = "none";
window.location.reload();
}
else alert(message);
} catch(error) {
console.error("Registration failed:", error);
alert("❌ An error occurred while registering up.");
}
}

View File

@@ -21,7 +21,7 @@ export function createDoctorCard(doctor) {
email.textContent = doctor.email;
const availability = document.createElement("h3");
availability.textContent = doctor.availability.join(", ");
availability.textContent = doctor.availableTimes.join(", ");
infoDiv.appendChild(name);
infoDiv.appendChild(specialization);
@@ -35,8 +35,8 @@ export function createDoctorCard(doctor) {
const removeBtn = document.createElement("button");
removeBtn.textContent = "Delete";
removeBtn.addEventListener("click", async () => {
if (confirm("Arey you sure?") == true) {
const token = localStorage.getItem("TOKEN");
if (confirm("Are you sure?") == true) {
const token = localStorage.getItem("token");
const result = await deleteDoctor(doctor.id, token);
if (result.success) {
card.remove();
@@ -57,7 +57,7 @@ export function createDoctorCard(doctor) {
const bookNow = document.createElement("button");
bookNow.textContent = "Book Now";
bookNow.addEventListener("click", async (e) => {
const token = localStorage.getItem("TOKEN");
const token = localStorage.getItem("token");
const patientData = await getPatientData(token);
showBookingOverlay(e, doctor, patientData);
});

View File

@@ -14,7 +14,7 @@ function renderHeader() {
}
const role = localStorage.getItem("userRole");
const token = localStorage.getItem("TOKEN");
const token = localStorage.getItem("token");
let headerContent = `<header class="header">
<div class="logo-section">
@@ -30,7 +30,7 @@ function renderHeader() {
return;
} else if (role === "admin") {
headerContent += `
<button id="addDocBtn" class="adminBtn" onclick="openModal('addDoctor')">Add Doctor</button>
<button id="addDocBtn" class="adminBtn">Add Doctor</button>
<a href="#" onclick="logout()">Logout</a>`;
} else if (role === "doctor") {
headerContent += `
@@ -67,12 +67,15 @@ function attachHeaderButtonListeners() {
function logout() {
localStorage.removeItem("userRole");
localStorage.removeItem("TOKEN");
localStorage.removeItem("token");
window.location.href = "/";
}
function logoutPatient() {
localStorage.removeItem("userRole");
selectRole('patient');
window.location.href='/pages/loggedPatientDashboard.html';
}
localStorage.removeItem("token");
setRole('patient')
window.location.href='/pages/patientDashboard.html';
}
renderHeader();

View File

@@ -6,22 +6,21 @@ export function openModal(type) {
<h2>Add Doctor</h2>
<input type="text" id="doctorName" placeholder="Doctor Name" class="input-field">
<select id="specialization" class="input-field select-dropdown">
<option value="">Specialization</option>
<option value="cardiologist">Cardiologist</option>
<option value="dermatologist">Dermatologist</option>
<option value="neurologist">Neurologist</option>
<option value="pediatrician">Pediatrician</option>
<option value="orthopedic">Orthopedic</option>
<option value="gynecologist">Gynecologist</option>
<option value="psychiatrist">Psychiatrist</option>
<option value="dentist">Dentist</option>
<option value="ophthalmologist">Ophthalmologist</option>
<option value="ent">ENT Specialist</option>
<option value="urologist">Urologist</option>
<option value="oncologist">Oncologist</option>
<option value="gastroenterologist">Gastroenterologist</option>
<option value="general">General Physician</option>
<option value="">Specialization</option>
<option value="cardiologist">Cardiologist</option>
<option value="dermatologist">Dermatologist</option>
<option value="neurologist">Neurologist</option>
<option value="pediatrician">Pediatrician</option>
<option value="orthopedic">Orthopedic</option>
<option value="gynecologist">Gynecologist</option>
<option value="psychiatrist">Psychiatrist</option>
<option value="dentist">Dentist</option>
<option value="ophthalmologist">Ophthalmologist</option>
<option value="ent">ENT Specialist</option>
<option value="urologist">Urologist</option>
<option value="oncologist">Oncologist</option>
<option value="gastroenterologist">Gastroenterologist</option>
<option value="general">General Physician</option>
</select>
<input type="email" id="doctorEmail" placeholder="Email" class="input-field">
<input type="password" id="doctorPassword" placeholder="Password" class="input-field">
@@ -98,4 +97,4 @@ export function openModal(type) {
if (type === 'doctorLogin') {
document.getElementById('doctorLoginBtn').addEventListener('click', doctorLoginHandler);
}
}
}

View File

@@ -20,4 +20,4 @@ export function createPatientRow(patient, appointmentId, doctorId) {
});
return tr;
}
}

View File

@@ -2,23 +2,26 @@ import { getAllAppointments } from "./services/appointmentRecordService.js";
import { createPatientRow } from "./components/patientRows.js";
const patientTable = document.getElementById("patientTableBody");
const token = localStorage.getItem("TOKEN");
const token = localStorage.getItem("token");
var selectedDate = new Date();
var selectedDate = new Date().toISOString().split('T')[0]; //YYYY-MM-DD
var patientName = null;
window.onload = function () {
document.getElementById("searchBar").addEventListener("input", filterPatientsOnChange);
document.getElementById('todayButton').addEventListener('click', () => {
selectedDate = new Date();
selectedDate = new Date().toISOString().split('T')[0]; //YYYY-MM-DD
document.getElementById('datePicker').value = selectedDate;
loadAppointments();
});
document.getElementById('datePicker').addEventListener('click', () => {
document.getElementById('datePicker').addEventListener('change', () => {
selectedDate = document.getElementById('datePicker').value;
loadAppointments();
});
}
loadAppointments();
};
export async function filterPatientsOnChange() {
patientName = document.getElementById("searchBar").value.trim();
@@ -28,75 +31,22 @@ export async function filterPatientsOnChange() {
loadAppointments();
}
export async loadAppointments() {
export async function loadAppointments() {
try {
const appointments = await getAllAppointments(selectedDate, patientName, token);
console.log(appointments);
if (appointments.length == 0) {
patientTable.innerHTML = "<tr><td>No Appointments found for today</td></tr>”;
patientTable.innerHTML = "<tr><td colspan=5>No Patients found for the selected day</td></tr>";
return;
}
patientTable.innerHTML = "";
for (appointment : appointments) {
const row = createPatientRow(appointment.patient, appointment.id, appointment.doctorId);
patientTable.appendChild(row);
}
appointments.forEach(appointment => {
const row = createPatientRow(appointment.patient, appointment.id, appointment.doctorId);
patientTable.appendChild(row);
});
} catch (error) {
console.error("Error: ", error);
patientTable.innerHTML = "<tr><td>Failed to load appointments</td></tr>;
patientTable.innerHTML = "<tr><td colspan=5>Error loading patients. Try again later.</td></tr>";
}
}
/*
Import getAllAppointments to fetch appointments from the backend
Import createPatientRow to generate a table row for each patient appointment
Get the table body where patient rows will be added
Initialize selectedDate with today's date in 'YYYY-MM-DD' format
Get the saved token from localStorage (used for authenticated API calls)
Initialize patientName to null (used for filtering by name)
Add an 'input' event listener to the search bar
On each keystroke:
- Trim and check the input value
- If not empty, use it as the patientName for filtering
- Else, reset patientName to "null" (as expected by backend)
- Reload the appointments list with the updated filter
Add a click listener to the "Today" button
When clicked:
- Set selectedDate to today's date
- Update the date picker UI to match
- Reload the appointments for today
Add a change event listener to the date picker
When the date changes:
- Update selectedDate with the new value
- Reload the appointments for that specific date
Function: loadAppointments
Purpose: Fetch and display appointments based on selected date and optional patient name
Step 1: Call getAllAppointments with selectedDate, patientName, and token
Step 2: Clear the table body content before rendering new rows
Step 3: If no appointments are returned:
- Display a message row: "No Appointments found for today."
Step 4: If appointments exist:
- Loop through each appointment and construct a 'patient' object with id, name, phone, and email
- Call createPatientRow to generate a table row for the appointment
- Append each row to the table body
Step 5: Catch and handle any errors during fetch:
- Show a message row: "Error loading appointments. Try again later."
When the page is fully loaded (DOMContentLoaded):
- Call renderContent() (assumes it sets up the UI layout)
- Call loadAppointments() to display today's appointments by default
*/
}

View File

@@ -4,9 +4,11 @@ import { createDoctorCard } from './components/doctorCard.js';
import { filterDoctors } from './services/doctorServices.js';
import { bookAppointment } from './services/appointmentRecordService.js';
document.addEventListener("DOMContentLoaded", () => {
loadDoctorCards();
loadDoctorCards();
document.getElementById("searchBar").addEventListener("input", filterDoctorsOnChange);
document.getElementById("filterTime").addEventListener("change", filterDoctorsOnChange);
document.getElementById("filterSpecialty").addEventListener("change", filterDoctorsOnChange);
});
function loadDoctorCards() {
@@ -84,16 +86,8 @@ export function showBookingOverlay(e, doctor, patient) {
});
}
// Filter Input
document.getElementById("searchBar").addEventListener("input", filterDoctorsOnChange);
document.getElementById("filterTime").addEventListener("change", filterDoctorsOnChange);
document.getElementById("filterSpecialty").addEventListener("change", filterDoctorsOnChange);
function filterDoctorsOnChange() {
const searchBar = document.getElementById("searchBar").value.trim();
const filterTime = document.getElementById("filterTime").value;
const filterSpecialty = document.getElementById("filterSpecialty").value;
@@ -104,8 +98,7 @@ function filterDoctorsOnChange() {
const specialty = filterSpecialty.length > 0 ? filterSpecialty : null;
filterDoctors(name, time, specialty)
.then(response => {
const doctors = response.doctors;
.then(doctors => {
const contentDiv = document.getElementById("content");
contentDiv.innerHTML = "";
@@ -135,4 +128,4 @@ export function renderDoctorCards(doctors) {
contentDiv.appendChild(card);
});
}
}

View File

@@ -6,7 +6,6 @@ import { filterDoctors } from './services/doctorServices.js';//call the same fun
import { patientSignup, patientLogin } from './services/patientServices.js';
document.addEventListener("DOMContentLoaded", () => {
loadDoctorCards();
});
@@ -27,9 +26,13 @@ document.addEventListener("DOMContentLoaded", () => {
}
})
document.getElementById("searchBar").addEventListener("input", filterDoctorsOnChange);
document.getElementById("filterTime").addEventListener("change", filterDoctorsOnChange);
document.getElementById("filterSpecialty").addEventListener("change", filterDoctorsOnChange);
function loadDoctorCards() {
getDoctors()
.then(doctors => {
const doctors = getDoctors().then(doctors => {
const contentDiv = document.getElementById("content");
contentDiv.innerHTML = "";
@@ -37,16 +40,8 @@ function loadDoctorCards() {
const card = createDoctorCard(doctor);
contentDiv.appendChild(card);
});
})
.catch(error => {
console.error("Failed to load doctors:", error);
});
})
}
// Filter Input
document.getElementById("searchBar").addEventListener("input", filterDoctorsOnChange);
document.getElementById("filterTime").addEventListener("change", filterDoctorsOnChange);
document.getElementById("filterSpecialty").addEventListener("change", filterDoctorsOnChange);
function filterDoctorsOnChange() {
@@ -54,14 +49,11 @@ function filterDoctorsOnChange() {
const filterTime = document.getElementById("filterTime").value;
const filterSpecialty = document.getElementById("filterSpecialty").value;
const name = searchBar.length > 0 ? searchBar : null;
const time = filterTime.length > 0 ? filterTime : null;
const specialty = filterSpecialty.length > 0 ? filterSpecialty : null;
filterDoctors(name, time, specialty)
.then(response => {
const doctors = response.doctors;
filterDoctors(name, time, specialty).then(doctors => {
const contentDiv = document.getElementById("content");
contentDiv.innerHTML = "";
@@ -75,11 +67,8 @@ function filterDoctorsOnChange() {
contentDiv.innerHTML = "<p>No doctors found with the given filters.</p>";
console.log("Nothing");
}
})
.catch(error => {
console.error("Failed to filter doctors:", error);
alert("❌ An error occurred while filtering doctors.");
});
})
}
window.signupPatient = async function () {
@@ -133,4 +122,4 @@ window.loginPatient = async function () {
}
}
}

View File

@@ -3,26 +3,28 @@
function selectRole(role) {
setRole(role);
const token = localStorage.getItem('token');
if (role === "admin") {
if (token) {
if (role === "admin" && token) {
window.location.href = `/adminDashboard/${token}`;
}
} if (role === "patient") {
} else if (role === "loggedPatient" && token) {
window.location.href = "/pages/loggedPatientDashboard.html";
} else if (role === "patient") {
window.location.href = "/pages/patientDashboard.html";
} else if (role === "doctor") {
if (token) {
window.location.href = `/doctorDashboard/${token}`;
} else if (role === "loggedPatient") {
window.location.href = "loggedPatientDashboard.html";
}
} else if (role === "doctor" && token) {
window.location.href = `/doctorDashboard/${token}`;
} else {
window.location.href = "/";
}
}
function renderContent() {
const role = getRole();
if (!role) {
console.error("No role set!")
window.location.href = "/"; // if no role, send to role selection page
return;
}
}
}

View File

@@ -5,12 +5,17 @@ const APPOINTMENT_API = `${API_BASE_URL}/appointments`;
//This is for the doctor to get all the patient Appointments
export async function getAllAppointments(date, patientName, token) {
const response = await fetch(`${APPOINTMENT_API}/${date}/${patientName}/${token}`);
if (!response.ok) {
throw new Error("Failed to fetch appointments");
}
return await response.json();
try {
const response = await fetch(`${APPOINTMENT_API}/${date}/${patientName}/${token}`);
if (!response.ok) {
throw new Error("Failed to fetch appointments");
}
const data = await response.json();
return data.appointments;
} catch (error) {
console.error("Error while fetching appointments:", error);
return [];
}
}
export async function bookAppointment(appointment, token) {
@@ -59,4 +64,4 @@ export async function updateAppointment(appointment, token) {
message: "Network error. Please try again later."
};
}
}
}

View File

@@ -12,7 +12,7 @@ export async function getDoctors() {
if (!response.ok) {
throw new Error(result.message);
}
return response.doctors;
return result.doctors;
} catch (error) {
console.log("Error fetching doctors:", error);
return [];
@@ -21,7 +21,7 @@ export async function getDoctors() {
export async function deleteDoctor(id, token) {
try {
const response = await fetch(`${DOCTOR_API}/${id}/{token}`, {
const response = await fetch(`${DOCTOR_API}/${id}/${token}`, {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' }
});
@@ -54,9 +54,15 @@ export async function saveDoctor(doctor, token) {
}
}
export async function filterDoctors(name ,time ,specialty) {
export async function filterDoctors(name, time, specialty) {
try {
const response = await fetch(`${DOCTOR_API}?name=${name}&time=${time}&specialty=${specialty}`, {
const url = new URL(DOCTOR_API);
if (name) url.searchParams.append("name", name);
if (time) url.searchParams.append("time", time);
if (specialty) url.searchParams.append("specialty", specialty);
const response = await fetch(url, {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
})
@@ -64,7 +70,7 @@ export async function filterDoctors(name ,time ,specialty) {
if (!response.ok) {
throw new Error(result.message);
}
return response.doctors;
return result.doctors;
} catch (error) {
console.error("Error :: filterDoctors :: ", error)
return [];

View File

@@ -19,6 +19,14 @@ window.onload = function () {
openModal('doctorLogin');
});
}
const patientBtn = document.getElementById('patientLogin');
if (patientBtn) {
patientBtn.addEventListener('click', () => {
selectRole('patient');
window.location.href = '/pages/patientDashboard.html';
});
}
}
window.adminLoginHandler = async function() {
@@ -35,7 +43,7 @@ window.adminLoginHandler = async function() {
if (!response.ok) {
throw new Error(result.message);
}
localStorage.setItem("TOKEN", result)
localStorage.setItem("token", result.token)
selectRole('admin');
} catch (error) {
alert("Invalid credentials!");
@@ -56,7 +64,7 @@ window.doctorLoginHandler = async function() {
if (!response.ok) {
throw new Error(result.message);
}
localStorage.setItem("TOKEN", result)
localStorage.setItem("token", result.token)
selectRole('doctor');
} catch (error) {
alert("Invalid credentials!");

View File

@@ -1,5 +1,7 @@
// patientServices
import { API_BASE_URL } from "../config/config.js";
const PATIENT_API = API_BASE_URL + '/patient'

View File

@@ -19,7 +19,7 @@
<div class="wrapper">
<div id="header"></div>
<main class="main-content">
<input type="text" id="searchBar" class="searchBar" placeholder="Search Bar for custom output" />
<input type="text" id="searchBar" class="searchBar" placeholder="Filter by doctor..." />
<div class="filter-wrapper">
<select class="filter-select" id="filterTime">
<option value="">Sort by Time</option>
@@ -44,10 +44,10 @@
<option value="General">General Physician</option>
</select>
</div>
<div id="content"></div>
</main>
<div id="content"></div>
<div id="footer"></div>
</div>
</div>
</body>
</html>
</html>

View File

@@ -22,14 +22,14 @@
<div id="header"></div>
<div class="main-content">
<h2>Patient <span>Appointment</span></h2>
<input type="text" id="searchBar" class="searchBar" placeholder="Search Bar for custom output" />
<div class="filter-wrapper">
<select class="filter-select" id="appointmentFilter">
<option value="allAppointments">All Appointments</option>
<option value="future">Upcoming Appointments</option>
<option value="past">Past Appointments</option>
</select>
</div>
<input type="text" id="searchBar" class="searchBar" placeholder="Filter by doctor.." />
<div class="filter-wrapper">
<select class="filter-select" id="appointmentFilter">
<option value="allAppointments">All Appointments</option>
<option value="future">Upcoming Appointments</option>
<option value="past">Past Appointments</option>
</select>
</div>
<table id="patientTable">
<thead class="table-header">
<tr>
@@ -49,4 +49,4 @@
</div>
</div>
</body>
</html>
</html>

View File

@@ -13,7 +13,6 @@
<script src="../js/util.js" defer></script>
<script src="../js/components/header.js" defer></script>
<script src="../js/components/footer.js" defer></script>
<script type="module" src="../js/components/modals.js"></script>
</head>
<body onload="renderContent()">
@@ -21,7 +20,7 @@
<div class="wrapper">
<div id="header"></div>
<main class="main-content">
<input type="text" id="searchBar" class="searchBar" placeholder="Search Bar for custom output" />
<input type="text" id="searchBar" class="searchBar" placeholder="Filter by doctor..." />
<div class="filter-wrapper">
<select class="filter-select" id="filterTime">
<option value="">Sort by Time</option>

View File

@@ -17,24 +17,44 @@
<div class="wrapper">
<div id="header"></div>
<main class="main-content">
<input type="text" id="searchBar" placeholder="search by doctor name" />
<select id="time">
<option></option>
</select>
<select id="speciality">
<option></option>
</select>
<div id="content"></div>
<input type="text" id="searchBar" class="searchBar" placeholder="search by doctor name" />
<div class="filter-wrapper">
<select id="filterTime" class="filter-select">
<option value="">Time</option>
<option value="AM">Morning</option>
<option value="PM">Afternoon</option>
</select>
<select id="filterSpecialty" class="filter-select">
<option value="">Speciality</option>
<option value="cardiologist">Cardiologist</option>
<option value="dermatologist">Dermatologist</option>
<option value="neurologist">Neurologist</option>
<option value="pediatrician">Pediatrician</option>
<option value="orthopedic">Orthopedic</option>
<option value="gynecologist">Gynecologist</option>
<option value="psychiatrist">Psychiatrist</option>
<option value="dentist">Dentist</option>
<option value="ophthalmologist">Ophthalmologist</option>
<option value="ent">ENT Specialist</option>
<option value="urologist">Urologist</option>
<option value="oncologist">Oncologist</option>
<option value="gastroenterologist">Gastroenterologist</option>
<option value="general">General Physician</option>
</select>
</div>
</main>
<div id="content"></div>
<div id="footer"></div>
</div>
</div>
<div id="modal" class="modal">
<span id="closeModal" class="close">&times;</span>
<div id="modal-body"></div>
<div class="modal-content">
<span class="close" id="closeModal">&times;</span>
<div id="modal-body"></div>
</div>
</div>
<script type="module" src="../js/services/adminDashboard.js" defer></script>
<script type="module" src="../js/components/doctorCard.js" defer></script>
<script type="module" src="/js/adminDashboard.js" defer></script>
<script type="module" src="/js/components/doctorCard.js" defer></script>
</body>
</html>

View File

@@ -6,14 +6,11 @@
<link rel="stylesheet" th:href="@{/assets/css/adminDashboard.css}">
<link rel="stylesheet" th:href="@{/assets/css/doctorDashboard.css}">
<link rel="stylesheet" th:href="@{/assets/css/style.css}">
<link rel="icon" type="image/png" th:href="@{/assets/images/logo/logo.png}" />
<script th:src="@{/js/render.js}" defer></script>
<script th:src="@{/js/util.js}" defer></script>
<script th:src="@{/js/components/header.js}" defer></script>
<script th:src="@{/js/components/footer.js}" defer></script>
<script th:src="@{/js/components/patientRows.js}" defer></script>
<script th:src="@{/js/components/patientServices.js}" defer></script>
<script th:src="@{/js/components/doctorDashboard.js}" defer></script>
<link rel="icon" type="image/png" th:href="@{/assets/images/logo/logo.png}" />
</head>
<body onload="renderContent()">
@@ -21,11 +18,13 @@
<div class="wrapper">
<div id="header"></div>
<div class="main-content">
<input type="text" id="searchBar" class="searchBar" placeholder="Search Bar for custom output" />
<button id="todayButton">Today</button>
<input type="date" id="datePicker" />
<input type="text" id="searchBar" class="searchBar" placeholder="Filter doctor..." />
<div class="filter-wrapper">
<button id="todayButton">Today</button>
<input type="date" id="datePicker" />
</div>
<table id="patientTable">
<thead>
<thead class="table-header">
<tr>
<th>Patient ID</th>
<th>Name</th>
@@ -40,6 +39,10 @@
<div id="footer"></div>
</div>
</div>
<script type="module" th:src="@{/js/services/patientServices.js}" defer></script>
<script type="module" th:src="@{/js/doctorDashboard.js}" defer></script>
<script type="module" th:src="@{/js/components/patientRows.js}" defer></script>
</body>
</html>