Changeset cdcff72 for backend


Ignore:
Timestamp:
09/04/26 19:36:17 (11 days ago)
Author:
MBK <marija.karapandzova@…>
Branches:
master
Children:
99ab276
Parents:
9af201e
Message:

Fix frontend appearance and fix bugs

Location:
backend/src
Files:
6 added
4 deleted
55 edited
12 moved

Legend:

Unmodified
Added
Removed
  • backend/src/main/java/medora/config/AuthorizationInterceptor.java

    r9af201e rcdcff72  
    11package medora.config;
     2
     3import medora.util.SecurityUtil;
     4import org.springframework.stereotype.Component;
     5import org.springframework.web.servlet.HandlerInterceptor;
    26
    37import jakarta.servlet.http.HttpServletRequest;
    48import jakarta.servlet.http.HttpServletResponse;
    5 import medora.util.SecurityUtil;
    6 import org.springframework.stereotype.Component;
    7 import org.springframework.web.servlet.HandlerInterceptor;
    89
    910/**
     
    1819 */
    1920@Component
    20 
    21 
    22 
    23 
    2421public class AuthorizationInterceptor implements HandlerInterceptor {
    2522
  • backend/src/main/java/medora/config/SecurityConfig.java

    r9af201e rcdcff72  
    1414    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    1515        http
    16                 .csrf(csrf -> csrf.disable())
    17                 .authorizeHttpRequests(auth -> auth
    18                         .anyRequest().permitAll()
    19                 );
     16            .csrf(csrf -> csrf.disable())
     17            .authorizeHttpRequests(auth -> auth
     18                .anyRequest().permitAll()
     19            );
    2020
    2121        return http.build();
  • backend/src/main/java/medora/config/WebConfig.java

    r9af201e rcdcff72  
    3232        Path uploadPath = Path.of("uploads").toAbsolutePath().normalize();
    3333        registry
    34                 .addResourceHandler("/uploads/**")
    35                 .addResourceLocations(uploadPath.toUri().toString() + "/");
     34            .addResourceHandler("/uploads/**")
     35            .addResourceLocations(uploadPath.toUri().toString() + "/");
    3636    }
    3737
  • backend/src/main/java/medora/controller/AuthController.java

    r9af201e rcdcff72  
    33import medora.dto.LoginRequest;
    44import medora.service.AuthService;
     5import medora.util.SecurityUtil;
    56import org.slf4j.Logger;
    67import org.slf4j.LoggerFactory;
     
    89import org.springframework.http.ResponseEntity;
    910import org.springframework.web.bind.annotation.*;
     11import jakarta.servlet.http.HttpServletRequest;
    1012
    1113import java.util.Map;
     14import java.util.HashMap;
    1215
    1316@RestController
     
    1922
    2023    private final AuthService authService;
     24    private final SecurityUtil securityUtil;
    2125
    22     public AuthController(AuthService authService) {
     26    public AuthController(AuthService authService, SecurityUtil securityUtil) {
    2327        this.authService = authService;
     28        this.securityUtil = securityUtil;
    2429    }
    2530
     
    6974        return ResponseEntity.ok(Map.of("status", "healthy"));
    7075    }
     76
     77    /**
     78     * Debug endpoint to test token validation
     79     */
     80    @GetMapping("/debug/token")
     81    public ResponseEntity<?> debugToken(HttpServletRequest httpRequest) {
     82        String role = securityUtil.getRoleFromRequest(httpRequest);
     83        String username = securityUtil.getUsernameFromRequest(httpRequest);
     84        Long userId = securityUtil.getUserIdFromRequest(httpRequest);
     85
     86        Map<String, Object> debug = new HashMap<>();
     87        debug.put("role", role);
     88        debug.put("username", username);
     89        debug.put("userId", userId);
     90        debug.put("isValid", role != null);
     91
     92        if (role == null) {
     93            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(debug);
     94        }
     95        return ResponseEntity.ok(debug);
     96    }
     97
     98    /**
     99     * Test endpoint - no auth required, returns test token
     100     */
     101    @GetMapping("/test/generate-token")
     102    public ResponseEntity<?> generateTestToken() {
     103        String testToken = authService.generateTestToken();
     104        Map<String, Object> response = new HashMap<>();
     105        response.put("token", testToken);
     106        response.put("message", "Copy this token and try it");
     107        return ResponseEntity.ok(response);
     108    }
    71109}
  • backend/src/main/java/medora/controller/BillingController.java

    r9af201e rcdcff72  
    244244    @PatchMapping("/{billId}/payment-status")
    245245    public ResponseEntity<?> updatePaymentStatus(@PathVariable Long billId,
    246                                                  @RequestBody UpdateBillingRequest request,
    247                                                  HttpServletRequest httpRequest) {
     246                                                @RequestBody UpdateBillingRequest request,
     247                                                HttpServletRequest httpRequest) {
    248248        try {
    249249            String role = securityUtil.getRoleFromRequest(httpRequest);
     
    344344        if (billing.getMedicalRecord() != null && billing.getMedicalRecord().getPatient() != null) {
    345345            patientName = billing.getMedicalRecord().getPatient().getFirstName() + " " +
    346                     billing.getMedicalRecord().getPatient().getLastName();
     346                         billing.getMedicalRecord().getPatient().getLastName();
    347347        }
    348348        return new BillingDTO(
  • backend/src/main/java/medora/controller/DepartmentController.java

    r9af201e rcdcff72  
    162162    @PutMapping("/{departmentId}")
    163163    public ResponseEntity<?> updateDepartment(@PathVariable Long departmentId,
    164                                               @RequestBody Map<String, String> request,
    165                                               HttpServletRequest httpRequest) {
     164                                             @RequestBody Map<String, String> request,
     165                                             HttpServletRequest httpRequest) {
    166166        try {
    167167            String role = securityUtil.getRoleFromRequest(httpRequest);
  • backend/src/main/java/medora/controller/DiagnosisController.java

    r9af201e rcdcff72  
    11package medora.controller;
    22
     3import medora.dto.DiagnosisDTO;
    34import medora.dto.CreateDiagnosisRequest;
    4 import medora.dto.DiagnosisDTO;
    55import medora.models.domain.Diagnosis;
    66import medora.service.DiagnosisService;
  • backend/src/main/java/medora/controller/DoctorController.java

    r9af201e rcdcff72  
    212212    @PutMapping("/{doctorId}")
    213213    public ResponseEntity<?> updateDoctor(@PathVariable Long doctorId,
    214                                           @RequestBody CreateDoctorRequest request,
    215                                           HttpServletRequest httpRequest) {
     214                                         @RequestBody CreateDoctorRequest request,
     215                                         HttpServletRequest httpRequest) {
    216216        try {
    217217            String role = securityUtil.getRoleFromRequest(httpRequest);
  • backend/src/main/java/medora/controller/LabController.java

    r9af201e rcdcff72  
    125125    @PutMapping("/{testId}")
    126126    public ResponseEntity<?> updateLabTest(@PathVariable Long testId,
    127                                            @RequestBody CreateLabTestRequest request) {
     127                                          @RequestBody CreateLabTestRequest request) {
    128128        try {
    129129            logger.info("Updating lab test with ID: {}", testId);
  • backend/src/main/java/medora/controller/MedicalRecordController.java

    r9af201e rcdcff72  
    11package medora.controller;
    22
    3 import medora.dto.AllergyDTO;
    43import medora.dto.ComprehensiveMedicalRecordDTO;
    54import medora.dto.MedicalRecordDTO;
     5import medora.dto.AllergyDTO;
    66import medora.dto.SymptomDTO;
    77import medora.models.domain.*;
  • backend/src/main/java/medora/controller/MedicalReportController.java

    r9af201e rcdcff72  
    1313import java.util.List;
    1414import java.util.Map;
     15import java.util.stream.Collectors;
    1516
    1617@RestController
  • backend/src/main/java/medora/controller/PatientController.java

    r9af201e rcdcff72  
    207207    @PutMapping("/{patientId}")
    208208    public ResponseEntity<?> updatePatient(@PathVariable Long patientId,
    209                                            @RequestBody CreatePatientRequest request,
    210                                            HttpServletRequest httpRequest) {
     209                                          @RequestBody CreatePatientRequest request,
     210                                          HttpServletRequest httpRequest) {
    211211        try {
    212212            String role = securityUtil.getRoleFromRequest(httpRequest);
  • backend/src/main/java/medora/controller/PrescriptionController.java

    r9af201e rcdcff72  
    11package medora.controller;
    22
     3import medora.dto.PrescriptionDTO;
    34import medora.dto.CreatePrescriptionRequest;
    4 import medora.dto.PrescriptionDTO;
    55import medora.models.domain.PrescriptionMedicalRecord;
    66import medora.service.PrescriptionService;
  • backend/src/main/java/medora/controller/ProcedureController.java

    r9af201e rcdcff72  
    334334    private Map<String, Object> convertResultToDTO(ProcedureResults result) {
    335335        return Map.of(
    336                 "resultId", result.getResultId(),
    337                 "procedureId", result.getProcedure().getProcedureId(),
    338                 "procedureType", result.getProcedure().getProcedureType(),
    339                 "resultDescription", result.getResultDescription() != null ? result.getResultDescription() : "",
    340                 "resultDate", result.getResultDate()
     336            "resultId", result.getResultId(),
     337            "procedureId", result.getProcedure().getProcedureId(),
     338            "procedureType", result.getProcedure().getProcedureType(),
     339            "resultDescription", result.getResultDescription() != null ? result.getResultDescription() : "",
     340            "resultDate", result.getResultDate()
    341341        );
    342342    }
    343343}
    344 
  • backend/src/main/java/medora/dto/BillingDetailDTO.java

    r9af201e rcdcff72  
    2121
    2222    public BillingDetailDTO(Long billId, Long patientId, String patientName, String patientEmbg, String patientPhone,
    23                             BigDecimal totalCost, String paymentStatus, LocalDate paymentDate,
    24                             LocalDate billDate, List<BillingItemDTO> procedures, List<BillingItemDTO> labTests) {
     23                           BigDecimal totalCost, String paymentStatus, LocalDate paymentDate,
     24                           LocalDate billDate, List<BillingItemDTO> procedures, List<BillingItemDTO> labTests) {
    2525        this.billId = billId;
    2626        this.patientId = patientId;
  • backend/src/main/java/medora/dto/CreateDoctorRequest.java

    r9af201e rcdcff72  
    1010@NoArgsConstructor
    1111@AllArgsConstructor
    12 public class CreateDoctorRequest {
     12public class
     13CreateDoctorRequest {
    1314    private String firstName;
    1415    private String lastName;
  • backend/src/main/java/medora/dto/LabResultDTO.java

    r9af201e rcdcff72  
    55import lombok.NoArgsConstructor;
    66import lombok.Setter;
    7 
    87import java.time.LocalDate;
    98
  • backend/src/main/java/medora/dto/MedicalRecordDTO.java

    r9af201e rcdcff72  
    55import lombok.NoArgsConstructor;
    66import lombok.Setter;
     7
     8import java.time.LocalDate;
    79
    810@Getter
  • backend/src/main/java/medora/dto/PrescriptionDTO.java

    r9af201e rcdcff72  
    55import lombok.NoArgsConstructor;
    66import lombok.Setter;
     7
     8import java.time.LocalDate;
    79
    810@Getter
  • backend/src/main/java/medora/dto/ProcedureRequestDTO.java

    r9af201e rcdcff72  
    55import lombok.NoArgsConstructor;
    66import lombok.Setter;
    7 
    87import java.time.LocalDate;
    98
  • backend/src/main/java/medora/dto/ProcedureResultDTO.java

    r9af201e rcdcff72  
    55import lombok.NoArgsConstructor;
    66import lombok.Setter;
    7 
    87import java.time.LocalDate;
    98
  • backend/src/main/java/medora/dto/RequestLabTestRequest.java

    r9af201e rcdcff72  
    55import lombok.NoArgsConstructor;
    66import lombok.Setter;
    7 
    87import java.time.LocalDate;
    98
  • backend/src/main/java/medora/dto/SubmitLabResultRequest.java

    r9af201e rcdcff72  
    55import lombok.NoArgsConstructor;
    66import lombok.Setter;
    7 
    87import java.time.LocalDate;
    98
  • backend/src/main/java/medora/models/domain/Admin.java

    r9af201e rcdcff72  
    11package medora.models.domain;
    22
    3 
    4 import jakarta.persistence.Column;
    5 import jakarta.persistence.Entity;
    6 import jakarta.persistence.Id;
    7 import jakarta.persistence.Table;
    8 import jakarta.validation.constraints.Pattern;
     3import jakarta.persistence.*;
    94import lombok.Getter;
    105import lombok.Setter;
     
    2015    private Long adminId;
    2116
    22     @Column(name = "username", nullable = false, unique = true)
    23     private String username;
     17    @OneToOne(optional = false)
     18    @JoinColumn(name = "user_id", nullable = false, unique = true)
     19    private User user;
    2420
    25     @Column(name = "name", nullable = false)
    26     private String name;
    27 
    28     @Column(name = "lastname", nullable = false)
    29     private String lastname;
    30 
    31     @Pattern(regexp = ".*@adminmedora.*")
    32     @Column(name = "email", nullable = false, unique = true)
    33     private String email;
     21    @Column(name = "permissions")
     22    private String permissions;
    3423
    3524    public Admin() {}
    3625
    37     public Admin(Long adminId, String username, String name, String lastname, String email) {
     26    public Admin(Long adminId, User user, String permissions) {
    3827        this.adminId = adminId;
    39         this.username = username;
    40         this.name = name;
    41         this.lastname = lastname;
    42         this.email = email;
     28        this.user = user;
     29        this.permissions = permissions;
     30    }
     31
     32    public Admin(Long adminId, User user) {
     33        this.adminId = adminId;
     34        this.user = user;
    4335    }
    4436}
  • backend/src/main/java/medora/models/domain/Billing.java

    r9af201e rcdcff72  
    44import jakarta.persistence.*;
    55import jakarta.validation.constraints.DecimalMin;
     6import jakarta.validation.constraints.NotBlank;
    67import lombok.Getter;
    78import lombok.Setter;
     
    3839    private MedicalRecord medicalRecord;
    3940
    40     @ManyToOne(optional = false)
    41     @JoinColumn(name = "admin_id", nullable = false)
     41    @ManyToOne(optional = true)
     42    @JoinColumn(name = "admin_id", nullable = true)
    4243    private Admin admin;
    4344
  • backend/src/main/java/medora/models/domain/Departments.java

    r9af201e rcdcff72  
    11package medora.models.domain;
    2 
    3 import jakarta.persistence.Column;
    4 import jakarta.persistence.Entity;
    5 import jakarta.persistence.Id;
    6 import jakarta.persistence.Table;
     2import jakarta.persistence.*;
    73import lombok.Getter;
    84import lombok.Setter;
  • backend/src/main/java/medora/models/domain/DoctorLevel.java

    r9af201e rcdcff72  
    11package medora.models.domain;
    2 
    3 import jakarta.persistence.Column;
    4 import jakarta.persistence.Entity;
    5 import jakarta.persistence.Id;
    6 import jakarta.persistence.Table;
     2import jakarta.persistence.*;
    73import lombok.Getter;
    84import lombok.Setter;
  • backend/src/main/java/medora/models/domain/DoctorSpecialization.java

    r9af201e rcdcff72  
    11package medora.models.domain;
    2 
    3 import jakarta.persistence.Column;
    4 import jakarta.persistence.Entity;
    5 import jakarta.persistence.Id;
    6 import jakarta.persistence.Table;
     2import jakarta.persistence.*;
    73import lombok.Getter;
    84import lombok.Setter;
  • backend/src/main/java/medora/models/domain/LabTechnician.java

    r9af201e rcdcff72  
    11package medora.models.domain;
    22
    3 
    4 import jakarta.persistence.Column;
    5 import jakarta.persistence.Entity;
    6 import jakarta.persistence.Id;
    7 import jakarta.persistence.Table;
    8 import jakarta.validation.constraints.Pattern;
     3import jakarta.persistence.*;
    94import lombok.Getter;
    105import lombok.Setter;
     
    2015    private Long technicianId;
    2116
    22     @Column(name = "username", nullable = false, unique = true)
    23     private String username;
     17    @OneToOne(optional = false)
     18    @JoinColumn(name = "user_id", nullable = false, unique = true)
     19    private User user;
    2420
    25     @Column(name = "name", nullable = false)
    26     private String name;
    27 
    28     @Column(name = "lastname", nullable = false)
    29     private String lastname;
    30 
    31     @Pattern(regexp = ".*@labmedora.*")
    32     @Column(name = "email", nullable = false, unique = true)
    33     private String email;
     21    @Column(name = "certification")
     22    private String certification;
    3423
    3524    public LabTechnician() {}
    3625
    37     public LabTechnician(Long technicianId, String username, String name, String lastname, String email) {
     26    public LabTechnician(Long technicianId, User user, String certification) {
    3827        this.technicianId = technicianId;
    39         this.username = username;
    40         this.name = name;
    41         this.lastname = lastname;
    42         this.email = email;
     28        this.user = user;
     29        this.certification = certification;
     30    }
     31
     32    public LabTechnician(Long technicianId, User user) {
     33        this.technicianId = technicianId;
     34        this.user = user;
    4335    }
    4436}
  • backend/src/main/java/medora/models/domain/MedicalRecordSymptoms.java

    r9af201e rcdcff72  
    2020    @Id
    2121    @ManyToOne(optional = false, fetch = FetchType.LAZY)
     22    @JoinColumn(name = "symptom_id")
    2223    private Symptoms symptom;
    2324
  • backend/src/main/java/medora/models/domain/MedicalReport.java

    r9af201e rcdcff72  
    77
    88import java.time.LocalDate;
     9import java.util.List;
     10import java.util.ArrayList;
    911
    1012@Getter
  • backend/src/main/java/medora/models/domain/Patient.java

    r9af201e rcdcff72  
    22
    33
    4 import com.fasterxml.jackson.annotation.JsonIgnore;
    54import jakarta.persistence.*;
    65import lombok.Getter;
     
    87import medora.models.enums.BloodType;
    98import medora.models.enums.Gender;
     9import com.fasterxml.jackson.annotation.JsonIgnore;
    1010
    1111import java.time.LocalDate;
  • backend/src/main/java/medora/models/domain/PerformedLabTests.java

    r9af201e rcdcff72  
    4848
    4949    public PerformedLabTests(Long performedTestId,
    50                              LabTests labTest,
    51                              Patient patient,
    52                              Doctors doctor,
    53                              LabTechnician technician,
    54                              LocalDate testDate,
    55                              String notes) {
     50                            LabTests labTest,
     51                            Patient patient,
     52                            Doctors doctor,
     53                            LabTechnician technician,
     54                            LocalDate testDate,
     55                            String notes) {
    5656        this.performedTestId = performedTestId;
    5757        this.labTest = labTest;
  • backend/src/main/java/medora/models/domain/ProcedureResults.java

    r9af201e rcdcff72  
    33
    44import jakarta.persistence.*;
     5import jakarta.validation.constraints.NotBlank;
    56import lombok.Getter;
    67import lombok.Setter;
  • backend/src/main/java/medora/models/domain/Referrals.java

    r9af201e rcdcff72  
    5050
    5151    public Referrals(Long referralId,
    52                      String reason,
    53                      LocalDate referralDate,
    54                      LocalDate appointmentDate,
    55                      LocalTime appointmentTime,
    56                      MedicalRecord medicalRecord,
    57                      Doctors fromDoctor,
    58                      Doctors toDoctor) {
     52                    String reason,
     53                    LocalDate referralDate,
     54                    LocalDate appointmentDate,
     55                    LocalTime appointmentTime,
     56                    MedicalRecord medicalRecord,
     57                    Doctors fromDoctor,
     58                    Doctors toDoctor) {
    5959
    6060        this.referralId = referralId;
  • backend/src/main/java/medora/models/domain/Symptoms.java

    r9af201e rcdcff72  
    22
    33
    4 import jakarta.persistence.Column;
    5 import jakarta.persistence.Entity;
    6 import jakarta.persistence.Id;
    7 import jakarta.persistence.Table;
     4import jakarta.persistence.*;
    85import jakarta.validation.constraints.NotBlank;
    96import lombok.Getter;
  • backend/src/main/java/medora/models/domain/User.java

    r9af201e rcdcff72  
    3434    private Boolean isActive = true;
    3535
    36     // Foreign key to patient  for PATIENT role
    37     @OneToOne(fetch = jakarta.persistence.FetchType.LAZY)
     36    // Foreign key to patient (only for PATIENT role)
     37    @OneToOne(fetch = FetchType.LAZY)
    3838    @JoinColumn(name = "patient_id")
    3939    private Patient patient;
    4040
    41     // Foreign key to doctor for DOCTOR role
    42     @OneToOne(fetch = jakarta.persistence.FetchType.LAZY)
     41    // Foreign key to doctor (only for DOCTOR role)
     42    @OneToOne(fetch = FetchType.LAZY)
    4343    @JoinColumn(name = "doctor_id")
    4444    private Doctors doctor;
  • backend/src/main/java/medora/repository/BillingRepository.java

    r9af201e rcdcff72  
    116116        WHERE bp.bill_id = :billId
    117117    """, nativeQuery = true)
    118     java.util.List<Object[]> findProceduresForBilling(@Param("billId") Long billId);
     118    List<Object[]> findProceduresForBilling(@Param("billId") Long billId);
    119119
    120120    // UC020 – Get lab tests for a billing record
     
    125125        WHERE blt.bill_id = :billId
    126126    """, nativeQuery = true)
    127     java.util.List<Object[]> findLabTestsForBilling(@Param("billId") Long billId);
     127    List<Object[]> findLabTestsForBilling(@Param("billId") Long billId);
    128128
    129129    // Cleanup: Delete test records
  • backend/src/main/java/medora/repository/ProcedureResultRepository.java

    r9af201e rcdcff72  
    3030    """)
    3131    List<ProcedureResults> findLatestResultsByProcedure(@Param("procedureId") Long procedureId);
     32
     33    @Query("""
     34        SELECT pr FROM ProcedureResults pr
     35        WHERE pr IN (
     36            SELECT mrpr.procedureResult FROM MedicalRecordProcedureResults mrpr
     37            WHERE mrpr.medicalRecord.recordId = :recordId
     38        )
     39        AND pr.procedure.procedureId = :procedureId
     40    """)
     41    List<ProcedureResults> findByMedicalRecordAndProcedure(@Param("recordId") Long recordId, @Param("procedureId") Long procedureId);
    3242}
  • backend/src/main/java/medora/service/AppointmentService.java

    r9af201e rcdcff72  
    181181    }
    182182
    183 
     183    /**
     184     * Mark appointment as completed
     185     */
    184186    @Transactional
    185187    public Appointment completeAppointment(Long appointmentId) {
     
    208210    }
    209211
     212    /**
     213     * Get appointment by ID
     214     */
    210215    @Transactional(readOnly = true)
    211216    public Optional<Appointment> getAppointmentById(Long appointmentId) {
     
    220225    }
    221226
    222 
     227    /**
     228     * Get appointments for patient
     229     */
    223230    @Transactional(readOnly = true)
    224231    public List<Appointment> getAppointmentsForPatient(Long patientId) {
     
    242249    }
    243250
    244 
     251    /**
     252     * Get appointments for doctor
     253     */
    245254    @Transactional(readOnly = true)
    246255    public List<Appointment> getAppointmentsForDoctor(Long doctorId) {
     
    264273    }
    265274
    266 
     275    /**
     276     * Get doctor's schedule for a specific date
     277     */
    267278    @Transactional(readOnly = true)
    268279    public List<Appointment> getDoctorSchedule(Long doctorId,
     
    290301    }
    291302
    292 
     303    /**
     304     * Get all appointments
     305     */
    293306    @Transactional(readOnly = true)
    294307    public List<Appointment> getAllAppointments() {
     
    299312    }
    300313
    301 
     314    /**
     315     * Find next available appointment slot for a doctor on a given date
     316     * Returns a LocalTime for the next available slot, or null if no slots available
     317     */
    302318    public LocalTime findNextAvailableSlot(Long doctorId, LocalDate appointmentDate) {
    303319        LocalTime[] timeSlots = {
  • backend/src/main/java/medora/service/AuthService.java

    r9af201e rcdcff72  
    66import org.slf4j.Logger;
    77import org.slf4j.LoggerFactory;
     8import org.springframework.security.crypto.password.PasswordEncoder;
    89import org.springframework.stereotype.Service;
    910import org.springframework.transaction.annotation.Transactional;
    1011
    1112import java.util.HashMap;
     13import java.util.List;
    1214import java.util.Map;
    1315import java.util.Optional;
     
    2022    private final UserRepository userRepository;
    2123    private final JwtUtil jwtUtil;
     24    private final PasswordEncoder passwordEncoder;
    2225
    23     public AuthService(UserRepository userRepository, JwtUtil jwtUtil) {
     26    public AuthService(UserRepository userRepository, JwtUtil jwtUtil, PasswordEncoder passwordEncoder) {
    2427        this.userRepository = userRepository;
    2528        this.jwtUtil = jwtUtil;
     29        this.passwordEncoder = passwordEncoder;
    2630    }
    2731
     
    4852        }
    4953
    50         // Simple password check (in production, use BCrypt)
    51         if (!user.getPassword().equals(password)) {
     54        // Verify password using BCrypt
     55        if (!passwordEncoder.matches(password, user.getPassword())) {
    5256            logger.warn("Failed login attempt for user: {}", username);
    5357            throw new RuntimeException("Invalid username or password");
     
    7276    }
    7377
     78    public String generateTestToken() {
     79        return jwtUtil.generateTokenWithDoctorId("admin", "ADMIN", 1L, null, null);
     80    }
     81
    7482    @Transactional
    7583    public void createUser(String username, String password, String role, String firstName, String lastName) {
     
    7886        }
    7987
    80         User user = new User(username, password, role, firstName, lastName);
     88        // Hash password before storing
     89        String hashedPassword = passwordEncoder.encode(password);
     90        User user = new User(username, hashedPassword, role, firstName, lastName);
    8191        userRepository.save(user);
    8292        logger.info("User created: {} with role: {}", username, role);
    8393    }
     94
     95    /**
     96     * One-time migration: re-hashes any user whose stored password is still
     97     * plaintext (i.e. not already a BCrypt hash) into a proper BCrypt hash.
     98     * Safe to call more than once — already-hashed users are skipped.
     99     *
     100     * Intended to be run once via a temporary CommandLineRunner bean, then
     101     * the bean should be removed so this doesn't run on every startup.
     102     */
     103    @Transactional
     104    public int migratePlaintextPasswords() {
     105        List<User> allUsers = userRepository.findAll();
     106        int migratedCount = 0;
     107
     108        for (User user : allUsers) {
     109            String currentPassword = user.getPassword();
     110
     111            // BCrypt hashes always start with $2a$, $2b$, or $2y$ — anything
     112            // else is assumed to still be plaintext and needs migrating
     113            if (currentPassword != null && !currentPassword.startsWith("$2")) {
     114                user.setPassword(passwordEncoder.encode(currentPassword));
     115                userRepository.save(user);
     116                migratedCount++;
     117                logger.info("Migrated password for user: {}", user.getUsername());
     118            }
     119        }
     120
     121        logger.info("Password migration complete: {} of {} users migrated", migratedCount, allUsers.size());
     122        return migratedCount;
     123    }
    84124}
  • backend/src/main/java/medora/service/BillingService.java

    r9af201e rcdcff72  
    221221        }
    222222
    223         logger.info("Total billing cost for bill ID {}: procedures={}, lab tests={}",
     223        logger.info("Total billing cost for bill ID {}: procedures={}, lab tests={}", 
    224224                billId, procedureCost, labTestCost);
    225225        return procedureCost.add(labTestCost);
     
    244244        // This is a placeholder - adjust based on your actual Procedure entity
    245245        logger.info("Adding procedure {} to billing record {}", procedureId, billId);
    246 
     246       
    247247        return null; // Will be implemented with ProcedureRepository injection
    248248    }
     
    464464            billingRepository.deleteAll();
    465465
    466             logger.info(" Deleted {} billing records successfully", billingCount);
     466            logger.info(" Deleted {} billing records successfully", billingCount);
    467467            return billingCount;
    468468        } catch (Exception e) {
    469             logger.error(" Error deleting billing records: {}", e.getMessage());
     469            logger.error(" Error deleting billing records: {}", e.getMessage());
    470470            e.printStackTrace();
    471471            throw e;
  • backend/src/main/java/medora/service/DepartmentService.java

    r9af201e rcdcff72  
    3232    }
    3333
    34 
     34    /**
     35     * UC023 – View Departments
     36     * Get all departments
     37     */
    3538    @Transactional(readOnly = true)
    3639    public List<Departments> getAllDepartments() {
     
    3942    }
    4043
    41 
     44    /**
     45     * Get department by ID
     46     */
    4247    @Transactional(readOnly = true)
    4348    public Optional<Departments> getDepartmentById(Long departmentId) {
     
    4954    }
    5055
    51 
     56    /**
     57     * Get department by name
     58     */
    5259    @Transactional(readOnly = true)
    5360    public Optional<Departments> getDepartmentByName(String departmentName) {
     
    5966    }
    6067
    61 
     68    /**
     69     * UC024 – View Doctors by Department
     70     * Get all doctors in a specific department
     71     */
    6272    @Transactional(readOnly = true)
    6373    public List<Doctors> getDoctorsByDepartment(Long departmentId) {
     
    7585    }
    7686
    77 
     87    /**
     88     * Create a new department
     89     */
    7890    @Transactional
    7991    public Departments createDepartment(Departments department) {
     
    8799    }
    88100
    89 
     101    /**
     102     * Update department
     103     */
    90104    @Transactional
    91105    public Departments updateDepartment(Long departmentId, Departments departmentDetails) {
  • backend/src/main/java/medora/service/DiagnosisService.java

    r9af201e rcdcff72  
    22
    33import medora.models.domain.Diagnosis;
     4import medora.models.domain.Patient;
    45import medora.models.domain.Doctors;
    5 import medora.models.domain.Patient;
    66import medora.repository.DiagnosisRepository;
     7import medora.repository.PatientRepository;
    78import medora.repository.DoctorRepository;
    8 import medora.repository.PatientRepository;
    99import org.slf4j.Logger;
    1010import org.slf4j.LoggerFactory;
     
    6969    }
    7070
     71    /**
     72     * Get diagnosis by ID
     73     */
    7174    @Transactional(readOnly = true)
    7275    public Optional<Diagnosis> getDiagnosisById(Long diagnosisId) {
     
    7881    }
    7982
    80 
     83    /**
     84     * Get diagnoses for patient
     85     */
    8186    @Transactional(readOnly = true)
    8287    public List<Diagnosis> getDiagnosesForPatient(Long patientId) {
     
    9196    }
    9297
    93 
     98    /**
     99     * Get diagnoses by doctor
     100     */
    94101    @Transactional(readOnly = true)
    95102    public List<Diagnosis> getDiagnosesByDoctor(Long doctorId) {
     
    105112
    106113
    107     @Transactional
    108     public Diagnosis updateDiagnosis(Long diagnosisId,
    109                                      String diagnosisName,
    110                                      String description) {
    111 
    112         if (diagnosisId == null || diagnosisId <= 0)
    113             throw new IllegalArgumentException("Diagnosis ID must be valid");
    114 
    115         Diagnosis diagnosis = diagnosisRepository.findById(diagnosisId)
    116                 .orElseThrow(() -> new RuntimeException("Diagnosis not found"));
    117 
    118         if (diagnosisName != null && !diagnosisName.isBlank())
    119             diagnosis.setName(diagnosisName);
    120 
    121         if (description != null)
    122             diagnosis.setDescription(description);
    123 
    124         return diagnosisRepository.save(diagnosis);
    125     }
    126114}
  • backend/src/main/java/medora/service/DoctorService.java

    r9af201e rcdcff72  
    11package medora.service;
    22
     3import medora.models.domain.Doctors;
    34import medora.models.domain.Departments;
    4 import medora.models.domain.Doctors;
     5import medora.repository.DoctorRepository;
    56import medora.repository.DepartmentRepository;
    6 import medora.repository.DoctorRepository;
    77import org.slf4j.Logger;
    88import org.slf4j.LoggerFactory;
     
    4646    }
    4747
    48 
     48    /**
     49     * Get doctor by email
     50     */
    4951    @Transactional(readOnly = true)
    5052    public Doctors getDoctorByEmail(String emailAddress) {
     
    5961    }
    6062
    61 
     63    /**
     64     * UC024 – View Doctors by Department
     65     */
    6266    @Transactional(readOnly = true)
    6367    public List<Doctors> getDoctorsByDepartment(Long departmentId) {
     
    7478    }
    7579
    76 
     80    /**
     81     * Get doctors by specialization
     82     */
    7783    @Transactional(readOnly = true)
    7884    public List<Doctors> getDoctorsBySpecialization(Long specializationId) {
     
    8692    }
    8793
    88 
     94    /**
     95     * Get doctors by level
     96     */
    8997    @Transactional(readOnly = true)
    9098    public List<Doctors> getDoctorsByLevel(Long levelId) {
     
    98106    }
    99107
    100 
     108    /**
     109     * Get all doctors
     110     */
    101111    @Transactional(readOnly = true)
    102112    public List<Doctors> getAllDoctors() {
     
    105115    }
    106116
    107 
     117    /**
     118     * Create doctor
     119     */
    108120    @Transactional
    109121    public Doctors createDoctor(Doctors doctor) {
     
    146158    }
    147159
    148 
     160    /**
     161     * Update doctor
     162     */
    149163    @Transactional
    150164    public Doctors updateDoctor(Long doctorId, Doctors doctorDetails) {
  • backend/src/main/java/medora/service/LabService.java

    r9af201e rcdcff72  
    223223    @Transactional(readOnly = true)
    224224    public List<PerformedLabTests> getPendingLabTests() {
    225         // Get all performed tests and filter to only those without results
    226         List<PerformedLabTests> allTests = performedLabTestRepository.findAll();
    227 
    228         return allTests.stream()
    229                 .filter(test -> {
    230                     // Get the patient's medical record
    231                     Optional<MedicalRecord> recordOpt = medicalRecordRepository.findByPatientPatientId(test.getPatient().getPatientId());
    232                     if (recordOpt.isEmpty()) {
    233                         return true; // No medical record, so no results possible
    234                     }
    235 
    236                     MedicalRecord record = recordOpt.get();
    237                     // Check if this test has results in this medical record
    238                     List<MedicalRecordLabResults> results = medicalRecordLabResultRepository
    239                             .findByMedicalRecordRecordId(record.getRecordId());
    240 
    241                     // Filter to only results for this specific test
    242                     return results.stream()
    243                             .noneMatch(r -> r.getLabResult().getLabTest().getTestId().equals(test.getLabTest().getTestId()));
    244                 })
    245                 .toList();
     225        logger.info("Fetching all pending lab test requests");
     226        return performedLabTestRepository.findAll();
    246227    }
    247228
  • backend/src/main/java/medora/service/MedicalObservationsService.java

    r9af201e rcdcff72  
    22
    33
    4 import medora.models.domain.*;
    5 import medora.repository.*;
     4import medora.models.domain.MedicalRecordSymptoms;
     5import medora.models.domain.MedicalRecordAllergies;
     6import medora.models.domain.Symptoms;
     7import medora.models.domain.Allergies;
     8import medora.models.domain.MedicalRecord;
     9import medora.repository.SymptomRepository;
     10import medora.repository.AllergyRepository;
     11import medora.repository.MedicalRecordSymptomRepository;
     12import medora.repository.MedicalRecordAllergyRepository;
     13import medora.repository.MedicalRecordRepository;
    614import org.slf4j.Logger;
    715import org.slf4j.LoggerFactory;
     
    6169                .orElseThrow(() -> new RuntimeException("Symptom not found with ID: " + symptomId));
    6270
    63 
     71        // Check if symptom is already recorded
    6472        if (medicalRecordSymptomRepository.existsByMedicalRecordRecordIdAndSymptomSymptomId(
    6573                medicalRecordId, symptomId)) {
     
    93101
    94102   
    95     // For ALLERGIES
     103    // ================= ALLERGIES OPERATIONS =================
    96104
    97105    /**
     
    128136    }
    129137
    130 
     138    /**
     139     * Get all allergies for a patient's medical record
     140     */
    131141    @Transactional(readOnly = true)
    132142    public List<MedicalRecordAllergies> getAllergiesForMedicalRecord(Long medicalRecordId) {
     
    143153    }
    144154
     155   
    145156
     157    /**
     158     * Get all available symptoms
     159     */
    146160    @Transactional(readOnly = true)
    147161    public List<Symptoms> getAllSymptoms() {
     
    150164    }
    151165
    152 
     166    /**
     167     * Get all available allergies
     168     */
    153169    @Transactional(readOnly = true)
    154170    public List<Allergies> getAllAllergies() {
  • backend/src/main/java/medora/service/MedicalRecordService.java

    r9af201e rcdcff72  
    11package medora.service;
    22
    3 import medora.models.domain.*;
     3import medora.models.domain.Allergies;
     4import medora.models.domain.MedicalRecord;
     5import medora.models.domain.MedicalRecordAllergies;
     6import medora.models.domain.MedicalRecordSymptoms;
     7import medora.models.domain.Symptoms;
    48import medora.models.domain.id.MedicalRecordAllergyId;
    59import medora.models.domain.id.MedicalRecordLabResultId;
    610import medora.models.domain.id.MedicalRecordProcedureId;
    711import medora.models.domain.id.MedicalRecordSymptomId;
    8 import medora.repository.*;
     12import medora.repository.AllergyRepository;
     13import medora.repository.MedicalRecordAllergyRepository;
     14import medora.repository.MedicalRecordLabResultRepository;
     15import medora.repository.MedicalRecordProcedureRepository;
     16import medora.repository.MedicalRecordRepository;
     17import medora.repository.MedicalRecordSymptomRepository;
     18import medora.repository.PatientRepository;
     19import medora.repository.SymptomRepository;
    920import org.slf4j.Logger;
    1021import org.slf4j.LoggerFactory;
     
    94105    }
    95106
    96     // UC015 – Link Medical Data to Medical Record
     107    // UC015 – Link Medical Data to Medical Record ⭐ (IMPORTANT FIX)
    97108    @Transactional
    98109    public MedicalRecord linkMedicalData(Long recordId, MedicalRecord updatedData) {
     
    109120                .orElseThrow(() -> new RuntimeException("Medical record not found"));
    110121
     122        // Safely link many-to-many/join-table rows.
     123        // The project currently models join-tables as explicit entities (e.g. MedicalRecordProcedures),
     124        // but `MedicalRecord` entity in this codebase doesn't expose collection getters yet.
     125        // To avoid compile problems and still support DTOs that may contain collections, use reflection:
    111126
    112127        try {
     
    126141                }
    127142            } catch (NoSuchMethodException ignored) {
    128 
     143                // updatedData has no getProcedures() — nothing to do
    129144            }
    130145
     
    144159                }
    145160            } catch (NoSuchMethodException ignored) {
    146 
     161                // updatedData has no getLabResults() — nothing to do
    147162            }
    148163
     
    157172                        MedicalRecordAllergyId mraId = new MedicalRecordAllergyId(recordId, allergyId);
    158173                        if (medicalRecordAllergyRepository.findById(mraId).isEmpty()) {
    159 
     174                            // Create allergy join entity manually and save
    160175                            MedicalRecordAllergies allergyJoin =
    161176                                new MedicalRecordAllergies();
    162177                            allergyJoin.setMedicalRecord(record);
    163 
     178                            // Load allergy entity would require allergyRepo, which we don't have injected yet
     179                            // For now, just save the join if it doesn't exist (via repository direct save)
    164180                            logger.debug("Allergy {} linking deferred (missing allergyRepo)", allergyId);
    165181                        }
     
    167183                }
    168184            } catch (NoSuchMethodException ignored) {
    169 
     185                // updatedData has no getAllergies() — nothing to do
    170186            }
    171187
     
    185201                }
    186202            } catch (NoSuchMethodException ignored) {
    187 
     203                // updatedData has no getSymptoms() — nothing to do
    188204            }
    189205        } catch (ReflectiveOperationException e) {
    190 
     206            // If reflection fails for unexpected reasons, log and rethrow as runtime to avoid silent data loss.
    191207            throw new RuntimeException("Failed to link medical data via reflection", e);
    192208        }
     
    196212    }
    197213
     214    // UPDATE medical record
    198215    @Transactional
    199216    public MedicalRecord updateMedicalRecord(Long recordId, MedicalRecord recordDetails) {
     
    214231    }
    215232
     233    // helper
    216234    @Transactional(readOnly = true)
    217235    public List<MedicalRecord> getAllMedicalRecords() {
     
    273291    }
    274292
    275 
     293    // Reflection helper: try to extract an id from an object using common getter names
    276294    private Long extractId(Object obj, String... candidateGetters) {
    277295        if (obj == null) return null;
     
    282300                if (val instanceof Number) return ((Number) val).longValue();
    283301            } catch (ReflectiveOperationException ignored) {
    284 
     302                // try next
    285303            }
    286304        }
  • backend/src/main/java/medora/service/MedicalReportService.java

    r9af201e rcdcff72  
    11package medora.service;
    22
     3import medora.models.domain.*;
    34import medora.dto.*;
    4 import medora.models.domain.*;
    55import medora.repository.*;
    66import org.slf4j.Logger;
     
    1010
    1111import java.time.LocalDate;
     12import java.util.HashSet;
    1213import java.util.List;
    1314import java.util.Optional;
     15import java.util.Set;
    1416import java.util.stream.Collectors;
    1517
  • backend/src/main/java/medora/service/PatientService.java

    r9af201e rcdcff72  
    22
    33
     4import medora.models.domain.Patient;
    45import medora.models.domain.MedicalRecord;
    5 import medora.models.domain.Patient;
     6import medora.repository.PatientRepository;
    67import medora.repository.MedicalRecordRepository;
    7 import medora.repository.PatientRepository;
    88import org.slf4j.Logger;
    99import org.slf4j.LoggerFactory;
  • backend/src/main/java/medora/service/PrescriptionService.java

    r9af201e rcdcff72  
    11package medora.service;
    22
     3import medora.models.domain.Prescriptions;
     4import medora.models.domain.PrescriptionMedicalRecord;
    35import medora.models.domain.MedicalRecord;
    4 import medora.models.domain.PrescriptionMedicalRecord;
    5 import medora.models.domain.Prescriptions;
     6import medora.repository.PrescriptionRepository;
     7import medora.repository.PrescriptionMedicalRecordRepository;
    68import medora.repository.MedicalRecordRepository;
    7 import medora.repository.PrescriptionMedicalRecordRepository;
    8 import medora.repository.PrescriptionRepository;
    99import org.slf4j.Logger;
    1010import org.slf4j.LoggerFactory;
  • backend/src/main/java/medora/service/ProcedureService.java

    r9af201e rcdcff72  
    297297                .orElseThrow(() -> new RuntimeException("Procedure not found"));
    298298
    299         ProcedureResults result = new ProcedureResults();
    300         result.setProcedure(procedure);
    301         result.setResultDescription(resultDescription);
    302         result.setResultDate(resultDate);
    303 
    304         ProcedureResults savedResult = procedureResultRepository.save(result);
    305 
    306         // Link to medical record
    307         MedicalRecordProcedureResults link = new MedicalRecordProcedureResults(record, savedResult);
    308         medicalRecordProcedureResultRepository.save(link);
    309 
    310         logger.info("Stored procedure result {} for medical record {}", savedResult.getResultId(), medicalRecordId);
    311         return savedResult;
     299        // Check if result already exists for this medical record and procedure
     300        List<ProcedureResults> existingResults = procedureResultRepository.findByMedicalRecordAndProcedure(medicalRecordId, procedureId);
     301
     302        ProcedureResults result;
     303        if (!existingResults.isEmpty()) {
     304            // Update existing result
     305            result = existingResults.get(0);
     306            result.setResultDescription(resultDescription);
     307            result.setResultDate(resultDate);
     308            ProcedureResults savedResult = procedureResultRepository.save(result);
     309            logger.info("Updated procedure result {} for medical record {}", savedResult.getResultId(), medicalRecordId);
     310            return savedResult;
     311        } else {
     312            // Create new result
     313            result = new ProcedureResults();
     314            result.setProcedure(procedure);
     315            result.setResultDescription(resultDescription);
     316            result.setResultDate(resultDate);
     317
     318            // Save result first to generate ID
     319            ProcedureResults savedResult = procedureResultRepository.save(result);
     320
     321            // Then link to medical record
     322            MedicalRecordProcedureResults link = new MedicalRecordProcedureResults(record, savedResult);
     323            medicalRecordProcedureResultRepository.save(link);
     324            logger.info("Created new procedure result {} for medical record {}", savedResult.getResultId(), medicalRecordId);
     325            return savedResult;
     326        }
    312327    }
    313328
  • backend/src/main/java/medora/service/ReferralService.java

    r9af201e rcdcff72  
    106106    }
    107107
    108 
     108    /**
     109     * Get referral by ID
     110     */
    109111    @Transactional(readOnly = true)
    110112    public Optional<Referrals> getReferralById(Long referralId) {
     
    116118    }
    117119
    118 
     120    /**
     121     * Get referrals for a patient
     122     */
    119123    @Transactional(readOnly = true)
    120124    public List<Referrals> getReferralsForPatient(Long patientId) {
     
    131135    }
    132136
    133 
     137    /**
     138     * Get referrals made by a doctor
     139     */
    134140    @Transactional(readOnly = true)
    135141    public List<Referrals> getReferralsByFromDoctor(Long doctorId) {
     
    146152    }
    147153
     154    /**
     155     * Get referrals received by a doctor
     156     */
    148157    @Transactional(readOnly = true)
    149158    public List<Referrals> getReferralsToDoctor(Long doctorId) {
     
    160169    }
    161170
    162 
     171    /**
     172     * Create appointment for referral in a separate transaction
     173     * Using REQUIRES_NEW ensures appointment creation failures don't rollback the referral
     174     */
    163175    @Transactional(propagation = org.springframework.transaction.annotation.Propagation.REQUIRES_NEW)
    164176    private void createAppointmentForReferral(Long patientId, Long doctorId, LocalDate appointmentDate, LocalTime appointmentTime) {
  • backend/src/main/java/medora/util/JwtUtil.java

    r9af201e rcdcff72  
    3030    public void init() {
    3131        logger.info("JwtUtil initialized - JWT Secret length: {}, Expiration: {}ms",
    32                 jwtSecret != null ? jwtSecret.length() : 0, jwtExpirationMs);
     32            jwtSecret != null ? jwtSecret.length() : 0, jwtExpirationMs);
    3333        if (jwtSecret == null || jwtSecret.isEmpty()) {
    3434            logger.error("⚠️ JWT_SECRET is not set or empty!");
     
    123123
    124124            logger.info("🔐 Validating token - secret hash: {}, secret length: {}, token length: {}",
    125                     jwtSecret.hashCode(), jwtSecret.length(), token.length());
     125                jwtSecret.hashCode(), jwtSecret.length(), token.length());
    126126
    127127            Jwts.parser()
  • backend/src/main/resources/application.properties

    r9af201e rcdcff72  
    2323spring.jackson.serialization.fail-on-empty-beans=false
    2424spring.jackson.default-property-inclusion=non_null
    25 
  • backend/src/main/resources/db/migration/V1.1__Create_Daily_Billing_View.sql

    r9af201e rcdcff72  
    1 -- Create materialized view for daily patient billing totals
     1 -- Create materialized view for daily patient billing totals
    22CREATE MATERIALIZED VIEW IF NOT EXISTS daily_patient_billing_totals AS
    33SELECT
  • backend/src/main/resources/db/migration/V1.2__Create_Users_Table.sql

    r9af201e rcdcff72  
    1 user
    21-- Create users table
    32CREATE TABLE IF NOT EXISTS users (
     
    3231)
    3332LIMIT 39;
     33
     34-- Insert doctor users (password: doctor123 for all)
     35-- These will be linked to existing doctors by email
     36INSERT INTO users (username, password, role, first_name, last_name, doctor_id, is_active)
     37SELECT d.email_address, 'doctor123', 'DOCTOR', d.first_name, d.last_name, d.doctor_id, true
     38FROM doctors d
     39WHERE NOT EXISTS (
     40    SELECT 1 FROM users u WHERE u.username = d.email_address
     41);
  • backend/src/test/java/medora/Medora4ApplicationTests.java

    r9af201e rcdcff72  
    55
    66@SpringBootTest
    7 class Medora5ApplicationTests {
     7class Medora4ApplicationTests {
    88
    99    @Test
Note: See TracChangeset for help on using the changeset viewer.