Skip to content

advanced-computer-lab-2023/Mern-overflow-Clinic

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

logo

El7a2ny Clinic App

El7a2ny Clinic is a comprehensive healthcare platform that caters to the diverse needs of doctors and patients, offering an integrated suite of features tailored to each user group. Doctors benefit from tools to streamline their practice, from updating professional information and managing appointments to accessing patient records, prescribing medications, and conducting video calls. Patients experience a user-friendly interface, allowing them to manage health records, schedule appointments, subscribe to health packages, and interact with healthcare providers through secure chat and video calls. The platform prioritizes efficiency, accessibility, and transparent communication, empowering healthcare stakeholders in their respective roles.

Badges

TypeScript JavaScript MongoDB Express.js React.js NodeJS JWT Git MUI GitHub Stripe JEST Docker Postman NPM

Table of Contents

Build Status πŸ”¨

  • The project is currently in development.
  • The project needs to be deployed through a cloud service.
  • The project needs more thorough testing.
  • Need to add screenshots, code examples, and tests to the README

⬆

Code Style πŸ“œ

The code style used is eslint and prettier. The code style is enforced using pre-commit hooks

To use the code style we follow, run these commands

Install pre-commit package by running

> pip install pre-commit

Once installed, run the following for a one-time setup

> pre-commit install

You will then need to run the following command each time before your next commit attempt

> npx prettier . --write

⬆

Demo & Screenshots πŸ“Έ

Authentication
Sign in Page

Sign In Page

Patient Registration

Patient Registration

Admin Controls
Manage Admins

Manage Admins

Manage Packages

Manage Packages

Manage Doctor Requests

Manage Doctor Requests

Change Password

Change Password

Patient Controls
Manage Family

Manage Family

Manage Profile

View My Profile

Manage Prescriptions

Manage Prescriptions

Chat/Video

Chat/Video

Doctor Controls
Manage Profile

Manage Profile

Manage Contracts

Manage Contracts

Manage Appointments

Manage Appointments

Manage Prescriptions

Manage Prescriptions

⬆

Tech Stack πŸ§°πŸ”§

Client: React, Redux, Material-UI, JavaScript

Server: Node, Express, MongoDB, Mongoose, TypeScript, JWT, Stripe API, Postman, Jest

General: Docker, Git & GitHub, VSCode

⬆

Features ✨

Guests can
  • Sign in to their account
  • Sign up as a patient
  • Request to sign up as a doctor
  • Reset forgotten password through OTP sent to email
Logged in System Users can
  • Change their password
  • Sign out
Admins can
  • Add another admin with a set username and password
  • Remove doctor/admin from the system
  • View all information uploaded by doctors who applied to join the platform
  • Accept or reject doctor proposals
  • Add/Update/Delete health packages with different price ranges
  • View a total sales report based on a chosen month
  • View information about any user on the system
Doctors can
  • Update their information (email, hourly rate, affiliation)
  • View and accept employment contract
  • Add their available time slots for appointments
  • Filter appointments by date/status
  • View information and health records of patients registered with them
  • View all new and old prescriptions and their statuses
  • View a list of all their patients
  • Search for a patient using their name
  • Filter patients based on upcoming appointments
  • Receive notifications of their appointments on the system and by mail
  • View a list of all their upcoming/past appointments
  • Filter appointments by date or status
  • Reschedule an appointment for a patient
  • Cancel an appointment
  • Receive notifications about canceled or rescheduled appointments on the system and by mail
  • Schedule a follow-up for a patient
  • Add / Delete medicine to/from the prescription from the pharmacy platform
  • Add / Update dosage for each medicine added to the prescription
  • Download the selected prescription (PDF)
  • Add new health records for a patient
  • Start / End a video call with a patient
  • Chat with a patient
  • Add a patient's prescription
  • Update a patient's prescription before it is submitted to the pharmacy
  • Accept or revoke a follow-up session request from a patient
  • View the amount in their wallet
