Skip to content

Commit

Permalink
appointment creation and slot fecthing for appointment logics updated
Browse files Browse the repository at this point in the history
  • Loading branch information
sinanptm committed Sep 15, 2024
1 parent 615848a commit 3612e13
Show file tree
Hide file tree
Showing 14 changed files with 48 additions and 70 deletions.
1 change: 1 addition & 0 deletions server/src/domain/entities/IAppointment.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export enum AppointmentStatus {
PAYMENT_PENDING = 'payment-pending',
PENDING = 'pending',
CONFIRMED = 'confirmed',
CANCELLED = 'cancelled',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ export default interface IAppointmentRepository {
create(appointment: IAppointment): Promise<void>;
update(appointment: IAppointment): Promise<void>;
updateStatusMany(appointmentIds: string[], status: AppointmentStatus): Promise<void>;
updateManyBySlotIds(slotIds: string[], fields: IAppointment): Promise<void>;
findOneBySlotId(slotId: string): Promise<IAppointment | null>;
updateManyBySlotIds(slotIds: string[],fields:IAppointment): Promise<void>;
}
findByDateAndSlot(appointmentDate: string, slotId:string): Promise<IAppointment | null>;
findManyByDateAndDoctorId(appointmentDate:string,doctorId:string):Promise<IAppointment[] | null>;
}
3 changes: 2 additions & 1 deletion server/src/domain/interface/repositories/ISlotRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ export default interface ISlotRepository {
update(slot: ISlot): Promise<void>;
createMany(slots: ISlot[]): Promise<void>;
deleteManyByDayAndTime(doctorId: string, day: Days, startTimes: string[]): Promise<void>
deleteManyByDaysAndTimes(doctorId:string,days:Days[],startTimes:string[]):Promise<void>;
findMany(doctorId: string): Promise<ISlot[] | null>;
findManyByDay(doctorId: string, day: Days, status?:SlotStatus): Promise<ISlot[] | null>;
findById(slotId: string): Promise<ISlot | null>;
findManyByDaysAndTimes(doctorId:string,days:Days[],startTimes:string[]):Promise<ISlot[]|null>;
deleteManyByDaysAndTimes(doctorId:string,days:Days[],startTimes:string[]):Promise<void>;
findManyNotInSlotIds(doctorId: string, day: Days, excludedSlotIds: string[]): Promise<ISlot[] | null>
}
5 changes: 0 additions & 5 deletions server/src/infrastructure/database/isValidObjId.ts

This file was deleted.

18 changes: 12 additions & 6 deletions server/src/infrastructure/repositories/AppointmentRepository.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,33 @@
import IAppointment, { AppointmentStatus } from "../../domain/entities/IAppointment";
import IAppointmentRepository from "../../domain/interface/repositories/IAppointmentRepository";
import AppointmentModel from "../database/AppointmentModel";
import { isValidObjectId } from "../database/isValidObjId";

export default class AppointmentRepository implements IAppointmentRepository {
model = AppointmentModel
async create(appointment: IAppointment): Promise<void> {
await this.model.create(appointment)
}
async update(appointment: IAppointment): Promise<void> {
if (!isValidObjectId(appointment._id!)) throw new Error("Invalid Object Id");
await this.model.findByIdAndUpdate(appointment._id,appointment,{new:true})
await this.model.findByIdAndUpdate(appointment._id, appointment, { new: true })
}

async findOneBySlotId(slotId: string): Promise<IAppointment | null> {
return await this.model.findOne({slotId});
return await this.model.findOne({ slotId });
}

async findManyByDateAndDoctorId(appointmentDate: string, doctorId: string): Promise<IAppointment[] | null> {
return await this.model.find({appointmentDate,doctorId});
}
async findByDateAndSlot(appointmentDate: string, slotId: string): Promise<IAppointment | null> {
return await this.model.findOne({ appointmentDate, slotId })
}

async updateStatusMany(appointmentIds: string[], status: AppointmentStatus): Promise<void> {
await this.model.updateMany({_id:{$in:appointmentIds}},{status})
await this.model.updateMany({ _id: { $in: appointmentIds } }, { status })
}

async updateManyBySlotIds(slotIds: string[], fields: Partial<IAppointment>): Promise<void> {
await this.model.updateMany({ slotId: { $in: slotIds } }, fields);
}

}
4 changes: 0 additions & 4 deletions server/src/infrastructure/repositories/DoctorRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import IDoctor from "../../domain/entities/IDoctor";
import IDoctorRepository from "../../domain/interface/repositories/IDoctorRepository";
import DoctorModel from "../database/DoctorModel";
import { Types } from 'mongoose'
import { isValidObjectId } from "../database/isValidObjId";
import { PaginatedResult } from "../../types";

