From 159376f578e529c98eb68dd4c94d9c56d4dec9d5 Mon Sep 17 00:00:00 2001 From: Sinan Date: Sat, 21 Sep 2024 21:12:58 +0530 Subject: [PATCH] slot use case updated --- server/package.json | 2 +- .../controllers/slot/SlotController.ts | 28 ++- .../presentation/routers/slots/SlotsRoutes.ts | 13 +- server/src/use_case/slot/CreateSlotUseCase.ts | 87 +++++++++ server/src/use_case/slot/DeleteSlotUseCase.ts | 73 ++++++++ server/src/use_case/slot/GetSlotUseCase.ts | 39 ++++ server/src/use_case/slot/SlotUseCases.ts | 177 ------------------ server/src/use_case/slot/UpdateSlotUseCase.ts | 18 ++ 8 files changed, 246 insertions(+), 191 deletions(-) create mode 100644 server/src/use_case/slot/CreateSlotUseCase.ts create mode 100644 server/src/use_case/slot/DeleteSlotUseCase.ts create mode 100644 server/src/use_case/slot/GetSlotUseCase.ts delete mode 100644 server/src/use_case/slot/SlotUseCases.ts create mode 100644 server/src/use_case/slot/UpdateSlotUseCase.ts diff --git a/server/package.json b/server/package.json index 5a68ea52..b9ebd939 100644 --- a/server/package.json +++ b/server/package.json @@ -15,7 +15,7 @@ "author": "", "license": "ISC", "dependencies": { - "@aws-sdk/lib-storage": "^3.637.0", + "@aws-sdk/client-s3": "^3.654.0", "@aws-sdk/s3-request-presigner": "^3.637.0", "aws-sdk": "^2.1687.0", "bcryptjs": "^2.4.3", diff --git a/server/src/presentation/controllers/slot/SlotController.ts b/server/src/presentation/controllers/slot/SlotController.ts index 510e5b0d..d588a7d9 100644 --- a/server/src/presentation/controllers/slot/SlotController.ts +++ b/server/src/presentation/controllers/slot/SlotController.ts @@ -1,17 +1,25 @@ import { NextFunction, Response } from "express"; import { CustomRequest, StatusCode } from "../../../types"; -import SlotUseCase from "../../../use_case/slot/SlotUseCases"; import { Days } from "../../../domain/entities/ISlot"; +import CreateSlotUseCase from "../../../use_case/slot/CreateSlotUseCase"; +import UpdateSlotUseCase from "../../../use_case/slot/UpdateSlotUseCase"; +import GetSlotUseCase from "../../../use_case/slot/GetSlotUseCase"; +import DeleteSlotUseCase from "../../../use_case/slot/DeleteSlotUseCase"; export default class DoctorController { - constructor(private slotUseCase: SlotUseCase) { } + constructor( + private createSlotUseCase :CreateSlotUseCase, + private updateSlotUseCase :UpdateSlotUseCase, + private getSlotUseCase : GetSlotUseCase, + private deleteSlotUseCase: DeleteSlotUseCase + ) { } async createManyByDay(req: CustomRequest, res: Response, next: NextFunction) { try { const doctorId = req.doctor?.id; const { slots, day } = req.body; - await this.slotUseCase.createManyByDay(doctorId!, slots, day); + await this.createSlotUseCase.createManyByDay(doctorId!, slots, day); res.status(StatusCode.Created).json({ message: 'Slots created successfully.' }); } catch (error) { @@ -23,7 +31,7 @@ export default class DoctorController { try { const doctorId = req.doctor?.id!; const { startTimes } = req.body; - await this.slotUseCase.createForAllDays(doctorId, startTimes) + await this.createSlotUseCase.createForAllDays(doctorId, startTimes) res.status(StatusCode.Created).json({ message: "Slots created successfully" }) } catch (error) { next(error) @@ -34,7 +42,7 @@ export default class DoctorController { try { const doctorId = req.doctor?.id; const { slots, day } = req.body; - await this.slotUseCase.deleteManyByDay(doctorId!, slots, day); + await this.deleteSlotUseCase.deleteManyByDay(doctorId!, slots, day); res.status(StatusCode.Success).json({ message: "Slots Deleted successfully" }) } catch (error) { @@ -47,7 +55,7 @@ export default class DoctorController { const doctorId = req.doctor?.id!; const { startTimes } = req.body; - await this.slotUseCase.deleteForAllDays(doctorId, startTimes) + await this.deleteSlotUseCase.deleteForAllDays(doctorId, startTimes) res.status(StatusCode.Success).json({ message: "Slots Deleted successfully" }) } catch (error) { next(error) @@ -57,7 +65,7 @@ export default class DoctorController { async update(req: CustomRequest, res: Response, next: NextFunction) { try { const slot = req.body; - await this.slotUseCase.update(slot); + await this.updateSlotUseCase.update(slot); res.status(StatusCode.Success).json({ message: "Slot updated successfully" }); } catch (error) { next(error); @@ -70,9 +78,9 @@ export default class DoctorController { const day = req.query.day as Days; let slots; if (Object.values(Days).includes(day)) { - slots = await this.slotUseCase.getSlotsByDay(doctorId!, day) + slots = await this.getSlotUseCase.getSlotsByDay(doctorId!, day) } else { - slots = await this.slotUseCase.getAllSlots(doctorId!); + slots = await this.getSlotUseCase.getAllSlots(doctorId!); } res.status(StatusCode.Success).json(slots); } catch (error) { @@ -85,7 +93,7 @@ export default class DoctorController { const doctorId = req.params.doctorId const date = req.query.date as string; - const slots = await this.slotUseCase.getSlotsByDate(doctorId, date) + const slots = await this.getSlotUseCase.getSlotsByDate(doctorId, date) res.status(StatusCode.Success).json(slots); } catch (error) { next(error); diff --git a/server/src/presentation/routers/slots/SlotsRoutes.ts b/server/src/presentation/routers/slots/SlotsRoutes.ts index c47dfabe..cfa30155 100644 --- a/server/src/presentation/routers/slots/SlotsRoutes.ts +++ b/server/src/presentation/routers/slots/SlotsRoutes.ts @@ -1,11 +1,14 @@ import express from 'express'; import SlotRepository from '../../../infrastructure/repositories/SlotRepository'; -import SlotUseCase from '../../../use_case/slot/SlotUseCases'; import SlotController from '../../controllers/slot/SlotController'; import DoctorAuthMiddleware from '../../middlewares/DoctorAuthMiddleware'; import TokenService from '../../../infrastructure/services/JWTService'; import AppointmentRepository from '../../../infrastructure/repositories/AppointmentRepository'; import JoiService from '../../../infrastructure/services/JoiService'; +import CreateSlotUseCase from '../../../use_case/slot/CreateSlotUseCase'; +import DeleteSlotUseCase from '../../../use_case/slot/DeleteSlotUseCase'; +import GetSlotUseCase from '../../../use_case/slot/GetSlotUseCase'; +import UpdateSlotUseCase from '../../../use_case/slot/UpdateSlotUseCase'; const router = express.Router(); @@ -15,8 +18,12 @@ const authorizeDoctor = new DoctorAuthMiddleware(tokenService); const appointmentRepository = new AppointmentRepository() const validatorService = new JoiService() -const slotUseCase = new SlotUseCase(slotRepository, appointmentRepository, validatorService); -const slotController = new SlotController(slotUseCase); +const createSlotUseCase = new CreateSlotUseCase(slotRepository, validatorService); +const deleteSlotUseCase = new DeleteSlotUseCase(slotRepository, appointmentRepository, validatorService); +const getSlotUseCase = new GetSlotUseCase(slotRepository, appointmentRepository, validatorService); +const updateSlotUseCase = new UpdateSlotUseCase(slotRepository, validatorService); + +const slotController = new SlotController(createSlotUseCase, updateSlotUseCase, getSlotUseCase, deleteSlotUseCase); router.post('/day', authorizeDoctor.exec, slotController.createManyByDay.bind(slotController)); router.delete('/day', authorizeDoctor.exec, slotController.deleteManyByDay.bind(slotController)); diff --git a/server/src/use_case/slot/CreateSlotUseCase.ts b/server/src/use_case/slot/CreateSlotUseCase.ts new file mode 100644 index 00000000..81703f38 --- /dev/null +++ b/server/src/use_case/slot/CreateSlotUseCase.ts @@ -0,0 +1,87 @@ +import ISlot, { SlotStatus, Days } from "../../domain/entities/ISlot"; +import ISlotRepository from "../../domain/interface/repositories/ISlotRepository"; +import IValidatorService from "../../domain/interface/services/IValidatorService"; +import CustomError from "../../domain/entities/CustomError"; +import { StatusCode } from "../../types"; + +export default class CreateSlotUseCase { + protected interval: number; + + constructor( + private slotRepository: ISlotRepository, + private validatorService: IValidatorService + ) { + this.interval = 1; + } + + async createManyByDay(doctorId: string, slots: ISlot[], day: Days): Promise { + this.validateSlotStartTimes(slots); + this.validatorService.validateEnum(day,Object.values(Days)); + + const existingSlots = await this.slotRepository.findManyByDay(doctorId, day); + const newSlots = slots + .filter(slot => !existingSlots?.some(existing => existing.startTime === slot.startTime)) + .map(slot => ({ + ...slot, + doctorId, + status: 'available' as SlotStatus, + endTime: this.calculateEndTime(slot.startTime!), + day, + })); + + if (newSlots.length > 0) { + await this.slotRepository.createMany(newSlots); + } + } + + async createForAllDays(doctorId: string, startTimes: string[]): Promise { + startTimes.forEach(time => { + this.validatorService.validateTimeFormat(time); + this.validatorService.validateLength(time, 7, 11); + }); + + const days = Object.values(Days); + const slotsByDay = days.reduce((acc, day) => { + acc[day] = startTimes.map(startTime => ({ + startTime, + })); + return acc; + }, {} as Record); + + for (const day of days) { + const slots = slotsByDay[day]; + await this.createManyByDay(doctorId, slots, day); + } + } + + private validateSlotStartTimes(slots: ISlot[]): void { + slots.forEach(slot => { + if (!slot.startTime) { + throw new CustomError(`Missing startTime for slot: ${JSON.stringify(slot)}`, StatusCode.BadRequest); + } + this.validatorService.validateTimeFormat(slot.startTime); + this.validatorService.validateLength(slot.startTime, 7, 11); + }); + } + + + private calculateEndTime(startTime: string): string { + const [time, period] = startTime.split(' '); + const [hoursStr, minutesStr] = time.split(":"); + let hours = parseInt(hoursStr, 10); + const minutes = parseInt(minutesStr, 10); + + if (period === 'PM' && hours < 12) hours += 12; + if (period === 'AM' && hours === 12) hours = 0; + + if (isNaN(hours) || isNaN(minutes)) { + throw new CustomError("Invalid start time format", StatusCode.BadRequest); + } + + const endHour = (hours + this.interval) % 24; + const endPeriod = endHour >= 12 ? 'PM' : 'AM'; + const displayHour = endHour % 12 || 12; + + return `${displayHour.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')} ${endPeriod}`; + } +} diff --git a/server/src/use_case/slot/DeleteSlotUseCase.ts b/server/src/use_case/slot/DeleteSlotUseCase.ts new file mode 100644 index 00000000..95bc9f0d --- /dev/null +++ b/server/src/use_case/slot/DeleteSlotUseCase.ts @@ -0,0 +1,73 @@ +import ISlot, { Days } from "../../domain/entities/ISlot"; +import ISlotRepository from "../../domain/interface/repositories/ISlotRepository"; +import IAppointmentRepository from "../../domain/interface/repositories/IAppointmentRepository"; +import { AppointmentStatus } from "../../domain/entities/IAppointment"; +import CustomError from "../../domain/entities/CustomError"; +import { StatusCode } from "../../types"; +import IValidatorService from "../../domain/interface/services/IValidatorService"; + +export default class DeleteSlotUseCase { + constructor( + private slotRepository: ISlotRepository, + private appointmentRepository: IAppointmentRepository, + private validatorService: IValidatorService + ) {} + + async deleteManyByDay(doctorId: string, slots: ISlot[], day: Days): Promise { + this.validateSlotStartTimes(slots); + this.validatorService.validateEnum(day,Object.values(Days)); + + const startTimes = slots.map(el => el.startTime!); + + const bookedSlots = await this.slotRepository.findManyByDay(doctorId, day, 'booked'); + + if (bookedSlots?.length) { + const bookedSlotIds = bookedSlots + .filter(slot => startTimes.includes(slot.startTime!)) + .map(slot => slot._id) + .filter((id): id is string => id !== undefined); + + if (bookedSlotIds.length > 0) { + await this.appointmentRepository.updateManyBySlotIds(bookedSlotIds, { + status: AppointmentStatus.CANCELLED + }); + } + } + await this.slotRepository.deleteManyByDayAndTime(doctorId, day, startTimes); + } + + async deleteForAllDays(doctorId: string, startTimes: string[]): Promise { + startTimes.forEach(time => { + this.validatorService.validateTimeFormat(time); + this.validatorService.validateLength(time, 7, 11); + }); + + const days = Object.values(Days); + + const slots = await this.slotRepository.findManyByDaysAndTimes(doctorId, days, startTimes); + + if (slots?.length) { + const bookedSlotIds = slots + .filter(slot => slot.status === 'booked') + .map(slot => slot._id) + .filter((id): id is string => id !== undefined); + + if (bookedSlotIds.length > 0) { + await this.appointmentRepository.updateManyBySlotIds(bookedSlotIds, { + status: AppointmentStatus.CANCELLED + }); + } + await this.slotRepository.deleteManyByDaysAndTimes(doctorId, days, startTimes); + } + } + + private validateSlotStartTimes(slots: ISlot[]): void { + slots.forEach(slot => { + if (!slot.startTime) { + throw new CustomError(`Missing startTime for slot: ${JSON.stringify(slot)}`, StatusCode.BadRequest); + } + this.validatorService.validateTimeFormat(slot.startTime); + this.validatorService.validateLength(slot.startTime, 7, 11); + }); + } +} diff --git a/server/src/use_case/slot/GetSlotUseCase.ts b/server/src/use_case/slot/GetSlotUseCase.ts new file mode 100644 index 00000000..58f98542 --- /dev/null +++ b/server/src/use_case/slot/GetSlotUseCase.ts @@ -0,0 +1,39 @@ +import ISlot, { Days } from "../../domain/entities/ISlot"; +import ISlotRepository from "../../domain/interface/repositories/ISlotRepository"; +import IAppointmentRepository from "../../domain/interface/repositories/IAppointmentRepository"; +import IValidatorService from "../../domain/interface/services/IValidatorService"; + +export default class GetSlotUseCase { + constructor( + private slotRepository: ISlotRepository, + private appointmentRepository: IAppointmentRepository, + private validatorService: IValidatorService + ) {} + + async getAllSlots(doctorId: string): Promise { + this.validatorService.validateIdFormat(doctorId); + return await this.slotRepository.findMany(doctorId); + } + + async getSlotsByDay(doctorId: string, day: Days): Promise { + this.validatorService.validateEnum(day,Object.values(Days)); + this.validatorService.validateIdFormat(doctorId); + return await this.slotRepository.findManyByDay(doctorId, day); + } + + async getSlotsByDate(doctorId: string, date: string): Promise { + this.validatorService.validateIdFormat(doctorId); + this.validatorService.validateDateFormat(date); + const appointments = await this.appointmentRepository.findManyByDateAndDoctorId(date, doctorId); + const slotIds = appointments?.map(el => el.slotId!) || []; + const day = this.getDayFromDate(date); + return await this.slotRepository.findManyNotInSlotIds(doctorId, day, slotIds); + } + + private getDayFromDate(date: string): Days { + const dayOfWeek = new Date(date).getUTCDay(); + const dayNames = Object.values(Days); + return dayNames[dayOfWeek] as Days; + } + +} diff --git a/server/src/use_case/slot/SlotUseCases.ts b/server/src/use_case/slot/SlotUseCases.ts deleted file mode 100644 index bd10b140..00000000 --- a/server/src/use_case/slot/SlotUseCases.ts +++ /dev/null @@ -1,177 +0,0 @@ -import ISlot, { SlotStatus, Days } from "../../domain/entities/ISlot"; -import ISlotRepository from "../../domain/interface/repositories/ISlotRepository"; -import IAppointmentRepository from "../../domain/interface/repositories/IAppointmentRepository"; -import IValidatorService from "../../domain/interface/services/IValidatorService"; -import { AppointmentStatus } from "../../domain/entities/IAppointment"; -import CustomError from "../../domain/entities/CustomError"; -import { StatusCode } from "../../types"; - -export default class SlotUseCase { - protected interval: number; - - constructor( - private slotRepository: ISlotRepository, - private appointmentRepository: IAppointmentRepository, - private validatorService: IValidatorService - ) { - this.interval = 1; - } - - - async createManyByDay(doctorId: string, slots: ISlot[], day: Days): Promise { - this.validateSlotStartTimes(slots); - this.validateDay(day); - - const existingSlots = await this.slotRepository.findManyByDay(doctorId, day); - const newSlots = slots - .filter(slot => !existingSlots?.some(existing => existing.startTime === slot.startTime)) - .map(slot => ({ - ...slot, - doctorId, - status: 'available' as SlotStatus, - endTime: this.calculateEndTime(slot.startTime!), - day, - })); - - if (newSlots.length > 0) { - await this.slotRepository.createMany(newSlots); - } - } - - async createForAllDays(doctorId: string, startTimes: string[]): Promise { - startTimes.forEach(time => { - this.validatorService.validateTimeFormat(time); - this.validatorService.validateLength(time, 7, 11); - }); - - const days = Object.values(Days); - const slotsByDay = days.reduce((acc, day) => { - acc[day] = startTimes.map(startTime => ({ - startTime, - })); - return acc; - }, {} as Record); - - for (const day of days) { - const slots = slotsByDay[day]; - await this.createManyByDay(doctorId, slots, day); - } - } - - async deleteManyByDay(doctorId: string, slots: ISlot[], day: Days): Promise { - this.validateSlotStartTimes(slots); - this.validateDay(day); - - const startTimes = slots.map(el => el.startTime!); - - const bookedSlots = await this.slotRepository.findManyByDay(doctorId, day, 'booked'); - - if (bookedSlots?.length) { - const bookedSlotIds = bookedSlots - .filter(slot => startTimes.includes(slot.startTime!)) - .map(slot => slot._id) - .filter((id): id is string => id !== undefined); - - if (bookedSlotIds.length > 0) { - await this.appointmentRepository.updateManyBySlotIds(bookedSlotIds, { - status: AppointmentStatus.CANCELLED - }); - } - } - await this.slotRepository.deleteManyByDayAndTime(doctorId, day, startTimes); - } - - async deleteForAllDays(doctorId: string, startTimes: string[]): Promise { - startTimes.forEach(time => { - this.validatorService.validateTimeFormat(time); - this.validatorService.validateLength(time, 7, 11); - }); - - const days = Object.values(Days); - - const slots = await this.slotRepository.findManyByDaysAndTimes(doctorId, days, startTimes); - - if (slots?.length) { - const bookedSlotIds = slots - .filter(slot => slot.status === 'booked') - .map(slot => slot._id) - .filter((id): id is string => id !== undefined); - - if (bookedSlotIds.length > 0) { - await this.appointmentRepository.updateManyBySlotIds(bookedSlotIds, { - status: AppointmentStatus.CANCELLED - }); - } - await this.slotRepository.deleteManyByDaysAndTimes(doctorId, days, startTimes); - } - } - - async update(slot: ISlot): Promise { - this.validatorService.validateIdFormat(slot._id!); - this.validatorService.validateTimeFormat(slot.startTime!); - await this.slotRepository.update(slot); - } - - async getAllSlots(doctorId: string): Promise { - this.validatorService.validateIdFormat(doctorId); - return await this.slotRepository.findMany(doctorId); - } - - async getSlotsByDay(doctorId: string, day: Days): Promise { - this.validateDay(day); - this.validatorService.validateIdFormat(doctorId); - return await this.slotRepository.findManyByDay(doctorId, day); - } - - async getSlotsByDate(doctorId: string, date: string): Promise { - this.validatorService.validateIdFormat(doctorId); - this.validatorService.validateDateFormat(date); - const appointments = await this.appointmentRepository.findManyByDateAndDoctorId(date, doctorId); - const slotIds = appointments?.map(el => el.slotId!) || []; - const day = this.getDayFromDate(date); - return await this.slotRepository.findManyNotInSlotIds(doctorId, day, slotIds); - } - - - private getDayFromDate(date: string): Days { - const dayOfWeek = new Date(date).getUTCDay(); - const dayNames = Object.values(Days); - return dayNames[dayOfWeek] as Days; - } - - private validateSlotStartTimes(slots: ISlot[]): void { - slots.forEach(slot => { - if (!slot.startTime) { - throw new CustomError(`Missing startTime for slot: ${JSON.stringify(slot)}`, StatusCode.BadRequest); - } - this.validatorService.validateTimeFormat(slot.startTime); - this.validatorService.validateLength(slot.startTime, 7, 11); - }); - } - - private validateDay(day: Days): void { - if (!Object.values(Days).includes(day)) { - throw new CustomError('Invalid or missing day.', StatusCode.BadRequest); - } - } - - private calculateEndTime(startTime: string): string { - const [time, period] = startTime.split(' '); - const [hoursStr, minutesStr] = time.split(":"); - let hours = parseInt(hoursStr, 10); - const minutes = parseInt(minutesStr, 10); - - if (period === 'PM' && hours < 12) hours += 12; - if (period === 'AM' && hours === 12) hours = 0; - - if (isNaN(hours) || isNaN(minutes)) { - throw new CustomError("Invalid start time format", StatusCode.BadRequest); - } - - const endHour = (hours + this.interval) % 24; - const endPeriod = endHour >= 12 ? 'PM' : 'AM'; - const displayHour = endHour % 12 || 12; - - return `${displayHour.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')} ${endPeriod}`; - } -} diff --git a/server/src/use_case/slot/UpdateSlotUseCase.ts b/server/src/use_case/slot/UpdateSlotUseCase.ts new file mode 100644 index 00000000..680759ec --- /dev/null +++ b/server/src/use_case/slot/UpdateSlotUseCase.ts @@ -0,0 +1,18 @@ +import ISlot from "../../domain/entities/ISlot"; +import ISlotRepository from "../../domain/interface/repositories/ISlotRepository"; +import IValidatorService from "../../domain/interface/services/IValidatorService"; + +export default class UpdateSlotUseCase { + constructor( + private slotRepository: ISlotRepository, + private validatorService: IValidatorService + ) {} + + + async update(slot: ISlot): Promise { + this.validatorService.validateIdFormat(slot._id!); + this.validatorService.validateTimeFormat(slot.startTime!); + await this.slotRepository.update(slot); + } + +}