Patients can
  • Upload/remove documents (PDF, JPEG, JPG, PNG) for their medical history
  • View uploaded health records
  • Add family members to the system
  • Link another existing patient's account as a family member
  • View registered family members
  • Choose to pay for their appointments using a wallet or credit card
  • Enter credit card details and pay for an appointment using Stripe
  • Filter appointments by date/status
  • View all new and old prescriptions and their statuses
  • View health package options and details
  • Subscribe to a health package for themselves and their family members
  • Pay for the chosen health package using the wallet or credit card
  • View subscribed health packages for themselves and their family members
  • View the status of their health care package subscription
  • Cancel a subscription to a health package
  • View a list of all doctors along with their specialty, and session price (based on subscribed health package if any)
  • Search for a doctor by name and/or specialty
  • Filter a doctor by specialty and/or availability on a certain date and at a specific time
  • View details about a specific selected doctor
  • Select an appointment date and time for themselves or for a family member
  • Receive a notification of their appointment on the system and by mail
  • View a list of their upcoming/past appointments
  • Filter appointments by date or status
  • Reschedule an appointment for themselves or for a family member
  • Cancel an appointment for themselves or for a family member
  • Receive notification about canceled or rescheduled appointments on the system and by mail
  • View a list of all their prescriptions
  • Filter prescriptions based on date or doctor or fulfillment status
  • View the details of a selected prescription
  • Pay directly for the prescription items by wallet or credit card
  • Download a prescription (PDF)
  • Start / End a video call with a doctor
  • Chat with a doctor
  • Request a follow-up to a previous appointment for themselves or a family member
  • Receive a refund in their wallet when a doctor cancels an appointment
  • View the amount in their wallet

⬆

Code Examples πŸ‘‰

Delete an Admin from System
const deleteAdmin = async (req: Request, res: Response) => {
  const id = req.params.id;
  const adminToDelete = adminstrator
    .findByIdAndDelete({ _id: id })
    .then((adminToDelete) => {
      res.status(200).json(adminToDelete);
    })
    .catch((err) => {
      res.status(400).json(err);
    });
};
Create an Appointment
const createAppointment = async (req: Request, res: Response) => {
  req.body.duration = 1;
  req.body.status = "upcoming";
  req.body.appointmentType = "regular";
  const patientEmail = await Users.findById(req.body.patient).then(
    (pat) => pat?.email,
  );
  const doctorEmail = await Users.findById(req.body.doctor).then(
    (doc) => doc?.email,
  );

  if (patientEmail === undefined || doctorEmail === undefined) {
    return res.status(400).json();
  }
  const newApt = appointment
    .create(req.body)
    .then((newApt) => {
      const subject = "Appointment Booked";
      let html = `Hello patient, \n A new appointment was booked with date ${req.body.date}. \n Please be on time. \n With Love, \n El7a2ny Clinic xoxo.`;
      sendMailService.sendMail(patientEmail, subject, html);
      html = `Hello doctor, \n A new appointment was booked with date ${req.body.date}. \n Please be on time. \n With Love, \n El7a2ny Clinic xoxo.`;
      sendMailService.sendMail(doctorEmail, subject, html);
      return res.status(200).json(newApt);
    })
    .catch((err) => {
      console.log(err);
      return res.status(400).json(err);
    });
};
Create Contract
const createContract = async (req: Request, res: Response) => {
    req.body.date = Date.now();
    req.body.admin = req.params.id;

    const newContract = contract
    .create(req.body)
    .then((newContract) => {
        return res.status(200).json(newContract);
    })
    .catch((err) => {
        return res.status(400).json(err);
    });
}
Add Admin Form Component
<Box component="form" onSubmit={handleSubmit(onSubmit)}>
  <TextField label="Username" />
  <TextField label="Email" />
  <TextField label="Password" />
  <Button type="submit" variant="outlined">
    Add Admin
  </Button>
</Box>
AppBar Component
<AppBar position="static">
  <Toolbar>
    <IconButton>
      <MenuIcon />
    </IconButton>
    <Typography>{props.title}</Typography>
    <Button type="button" color="inherit" onClick={handleLogout}>
      {" "}
      Log out{" "}
    </Button>
  </Toolbar>
</AppBar>

⬆

Installation πŸ“₯

Clone the project

> git clone https://github.com/advanced-computer-lab-2023/Mern-overflow-Clinic

Go to the project directory

> cd Mern-overflow-Clinic

Install dependencies

> cd server && npm i && cd -
> cd client && npm i && cd -

⬆

How to Use ❓

Using Docker

First, you need to build the container. You need to do this the first time only.

> make build

Start the back-end

> make up

Start the client side

> make f-up
Manually

Start the back-end server

> cd server && npm run dev

Start the client side

> cd client && npm start

Environment Variables πŸ“ƒ

To run this project, you will need to add the following environment variables to your server/.env file. You can find an environment variables file example in server/.env.example

MONGO_URI

PORT

JWT_SECRET

EMAIL

EMAILPASSWORD

⬆

API Reference πŸ“–

