From 3ece3be32033e20ca06731c43e28127e6222c710 Mon Sep 17 00:00:00 2001 From: ThamojDinujaya Date: Thu, 6 Nov 2025 19:03:16 +0530 Subject: [PATCH 1/2] updated authentications in securityconfig projects visibilty --- .../java/com/example/ead_backend/config/SecurityConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/example/ead_backend/config/SecurityConfig.java b/src/main/java/com/example/ead_backend/config/SecurityConfig.java index 8bba667..405a4e7 100644 --- a/src/main/java/com/example/ead_backend/config/SecurityConfig.java +++ b/src/main/java/com/example/ead_backend/config/SecurityConfig.java @@ -45,7 +45,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .requestMatchers("/api/appointments/availability").permitAll() // All other appointment endpoints require authentication .requestMatchers("/api/appointments/**").authenticated() - .requestMatchers("/api/projects/**").permitAll() + .requestMatchers("/api/projects/**").authenticated() .requestMatchers("/api/password/forgot", "/api/password/verify-otp", "/api/password/reset").permitAll() .requestMatchers("/api/password/change").authenticated() .requestMatchers("/api/admin/**").hasRole("ADMIN") From 7ee12c4b68d6b36214726f7085d4bd46e08f79b4 Mon Sep 17 00:00:00 2001 From: ThamojDinujaya Date: Thu, 6 Nov 2025 20:09:18 +0530 Subject: [PATCH 2/2] Created Timelog functionality --- .../controller/AppointmentController.java | 13 +++ .../ead_backend/model/entity/TimeLog.java | 18 ++++- .../repository/TimeLogRepository.java | 4 + .../service/impl/AppointmentServiceImpl.java | 79 +++++++++++++++++++ 4 files changed, 113 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/example/ead_backend/controller/AppointmentController.java b/src/main/java/com/example/ead_backend/controller/AppointmentController.java index eef855b..fc4e200 100644 --- a/src/main/java/com/example/ead_backend/controller/AppointmentController.java +++ b/src/main/java/com/example/ead_backend/controller/AppointmentController.java @@ -8,7 +8,9 @@ import com.example.ead_backend.service.AppointmentService; import java.util.List; +import java.util.Map; import java.security.Principal; +import java.time.LocalDate; @RestController @Slf4j @@ -59,6 +61,17 @@ public List getAll(@RequestParam(required = false) String custom return appointmentService.getAllAppointments(); } + // Availability: return object with 'booked' (HH:mm list) for a given date (YYYY-MM-DD) + @GetMapping("/availability") + public Map getAvailability(@RequestParam("date") String dateStr) { + LocalDate date = LocalDate.parse(dateStr); + List booked = appointmentService.getBookedStartTimes(date); + return Map.of( + "date", dateStr, + "booked", booked + ); + } + @PutMapping("/{id}") public AppointmentDTO update(@PathVariable String id, @RequestBody AppointmentDTO dto, Principal principal) { // Verify user owns the appointment they're trying to update diff --git a/src/main/java/com/example/ead_backend/model/entity/TimeLog.java b/src/main/java/com/example/ead_backend/model/entity/TimeLog.java index 751718c..8fca6dd 100644 --- a/src/main/java/com/example/ead_backend/model/entity/TimeLog.java +++ b/src/main/java/com/example/ead_backend/model/entity/TimeLog.java @@ -2,11 +2,27 @@ import jakarta.persistence.*; import lombok.Data; +import org.hibernate.annotations.UuidGenerator; +import java.time.LocalDate; @Entity -@Table(name = "TimeLog") +@Table(name = "TimeLogs") @Data public class TimeLog { @Id + @UuidGenerator + @Column(columnDefinition = "VARCHAR(255)") private String timeLogId; + + @Column(nullable = false) + private LocalDate date; + + @Column(name = "start_time", nullable = false) + private String startTime; // HH:mm + + @Column(name = "end_time", nullable = false) + private String endTime; // HH:mm + + @Column(nullable = false) + private String type; // e.g., BLOCKED, MAINTENANCE } diff --git a/src/main/java/com/example/ead_backend/repository/TimeLogRepository.java b/src/main/java/com/example/ead_backend/repository/TimeLogRepository.java index 46ffd50..fe68562 100644 --- a/src/main/java/com/example/ead_backend/repository/TimeLogRepository.java +++ b/src/main/java/com/example/ead_backend/repository/TimeLogRepository.java @@ -3,9 +3,13 @@ import com.example.ead_backend.model.entity.TimeLog; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.time.LocalDate; +import java.util.List; @Repository public interface TimeLogRepository extends JpaRepository { + List findByDate(LocalDate date); + List findByDateAndType(LocalDate date, String type); } diff --git a/src/main/java/com/example/ead_backend/service/impl/AppointmentServiceImpl.java b/src/main/java/com/example/ead_backend/service/impl/AppointmentServiceImpl.java index c34a3e8..c5feb1e 100644 --- a/src/main/java/com/example/ead_backend/service/impl/AppointmentServiceImpl.java +++ b/src/main/java/com/example/ead_backend/service/impl/AppointmentServiceImpl.java @@ -3,24 +3,39 @@ import com.example.ead_backend.service.AppointmentService; import com.example.ead_backend.dto.AppointmentDTO; import com.example.ead_backend.model.entity.Appointment; +import com.example.ead_backend.model.entity.TimeLog; +import com.example.ead_backend.model.enums.AppointmentStatus; import com.example.ead_backend.repository.AppointmentRepository; +import com.example.ead_backend.repository.TimeLogRepository; import com.example.ead_backend.mapper.AppointmentMapper; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; +import java.time.LocalDate; +import java.time.LocalTime; @Service @RequiredArgsConstructor public class AppointmentServiceImpl implements AppointmentService { private final AppointmentRepository appointmentRepository; + private final TimeLogRepository timeLogRepository; private final AppointmentMapper appointmentMapper; @Override public AppointmentDTO createAppointment(AppointmentDTO dto) { + // Enforce unique slot per date (by start time). This is global; can be extended per-employee later. + LocalDate date = dto.getDate(); + String start = dto.getStartTime(); + if (date != null && start != null && appointmentRepository.existsByDateAndStartTime(date, start)) { + throw new IllegalStateException("Time slot already booked for this date"); + } Appointment entity = appointmentMapper.toEntity(dto); // ensure new fields are copied (mapper should handle this if configured) entity.setCustomerId(dto.getCustomerId()); @@ -57,6 +72,18 @@ public AppointmentDTO updateAppointment(String id, AppointmentDTO dto) { Appointment existing = appointmentRepository.findById(id) .orElseThrow(() -> new RuntimeException("Appointment not found with id " + id)); + // If changing date/startTime, enforce uniqueness + LocalDate newDate = dto.getDate(); + String newStart = dto.getStartTime(); + if (newDate != null && newStart != null) { + boolean slotTaken = appointmentRepository.existsByDateAndStartTime(newDate, newStart); + // allow updating to same slot the record already has + boolean sameSlot = newDate.equals(existing.getDate()) && newStart.equals(existing.getStartTime()); + if (slotTaken && !sameSlot) { + throw new IllegalStateException("Time slot already booked for this date"); + } + } + existing.setService(dto.getService()); existing.setCustomerId(dto.getCustomerId()); existing.setVehicleId(dto.getVehicleId()); @@ -79,4 +106,56 @@ public AppointmentDTO updateAppointment(String id, AppointmentDTO dto) { public void deleteAppointment(String id) { appointmentRepository.deleteById(id); } + + @Override + public List getBookedStartTimes(LocalDate date) { + // 1) Appointment-based bookings (exclude CANCELLED) + List bookedFromAppointments = appointmentRepository.findByDate(date) + .stream() + .filter(a -> a.getStatus() != AppointmentStatus.CANCELLED) + .map(Appointment::getStartTime) + .collect(Collectors.toList()); + + // 2) TimeLog-based blocked intervals expanded into 30-minute slot starts + List logs = timeLogRepository.findByDate(date); + Set bookedFromLogs = new HashSet<>(); + for (TimeLog log : logs) { + LocalTime start = LocalTime.parse(safeHHMM(log.getStartTime())); + LocalTime end = LocalTime.parse(safeHHMM(log.getEndTime())); + for (LocalTime t = start; !t.isAfter(end.minusMinutes(30)); t = t.plusMinutes(30)) { + bookedFromLogs.add(toHHMM(t)); + } + } + + // 3) Merge and return unique HH:mm values + Set all = new HashSet<>(bookedFromAppointments); + all.addAll(bookedFromLogs); + return new ArrayList<>(all); + } + + private static String toHHMM(LocalTime t) { + return String.format("%02d:%02d", t.getHour(), t.getMinute()); + } + + private static String safeHHMM(String t) { + if (t == null) return "00:00"; + String s = t.trim(); + if (s.contains("-")) s = s.split("-")[0].trim(); + if (s.matches("^\\d{1,2}:\\d{2}\\s*[AaPp][Mm]$")) { + // convert 12h to 24h + String[] parts = s.split(" "); + String[] hm = parts[0].split(":"); + int h = Integer.parseInt(hm[0]); + String m = hm[1]; + String mer = parts[1].toUpperCase(); + if (mer.equals("PM") && h != 12) h += 12; + if (mer.equals("AM") && h == 12) h = 0; + return String.format("%02d:%s", h, m); + } + // strip seconds if any + if (s.matches("^\\d{2}:\\d{2}:\\d{2}$")) return s.substring(0,5); + // pad hour if needed + if (s.matches("^\\d{1}:\\d{2}$")) return "0" + s; + return s; + } }