diff --git a/client/package-lock.json b/client/package-lock.json index 2024e09..0f8e33d 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -19314,16 +19314,16 @@ } }, "node_modules/typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=14.17" + "node": ">=4.2.0" } }, "node_modules/unbox-primitive": { @@ -34327,9 +34327,9 @@ } }, "typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "peer": true }, "unbox-primitive": { diff --git a/server/src/models/user.model.ts b/server/src/models/user.model.ts index 8aa1013..c1004e1 100644 --- a/server/src/models/user.model.ts +++ b/server/src/models/user.model.ts @@ -4,6 +4,7 @@ import mongoose, { Document, Schema } from 'mongoose'; interface FamilyMemberWithID { userID: mongoose.Types.ObjectId; + nationalID: string; relation: 'Husband' | 'Wife' | 'Child'; } @@ -11,6 +12,8 @@ interface FamilyMemberWithDetails { name: string; nationalID: string; phone: string; + birthDate: Date; + gender: 'Male' | 'Female'; relation: 'Husband' | 'Wife' | 'Child'; } @@ -144,6 +147,8 @@ const userSchema = new Schema( name: String, nationalID: String, phone: String, + birthDate: Date, + gender: { type: String, enum: ['Male', 'Female'] }, relation: { type: String, enum: ['Husband', 'Wife', 'Child'], @@ -152,7 +157,6 @@ const userSchema = new Schema( } ], validate: { - //TODO validator: function (arr: any) { try { arr.forEach((elem: any) => { diff --git a/server/src/routes/user.route.ts b/server/src/routes/user.route.ts index c7bd373..cc710a7 100644 --- a/server/src/routes/user.route.ts +++ b/server/src/routes/user.route.ts @@ -2,6 +2,7 @@ import express from 'express'; import controller from '../controllers/controller'; import { isAuthenticated, isAuthorized, queryParser } from '../middlewares'; import { getPatients, selectPatient } from '../services/doctor.service'; +import { addFamilyMember } from '../services//patient.service'; const UserMeRouter = express.Router(); UserMeRouter.get('/me/patient/', (req, res) => controller(res)(getPatients)(req.body.doctorID, req.query)); @@ -9,5 +10,8 @@ UserMeRouter.get('/me/patient/:id', (req, res) => controller(res)(selectPatient) UserMeRouter.put('/me/info/', (req, res) => { // controller(res)(updateInfo)(req.body); }); - +UserMeRouter.post('/me/family', (req, res) => { + // TODO if login is used we should add the patient id in the body form the jwt token + controller(res)(addFamilyMember)(req.body); +}); export default UserMeRouter; diff --git a/server/src/services/appointment.service.ts b/server/src/services/appointment.service.ts index f169a28..3774880 100644 --- a/server/src/services/appointment.service.ts +++ b/server/src/services/appointment.service.ts @@ -1,5 +1,7 @@ import AppointmentModel from '../models/appointment.model'; import { StatusCodes } from 'http-status-codes'; +import mongoose from 'mongoose'; +import { HttpError } from '../utils'; const getUpcomingPatients = async (doctorID: string) => { //get patients that have appointments with this doctor const patients = await AppointmentModel.find({ doctorID: doctorID }).distinct('patientID').populate('patientID'); @@ -28,3 +30,36 @@ const getPatients = async (doctorID: string) => { result: patients }; }; +const filterAppointment = async ( + doctorID?: string, + patientID?: string, + status?: string, + startDate?: Date, + endDate?: Date +) => { + try { + let filter = {}; + if (doctorID) filter = { ...filter, doctorID: new mongoose.Types.ObjectId(doctorID) }; + if (patientID) filter = { ...filter, patientID: new mongoose.Types.ObjectId(patientID) }; + if (status) filter = { ...filter, status: status }; + if (startDate && endDate) { + filter = { + ...filter, + startTime: { $gte: startDate, $lte: endDate }, + endTime: { $gte: startDate, $lte: endDate } + }; + } else if (startDate) { + filter = { ...filter, startTime: { $lte: startDate }, endTime: { $gte: startDate } }; + } else { + filter = { ...filter, startTime: { $lte: { endDate } }, endTime: { $gte: endDate } }; + } + const result = await AppointmentModel.find(filter); + return { + result: result, + status: StatusCodes.OK, + message: 'Successfully retrieved appointments' + }; + } catch (e) { + throw new HttpError(StatusCodes.INTERNAL_SERVER_ERROR, `Error happened while retrieving appointments${e}`); + } +}; diff --git a/server/src/services/patient.service.ts b/server/src/services/patient.service.ts index ddcebbc..f52641c 100644 --- a/server/src/services/patient.service.ts +++ b/server/src/services/patient.service.ts @@ -1,4 +1,4 @@ -import UserModel, { IPatient, IUserDocument, IUser, IDoctor } from '../models/user.model'; +import UserModel, { IPatient, IUserDocument, IUser, IDoctor, FamilyMember } from '../models/user.model'; import PackageModel, { IPackageDocument } from '../models/package.model'; import mongoose, { Document } from 'mongoose'; import { getAllDoctor } from './doctor.service'; @@ -17,6 +17,118 @@ const hasActivePackage = (patient: (IPatient & Document) | IPatient): Boolean => const getPatientByID = async (patientId: string) => { return UserModel.findOne({ _id: new mongoose.Types.ObjectId(patientId) }); }; +const addFamilyMember = async (body: any) => { + console.log(body); + if (!(body.patientID && body.relation && body.nationalID)) { + throw new HttpError(StatusCodes.BAD_REQUEST, 'Please provide patientID, relation and nationalID'); + } + if (body.name && body.birthDate && body.gender) { + return await addNonUserFamilyMember( + body.patientID, + body.name, + body.nationalID, + body.birthDate, + body.gender, + body.relation + ); + } else if (body.userID) { + return await addUserFamilyMember(body.packageID, body.userID, body.relation, body.nationalID); + } + throw new HttpError( + StatusCodes.BAD_REQUEST, + 'Either name, nationalID,gender,birthDate, and phone should be provided, or userID and relation should be provided.' + ); +}; +const addUserFamilyMember = async (patientID: string, userID: string, relation: string, nationalID: string) => { + try { + const filter = { _id: new mongoose.Types.ObjectId(patientID) }; + const update = { + $push: { + family: { + userID: new mongoose.Types.ObjectId(userID), + relation: relation, + nationalID: nationalID + } + } + }; + + const updatedUser = await UserModel.findOneAndUpdate(filter, update, { new: true }).catch((e) => { + console.log(e); + }); + return { + result: updatedUser, + status: StatusCodes.OK, + message: 'Family member added successfully' + }; + } catch (error) { + throw new HttpError(StatusCodes.INTERNAL_SERVER_ERROR, `Unable to add the family member: ${error}`); + } +}; + +const getFamily = async (patientID: string) => { + try { + const family = await UserModel.find({ + _id: new mongoose.Types.ObjectId(patientID) + }).select({ + family: 1 + }); + let result = []; + for (const member of family as unknown as FamilyMember[]) { + let memberResult = { ...(member as any).toObject() }; + if (memberResult.userID) { + const memberInfo = await UserModel.findById(memberResult.userID as mongoose.Types.ObjectId).select({ + name: 1, + birthDate: 1, + gender: 1, + phone: 1 + }); + memberResult = { ...memberResult, ...memberInfo!.toObject() }; + delete memberResult.userID; + } + result.push(memberResult); + } + return { + result: result, + status: StatusCodes.OK, + message: 'Family members retrieved successfully' + }; + } catch (e) { + throw new HttpError(StatusCodes.INTERNAL_SERVER_ERROR, `Unable to add the family member${e}`); + } +}; +const addNonUserFamilyMember = async ( + patientID: string, + memberName: string, + nationalID: string, + birthDate: Date, + gender: string, + relation: string +) => { + try { + const filter = { _id: new mongoose.Types.ObjectId(patientID) }; + const update = { + $push: { + family: { + name: memberName, + nationalID: nationalID, + birthDate: birthDate, + gender: gender, + relation: relation + } + } + }; + + const updatedUser = await UserModel.findOneAndUpdate(filter, update, { new: true }); + + return { + result: updatedUser, + status: StatusCodes.OK, + message: 'Family member added successfully' + }; + } catch (error) { + throw new HttpError(StatusCodes.INTERNAL_SERVER_ERROR, `Unable to add the family member: ${error}`); + } +}; const viewAllDoctorsForPatient = async (patientId: string, doctorName?: string, specialty?: string, date?: Date) => { try { @@ -133,10 +245,26 @@ const selectPrescription = async (prescriptionID: string) => { }; } }; +const getPatientHealthRecord = async (patientID: string) => { + try { + const result = await UserModel.findOne({ _id: new mongoose.Types.ObjectId(patientID) }).select('medicalHistory'); + return { + result: result, + status: StatusCodes.OK, + message: 'Successfully retrieved health record' + }; + } catch (e) { + throw new HttpError(StatusCodes.INTERNAL_SERVER_ERROR, `Error happened while retrieving health record${e}`); + } +}; export { viewAllDoctorsForPatient, calculateFinalSessionPrice, getAllPrescription, filterPrescriptions, - selectPrescription + selectPrescription, + getFamily, + hasActivePackage, + getPatientHealthRecord, + addFamilyMember }; diff --git a/server/src/tests/fakeData/generate.ts b/server/src/tests/fakeData/generate.ts new file mode 100644 index 0000000..19224cd --- /dev/null +++ b/server/src/tests/fakeData/generate.ts @@ -0,0 +1,170 @@ +import mongoose from 'mongoose'; +import { faker } from '@faker-js/faker'; +import UserModel, { + IUser, + IDoctor, + IPatient, + FamilyMember, + IEmergencyContact, + ICommonUser +} from '../../models/user.model'; // Adjust the path based on your project structure +import { getPackageById } from '../../services/package.service'; +const connectionString = 'mongodb://127.0.0.1:27017/clinic'; +const generateFakeUser = async (): Promise => { + let user: ICommonUser = { + name: { + first: faker.internet.displayName(), + middle: faker.internet.displayName(), + last: faker.internet.displayName() + }, + email: faker.internet.email(), + username: faker.internet.userName(), + password: faker.internet.password(), + birthDate: faker.date.past(), + gender: randomArrayElement(['Male', 'Female']), + phone: faker.phone.number(), + addresses: [faker.location.secondaryAddress()], + role: 'Patient', //randomArrayElement(['Patient', 'Doctor', 'Administrator']), + profileImage: faker.image.avatar(), + isEmailVerified: faker.number.int() % 2 === 0, + wallet: faker.number.int(), + isCorrectPassword: async (password: string) => { + return true; + } + }; + let restOfAttributes: IPatient | IDoctor | null = null; + if (user.role === 'Patient') { + restOfAttributes = user as IPatient; + restOfAttributes.family = generateFakeFamily(1); + restOfAttributes.emergencyContact = generateFakeEmergencyContacts(); + restOfAttributes.medicalHistory = generateFakeMedicalHistory(); + restOfAttributes.package = await generateFakePackageForUser(); + } else if (user.role === 'Doctor') { + restOfAttributes = user as IDoctor; + restOfAttributes.hourRate = faker.number.int({ min: 50, max: 200 }); + restOfAttributes.hospital = faker.company.name(); + restOfAttributes.educationBackground = faker.lorem.sentence(); + restOfAttributes.specialty = faker.lorem.word(); + restOfAttributes.weeklySlots = generateFakeWeeklySlots(); + restOfAttributes.vacations = generateFakeVacations(); + } + + return { ...user, ...restOfAttributes }; +}; + +const generateFakeFamily = (familyCount: number): FamilyMember[] => { + let family = []; + for (let i = 0; i < familyCount + 1; i++) { + family.push(generateFamilyMember()); + } + + return []; +}; +const generateFamilyMember = () => { + return { + name: faker.internet.displayName(), + nationalID: faker.number.int({ min: 0, max: 9 }).toString().repeat(15), + phone: faker.number.int({ min: 0, max: 9 }).toString().repeat(11), + relation: randomArrayElement(['Husband', 'Wife', 'Child']) + }; +}; +const generateFakeEmergencyContacts = (): IEmergencyContact[] => { + const emergencyContacts = []; + for (let i = 0; i < 2; i++) { + emergencyContacts.push({ + name: faker.internet.displayName(), + phone: faker.phone.number(), + relation: randomArrayElement(['Husband', 'Wife', 'Child']) + }); + } + + return emergencyContacts; +}; + +const generateFakeMedicalHistory = (): { name: string; medicalRecord: string }[] => { + const medicalHistory = []; + for (let i = 0; i < 3; i++) { + medicalHistory.push({ + name: faker.lorem.word(), + medicalRecord: faker.lorem.sentence() + }); + } + + return medicalHistory; +}; +import PackageModel from '../../models/package.model'; + +const generateFakePackage = async () => { + const pkg = { + name: randomArrayElement(['Gold', 'Silver', 'Platinum']), + price: faker.number.int({ min: 100, max: 2000 }), + sessionDiscount: faker.number.int({ min: 0, max: 99 }), + medicineDiscount: faker.number.int({ min: 0, max: 99 }), + familyDiscount: faker.number.int({ min: 0, max: 99 }), + isLatest: true + }; + mongoose.connect(connectionString); + const newPkg = new PackageModel(pkg); + await newPkg.save(); + return newPkg; +}; +const getPackageId = () => { + return generateFakePackage().then((result) => result._id); +}; +const generateFakePackageForUser = async (): Promise< + | { packageID: mongoose.Types.ObjectId; packageStatus: 'Subscribed' | 'Unsubscribed' | 'Cancelled'; endDate: Date } + | undefined +> => { + const packageID = await getPackageId(); + const packageStatus = randomArrayElement(['Subscribed', 'Unsubscribed', 'Cancelled']); + const endDate = faker.date.future(); + return { packageID, packageStatus, endDate }; +}; +const randomArrayElement = (arr: any) => { + return arr[faker.number.int({ min: 0, max: arr.length })]; +}; +const generateFakeWeeklySlots = (): { [day: string]: { from: number; to: number; maxPatients: number }[] } => { + const weeklySlots: { [day: string]: { from: number; to: number; maxPatients: number }[] } = {}; + const daysOfWeek = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; + + for (const day of daysOfWeek) { + weeklySlots[day] = []; + + for (let i = 0; i < 10; i++) { + const startHour = faker.number.int({ min: 8, max: 18 }); + const endHour = startHour + faker.number.int({ min: 1, max: 3 }); + const maxPatients = faker.number.int({ min: 1, max: 10 }); + + weeklySlots[day].push({ + from: startHour, + to: endHour, + maxPatients + }); + } + } + + return weeklySlots; +}; +const generateFakeVacations = (): { from: Date; to: Date }[] => { + const vacations = []; + for (let i = 0; i < 2; i++) { + const startDate = faker.date.future({ years: 1 }); + const endDate = new Date(startDate); + endDate.setDate(endDate.getDate() + faker.number.int({ min: 1, max: 10 })); + vacations.push({ + from: startDate, + to: endDate + }); + } + return vacations; +}; + +const NUM_USERS_TO_GENERATE = 1; + +const generateFakeData = async (): Promise => { + const fakeUsers: IUser[] = await Promise.all(Array.from({ length: NUM_USERS_TO_GENERATE }, generateFakeUser)); + console.log(fakeUsers); +}; + +// generateFakeData().then(() => console.log('Fake data generated successfully.')); +console.log(generateFamilyMember());