Authentication routes
method route returns
POST /auth/login/ Log in
POST /auth/logout/ Log out
POST /auth/reset/ Reset Password
POST /auth/resetwithtoken/ Reset Password with Token
POST /auth/change/ Change Password
POST /auth/token/ Sends User Token to Pharmacy
Admin routes
method route returns
GET /admins/ View all admins
POST /admins/:id/createContract Create a contract
POST /admins/ Create an admin
POST /admins/acceptDoctorRequest/ Accept a doctor request
POST /admins/rejectDoctorRequest/ Reject a doctor request
DELETE /admins/:id/ Delete an admin
Doctor routes
method route returns
GET /doctors/ View all doctors
GET /doctors/pendingDoctors/ View all pending doctors
GET /doctors/:id/ View all details about a doctor
GET /doctors/:id/slots/ View all slots of a doctor
GET /doctors/:id/completedAppointments/ View all completed appointments of a doctor
GET /doctors/:id/ View details of a doctor
GET /doctors/:id/wallet/ View wallet amount of a doctor
GET /doctors/:id/patients/ View all patients of a doctor
GET /doctors/:id/registeredPatients/ View all patients having upcoming appointment with a doctor
GET /doctors/:id/patients/:pId/ View all details about a patient by Id
GET /doctors/:id/search/ View all details about a patient by name
GET /doctors/:id/res/ View all patients having non-cancelled appointment with a doctor
GET /doctors/:dId/search/ Patients can view details of a selected doctor
GET /doctors/doctorsSearch/ Patients can search for a doctor by name or speciality
POST /doctors/ Create a doctor
POST /doctors/filter/ View doctors based on specialization
POST /doctors/:id/addHealthRecord/ Add a health record
POST /doctors/:id/createFollowup/ View doctors based on specialization
PUT /doctors/:id/ Update a doctor's details
PUT /doctors/:id/acceptContract Accept a contract
PUT /doctors/:id/rejectContract/ Reject a contract
PUT /doctors:id/addSlots/ Add slots for a doctor
DELETE /doctors:id/ Delete a doctor
Patient routes
method route returns
GET /patients/ View all patients
GET /patients/:id/ View all details about a patient
GET /patients/:id/family/ View all family members of a patient
GET /patients/:id/relatives/ View all details of one relative
GET /patients/:id/price/ View doctors by session price
GET /patients/:id/prescriptions/ View a patient's prescriptions
GET /patients/:id/packages/ View a patient's packages
GET /patients/:id/packages/:pId/discount View a patient's package discount
GET /patients/:id/wallet/ View a patient's wallet amount
GET /patients/:id/documents/ View a patient's documents
GET /patients/:id/document/ View a patient's document
GET /patients/:id/healthRecords/ View a patient's health records
POST /patients/ Create a patient
POST /patients/:id/familyMember/ Add a family member to a patient
POST /patients/linkfamilyMember/ Link a patient's account to another patient's family
POST /patients/:id/documents/ Add a document to a patient's account
POST /patients/:id/packages/:packageId Add a package to a patient's account
POST /patients:id/packages/:pId/:packageId/ Add a package to a family member
POST /patients/:id/prescriptionsFilter/ Filter a patient's prescriptions
DELETE /patients/:id/ Delete a patient
DELETE /patients/:id/documents/ Delete a document
DELETE /patients/:id/packages/ Delete a package
DELETE /patients/:id/packages/pId/ Delete a package from a family member
Package routes
method route returns
GET /packages/ View all packages
GET /packages/:id/ View details of a package
POST /packages/ Create a package
PUT /packages/:id/ Update details of a package
DELETE /packages/:id/ Delete a package
Contract routes
method route returns
GET /contracts/:id/ Read a contract
PUT /contracts/ Update a contract
DELETE /contracts/ Delete a contract
Appointment routes
method route returns
GET /appointments/ View all appointments
GET /appointments/:id/ View details of an appointment
GET /appointments/all/:id/ View all appointments of a user (doctor/patient)
POST /appointments/ Create an appointment
POST /appointments/filter/:id/ Filter appointments
POST /appointments/update/ Update an appointment
POST /appointments/createAppointmentsForRelations/ Create an appointment for a family member
DELETE /appointments/:id/ Delete an appointment
Prescription routes
method route returns
GET /prescriptions/:id View details of a prescription
POST /prescriptions/ Create a prescription
PUT /prescriptions/:id Update details of a prescription
PUT /prescriptions/:id/collect Collect a prescription
DELETE /prescriptions/:id Delete a prescription
Payment routes
method route returns
POST /create-checkout-session/appointments/ Pay for an appointment using credit card
POST /create-checkout-session/healthPackages/ Pay for a health package using credit card
POST /walletPayment/appointments/ Pay for an appointment using wallet
POST /walletPayment/healthPackages/ Pay for a health package using wallet

