From 314390e04bb783e1fa6b7fe0793cd9ce7e03b5e6 Mon Sep 17 00:00:00 2001 From: Abdulrahman Fahmy Date: Thu, 12 Oct 2023 08:18:30 +0300 Subject: [PATCH 1/5] Added fake date generator for user and package --- server/src/tests/fakeData/generate.ts | 169 ++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 server/src/tests/fakeData/generate.ts diff --git a/server/src/tests/fakeData/generate.ts b/server/src/tests/fakeData/generate.ts new file mode 100644 index 0000000..02dbd26 --- /dev/null +++ b/server/src/tests/fakeData/generate.ts @@ -0,0 +1,169 @@ +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: 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 = 10; + +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.')); From 5c3abbaab550952c8fe3e94739e8fc10b1e7c327 Mon Sep 17 00:00:00 2001 From: Abdulrahman Fahmy Date: Fri, 13 Oct 2023 22:11:28 +0300 Subject: [PATCH 2/5] feature: add&get family members --- server/src/models/user.model.ts | 6 +- server/src/services/patient.service.ts | 83 +++++++++++++++++++++++++- 2 files changed, 86 insertions(+), 3 deletions(-) diff --git a/server/src/models/user.model.ts b/server/src/models/user.model.ts index 9458570..985896a 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'], @@ -155,7 +160,6 @@ const userSchema = new Schema( return this.role === 'Patient'; }, validate: { - //TODO validator: function (arr: any) { try { arr.forEach((elem: any) => { diff --git a/server/src/services/patient.service.ts b/server/src/services/patient.service.ts index ddcebbc..258cc76 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'; @@ -18,6 +18,83 @@ const getPatientByID = async (patientId: string) => { return UserModel.findOne({ _id: new mongoose.Types.ObjectId(patientId) }); }; +const addUserFamilyMember = async (patientID: string, userID: string, relation: string) => { + const filter = { _id: patientID }; + const update = { + userID: new mongoose.Types.ObjectId(userID), + relation: relation + }; + + const updatedUser = await UserModel.findOneAndUpdate(filter, update, { new: true }) + .then((user) => user) + .catch((e) => { + throw new HttpError(StatusCodes.INTERNAL_SERVER_ERROR, `Unable to add the family member${e}`); + }); + return { + result: updatedUser, + status: StatusCodes.OK, + message: 'family member added successfully' + }; +}; +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 +) => { + const filter = { _id: new mongoose.Types.ObjectId(patientID) }; + const update = { + name: memberName, + nationalID: nationalID, + birthDate: birthDate, + gender: gender, + relation: relation + }; + + const updatedUser = await UserModel.findOneAndUpdate(filter, update, { new: true }) + .then((user) => user) + .catch((e) => { + throw new HttpError(StatusCodes.INTERNAL_SERVER_ERROR, `Unable to add the family member${e}`); + }); + return { + result: updatedUser, + status: StatusCodes.OK, + message: 'family member added successfully' + }; +}; const viewAllDoctorsForPatient = async (patientId: string, doctorName?: string, specialty?: string, date?: Date) => { try { var sessionDiscount = 0; @@ -138,5 +215,7 @@ export { calculateFinalSessionPrice, getAllPrescription, filterPrescriptions, - selectPrescription + selectPrescription, + getFamily, + hasActivePackage }; From c238891d5302b31e524ac508e54675b74df0fee8 Mon Sep 17 00:00:00 2001 From: Abdulrahman Fahmy Date: Sat, 14 Oct 2023 13:09:11 +0300 Subject: [PATCH 3/5] stories (9:12) --- client/package-lock.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) 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": { From 3badfdd352f010ddedd5905f6f32440adb8490aa Mon Sep 17 00:00:00 2001 From: Abdulrahman Fahmy Date: Sat, 14 Oct 2023 13:35:57 +0300 Subject: [PATCH 4/5] added missed functions --- server/src/services/appointment.service.ts | 35 ++++++++++++++++++++++ server/src/services/patient.service.ts | 15 +++++++++- 2 files changed, 49 insertions(+), 1 deletion(-) 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 258cc76..77d3e8e 100644 --- a/server/src/services/patient.service.ts +++ b/server/src/services/patient.service.ts @@ -210,6 +210,18 @@ 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, @@ -217,5 +229,6 @@ export { filterPrescriptions, selectPrescription, getFamily, - hasActivePackage + hasActivePackage, + getPatientHealthRecord }; From f31c54bf15b46270b25b29889a15828fbb484ebe Mon Sep 17 00:00:00 2001 From: Abdulrahman Fahmy Date: Sat, 14 Oct 2023 14:45:10 +0300 Subject: [PATCH 5/5] test1 --- server/src/routes/user.route.ts | 6 +- server/src/services/patient.service.ts | 106 +++++++++++++++++-------- server/src/tests/fakeData/generate.ts | 7 +- 3 files changed, 80 insertions(+), 39 deletions(-) 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/patient.service.ts b/server/src/services/patient.service.ts index 77d3e8e..f52641c 100644 --- a/server/src/services/patient.service.ts +++ b/server/src/services/patient.service.ts @@ -17,25 +17,54 @@ 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 addUserFamilyMember = async (patientID: string, userID: string, relation: string) => { - const filter = { _id: patientID }; - const update = { - userID: new mongoose.Types.ObjectId(userID), - relation: relation - }; - - const updatedUser = await UserModel.findOneAndUpdate(filter, update, { new: true }) - .then((user) => user) - .catch((e) => { - throw new HttpError(StatusCodes.INTERNAL_SERVER_ERROR, `Unable to add the family member${e}`); + 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' - }; + 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({ @@ -75,26 +104,32 @@ const addNonUserFamilyMember = async ( gender: string, relation: string ) => { - const filter = { _id: new mongoose.Types.ObjectId(patientID) }; - const update = { - name: memberName, - nationalID: nationalID, - birthDate: birthDate, - gender: gender, - relation: relation - }; + 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 }) - .then((user) => user) - .catch((e) => { - throw new HttpError(StatusCodes.INTERNAL_SERVER_ERROR, `Unable to add the family member${e}`); - }); - return { - result: updatedUser, - status: StatusCodes.OK, - message: 'family member added successfully' - }; + 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 { var sessionDiscount = 0; @@ -230,5 +265,6 @@ export { selectPrescription, getFamily, hasActivePackage, - getPatientHealthRecord + getPatientHealthRecord, + addFamilyMember }; diff --git a/server/src/tests/fakeData/generate.ts b/server/src/tests/fakeData/generate.ts index 02dbd26..19224cd 100644 --- a/server/src/tests/fakeData/generate.ts +++ b/server/src/tests/fakeData/generate.ts @@ -24,7 +24,7 @@ const generateFakeUser = async (): Promise => { gender: randomArrayElement(['Male', 'Female']), phone: faker.phone.number(), addresses: [faker.location.secondaryAddress()], - role: randomArrayElement(['Patient', 'Doctor', 'Administrator']), + role: 'Patient', //randomArrayElement(['Patient', 'Doctor', 'Administrator']), profileImage: faker.image.avatar(), isEmailVerified: faker.number.int() % 2 === 0, wallet: faker.number.int(), @@ -159,11 +159,12 @@ const generateFakeVacations = (): { from: Date; to: Date }[] => { return vacations; }; -const NUM_USERS_TO_GENERATE = 10; +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.')); +// generateFakeData().then(() => console.log('Fake data generated successfully.')); +console.log(generateFamilyMember());