export default class DoctorRepository implements IDoctorRepository {
Expand All @@ -11,8 +10,6 @@ export default class DoctorRepository implements IDoctorRepository {
return await this.model.findOne({ email }).select(["-token", "-password"]);
}
async findByID(id: string | Types.ObjectId,): Promise<IDoctor | null> {
if (typeof id === 'string' && !isValidObjectId(id)) throw new Error("Invalid Object Id");

return await this.model.findById(id).select(["-token", "-password"]);
}
async findByEmailWithCredentials(email: string): Promise<IDoctor | null> {
Expand All @@ -22,7 +19,6 @@ export default class DoctorRepository implements IDoctorRepository {
return (await this.model.create(doctor))._id;
}
async update(doctor: IDoctor): Promise<IDoctor | null> {
if (!isValidObjectId(doctor._id!)) throw new Error("Invalid Object Id");
return await this.model.findByIdAndUpdate(doctor._id, doctor, { new: true });
}
async findMany(offset: number, limit: number, isVerified: boolean, isBlocked: boolean): Promise<PaginatedResult<IDoctor>> {
Expand Down
5 changes: 0 additions & 5 deletions server/src/infrastructure/repositories/PatientRepository.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { IPatient } from "../../domain/entities/IPatient";
import IPatientRepository from "../../domain/interface/repositories/IPatientRepository";
import { PaginatedResult } from "../../types";
import { isValidObjectId } from "../database/isValidObjId";
import PatientModel from "../database/PatientModel";

export default class PatientRepository implements IPatientRepository {
Expand Down Expand Up @@ -43,13 +42,9 @@ export default class PatientRepository implements IPatientRepository {
return await this.model.findOne({ email }).select(["-password", "-token"]);
}
async findByIdAndUpdate(id: string, patient: IPatient): Promise<IPatient | null> {
if (!isValidObjectId(id)) throw new Error("Invalid Object Id");
return await this.model.findByIdAndUpdate(id, patient, { new: true });
}
async findById(id: string): Promise<IPatient | null> {
if (!isValidObjectId(id)) {
throw new Error(`Invalid Object Id`);
}
return await this.model.findById(id).select(["-password", "-token"]);
}
async findByEmailWithCredentials(email: string): Promise<IPatient | null> {
Expand Down
10 changes: 8 additions & 2 deletions server/src/infrastructure/repositories/SlotRepository.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import ISlot, { Days, SlotStatus } from "../../domain/entities/ISlot";
import ISlotRepository from "../../domain/interface/repositories/ISlotRepository";
import { isValidObjectId } from "../database/isValidObjId";
import SlotModel from "../database/SlotModel";

export default class SlotRepository implements ISlotRepository {
Expand All @@ -27,6 +26,14 @@ export default class SlotRepository implements ISlotRepository {
startTime: { $in: startTimes }
});
}

async findManyNotInSlotIds(doctorId: string, day: Days, excludedSlotIds: string[]): Promise<ISlot[] | null> {
return await this.model.find({
doctorId,
day,
_id: { $nin: excludedSlotIds }
});
}
async update(slot: ISlot): Promise<void> {
await this.model.findByIdAndUpdate(slot._id, slot, { upsert: true });
}
Expand All @@ -44,7 +51,6 @@ export default class SlotRepository implements ISlotRepository {
}

async findById(slotId: string): Promise<ISlot | null> {
if (!isValidObjectId(slotId)) throw new Error("Invalid Object Id")
return await this.model.findById(slotId)
}

Expand Down
4 changes: 2 additions & 2 deletions server/src/infrastructure/services/JoiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export default class JoiService implements IValidatorService {

public validateTimeFormat(time: string): boolean {
const schema = Joi.string().pattern(/^([01]\d|2[0-3]):([0-5]\d) (AM|PM)$/);

const { error } = schema.validate(time);
if (error) {
throw new ValidationError('Invalid time format, must be in "HH:MM AM/PM" format', StatusCode.BadRequest);
Expand All @@ -87,7 +87,7 @@ export default class JoiService implements IValidatorService {
if (error) {
throw new ValidationError(
'Password must be at least 8 characters, include at least one uppercase letter, one number, and one special character',
StatusCode.BadRequest
StatusCode.UnprocessableEntity
);
}
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { NextFunction, Request, Response } from "express";
import AuthenticationUseCase from "../../../use_case/doctor/AuthenticationUseCase";
import { Cookie, StatusCode } from "../../../types";
import IDoctor from "../../../domain/entities/IDoctor";
import { isValidatePassword, isValidEmail } from "../../validators/entitieValidators";

export default class AuthDoctorController {
constructor(private authDoctorUseCase: AuthenticationUseCase) {}
Expand Down
21 changes: 0 additions & 21 deletions server/src/presentation/validators/AppointmentValidator.ts

This file was deleted.

14 changes: 0 additions & 14 deletions server/src/presentation/validators/entitieValidators.ts

This file was deleted.

13 changes: 10 additions & 3 deletions server/src/use_case/appointment/AppointmentUseCase.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import IAppointment, { AppointmentStatus, AppointmentType } from "../../domain/entities/IAppointment";
import { ValidationError } from "../../domain/entities/ValidationError";
import IAppointmentRepository from "../../domain/interface/repositories/IAppointmentRepository";
import ISlotRepository from "../../domain/interface/repositories/ISlotRepository";
import IValidatorService from "../../domain/interface/services/IValidatorService";
import { StatusCode } from "../../types";

export default class AppointmentUseCase {
constructor(
Expand All @@ -21,9 +23,14 @@ export default class AppointmentUseCase {
if (notes) this.validatorService.validateLength(notes, 0, 255);

const slot = await this.slotRepository.findById(slotId!);
if (!slot) throw new Error("Slot Not Found");
if (slot.status === 'booked') throw new Error("Slot already booked");
slot!.status = 'booked';
if (!slot) throw new ValidationError("Slot Not Found", StatusCode.NotFound);

if (slot.status === 'booked') {
const bookedAppointment = await this.appointRepository.findByDateAndSlot(appointmentDate!, slotId!);
if (bookedAppointment) throw new ValidationError("Slot already booked", StatusCode.Conflict);
} else {
slot!.status = 'booked';
}
await this.slotRepository.update(slot);
await this.appointRepository.create({
slotId,
Expand Down
13 changes: 9 additions & 4 deletions server/src/use_case/slot/SlotUseCases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ 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 { ValidationError } from "../../domain/entities/ValidationError";
import { StatusCode } from "../../types";

export default class SlotUseCase {
protected interval: number;
Expand Down Expand Up @@ -123,10 +125,13 @@ export default class SlotUseCase {

async getSlotsByDate(doctorId: string, date: string): Promise<ISlot[] | null> {
this.validatorService.validateIdFormat(doctorId);
const appointments = await this.appointmentRepository.findManyByDateAndDoctorId(date, doctorId);
const slotIds = appointments?.map(el => el.slotId!) || [];
const day = this.getDayFromDate(date);
return await this.slotRepository.findManyByDay(doctorId, day, "available");
return await this.slotRepository.findManyNotInSlotIds(doctorId, day, slotIds);
}


private getDayFromDate(date: string): Days {
const dayOfWeek = new Date(date).getUTCDay();
const dayNames = Object.values(Days);
Expand All @@ -136,7 +141,7 @@ export default class SlotUseCase {
private validateSlotStartTimes(slots: ISlot[]): void {
slots.forEach(slot => {
if (!slot.startTime) {
throw new Error(`Missing startTime for slot: ${JSON.stringify(slot)}`);
throw new ValidationError(`Missing startTime for slot: ${JSON.stringify(slot)}`,StatusCode.BadRequest);
}
this.validatorService.validateTimeFormat(slot.startTime);
this.validatorService.validateLength(slot.startTime, 7, 11);
Expand All @@ -145,7 +150,7 @@ export default class SlotUseCase {

private validateDay(day: Days): void {
if (!Object.values(Days).includes(day)) {
throw new Error('Invalid or missing day.');
throw new ValidationError('Invalid or missing day.',StatusCode.BadRequest);
}
}

Expand All @@ -159,7 +164,7 @@ export default class SlotUseCase {
if (period === 'AM' && hours === 12) hours = 0;

if (isNaN(hours) || isNaN(minutes)) {
throw new Error("Invalid start time format");
throw new ValidationError("Invalid start time format",StatusCode.BadRequest);
}

const endHour = (hours + this.interval) % 24;
Expand Down

1 comment on commit 3612e13

@vercel
Copy link

@vercel vercel bot commented on 3612e13 Sep 15, 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.