⬆

Tests πŸ§ͺ

Backend testing is done using jest. To run the tests, run the following command

> cd server && npm run test

Model tests make sure the respective entity models are correct by creating new entities. They also make sure the Models raise the appropriate errors when required (i.e when an email is invalid)

A few examples of model tests in the following snippets:

Check if Admin email exists
test('should throw an error if email is missing', async () => {
        const admin = new Adminstrator({
            username: 'testadmin',
            passwordHash: 'password',
        });
        await expect(admin.save()).rejects.toThrow('Adminstrator validation failed: email: Path `email` is required.');
    });
Check if Appointment has a duration
test('should throw an error if duration is missing', async () => {
      const appointmentWithoutDuration = {
          doctor: new Types.ObjectId(),
          patient: new Types.ObjectId(),
          date: new Date(),
          status: 'upcoming',
          price: 100,
          appointmentType: 'regular',
      };

      const appointment = new Appointment(appointmentWithoutDuration);
      await expect(appointment.save()).rejects.toThrow('Appointment validation failed: duration: Path `duration` is required.');
    });
Check if Medicine has a quantity
test('should throw an error if medQuantity is missing in medicines', async () => {
        const cartWithMissingMedQuantity = {
            patient: new mongoose.Types.ObjectId(),
            medicines: [
                { medName: 'Medicine1', medPrice: 10 },
            ],
        };
        const cart = new Cart(cartWithMissingMedQuantity);
        await expect(cart.save()).rejects.toThrow('Cart validation failed: medicines.0.medQuantity: Path `medQuantity` is required.');
    });
Check if Cart's medicines have quantity
test('should throw an error if medQuantity is missing in medicines', async () => {
        const cartWithMissingMedQuantity = {
            patient: new mongoose.Types.ObjectId(),
            medicines: [
                { medName: 'Medicine1', medPrice: 10 },
            ],
        };
        const cart = new Cart(cartWithMissingMedQuantity);
        await expect(cart.save()).rejects.toThrow('Cart validation failed: medicines.0.medQuantity: Path `medQuantity` is required.');
    });
Check if a Chat updatedAt value is valid
test('should throw an error for invalid updatedAt value', async () => {
        const chatWithInvalidUpdatedAt = {
            chatName: 'Test Chat',
            isGroupChat: false,
            users: [new Types.ObjectId(), new Types.ObjectId()],
            createdAt: new Date(),
            updatedAt: 'invalidDate',
        };

        const chat = new ChatModel(chatWithInvalidUpdatedAt);
        await expect(chat.save()).rejects.toThrow('Chat validation failed: updatedAt: Cast to date failed for value "invalidDate" (type string) at path "updatedAt"');
    });
Check if a Contract has a doctor
test('should throw an error if doctor is missing', async () => {
        const contractWithoutDoctor = {
            admin: new Types.ObjectId(),
            clinicMarkup: 10,
        };

        const contract = new Contract(contractWithoutDoctor);
        await expect(contract.save()).rejects.toThrow('Contract validation failed: doctor: Path `doctor` is required.');
    });
Check if a Doctor has an hourly rate
test('should throw an error if hourlyRate is missing', async () => {
        const doctorWithoutHourlyRate = {
            email: "valid@gmail.com",
            passwordHash: "password",
            username: "username",
            name: 'Dr. John Doe',
            dateOfBirth: new Date(),
            affiliation: 'Medical Center',
            education: 'Medical Degree',
            files: [{ filename: 'file1.txt', path: '/path/to/file1.txt' }],
            status: 'pending',
            speciality: 'Cardiology',
            wallet: 0.0,
        };

        const doctor = new Doctor(doctorWithoutHourlyRate);
        await expect(doctor.save()).rejects.toThrow('Doctor validation failed: hourlyRate: Path `hourlyRate` is required.');
    });
Check if a Contract has a valid date
test('should throw an error if date is invalid', async () => {
        const healthRecordWithEmptyDiagnosis = {
            patient: new Types.ObjectId(),
            diagnosis: 'Headache',
            date: "invalid"
        };

        const healthRecord = new HealthRecords(healthRecordWithEmptyDiagnosis);
        await expect(healthRecord.save()).rejects.toThrow('HealthRecords validation failed: date: Cast to date failed for value \"invalid\" (type string) at path \"date\"');
    });
