El7a2ny Pharmacy is a platform that facilitates a streamlined healthcare experience, catering to the distinct needs of pharmacists and patients. Pharmacists efficiently manage medicine-related activities, monitoring sales, updating medicine details, and receiving real-time notifications about stock levels. They can engage in direct communication with patients, view detailed sales reports, and effectively handle their wallets. On the patient side, the interface is user-friendly, allowing for easy management of wallets, cart items, and orders. Patients can effortlessly check out using multiple payment methods, add new delivery addresses, and receive alternative suggestions for out-of-stock medicines based on active ingredients. This platform prioritizes efficiency, transparency, and personalized healthcare interactions for pharmacists and patients alike.
- Build Status π¨
- Code Style π
- Demo & Screenshots πΈ
- Tech Stack π§°π§
- Features β¨
- Code Examples π
- Installation π₯
- How to Use β
- API Reference π
- Tests π§ͺ
- Contribute π€
- Credits π
- Authors π§βπ»οΈ
- License βοΈ
- Feedback β
- 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
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
Client: React, Redux, Material-UI, JavaScript
Server: Node, Express, MongoDB, Mongoose, TypeScript, JWT, Stripe API, Postman, Jest
General: Docker, Git & GitHub, VSCode
Guests can
- Sign in to their account
- Sign up as a patient
- Request to sign up as a pharmacist
- Reset forgotten password through OTP sent to email
Logged in System Users can
- Change their password
- Sign out
- View a list of all available medicines (photo, price, description)
- Search for medicine based on name
- Filter medicines based on medicinal use
Admins can
- Add another admin with a set username and password
- Remove pharmacist/patient/admin from the system
- View all information uploaded by pharmacists who applied to join the platform
- Accept or reject pharmacist proposals
- View a total sales report based on a chosen month
- View information about any user on the system
Pharmacists can
- View the available quantity and sales of each medicine
- Add a medicine with its details, price, and available quantity
- Upload medicine image
- Edit medicine details and price
- Archive / unarchive a medicine
- View a total sales report based on a chosen month
- Filter sales report based on a medicine/date
- Chat with a patient
- View the amount in my wallet
- Receive a notification once a medicine is out of stock on the system and via email
Patients can
- Chat with a pharmacist
- View the amount in their wallet
- Add an over-the-counter medicine or a prescription medicine included in their prescriptions in their cart
- View their cart items
- Remove an item from their cart
- Update the amount of an item in their cart
- Check out their orders with address and payment method (wallet/COD/credit card)
- Add new delivery addresses
- View details and status of all their orders
- Cancel a pending order
- View alternatives to a medicine that is out of stock based on main active ingredient
Add Address to a Patient
const addAddress = async (req: Request, res: Response) => {
const patientId = req.params.patientId;
const newAddress = req.body.newAddress;
try {
const existingPatient = await patient.findOne({ _id: patientId });
if (!existingPatient) {
return res.status(404).json({ message: "Patient not found" });
}
existingPatient.address.push(newAddress);
await existingPatient.save();
res.json({ message: "Address added successfully" });
} catch (error) {
console.error(error);
res.status(500).json({ message: "Internal Server Error" });
}
};
Archive / Unarchive a Medicine
const archiveMedicine = async (req: Request, res: Response) => {
const id = req.params.id;
try {
const existingMedicine = await medicine.findById(id);
if (!existingMedicine) {
return res.status(404).send({ message: "Medicine not found" });
}
existingMedicine.isArchived = !existingMedicine.isArchived;
const updatedMed = await existingMedicine.save();
res.status(200).send(updatedMed);
} catch (error) {
res.status(400).send(error);
}
};
List Adminstrators
const listAdminstrators = async (req: Request, res: Response) => {
const adminstrators = adminstrator
.find({})
.then((admns) => res.status(200).json(admns))
.catch((err) => {
res.status(400).json(err);
});
};
Not Found Page Component
import NotFoundImg from "./assets/photos/not-found.png";
<Container>
<div>
<h1>404 - Page Not Found</h1>
<p>Sorry, the page you're looking for does not exist.</p>
<img src={NotFoundImg} />
</div>
<Button variant="contained" component={Link} to="/patient/medicines">
{" "}
Return to Homepage{" "}
</Button>
</Container>;
AppBar Component
<AppBar position="static">
<Toolbar>
<IconButton>
<MenuIcon />
</IconButton>
<Typography>{props.title}</Typography>
{props.cart && (
<IconButton component={Link} to="/patient/cart">
<ShoppingCartIcon />
</IconButton>
)}
<IconButton component={Link} onClick={handleLogout}>
<LogoutIcon />
</IconButton>
</Toolbar>
</AppBar>
Clone the project
> git clone https://github.com/advanced-computer-lab-2023/Mern-overflow-Pharmacy
Go to the project directory
> cd Mern-overflow-Pharmacy
Install dependencies
> cd server && npm i && cd -
> cd client && npm i && cd -
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
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
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 |
Admin routes
method | route | returns |
---|---|---|
GET | /adminstrators/ |
View all admins |
POST | /adminstrators/ |
Create an admin |
DELETE | /adminstrators/:id/ |
Delete an admin |
Patient routes
method | route | returns |
---|---|---|
GET | /patients/ |
View all patients |
GET | /patients/:id/ |
View details of a patient |
GET | /patients/address/:id/ |
View addresses of a patient |
POST | /patients/ |
Create a patient |
PUT | /patients/address/:id/ |
Add an adderss to a patient |
DELETE | /patients/:id/ |
Delete a patient |
Pharmacist routes
method | route | returns |
---|---|---|
GET | /pharmacists/ |
View all accepted pharmacists |
GET | /pharmacists/listAll/ |
View all pending pharmacist requests |
GET | /pharmacists/viewAll/ |
View all pharmacist requests |
GET | /pharmacists/:id/ |
View details of a pharmacist |
POST | /pharmacists/ |
Create a pharmacist request |
POST | /pharmacists/acceptPharmacist/ |
Accept a pharmacist request |
POST | /pharmacists/rejectPharmacist/ |
Reject a pharmacist request |
PUT | /pharmacists/:id/ |
Update pharmacist request to accepted |
DELETE | /pharmacists/:id/ |
Delete a pharmacist |
Medicine routes
method | route | returns |
---|---|---|
GET | /medicines/ |
View the sales and quantity of medicines |
GET | /medicines/view/ |
View all available medicines only |
GET | /medicines/viewAll/ |
View all details about all medicines |
GET | /medicines/search/ |
View medicines by selected name query |
GET | /medicines/filter/ |
View medicines by selected medicinal use |
POST | /medicines/ |
Create a medicine |
PUT | /medicines/:id/ |
Update medicine details |
PUT | /medicines/:id/archive/ |
Archive / Unarchive a medicine |
Cart routes
method | route | returns |
---|---|---|
GET | /cart/:id/ |
View cart items |
POST | /cart/:id/add/ |
Add a medicine to cart |
POST | /cart/:id/changeAmount/ |
Change a medicine's quantity in cart |
PUT | /cart/:id/empty/ |
Empty cart |
DELETE | /cart/:id/:medName/ |
Remove a medicine by name from cart |
Order routes
method | route | returns |
---|---|---|
GET | /orders/:id/ |
View orders by patient id |
GET | /orders/salesreport/ |
View sales report |
POST | /orders/:id/add/ |
Add an order |
PUT | /orders/:id/ |
Cancel pending order |
Payment routes
method | route | returns |
---|---|---|
POST | /walletPayment/shoppingCart/ |
Pay for an order using wallet |
POST | /create-checkout-session/shoppingCart/ |
Pay for an order using credit card |
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 valid
test('should throw an error if email is invalid', async () => {
const admin = new Adminstrator({
username: 'testuser',
passwordHash: 'password',
email: 'invalidemail',
});
await expect(admin.save()).rejects.toThrow('invalid email');
});
Check if Cart has a patient
test('should throw an error if patient is missing', async () => {
const cartWithoutPatient = {
medicines: [
{ medName: 'Medicine1', medPrice: 10, medQuantity: 2 },
{ medName: 'Medicine2', medPrice: 15, medQuantity: 3 },
],
};
const cart = new Cart(cartWithoutPatient);
await expect(cart.save()).rejects.toThrow('Cart validation failed: patient: Path `patient` is required.');
});
Check if a Chat has a name
test('should throw an error if chatName is missing', async () => {
const chatWithoutChatName = {
isGroupChat: false,
users: [new Types.ObjectId(), new Types.ObjectId()],
createdAt: new Date(),
updatedAt: new Date(),
};
const chat = new ChatModel(chatWithoutChatName);
await expect(chat.save()).rejects.toThrow('Chat validation failed: chatName: Path `chatName` is required.');
});
Check if a Medicine has a name
test('should throw an error if name is missing', async () => {
const medicineWithoutName = {
medicinalUse: 'Pain Relief',
details: { description: 'Test description', activeIngredients: ['Ingredient1', 'Ingredient2'] },
price: 20,
availableQuantity: 100,
sales: 50,
image: 'test_image.jpg',
overTheCounter: true,
isArchived: false,
};
const medicine = new Medicine(medicineWithoutName);
await expect(medicine.save()).rejects.toThrow('Medicine validation failed: name: Path `name` is required.');
});
Check if a Message has a sender
test('should throw an error if sender is missing', async () => {
const messageWithoutSender = {
content: 'Test message content',
chat: new Types.ObjectId(),
readBy: [new Types.ObjectId()],
createdAt: new Date(),
updatedAt: new Date(),
};
const message = new MessageModel(messageWithoutSender);
await expect(message.save()).rejects.toThrow('Message validation failed: sender: Path `sender` is required.');
});
Check if an Order has a patient
test('should throw an error if patient is missing', async () => {
const orderWithoutPatient = {
status: 'pending',
date: new Date(),
total: 100,
address: 'Test address',
paymentMethod: 'cash on delivery',
medicines: [{ medName: 'Medicine1', medPrice: 10, medQuantity: 2 }],
};
const order = new Order(orderWithoutPatient);
await expect(order.save()).rejects.toThrow('Order validation failed: patient: Path `patient` is required.');
});
Check if a Package has a name
test('should throw an error if name is missing', async () => {
const packageWithoutName = {
price: 50,
discountOnDoctorSessions: 10,
discountOnMedicine: 5,
discountForFamily: 15,
};
const packageItem = new Package(packageWithoutName);
await expect(packageItem.save()).rejects.toThrow('Package validation failed: name: Path `name` is required.');
});
Check if a Patient has a name
test('should throw an error if name is missing', async () => {
const patientWithoutName = {
email: "email@gmail.com",
passwordHash: "password",
username: "username",
nationalId: '123456789',
dateOfBirth: new Date(),
gender: 'male',
mobileNumber: '+12345678',
emergencyContact: {
name: 'EmergencyContact',
mobileNumber: '+12345678',
relation: 'parent',
},
};
const patient = new Patient(patientWithoutName);
await expect(patient.save()).rejects.toThrow('Patient validation failed: name: Path `name` is required.');
});
Check if a Pharmacist has a date of birth
test('should throw an error if dateOfBirth is missing', async () => {
const pharmacistWithoutDateOfBirth = {
email: "email@gmail.com",
passwordHash: "password",
username: "username",
name: 'John Doe',
hourlyRate: 50,
affiliation: 'Pharmacy ABC',
education: 'Pharmacist Degree',
files: [{ filename: 'file1.txt', path: '/path/to/file1.txt' }],
status: 'pending',
};
const pharmacist = new Pharmacist(pharmacistWithoutDateOfBirth);
await expect(pharmacist.save()).rejects.toThrow('pharmacist validation failed: dateOfBirth: Path `dateOfBirth` is required.');
});
Check if a Prescription has a doctor reference
test('should throw an error if doctor is missing', async () => {
const prescriptionWithoutDoctor = {
patient: new Types.ObjectId(),
medicine: [{ medId: new Types.ObjectId(), dailyDosage: 1 }],
date: new Date(),
filled: false,
};
const prescription = new Prescription(prescriptionWithoutDoctor);
await expect(prescription.save()).rejects.toThrow('Prescription validation failed: doctor: Path `doctor` is required.');
});
Contributions are always welcome!
- Fork the repository
- Clone the repository
- Install dependencies
- Create a new branch
- Make your changes
- Commit and push your changes
- Create a pull request
- Wait for your pull request to be reviewed and merged
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.
- JWT docs
- Stripe docs
- Node.js docs
- Express.js docs
- React.js docs
- MongoDB docs
- Mongoose docs
- SimpliLearn Blog about MERN
- MERN Stack | GeeksforGeeks
- MongoDB guide to MERN
- NetNinja MERN playlist
- MERN stack tutorial | freeCodecAmp
Abdelrahman Salah | Omar Wael | John Fayez | Ahmed Wael | Mohamed Mohey |
---|---|---|---|---|
Ahmed Yasser | Alaa Aref | Ibrahim Soltan | Logine Mohamed | Mohamed Elshekha |
-
This software product is open source under the Apache 2.0 License.
-
Stripe is licensed under the Apache License 2.0
We would love to hear from you. If you have any feedback, please reach out to us at john.f.roufaeil@gmail.com