PolyMedica is a comprehensive healthcare solution that seamlessly integrates two virtual platforms: PolyMedica Clinics and PolyMedica Pharmacy. This innovative system is built on a microservices architecture, ensuring flexibility, scalability, and efficiency.
The System mainly composed of 6 services, the communication among them is done by either synchronous HTTP requests using Axios or asynchronous communication using Apache Kafka:
- Clinic Service
- Patient Service
- Authentication Service
- Communication Service
- Payment Service
- Pharmacy Service
To enhance code quality and ensure a stable frontend, we're working on implementing comprehensive automated tests using Jest for our React application built with Material-UI (MUI). These tests will cover unit testing, integration testing, and UI component testing to guarantee a seamless user experience.
We're excited to introduce AI models to augment our system's capabilities:
-
Doctor Assistance AI: This AI model will assist doctors by analyzing symptoms input by the patient, suggesting suitable medicines, and providing dosage recommendations. It will also create reminders for doctors regarding prescribed medications and their quantities.
-
Patient Assistance AI: Suggest replacements for medicines out of stock for the patient
- Indentation: Use 2 spaces.
- Naming Conventions: camelCase for variables/functions, PascalCase for React components.
- ESLint: Utilize appropriate ESLint configurations for Node.js and React.
- Routing: Follow RESTful conventions for organized routes.
- Middleware: Use for route-specific logic.
- Error Handling: Implement middleware for consistent error responses.
- Naming Conventions: Maintain consistent naming for collections (singular nouns).
- Schema Design: Ensure consistency across collections.
- Indexes: Optimize with appropriate indexes for queries.
- MUI Components: Leverage Material-UI components and adhere to their guidelines.
- Folder Structure: Organize components by features/functions.
- State Management: Use Redux/Context API for complex state (if needed).
- Lifecycle Methods: Prefer hooks and functional components.
- Branching: Follow Gitflow (feature branches, develop, master).
- Pull Requests: Require clear descriptions and peer reviews before merging.
Poly-Medica Clinic
├── clinic
│ ├── ...
│ └── (Clinic Service Code)
├── patient
│ ├── ...
│ └── (Patient Service Code)
├── authentication
│ ├── ...
│ └── (Authentication Service Code)
├── communication
│ ├── ...
│ └── (Communication Service Code)
├── payment
│ ├── ...
│ └── (Payment Service Code)
└── client
├── ...
└── (Client Application Code)
Poly-Medica Pharmacy
├── pharmacy
│ ├── ...
│ └── (Pharmacy Service Code)
└── client
├── ...
└── (Client Application Code)
- Node.js
- Express
- React
- MongoDB
- Mongoose
- Jest
- Material-UI
- Stripe
- Git
- Github Actions
- MongoDB Atlas
- Postman
- VSCode
- Babel
- Socket IO
- JWT
- Docker
- Apache Kafka
- ESlint
- Redux
- Node Mailer
The system serves different type of users (Patient, Doctor , Admin )
As a Guest I can
- Sign up as a patient
- submit a request to register as a doctor
- upload and submit required documents upon registrationas a doctor
As a Patient I can
- Add family members
- Link another patient's account
- Choose to pay for my appointment using my wallet or credit card
- Enter credit card details and pay for an appointment
- View registered family members
- View uploaded health records
- View, Reschedule and Filter appointments
- Cancel an appointment for myself or for a family member
- View, Download and Filter all prescriptions
- choose the way to pay a prescription
- Receive a notification
- Request a follow-up to a previous appointment
- Chat with a doctor
- Start and End a video call with a doctor
As a Doctor I can
- Update My personal information like email, affiliation and hourly rate
- View and accept the employment contract
- Add my available time slots for appointments
- View uploaded health records
- Add a new health records
- View all prescriptions
- View, Search and Filter a list of my patients
- Select a patient from the list of patients
- Receive a notification
- View, Reschedule and Filter appointments
- Cancel an appointment
- Schedule a follow-up for a patient
- Add and Delete medicine from a prescription
- Add and Update dosage for each medicine added to the prescription
- Download selected prescription
- Update a patient's prescription before it is submitted to the pharmacy
- Accept or Revoke a follow-up session request from a patient -Chat with a patient
- Start and End a video call with a patient
As an Admin I can
- Add another adminstrator
- Remove a doctor or a patient or an admin from the system
- View all of the information uploaded by a doctor to apply to join the platform
- Accept or Reject the request of a doctor to join the platform
- Add, Update and Delete health packages with different price ranges.
- Accept a request for the registration of a doctor
Admin Details
import React from 'react';
import DoctorIcon from '../../../assets/images/icons/DoctorIcon.png';
import EmailIcon from '@mui/icons-material/Email';
import StarBorderIcon from '@mui/icons-material/StarBorder';
import {
Dialog,
DialogTitle,
DialogContent,
Typography,
DialogActions,
Button,
useTheme,
} from '@mui/material';
import { styled } from '@mui/system';
import { useAdminContext } from 'hooks/useAdminContext';
import { commonStyles } from 'ui-component/CommonStyles';
const useStyles = styled(() => commonStyles);
const AdminDetails = () => {
const classes = useStyles();
const theme = useTheme();
const title = ' ';
const { setSelectedAdmin, setErrorMessage, selectedAdmin } = useAdminContext();
const handleDialogClose = () => {
setSelectedAdmin(null);
setErrorMessage('');
};
return (
<Dialog
open={selectedAdmin}
onClose={handleDialogClose}
PaperProps={{ sx: { minWidth: theme.breakpoints.values.md > 800 ? 500 : 300 } }}
>
{selectedAdmin && (
<>
<DialogTitle align='center' variant='h2'>
{selectedAdmin.userName}
</DialogTitle>
<DialogContent>
<div className={classes.container}>
<div>
<img
src={DoctorIcon}
alt={`${title} ${selectedAdmin.userName}`}
width='100'
height='100'
/>
<Typography variant='h4' sx={{ marginTop: '1em' }}>
{`${title} ${selectedAdmin.userName}`}
</Typography>
</div>
<div className={classes.infoContainer}>
<div className={classes.emailContainer}>
<EmailIcon className={classes.iconMargin} />
<Typography variant='body1'>{selectedAdmin.email}</Typography>
</div>
<div className={classes.emailContainer}>
<StarBorderIcon className={classes.iconMargin} />
<Typography variant='body1'>
{selectedAdmin.mainAdmin ? 'Main Admin' : 'Sub Admin'}
</Typography>
</div>
</div>
</div>
</DialogContent>
<DialogActions>
<Button onClick={handleDialogClose} color='primary'>
Close
</Button>
</DialogActions>
</>
)}
</Dialog>
);
};
export default AdminDetails;
Search Context
import React, { createContext, useContext, useState } from 'react';
const SearchContext = createContext();
export const useSearch = () => {
return useContext(SearchContext);
};
export const SearchProvider = ({ children }) => {
const [searchQuery, setSearchQuery] = useState('');
const updateSearchQuery = (query) => {
setSearchQuery(query);
};
return (
<SearchContext.Provider value={{ searchQuery, updateSearchQuery }}>
{children}
</SearchContext.Provider>
);
};
Get admins API
app.get('/admins', async (req, res) => {
try {
const admins = await service.findAllAdmins();
res.status(OK_STATUS_CODE).json({ admins });
} catch (err) {
res.status(ERROR_STATUS_CODE).json({ err: err.message });
}
});
Users Model
import mongoose from 'mongoose';
import { USER_ARR_ENUM } from '../../utils/Constants.js';
const userSchema = mongoose.Schema({
userId:{
type: mongoose.Schema.Types.ObjectId,
required:true,
unique: true,
},
email:{
type:String,
required:true,
unique: true,
},
userName:{
type:String,
required:true,
unique: true
},
password:{
type:String,
required:true
},
type:{
type: String,
enum: USER_ARR_ENUM,
required:true
},
});
userSchema.statics.signup = async function (
userId,
email,
password,
userName,
type,
) {
const userRecord = new this({
userId: new mongoose.Types.ObjectId(userId),
email,
password,
userName,
type,
});
const result = await userRecord.save();
return result;
};
const User = mongoose.model('User', userSchema);
export default User;
Notification
import PropTypes from 'prop-types';
import { useTheme } from '@mui/material/styles';
import { Box, Chip, Drawer, Stack, useMediaQuery } from '@mui/material';
import PerfectScrollbar from 'react-perfect-scrollbar';
import { BrowserView, MobileView } from 'react-device-detect';
import MenuList from './MenuList';
import LogoSection from './LogoSection';
import { drawerWidth } from 'store/constant'; code exa
const Sidebar = ({ drawerOpen, drawerToggle, window }) => {
const theme = useTheme();
const matchUpMd = useMediaQuery(theme.breakpoints.up('md'));
const drawer = (
<>
<Box sx={{ display: { xs: 'block', md: 'none' } }}>
<Box sx={{ display: 'flex', p: 2, mx: 'auto' }}>
<LogoSection />
</Box>
</Box>
<BrowserView>
<PerfectScrollbar
component="div"
style={{
height: !matchUpMd ? 'calc(100vh - 56px)' : 'calc(100vh - 88px)',
paddingLeft: '16px',
paddingRight: '16px'
}}
>
<MenuList />
<Stack direction="row" justifyContent="center" sx={{ mb: 2 }}>
<Chip label={process.env.REACT_APP_VERSION} disabled chipcolor="secondary" size="small" sx={{ cursor: 'pointer' }} />
</Stack>
</PerfectScrollbar>
</BrowserView>
<MobileView>
<Box sx={{ px: 2 }}>
<MenuList />
<Stack direction="row" justifyContent="center" sx={{ mb: 2 }}>
<Chip label={process.env.REACT_APP_VERSION} disabled chipcolor="secondary" size="small" sx={{ cursor: 'pointer' }} />
</Stack>
</Box>
</MobileView>
</>
);
const container = window !== undefined ? () => window.document.body : undefined;
return (
<Box component="nav" sx={{ flexShrink: { md: 0 }, width: matchUpMd ? drawerWidth : 'auto' }} aria-label="mailbox folders">
<Drawer
container={container}
variant={matchUpMd ? 'persistent' : 'temporary'}
anchor="left"
open={drawerOpen}
onClose={drawerToggle}
sx={{
'& .MuiDrawer-paper': {
width: drawerWidth,
background: theme.palette.background.default,
color: theme.palette.text.primary,
borderRight: 'none',
[theme.breakpoints.up('md')]: {
top: '88px'
}
}
}}
ModalProps={{ keepMounted: true }}
color="inherit"
>
{drawer}
</Drawer>
</Box>
);
};
Sidebar.propTypes = {
drawerOpen: PropTypes.bool,
drawerToggle: PropTypes.func,
window: PropTypes.object
};
export default Sidebar;
> git clone https://github.com/advanced-computer-lab-2023/poly-medica-Clinic.git
> cd poly-medica-clinic
> chmod +x install-all.sh
> ./install-all.sh
The API documentation is created using Swagger. To access it, follow these steps:
- Ensure the service is running.
- Open your browser and navigate to
localhost:SERVICE_PORT/api-docs
.
The testing is done using jest
. To run the tests, run the following command:
- Make sure you are at the root directory of the project
> chmod +x run-tests.sh
>./run-tests.sh
Faker.js
is used to generate data to test different models
There are tests done for the following models : User
, Admin
, Patient
, Doctor
, Appointment
, Health Package
, Order
, Prescription
To run the project
- Make sure you are at the root directory of the project
- The script will run all the services and the client except the pharmacy service, which could be found at this repository
> npm install -g concurrently
> chmod +x run-all.sh
> ./run-all.sh
All services and client will be running on the specified ports on your env files.
To run this project, you will need to add the following environment variables to your .env file for all services
Authentication and Communication envs
MONGO_URI
MONGO_URI_TEST
JWT_SECRET
PORT
RESETEMAIL
RESETPASSWORD
Clinic and Patient envs
MONGO_URI
MONGO_URI_TEST
JWT_SECRET
PORT
Payment envs
SecretKey
PublicKey
PORT
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
The Project is open source following MIT License