Check if a Contract has a valid date
test('should throw an error if date is invalid', async () => {
        const healthRecordWithEmptyDiagnosis = {
            patient: new Types.ObjectId(),
            diagnosis: 'Headache',
            date: "invalid"
        };

        const healthRecord = new HealthRecords(healthRecordWithEmptyDiagnosis);
        await expect(healthRecord.save()).rejects.toThrow('HealthRecords validation failed: date: Cast to date failed for value \"invalid\" (type string) at path \"date\"');
    });
Check if a Medicine has a valid price
test('should throw an error for invalid price value (non-number)', async () => {
    const medicineWithInvalidPrice = {
      name: 'Test Medicine',
      medicinalUse: 'Pain Relief',
      details: { description: 'Test description', activeIngredients: ['Ingredient1', 'Ingredient2'] },
      price: 'invalidPrice',
      availableQuantity: 100,
      sales: 50,
      image: 'test_image.jpg',
      overTheCounter: true,
      isArchived: false,
    };

    const medicine = new Medicine(medicineWithInvalidPrice);
    await expect(medicine.save()).rejects.toThrow('Medicine validation failed: price: Cast to Number failed for value "invalidPrice" (type string) at path "price"');
  });
Check if a Message has a content
test('should throw an error if content is missing', async () => {
        const messageWithoutContent = {
            sender: new Types.ObjectId(),
            chat: new Types.ObjectId(),
            readBy: [new Types.ObjectId()],
            createdAt: new Date(),
            updatedAt: new Date(),
        };

        const message = new MessageModel(messageWithoutContent);
        await expect(message.save()).rejects.toThrow('Message validation failed: content: Path `content` is required.');
    });
Check if a Notification has a content
test('should throw an error if content is empty', async () => {
        const notificationWithEmptyContent = {
            receiver: new Types.ObjectId(),
            content: '',
        };

        const notification = new Notification(notificationWithEmptyContent);
        await expect(notification.save()).rejects.toThrow('Notification validation failed: content: Path `content` is required.');
    });
Check if a Package's discount is missing
test('should throw an error if discountOnMedicine is missing', async () => {
        const packageWithoutDiscountOnMedicine = {
            name: 'Basic Package',
            price: 50,
            discountOnDoctorSessions: 10,
            discountForFamily: 15,
        };

        const packageItem = new Package(packageWithoutDiscountOnMedicine);
        await expect(packageItem.save()).rejects.toThrow('Package validation failed: subscriptionPeriod: Path `subscriptionPeriod` is required., discountOnMedicine: Path `discountOnMedicine` is required.');
    });
Check if a Patient has a gender
test('should throw an error if gender is missing', async () => {
    const patientWithoutGender = {
      wallet: 0,
      email: "email@gmail.com",
      passwordHash: "password",
      username: "username",
      name: 'John Doe',
      nationalId: '123456789',
      dateOfBirth: new Date(),
      mobileNumber: '+12345678',
      emergencyContact: {
        name: 'EmergencyContact',
        mobileNumber: '+12345678',
        relation: 'parent',
      },
    };

      const patient = new Patient(patientWithoutGender);
      await expect(patient.save()).rejects.toThrow('Patient validation failed: gender: Path `gender` is required.');
  });
Check if a Prescription has a patient
test('should throw an error if patient is missing', async () => {
        const prescriptionWithoutPatient = {
            doctor: new Types.ObjectId(),
            medicine: [{ medId: new Types.ObjectId(), dailyDosage: 1 }],
            date: new Date(),
            filled: false,
        };

        const prescription = new Prescription(prescriptionWithoutPatient);
        await expect(prescription.save()).rejects.toThrow('Prescription validation failed: patient: Path `patient` is required.');
    });

image

⬆

Contribute 🀝

Contributions are always welcome!

Getting Started

  1. Fork the repository
  2. Clone the repository
  3. Install dependencies
  4. Create a new branch
  5. Make your changes
  6. Commit and push your changes
  7. Create a pull request
  8. Wait for your pull request to be reviewed and merged

Code of Conduct

This project follows the Contributor Covenant Code of Conduct. Please read the full text so that you can understand what actions will and will not be tolerated.

⬆

Credits πŸ™

⬆

Authors πŸ§‘β€πŸ’»οΈ

Abdelrahman Salah Omar Wael John Fayez Ahmed Wael Mohamed Mohey
Ahmed Yasser Alaa Aref Ibrahim Soltan Logine Mohamed Mohamed Elshekha

⬆

License βš–οΈ

  • This software product is open source under the Apache 2.0 License.

  • Stripe is licensed under the Apache License 2.0

⬆

Feedback ⭐

We would love to hear from you. If you have any feedback, please reach out to us at john.f.roufaeil@gmail.com

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published