-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
163 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { StatusCode } from "../../types"; | ||
|
||
export class ValidationError extends Error { | ||
public statusCode: StatusCode | ||
constructor(message: string, statusCode: StatusCode) { | ||
super(message); | ||
this.name = 'ValidationError'; | ||
this.statusCode = statusCode | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
export default interface IValidatorService { | ||
validateRequiredFields(input: object): void; | ||
validateEmailFormat(email: string): boolean; | ||
validateLength(field: string, minLength: number, maxLength?: number): boolean; | ||
validateIdFormat(id: string): boolean; | ||
validatePhoneNumber(phoneNumber: string): boolean; | ||
validateDateFormat(date: string): boolean; | ||
validateEnum(field: string, enumValues: string[]): boolean; | ||
validatePassword(password: string): boolean; | ||
validateBoolean(value: any): boolean; | ||
} |
7 changes: 7 additions & 0 deletions
7
server/src/domain/interface/validators/IAppointmentValidator.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import Joi from "joi"; | ||
import IAppointment from "../../entities/IAppointment"; | ||
|
||
export interface IAppointmentValidator { | ||
createValidate(appointment: IAppointment): Joi.ValidationResult; | ||
updateValidate(appointment: IAppointment): Joi.ValidationResult; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import Joi from "joi"; | ||
import { StatusCode } from '../../types'; | ||
import IValidatorService from "../../domain/interface/services/IValidatorService"; | ||
import { ValidationError } from "../../domain/entities/ValidationError"; | ||
|
||
export default class JoiService implements IValidatorService { | ||
|
||
public validateRequiredFields(input: object): void { | ||
const schema = Joi.object().keys(Object.keys(input).reduce((acc, key) => { | ||
acc[key] = Joi.required(); | ||
return acc; | ||
}, {} as any)); | ||
|
||
const { error } = schema.validate(input); | ||
if (error) { | ||
throw new ValidationError(`Missing required fields: ${error.details.map(detail => detail.message).join(", ")}`, StatusCode.BadRequest); | ||
} | ||
} | ||
|
||
public validateEmailFormat(email: string): boolean { | ||
const schema = Joi.string().email(); | ||
const { error } = schema.validate(email); | ||
if (error) { | ||
throw new ValidationError('Invalid email format', StatusCode.BadRequest); | ||
} | ||
return true; | ||
} | ||
|
||
public validateLength(field: string, minLength: number, maxLength: number = Infinity): boolean { | ||
const schema = Joi.string().min(minLength).max(maxLength); | ||
const { error } = schema.validate(field); | ||
if (error) { | ||
throw new ValidationError(`Invalid length for field, expected between ${minLength} and ${maxLength} characters`, StatusCode.BadRequest); | ||
} | ||
return true; | ||
} | ||
|
||
public validateIdFormat(id: string): boolean { | ||
const schema = Joi.string().pattern(new RegExp("^[a-fA-F0-9]{24}$")); | ||
const { error } = schema.validate(id); | ||
if (error) { | ||
throw new ValidationError('Invalid ID format', StatusCode.BadRequest); | ||
} | ||
return true; | ||
} | ||
|
||
public validatePhoneNumber(phoneNumber: string): boolean { | ||
const schema = Joi.string().pattern(new RegExp("^[0-9]{10}$")); | ||
const { error } = schema.validate(phoneNumber); | ||
if (error) { | ||
throw new ValidationError('Invalid phone number format', StatusCode.BadRequest); | ||
} | ||
return true; | ||
} | ||
|
||
public validateDateFormat(date: string): boolean { | ||
const schema = Joi.date().iso(); | ||
const { error } = schema.validate(date); | ||
if (error) { | ||
throw new ValidationError('Invalid date format', StatusCode.BadRequest); | ||
} | ||
return true; | ||
} | ||
|
||
public validateEnum(field: string, enumValues: string[]): boolean { | ||
const schema = Joi.string().valid(...enumValues); | ||
const { error } = schema.validate(field); | ||
if (error) { | ||
throw new ValidationError(`Invalid value for field, expected one of: ${enumValues.join(", ")}`, StatusCode.BadRequest); | ||
} | ||
return true; | ||
} | ||
|
||
public validatePassword(password: string): boolean { | ||
const schema = Joi.string().min(8).pattern(/^(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/); | ||
const { error } = schema.validate(password); | ||
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 | ||
); | ||
} | ||
return true; | ||
} | ||
|
||
|
||
public validateBoolean(value: any): boolean { | ||
const schema = Joi.boolean(); | ||
const { error } = schema.validate(value); | ||
if (error) { | ||
throw new ValidationError('Invalid boolean value', StatusCode.BadRequest); | ||
} | ||
return true; | ||
} | ||
} |
12 changes: 3 additions & 9 deletions
12
server/src/presentation/controllers/appointment/AppointmentControllers.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,40 @@ | ||
import IAppointment, { AppointmentStatus } from "../../domain/entities/IAppointment"; | ||
import IAppointment, { AppointmentStatus, AppointmentType } from "../../domain/entities/IAppointment"; | ||
import IAppointmentRepository from "../../domain/interface/repositories/IAppointmentRepository"; | ||
import ISlotRepository from "../../domain/interface/repositories/ISlotRepository"; | ||
import IValidatorService from "../../domain/interface/services/IValidatorService"; | ||
|
||
export default class AppointmentUseCase { | ||
constructor( | ||
private appointRepository: IAppointmentRepository, | ||
private slotRepository: ISlotRepository | ||
private slotRepository: ISlotRepository, | ||
private validatorService: IValidatorService | ||
) { } | ||
|
||
async create({ slotId, ...appointment }: IAppointment, patientId: string): Promise<void> { | ||
async create({ slotId, appointmentDate, appointmentType, reason, doctorId, notes }: IAppointment, patientId: string): Promise<void> { | ||
this.validatorService.validateRequiredFields({ slotId, appointmentDate, appointmentType, reason, doctorId }); | ||
this.validatorService.validateIdFormat(doctorId!); | ||
this.validatorService.validateIdFormat(slotId!); | ||
this.validatorService.validateIdFormat(patientId!) | ||
this.validatorService.validateEnum(appointmentType!, Object.values(AppointmentType)); | ||
this.validatorService.validateDateFormat(appointmentDate!); | ||
this.validatorService.validateLength(reason!, 1, 255); | ||
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"); | ||
if (slot.status === 'booked') throw new Error("Slot already booked"); | ||
slot!.status = 'booked'; | ||
await this.slotRepository.update(slot); | ||
await this.appointRepository.create({ ...appointment, slotId, patientId, status: AppointmentStatus.PENDING }); | ||
await this.appointRepository.create({ | ||
slotId, | ||
patientId, | ||
status: AppointmentStatus.PENDING, | ||
notes, | ||
appointmentDate, | ||
appointmentType, | ||
reason, | ||
doctorId | ||
}); | ||
} | ||
|
||
} |