Skip to content

Commit

Permalink
slot use case updated
Browse files Browse the repository at this point in the history
  • Loading branch information
sinanptm committed Sep 21, 2024
1 parent 99a6277 commit 159376f
Show file tree
Hide file tree
Showing 8 changed files with 246 additions and 191 deletions.
2 changes: 1 addition & 1 deletion server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
28 changes: 18 additions & 10 deletions server/src/presentation/controllers/slot/SlotController.ts
Original file line number Diff line number Diff line change
@@ -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) {
Expand All @@ -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)
Expand All @@ -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) {
Expand All @@ -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)
Expand All @@ -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);
Expand All @@ -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) {
Expand All @@ -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);
Expand Down
13 changes: 10 additions & 3 deletions server/src/presentation/routers/slots/SlotsRoutes.ts
Original file line number Diff line number Diff line change
@@ -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();

Expand All @@ -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));
Expand Down
87 changes: 87 additions & 0 deletions server/src/use_case/slot/CreateSlotUseCase.ts
Original file line number Diff line number Diff line change
@@ -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<void> {
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<void> {
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<Days, ISlot[]>);

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}`;
}
}
73 changes: 73 additions & 0 deletions server/src/use_case/slot/DeleteSlotUseCase.ts
Original file line number Diff line number Diff line change
@@ -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<void> {
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<void> {
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);
});
}
}
39 changes: 39 additions & 0 deletions server/src/use_case/slot/GetSlotUseCase.ts
Original file line number Diff line number Diff line change
@@ -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<ISlot[] | null> {
this.validatorService.validateIdFormat(doctorId);
return await this.slotRepository.findMany(doctorId);
}

async getSlotsByDay(doctorId: string, day: Days): Promise<ISlot[] | null> {
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<ISlot[] | null> {
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;
}

}
Loading

1 comment on commit 159376f

@vercel
Copy link

@vercel vercel bot commented on 159376f Sep 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.