Initial version

This commit is contained in:
Flatlogic Bot 2025-07-01 21:00:13 +00:00
commit f8eadd2e74
339 changed files with 54324 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
node_modules/
*/node_modules/
*/build/

187
502.html Normal file
View File

@ -0,0 +1,187 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Service Starting</title>
<style>
body {
font-family: sans-serif;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height: 100vh;
background-color: #EFF2FF;
margin: 0;
padding: 20px;
}
.container {
text-align: center;
padding: 30px 40px;
background-color: #fff;
border-radius: 20px;
margin-bottom: 20px;
max-width: 538px;
width: 100%;
box-shadow: 0 13px 34px 0 rgba(167, 187, 242, 0.2);
box-sizing: border-box;
}
#status-heading {
font-size: 24px;
font-weight: 700;
color: #02004E;
margin-bottom: 20px;
}
h2 {
color: #333;
margin-bottom: 15px;
}
p {
color: #666;
font-size: 1.1em;
margin-bottom: 10px;
}
.tip {
font-weight: 300;
font-size: 17px;
line-height: 150%;
letter-spacing: 0;
text-align: center;
margin-top: 30px;
}
.loader-container {
position: relative;
display: flex;
justify-content: center;
align-items: center;
}
.loader {
width: 100px;
aspect-ratio: 1;
border-radius: 50%;
background:
radial-gradient(farthest-side, #5C7EF1 94%, #0000) top/8px 8px no-repeat,
conic-gradient(#0000 30%, #5C7EF1);
-webkit-mask: radial-gradient(farthest-side, #0000 calc(100% - 8px), #000 0);
animation: l13 2s infinite linear;
}
@keyframes l13 {
100% {
transform: rotate(1turn)
}
}
.app-logo {
position: absolute;
width: 36px;
}
.panel {
padding: 0 18px;
display: none;
background-color: white;
overflow: hidden;
margin-top: 10px;
}
.show {
display: block;
}
.project-info {
border: 1px solid #8C9DFF;
border-radius: 10px;
padding: 12px 16px;
max-width: 600px;
margin: 40px auto;
background-color: #FBFCFF;
}
.project-info h2 {
color: #02004E;
font-size: 14px;
font-weight: 500;
margin-bottom: 10px;
text-align: left;
}
.project-info p {
color: #686791;
font-size: 12px;
font-weight: 400;
text-align: left;
}
</style>
</head>
<body>
<div class="container">
<h2 id="status-heading">Loading the app, just a moment…</h2>
<p class="tip">The application is currently launching. The page will automatically refresh once site is
available.</p>
<div class="project-info">
<h2>medease</h2>
<p>Comprehensive healthcare management system for hospitals.</p>
</div>
<div class="loader-container">
<img src="https://flatlogic.com/blog/wp-content/uploads/2025/05/logo-bot-1.png" alt="App Logo"
class="app-logo">
<div class="loader"></div>
</div>
<div class="panel">
<video width="100%" height="315" controls loop>
<source
src="https://flatlogic.com/blog/wp-content/uploads/2025/04/20250430_1336_professional_dynamo_spinner_simple_compose_01jt349yvtenxt7xhg8hhr85j8.mp4"
type="video/mp4">
Your browser does not support the video tag.
</video>
</div>
</div>
<script>
function checkAvailability() {
fetch('/')
.then(response => {
if (response.ok) {
window.location.reload();
} else {
setTimeout(checkAvailability, 5000);
}
})
.catch(() => {
setTimeout(checkAvailability, 5000);
});
}
document.addEventListener('DOMContentLoaded', checkAvailability);
document.addEventListener('DOMContentLoaded', function () {
const appTitle = document.querySelector('#status-heading');
const panel = document.querySelector('.panel');
const video = panel.querySelector('video');
let clickCount = 0;
appTitle.addEventListener('click', function () {
clickCount++;
if (clickCount === 5) {
panel.classList.toggle('show');
if (panel.classList.contains('show')) {
video.play();
} else {
video.pause();
}
clickCount = 0;
}
});
});
</script>
</body>
</html>

17
Dockerfile Normal file
View File

@ -0,0 +1,17 @@
FROM node:20.15.1-alpine AS builder
RUN apk add --no-cache git
WORKDIR /app
COPY frontend/package.json frontend/yarn.lock ./
RUN yarn install --pure-lockfile
COPY frontend .
RUN yarn build
FROM node:20.15.1-alpine
WORKDIR /app
COPY backend/package.json backend/yarn.lock ./
RUN yarn install --pure-lockfile
COPY backend .
COPY --from=builder /app/build /app/public
CMD ["yarn", "start"]

73
Dockerfile.dev Normal file
View File

@ -0,0 +1,73 @@
# Base image for Node.js dependencies
FROM node:20.15.1-alpine AS frontend-deps
RUN apk add --no-cache git
WORKDIR /app/frontend
COPY frontend/package.json frontend/yarn.lock ./
RUN yarn install --pure-lockfile
FROM node:20.15.1-alpine AS backend-deps
RUN apk add --no-cache git
WORKDIR /app/backend
COPY backend/package.json backend/yarn.lock ./
RUN yarn install --pure-lockfile
FROM node:20.15.1-alpine AS app-shell-deps
RUN apk add --no-cache git
WORKDIR /app/app-shell
COPY app-shell/package.json app-shell/yarn.lock ./
RUN yarn install --pure-lockfile
# Nginx setup and application build
FROM node:20.15.1-alpine AS build
RUN apk add --no-cache git nginx
RUN apk add --no-cache lsof procps
RUN yarn global add concurrently
RUN mkdir -p /app/pids
# Make sure to add yarn global bin to PATH
ENV PATH /root/.yarn/bin:/root/.config/yarn/global/node_modules/.bin:$PATH
# Copy dependencies
WORKDIR /app
COPY --from=frontend-deps /app/frontend /app/frontend
COPY --from=backend-deps /app/backend /app/backend
COPY --from=app-shell-deps /app/app-shell /app/app-shell
COPY frontend /app/frontend
COPY backend /app/backend
COPY app-shell /app/app-shell
COPY docker /app/docker
# Copy Nginx configuration
COPY nginx.conf /etc/nginx/nginx.conf
# Copy custom error page
COPY 502.html /usr/share/nginx/html/502.html
# Change owner and permissions of the error page
RUN chown nginx:nginx /usr/share/nginx/html/502.html && \
chmod 644 /usr/share/nginx/html/502.html
# Copy all files from root to /app
COPY . /app
# Expose the port the app runs on
EXPOSE 8080
ENV NODE_ENV=dev_stage
ENV FRONT_PORT=3001
ENV BACKEND_PORT=3000
ENV APP_SHELL_PORT=4000
CMD ["sh", "-c", "\
yarn --cwd /app/frontend dev & echo $! > /app/pids/frontend.pid && \
yarn --cwd /app/backend start & echo $! > /app/pids/backend.pid && \
sleep 10 && nginx -g 'daemon off;' & \
NGINX_PID=$! && \
echo 'Waiting for backend (port 3000) to be available...' && \
while ! nc -z localhost ${BACKEND_PORT}; do \
sleep 2; \
done && \
echo 'Backend is up. Starting app_shell for Git check...' && \
yarn --cwd /app/app-shell start && \
wait $NGINX_PID"]

74
LICENSE.MD Normal file
View File

@ -0,0 +1,74 @@
Flatlogic Community Licence 1.0.0
---------------------------------
Required Notice: Copyright © 2025 Flatlogic sp. z o.o. (https://flatlogic.com)
## Acceptance
In order to get any licence under these terms, you must agree to them as both strict obligations and conditions to all your licences.
## Copyright Licence
The licensor grants you a copyright licence for the software to do everything you might do with the software that would otherwise infringe the licensors copyright in it for any permitted purpose. However, you may only distribute the software according to **DistributionLicence** and make changes or new works based on the software according to **ChangesandNewWorksLicence**.
## Distribution Licence
The licensor grants you an additional copyright licence to distribute copies of the software. Your licence to distribute covers distributing the software with changes and new works permitted by **ChangesandNewWorksLicence**.
## Notices
You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms or the URL for them above, as well as copies of any plaintext lines beginning with `Required Notice:` that the licensor provided with the software. For example:
> Required Notice: Copyright © 2025 Flatlogic sp. zo.o. (https://flatlogic.com)
## Changes and New Works Licence
The licensor grants you an additional copyright licence to make changes and new works based on the software for any permitted purpose.
## Patent Licence
The licensor grants you a patent licence for the software that covers patent claims the licensor can license, or becomes able to license, that you would infringe by using the software.
## Noncompete
Any purpose is a permitted purpose, **except for providing any product that competes with the software or any product the licensor or any of its affiliates provides using the software.**
## Competition
Goods and services compete even when they provide functionality through different kinds of interfaces or for different technical platforms. Applications can compete with services, libraries with plugins, frameworks with development tools, and so on, even if theyre written in different programming languages or for different computer architectures. Goods and services compete even when provided free of charge.
If you market a product as a practical substitute for the software or another product, it definitely competes.
## New Products
If you are using the software to provide a product that does not compete, but the licensor or any of its affiliates brings your product into competition by providing a new version of the software or another product using the software, you may continue using versions of the software available under these terms beforehand to provide your competing product, but not any later versions.
## Discontinued Products
You may begin using the software to compete with a product or service that the licensor or any of its affiliates has stopped providing, unless the licensor includes a plaintext line beginning with `Licensor Line of Business:` with the software that mentions that line of business. For example:
> Licensor Line of Business: Flatlogic Generator SaaS (https://flatlogic.com/generator)
## Sales of Business
If the licensor or any of its affiliates sells a line of business developing the software or using the software to provide a product, the buyer can also enforce **Noncompete** for that product.
## Fair Use
You may have “fair use” rights for the software under the law. These terms do not limit them.
## No Other Rights
These terms do not allow you to sublicense or transfer any of your licences to anyone else, or prevent the licensor from granting licences to anyone else. These terms do not imply any other licences.
## Patent Defence
If you make any written claim that the software infringes or contributes to infringement of any patent, your patent licence for the software granted under these terms ends immediately. If your company makes such a claim, your patent licence ends immediately for work on behalf of your company.
## Violations
The first time you are notified in writing that you have violated any of these terms, or done anything with the software not covered by your licences, your licences can nonetheless continue if you come into full compliance with these terms, and take practical steps to correct past violations, within 32days of receiving notice. Otherwise, all your licences end immediately.
## No Liability
As far as the law allows, the software comes asis, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim.
## Definitions
*The licensor* is Flatlogic sp. zo.o., and *the software* is the **Flatlogic Community Template** we make available under these terms.
*A product* can be a good or service, or a combination of them.
*You* refers to the individual or entity agreeing to these terms.
*Your company* is any legal entity, sole proprietorship, or other kind of organisation that you work for, plus all its affiliates.
*Affiliates* means the other organisations that an organisation has control over, is under the control of, or is under common control with.
*Control* means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. Control can be direct or indirect.
*Your licences* are all the licences granted to you for the software under these terms.
*Use* means anything you do with the software requiring one of your licences.

170
README.MD Normal file
View File

@ -0,0 +1,170 @@
# Project Setup & Local Development
## Tech Stack
| Layer | Technology |
| --------- | -------------------- |
| Frontend | **React JS** |
| Backend | **Node JS** |
| Database | **PostgreSQL** |
| Container | **Docker & Compose** |
---
## 1  Run Locally (without Docker)
### 1.1 Backend
```bash
cd backend
# install dependencies
yarn install
```
\#### Configure PostgreSQL
<details>
<summary><strong>macOS</strong></summary>
```bash
brew install postgres
```
</details>
<details>
<summary><strong>Ubuntu</strong></summary>
```bash
sudo apt update
sudo apt install postgresql postgresql-contrib
```
</details>
\##### Create DB & Admin User
```bash
# log in as default superuser
psql postgres -U postgres
-- inside psql
CREATE ROLE admin WITH LOGIN PASSWORD 'admin_pass';
ALTER ROLE admin CREATEDB;
\q
# log in as the new user
psql postgres -U admin
-- inside psql
CREATE DATABASE db_<your_project_name>;
GRANT ALL PRIVILEGES ON DATABASE db_<your_project_name> TO admin;
\q
```
\##### Migrate & Start
```bash
yarn db:create # generate schema
yarn start # production build
```
### 1.2 Frontend
```bash
cd frontend
yarn install
yarn start
```
> Frontend devserver runs at **[http://localhost:3000](http://localhost:3000)** by default.
---
## 2  Run with Docker
```bash
cd docker
chmod +x wait-for-it.sh start-backend.sh
```
| Scenario | Command |
| ------------------------- | ---------------------------------- |
| **Fresh DB volume** | `rm -rf data && docker-compose up` |
| **Reuse existing volume** | `docker-compose up` |
Then open **[http://localhost:3000](http://localhost:3000)**.
Stop services with **Ctrl + C** or:
```bash
docker-compose down
```
> **Headsup:** Files inside the `docker/` folder and the root `Dockerfile` are used for cloud deployment. Changing them may break the pipeline.
---
## Folder Structure (toplevel)
```text
├── backend/ # Node JS API & services
├── frontend/ # React application
├── docker/ # Compose files & helper scripts
└── README.md # (this file)
```
---
## Troubleshooting
### “connection refused”
1. Port closed or backlog full.
2. Firewall (local or network).
3. Service not running.
Verify with:
```bash
telnet <host> <port>
```
### macOS
```bash
sudo service ssh status
```
### Ubuntu IP conflict check
```bash
arp-scan -I eth0 -l | grep <ipaddress>
arping <ipaddress>
```
### Reset PostgreSQL schema (macOS example)
```sql
DROP SCHEMA public CASCADE;
CREATE SCHEMA public;
GRANT ALL ON SCHEMA public TO postgres;
GRANT ALL ON SCHEMA public TO public;
```
---
## Cheatsheet
| Task | Command |
| ----------------------- | ---------------------------------- |
| **Create DB schema** | `yarn db:create` |
| **Start backend** | `yarn start` |
| **Start dev frontend** | `cd frontend && yarn start` |
| **Compose up (fresh)** | `rm -rf data && docker-compose up` |
| **Compose down** | `docker-compose down` |
<br/>
---
Made with ❤ by Flatlogic Platform.

26
app-shell/.eslintrc.cjs Normal file
View File

@ -0,0 +1,26 @@
const globals = require('globals');
module.exports = [
{
files: ['**/*.js', '**/*.ts', '**/*.tsx'],
languageOptions: {
ecmaVersion: 2021,
sourceType: 'module',
globals: {
...globals.browser,
...globals.node,
},
parser: '@typescript-eslint/parser',
},
plugins: ['@typescript-eslint'],
rules: {
'no-unused-vars': 'warn',
'no-console': 'off',
'indent': ['error', 2],
'quotes': ['error', 'single'],
'semi': ['error', 'always'],
'@typescript-eslint/no-unused-vars': 'warn',
},
},
];

11
app-shell/.prettierrc Normal file
View File

@ -0,0 +1,11 @@
{
"singleQuote": true,
"tabWidth": 2,
"printWidth": 80,
"trailingComma": "all",
"quoteProps": "as-needed",
"jsxSingleQuote": true,
"bracketSpacing": true,
"bracketSameLine": false,
"arrowParens": "always"
}

7
app-shell/.sequelizerc Normal file
View File

@ -0,0 +1,7 @@
const path = require('path');
module.exports = {
"config": path.resolve("src", "db", "db.config.js"),
"models-path": path.resolve("src", "db", "models"),
"seeders-path": path.resolve("src", "db", "seeders"),
"migrations-path": path.resolve("src", "db", "migrations")
};

23
app-shell/Dockerfile Normal file
View File

@ -0,0 +1,23 @@
FROM node:20.15.1-alpine
RUN apk update && apk add bash
# Create app directory
WORKDIR /usr/src/app
# Install app dependencies
# A wildcard is used to ensure both package.json AND package-lock.json are copied
# where available (npm@5+)
COPY package*.json ./
RUN yarn install
# If you are building your code for production
# RUN npm ci --only=production
# Bundle app source
COPY . .
EXPOSE 4000
CMD [ "yarn", "start" ]

13
app-shell/README.md Normal file
View File

@ -0,0 +1,13 @@
#test - template backend,
#### Run App on local machine:
##### Install local dependencies:
- `yarn install`
---
##### Start build:
- `yarn start`

42
app-shell/package.json Normal file
View File

@ -0,0 +1,42 @@
{
"name": "app-shell",
"description": "app-shell",
"scripts": {
"start": "node ./src/index.js"
},
"dependencies": {
"@babel/parser": "^7.26.7",
"adm-zip": "^0.5.16",
"axios": "^1.6.7",
"bcrypt": "5.1.1",
"cors": "2.8.5",
"eslint": "^9.13.0",
"express": "4.18.2",
"formidable": "1.2.2",
"helmet": "4.1.1",
"json2csv": "^5.0.7",
"jsonwebtoken": "8.5.1",
"lodash": "4.17.21",
"moment": "2.30.1",
"multer": "^1.4.4",
"passport": "^0.7.0",
"passport-google-oauth2": "^0.2.0",
"passport-jwt": "^4.0.1",
"passport-microsoft": "^0.1.0",
"postcss": "^8.5.1",
"sequelize-json-schema": "^2.1.1",
"pg": "^8.13.3"
},
"engines": {
"node": ">=18"
},
"private": true,
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^8.12.2",
"@typescript-eslint/parser": "^8.12.2",
"cross-env": "7.0.3",
"mocha": "8.1.3",
"nodemon": "^3.1.7",
"sequelize-cli": "6.6.2"
}
}

File diff suppressed because one or more lines are too long

16
app-shell/src/config.js Normal file
View File

@ -0,0 +1,16 @@
const config = {
schema_encryption_key: process.env.SCHEMA_ENCRYPTION_KEY || '',
project_uuid: 'a3903b27-e0be-4365-b29e-59df6f661b7f',
flHost: process.env.NODE_ENV === 'production' ? 'https://flatlogic.com/projects' : 'http://localhost:3000/projects',
gitea_domain: process.env.GITEA_DOMAIN || 'gitea.flatlogic.app',
gitea_username: process.env.GITEA_USERNAME || 'admin',
gitea_api_token: process.env.GITEA_API_TOKEN || null,
github_repo_url: process.env.GITHUB_REPO_URL || null,
github_token: process.env.GITHUB_TOKEN || null,
};
module.exports = config;

23
app-shell/src/helpers.js Normal file
View File

@ -0,0 +1,23 @@
const jwt = require('jsonwebtoken');
const config = require('./config');
module.exports = class Helpers {
static wrapAsync(fn) {
return function (req, res, next) {
fn(req, res, next).catch(next);
};
}
static commonErrorHandler(error, req, res, next) {
if ([400, 403, 404].includes(error.code)) {
return res.status(error.code).send(error.message);
}
console.error(error);
return res.status(500).send(error.message);
}
static jwtSign(data) {
return jwt.sign(data, config.secret_key, { expiresIn: '6h' });
}
};

54
app-shell/src/index.js Normal file
View File

@ -0,0 +1,54 @@
const express = require('express');
const cors = require('cors');
const app = express();
const bodyParser = require('body-parser');
const checkPermissions = require('./middlewares/check-permissions');
const modifyPath = require('./middlewares/modify-path');
const VCS = require('./services/vcs');
const executorRoutes = require('./routes/executor');
const vcsRoutes = require('./routes/vcs');
// Function to initialize the Git repository
function initRepo() {
const projectId = '32565';
return VCS.initRepo(projectId);
}
// Start the Express app on APP_SHELL_PORT (4000)
function startServer() {
const PORT = 4000;
app.listen(PORT, () => {
console.log(`Listening on port ${PORT}`);
});
}
// Run Git check after the server is up
function runGitCheck() {
initRepo()
.then(result => {
console.log(result?.message ? result.message : result);
// Here you can add additional logic if needed
})
.catch(err => {
console.error('Error during repo initialization:', err);
// Optionally exit the process if Git check is critical:
// process.exit(1);
});
}
app.use(cors({ origin: true }));
app.use(bodyParser.json());
app.use(checkPermissions);
app.use(modifyPath);
app.use('/executor', executorRoutes);
app.use('/vcs', vcsRoutes);
// Start the app_shell server
startServer();
// Now perform Git check
runGitCheck();
module.exports = app;

View File

@ -0,0 +1,17 @@
const config = require('../config');
function checkPermissions(req, res, next) {
const project_uuid = config.project_uuid;
const requiredHeader = 'X-Project-UUID';
const headerValue = req.headers[requiredHeader.toLowerCase()];
// Logging whatever request we're getting
console.log('Request:', req.url, req.method, req.body, req.headers);
if (headerValue && headerValue === project_uuid) {
next();
} else {
res.status(403).send({ error: 'Stop right there, criminal scum! Your project UUID is invalid or missing.' });
}
}
module.exports = checkPermissions;

View File

@ -0,0 +1,8 @@
function modifyPath(req, res, next) {
if (req.body && req.body.path) {
req.body.path = '../../../' + req.body.path;
}
next();
}
module.exports = modifyPath;

View File

@ -0,0 +1,312 @@
const express = require('express');
const multer = require('multer');
const upload = multer({ dest: 'uploads/' });
const fs = require('fs');
const ExecutorService = require('../services/executor');
const wrapAsync = require('../helpers').wrapAsync;
const router = express.Router();
router.post(
'/read_project_tree',
wrapAsync(async (req, res) => {
const { path } = req.body;
const tree = await ExecutorService.readProjectTree(path);
res.status(200).send(tree);
}),
);
router.post(
'/read_file',
wrapAsync(async (req, res) => {
const { path, showLines } = req.body;
const content = await ExecutorService.readFileContents(path, showLines);
res.status(200).send(content);
}),
);
router.post(
'/count_file_lines',
wrapAsync(async (req, res) => {
const { path } = req.body;
const content = await ExecutorService.countFileLines(path);
res.status(200).send(content);
}),
);
// router.post(
// '/read_file_header',
// wrapAsync(async (req, res) => {
// const { path, N } = req.body;
// try {
// const header = await ExecutorService.readFileHeader(path, N);
// res.status(200).send(header);
// } catch (error) {
// res.status(500).send({
// error: true,
// message: error.message,
// details: error.details || error.stack,
// validation: error.validation
// });
// }
// }),
// );
router.post(
'/read_file_line_context',
wrapAsync(async (req, res) => {
const { path, lineNumber, windowSize, showLines } = req.body;
try {
const context = await ExecutorService.readFileLineContext(path, lineNumber, windowSize, showLines);
res.status(200).send(context);
} catch (error) {
res.status(500).send({
error: true,
message: error.message,
details: error.details || error.stack,
validation: error.validation
});
}
}),
);
router.post(
'/write_file',
wrapAsync(async (req, res) => {
const { path, fileContents, comment } = req.body;
try {
await ExecutorService.writeFile(path, fileContents, comment);
res.status(200).send({ message: 'File written successfully' });
} catch (error) {
res.status(500).send({
error: true,
message: error.message,
details: error.details || error.stack,
validation: error.validation
});
}
}),
);
router.post(
'/insert_file_content',
wrapAsync(async (req, res) => {
const { path, lineNumber, newContent, message } = req.body;
try {
await ExecutorService.insertFileContent(path, lineNumber, newContent, message);
res.status(200).send({ message: 'File written successfully' });
} catch (error) {
res.status(500).send({
error: true,
message: error.message,
details: error.details || error.stack,
validation: error.validation
});
}
}),
);
router.post(
'/replace_file_line',
wrapAsync(async (req, res) => {
const { path, lineNumber, newText } = req.body;
try {
const result = await ExecutorService.replaceFileLine(path, lineNumber, newText);
res.status(200).send(result);
} catch (error) {
res.status(500).send({
error: true,
message: error.message,
details: error.details || error.stack,
validation: error.validation
});
}
}),
);
router.post(
'/replace_file_chunk',
wrapAsync(async (req, res) => {
const { path, startLine, endLine, newCode } = req.body;
try {
const result = await ExecutorService.replaceFileChunk(path, startLine, endLine, newCode);
res.status(200).send(result);
} catch (error) {
res.status(500).send({
error: true,
message: error.message,
details: error.details || error.stack,
validation: error.validation
});
}
}),
);
router.post(
'/delete_file_lines',
wrapAsync(async (req, res) => {
const { path, startLine, endLine, message } = req.body;
try {
const result = await ExecutorService.deleteFileLines(path, startLine, endLine, message);
res.status(200).send(result);
} catch (error) {
res.status(500).send({
error: true,
message: error.message,
details: error.details || error.stack,
validation: error.validation
});
}
}),
);
router.post(
'/validate_file',
wrapAsync(async (req, res) => {
const { path } = req.body;
try {
const validationResult = await ExecutorService.validateFile(path);
res.status(200).send({ validationResult });
} catch (error) {
res.status(500).send({
error: true,
message: error.message,
details: error.details || error.stack,
validation: error.validation
});
}
}),
);
router.post(
'/check_frontend_runtime_error',
wrapAsync(async (req, res) => {
try {
const result = await ExecutorService.checkFrontendRuntimeLogs();
res.status(200).send(result);
} catch (error) {
res.status(500).send({ error: error });
}
}),
);
router.post(
'/replace_code_block',
wrapAsync(async (req, res) => {
const {path, oldCode, newCode, message} = req.body;
try {
const response = await ExecutorService.replaceCodeBlock(path, oldCode, newCode, message);
res.status(200).send(response);
} catch (error) {
res.status(500).send({
error: true,
message: error.message,
details: error.details || error.stack,
validation: error.validation
})
}
})
)
router.post('/update_project_files_from_scheme',
upload.single('file'), // 'file' - name of the field in the form
async (req, res) => {
console.log('Request received');
console.log('Headers:', req.headers);
if (!req.file) {
return res.status(400).json({ error: 'No file uploaded' });
}
console.log('File info:', {
originalname: req.file.originalname,
path: req.file.path,
size: req.file.size,
mimetype: req.file.mimetype
});
try {
console.log('Starting update process...');
const result = await ExecutorService.updateProjectFilesFromScheme(req.file.path);
console.log('Update completed, result:', result);
console.log('Removing temp file...');
fs.unlinkSync(req.file.path);
console.log('Temp file removed');
console.log('Sending response...');
return res.json(result);
} catch (error) {
console.error('Error in route handler:', error);
if (req.file) {
try {
fs.unlinkSync(req.file.path);
console.log('Temp file removed after error');
} catch (unlinkError) {
console.error('Error removing temp file:', unlinkError);
}
}
console.error('Update project files error:', error);
return res.status(500).json({
error: error.message,
stack: process.env.NODE_ENV === 'development' ? error.stack : undefined
});
}
}
);
router.post(
'/get_db_schema',
wrapAsync(async (req, res) => {
try {
const jsonSchema = await ExecutorService.getDBSchema();
res.status(200).send({ jsonSchema });
} catch (error) {
res.status(500).send({ error: error });
}
}),
);
router.post(
'/execute_sql',
wrapAsync(async (req, res) => {
try {
const { query } = req.body;
const result = await ExecutorService.executeSQL(query);
res.status(200).send(result);
} catch (error) {
res.status(500).send({ error: error });
}
}),
);
router.post(
'/search_files',
wrapAsync(async (req, res) => {
try {
const { searchStrings } = req.body;
if (
typeof searchStrings !== 'string' &&
!(
Array.isArray(searchStrings) &&
searchStrings.every(item => typeof item === 'string')
)
) {
return res.status(400).send({ error: 'searchStrings must be a string or an array of strings' });
}
const result = await ExecutorService.searchFiles(searchStrings);
res.status(200).send(result);
} catch (error) {
res.status(500).send({ error: error.message });
}
}),
);
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

View File

@ -0,0 +1,40 @@
const express = require('express');
const wrapAsync = require('../helpers').wrapAsync; // Ваша обёртка для обработки асинхронных маршрутов
const VSC = require('../services/vcs');
const router = express.Router();
router.post('/init', wrapAsync(async (req, res) => {
const result = await VSC.initRepo();
res.status(200).send(result);
}));
router.post('/commit', wrapAsync(async (req, res) => {
const { message, files, dev_schema } = req.body;
const result = await VSC.commitChanges(message, files, dev_schema);
res.status(200).send(result);
}));
router.post('/log', wrapAsync(async (req, res) => {
const result = await VSC.getLog();
res.status(200).send(result);
}));
router.post('/rollback', wrapAsync(async (req, res) => {
const { ref } = req.body;
// const result = await VSC.checkout(ref);
const result = await VSC.revert(ref);
res.status(200).send(result);
}));
router.post('/sync-to-stable', wrapAsync(async (req, res) => {
const result = await VSC.mergeDevIntoMaster();
res.status(200).send(result);
}));
router.post('/reset-dev', wrapAsync(async (req, res) => {
const result = await VSC.resetDevBranch();
res.status(200).send(result);
}));
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

View File

@ -0,0 +1,88 @@
// Database.js
const { Client } = require('pg');
const config = require('../../../backend/src/db/db.config');
const env = process.env.NODE_ENV || 'development';
const dbConfig = config[env];
class Database {
constructor() {
this.client = new Client({
user: dbConfig.username,
password: dbConfig.password,
database: dbConfig.database,
host: dbConfig.host,
port: dbConfig.port
});
// Connect once, reuse the client
this.client.connect().catch(err => {
console.error('Error connecting to the database:', err);
throw err;
});
}
async executeSQL(query) {
try {
const result = await this.client.query(query);
return {
success: true,
rows: result.rows
};
} catch (error) {
console.error('Error executing query:', error);
throw error;
}
}
// Method to fetch simple table/column info from 'information_schema'
// (You can expand this to handle constraints, indexes, etc.)
async getDBSchema(schemaName = 'public') {
try {
const tableQuery = `
SELECT table_name
FROM information_schema.tables
WHERE table_schema = $1
AND table_type = 'BASE TABLE'
ORDER BY table_name
`;
const columnQuery = `
SELECT table_name, column_name, data_type, is_nullable
FROM information_schema.columns
WHERE table_schema = $1
ORDER BY table_name, ordinal_position
`;
const [tablesResult, columnsResult] = await Promise.all([
this.client.query(tableQuery, [schemaName]),
this.client.query(columnQuery, [schemaName]),
]);
// Build a simple schema object:
const tables = tablesResult.rows.map(row => row.table_name);
const columnsByTable = {};
columnsResult.rows.forEach(row => {
const { table_name, column_name, data_type, is_nullable } = row;
if (!columnsByTable[table_name]) columnsByTable[table_name] = [];
columnsByTable[table_name].push({ column_name, data_type, is_nullable });
});
// Combine tables with their columns
return tables.map(table => ({
table,
columns: columnsByTable[table] || [],
}));
} catch (error) {
console.error('Error fetching schema:', error);
throw error;
}
}
async close() {
await this.client.end();
}
}
module.exports = new Database();

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,16 @@
const { getNotification, isNotification } = require('../helpers');
module.exports = class ForbiddenError extends Error {
constructor(messageCode) {
let message;
if (messageCode && isNotification(messageCode)) {
message = getNotification(messageCode);
}
message = message || getNotification('errors.forbidden.message');
super(message);
this.code = 403;
}
};

View File

@ -0,0 +1,16 @@
const { getNotification, isNotification } = require('../helpers');
module.exports = class ValidationError extends Error {
constructor(messageCode) {
let message;
if (messageCode && isNotification(messageCode)) {
message = getNotification(messageCode);
}
message = message || getNotification('errors.validation.message');
super(message);
this.code = 400;
}
};

View File

@ -0,0 +1,30 @@
const _get = require('lodash/get');
const errors = require('./list');
function format(message, args) {
if (!message) {
return null;
}
return message.replace(/{(\d+)}/g, function (match, number) {
return typeof args[number] != 'undefined' ? args[number] : match;
});
}
const isNotification = (key) => {
const message = _get(errors, key);
return !!message;
};
const getNotification = (key, ...args) => {
const message = _get(errors, key);
if (!message) {
return key;
}
return format(message, args);
};
exports.getNotification = getNotification;
exports.isNotification = isNotification;

View File

@ -0,0 +1,100 @@
const errors = {
app: {
title: 'test',
},
auth: {
userDisabled: 'Your account is disabled',
forbidden: 'Forbidden',
unauthorized: 'Unauthorized',
userNotFound: `Sorry, we don't recognize your credentials`,
wrongPassword: `Sorry, we don't recognize your credentials`,
weakPassword: 'This password is too weak',
emailAlreadyInUse: 'Email is already in use',
invalidEmail: 'Please provide a valid email',
passwordReset: {
invalidToken: 'Password reset link is invalid or has expired',
error: `Email not recognized`,
},
passwordUpdate: {
samePassword: `You can't use the same password. Please create new password`,
},
userNotVerified: `Sorry, your email has not been verified yet`,
emailAddressVerificationEmail: {
invalidToken: 'Email verification link is invalid or has expired',
error: `Email not recognized`,
},
},
iam: {
errors: {
userAlreadyExists: 'User with this email already exists',
userNotFound: 'User not found',
disablingHimself: `You can't disable yourself`,
revokingOwnPermission: `You can't revoke your own owner permission`,
deletingHimself: `You can't delete yourself`,
emailRequired: 'Email is required',
},
},
importer: {
errors: {
invalidFileEmpty: 'The file is empty',
invalidFileExcel: 'Only excel (.xlsx) files are allowed',
invalidFileUpload:
'Invalid file. Make sure you are using the last version of the template.',
importHashRequired: 'Import hash is required',
importHashExistent: 'Data has already been imported',
userEmailMissing: 'Some items in the CSV do not have an email',
},
},
errors: {
forbidden: {
message: 'Forbidden',
},
validation: {
message: 'An error occurred',
},
searchQueryRequired: {
message: 'Search query is required',
},
},
emails: {
invitation: {
subject: `You've been invited to {0}`,
body: `
<p>Hello,</p>
<p>You've been invited to {0} set password for your {1} account.</p>
<p><a href='{2}'>{2}</a></p>
<p>Thanks,</p>
<p>Your {0} team</p>
`,
},
emailAddressVerification: {
subject: `Verify your email for {0}`,
body: `
<p>Hello,</p>
<p>Follow this link to verify your email address.</p>
<p><a href='{0}'>{0}</a></p>
<p>If you didn't ask to verify this address, you can ignore this email.</p>
<p>Thanks,</p>
<p>Your {1} team</p>
`,
},
passwordReset: {
subject: `Reset your password for {0}`,
body: `
<p>Hello,</p>
<p>Follow this link to reset your {0} password for your {1} account.</p>
<p><a href='{2}'>{2}</a></p>
<p>If you didn't ask to reset your password, you can ignore this email.</p>
<p>Thanks,</p>
<p>Your {0} team</p>
`,
},
},
};
module.exports = errors;

View File

@ -0,0 +1,67 @@
const axios = require('axios');
const config = require('../config.js');
class ProjectEventsService {
/**
* Sends a project event to the Rails backend
*
* @param {string} eventType - Type of the event
* @param {object} payload - Event payload data
* @param {object} options - Additional options
* @param {string} [options.conversationId] - Optional conversation ID
* @param {boolean} [options.isError=false] - Whether this is an error event
* @returns {Promise<object>} - Response from the webhook
*/
static async sendEvent(eventType, payload = {}, options = {}) {
try {
console.log(`[DEBUG] Sending project event: ${eventType}`);
const webhookUrl = `https://flatlogic.com/projects/events_webhook`;
// Prepare the event data
const eventData = {
project_uuid: config.project_uuid,
event_type: eventType,
payload: {
...payload,
message: `[APP] ${payload.message}`,
is_error: options.isError || false,
system_message: true,
is_command_info: true
}
};
// Add conversation ID if provided
if (options.conversationId) {
eventData.conversation_id = options.conversationId;
}
const headers = {
'Content-Type': 'application/json',
'x-project-uuid': config.project_uuid
};
console.log(`[DEBUG] Event data: ${JSON.stringify(eventData)}`);
const response = await axios.post(webhookUrl, eventData, { headers });
console.log(`[DEBUG] Event sent successfully, status: ${response.status}`);
return response.data;
} catch (error) {
console.error(`[ERROR] Failed to send project event: ${error.message}`);
if (error.response) {
console.error(`[ERROR] Response status: ${error.response.status}`);
console.error(`[ERROR] Response data: ${JSON.stringify(error.response.data)}`);
}
// Don't throw the error, just return a failed status
// This prevents errors in the event service from breaking app functionality
return {
success: false,
error: error.message
};
}
}
}
module.exports = ProjectEventsService;

File diff suppressed because it is too large Load Diff

3044
app-shell/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

7
backend/.sequelizerc Normal file
View File

@ -0,0 +1,7 @@
const path = require('path');
module.exports = {
"config": path.resolve("src", "db", "db.config.js"),
"models-path": path.resolve("src", "db", "models"),
"seeders-path": path.resolve("src", "db", "seeders"),
"migrations-path": path.resolve("src", "db", "migrations")
};

23
backend/Dockerfile Normal file
View File

@ -0,0 +1,23 @@
FROM node:20.15.1-alpine
RUN apk update && apk add bash
# Create app directory
WORKDIR /usr/src/app
# Install app dependencies
# A wildcard is used to ensure both package.json AND package-lock.json are copied
# where available (npm@5+)
COPY package*.json ./
RUN yarn install
# If you are building your code for production
# RUN npm ci --only=production
# Bundle app source
COPY . .
EXPOSE 8080
CMD [ "yarn", "start" ]

56
backend/README.md Normal file
View File

@ -0,0 +1,56 @@
#medease - template backend,
#### Run App on local machine:
##### Install local dependencies:
- `yarn install`
------------
##### Adjust local db:
###### 1. Install postgres:
- MacOS:
- `brew install postgres`
- Ubuntu:
- `sudo apt update`
- `sudo apt install postgresql postgresql-contrib`
###### 2. Create db and admin user:
- Before run and test connection, make sure you have created a database as described in the above configuration. You can use the `psql` command to create a user and database.
- `psql postgres --u postgres`
- Next, type this command for creating a new user with password then give access for creating the database.
- `postgres-# CREATE ROLE admin WITH LOGIN PASSWORD 'admin_pass';`
- `postgres-# ALTER ROLE admin CREATEDB;`
- Quit `psql` then log in again using the new user that previously created.
- `postgres-# \q`
- `psql postgres -U admin`
- Type this command to creating a new database.
- `postgres=> CREATE DATABASE db_medease;`
- Then give that new user privileges to the new database then quit the `psql`.
- `postgres=> GRANT ALL PRIVILEGES ON DATABASE db_medease TO admin;`
- `postgres=> \q`
------------
#### Api Documentation (Swagger)
http://localhost:8080/api-docs (local host)
http://host_name/api-docs
------------
##### Setup database tables or update after schema change
- `yarn db:migrate`
##### Seed the initial data (admin accounts, relevant for the first setup):
- `yarn db:seed`
##### Start build:
- `yarn start`

53
backend/package.json Normal file
View File

@ -0,0 +1,53 @@
{
"name": "medease",
"description": "medease - template backend",
"scripts": {
"start": "npm run db:migrate && npm run db:seed && npm run watch",
"db:migrate": "sequelize-cli db:migrate",
"db:seed": "sequelize-cli db:seed:all",
"db:drop": "sequelize-cli db:drop",
"db:create": "sequelize-cli db:create",
"watch": "node watcher.js"
},
"dependencies": {
"@google-cloud/storage": "^5.18.2",
"axios": "^1.6.7",
"bcrypt": "5.1.1",
"chokidar": "^4.0.3",
"cors": "2.8.5",
"csv-parser": "^3.0.0",
"express": "4.18.2",
"formidable": "1.2.2",
"helmet": "4.1.1",
"json2csv": "^5.0.7",
"jsonwebtoken": "8.5.1",
"lodash": "4.17.21",
"moment": "2.30.1",
"multer": "^1.4.4",
"mysql2": "2.2.5",
"nodemailer": "6.9.9",
"passport": "^0.7.0",
"passport-google-oauth2": "^0.2.0",
"passport-jwt": "^4.0.1",
"passport-microsoft": "^0.1.0",
"pg": "8.4.1",
"pg-hstore": "2.3.4",
"sequelize": "6.35.2",
"sequelize-json-schema": "^2.1.1",
"sqlite": "4.0.15",
"swagger-jsdoc": "^6.2.8",
"swagger-ui-express": "^5.0.0",
"tedious": "^18.2.4"
},
"engines": {
"node": ">=18"
},
"private": true,
"devDependencies": {
"cross-env": "7.0.3",
"mocha": "8.1.3",
"node-mocks-http": "1.9.0",
"nodemon": "2.0.5",
"sequelize-cli": "6.6.2"
}
}

27
backend/src/auth/auth.js Normal file
View File

@ -0,0 +1,27 @@
const config = require('../config');
const passport = require('passport');
const JWTstrategy = require('passport-jwt').Strategy;
const ExtractJWT = require('passport-jwt').ExtractJwt;
const UsersDBApi = require('../db/api/users');
passport.use(new JWTstrategy({
passReqToCallback: true,
secretOrKey: config.secret_key,
jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken()
}, async (req, token, done) => {
try {
const user = await UsersDBApi.findBy( {email: token.user.email});
if (user && user.disabled) {
return done (new Error(`User '${user.email}' is disabled`));
}
req.currentUser = user;
return done(null, user);
} catch (error) {
done(error);
}
}));

59
backend/src/config.js Normal file
View File

@ -0,0 +1,59 @@
const os = require('os');
const config = {
gcloud: {
bucket: "fldemo-files",
hash: "009f46ce11214895e551dafcc2ebe972"
},
bcrypt: {
saltRounds: 12
},
admin_pass: "a3903b27",
user_pass: "59df6f661b7f",
admin_email: "admin@flatlogic.com",
providers: {
LOCAL: 'local',
GOOGLE: 'google',
MICROSOFT: 'microsoft'
},
secret_key: process.env.SECRET_KEY || '',
remote: '',
port: process.env.NODE_ENV === "production" ? "" : "8080",
hostUI: process.env.NODE_ENV === "production" ? "" : "http://localhost",
portUI: process.env.NODE_ENV === "production" ? "" : "3000",
portUIProd: process.env.NODE_ENV === "production" ? "" : ":3000",
swaggerUI: process.env.NODE_ENV === "production" ? "" : "http://localhost",
swaggerPort: process.env.NODE_ENV === "production" ? "" : ":8080",
uploadDir: os.tmpdir(),
email: {
from: 'medease <app@flatlogic.app>',
host: 'email-smtp.us-east-1.amazonaws.com',
port: 587,
auth: {
user: process.env.EMAIL_USER || '',
pass: process.env.EMAIL_PASS,
},
tls: {
rejectUnauthorized: false
}
},
roles: {
admin: 'Administrator',
user: 'User',
},
project_uuid: 'a3903b27-e0be-4365-b29e-59df6f661b7f',
flHost: process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'dev_stage' ? 'https://flatlogic.com/projects' : 'http://localhost:3000/projects',
};
config.host = process.env.NODE_ENV === "production" ? config.remote : "http://localhost";
config.apiUrl = `${config.host}${config.port ? `:${config.port}` : ``}/api`;
config.swaggerUrl = `${config.swaggerUI}${config.swaggerPort}`;
config.uiUrl = `${config.hostUI}${config.portUI ? `:${config.portUI}` : ``}/#`;
config.backUrl = `${config.hostUI}${config.portUI ? `:${config.portUI}` : ``}`;
module.exports = config;

View File

@ -0,0 +1,335 @@
const db = require('../models');
const crypto = require('crypto');
const Utils = require('../utils');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class AppointmentsDBApi {
static async create(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const appointments = await db.appointments.create(
{
id: data.id || undefined,
start_time: data.start_time
||
null
,
end_time: data.end_time
||
null
,
status: data.status
||
null
,
importHash: data.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
},
{ transaction },
);
return appointments;
}
static async bulkImport(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
// Prepare data - wrapping individual data transformations in a map() method
const appointmentsData = data.map((item, index) => ({
id: item.id || undefined,
start_time: item.start_time
||
null
,
end_time: item.end_time
||
null
,
status: item.status
||
null
,
importHash: item.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
createdAt: new Date(Date.now() + index * 1000),
}));
// Bulk create items
const appointments = await db.appointments.bulkCreate(appointmentsData, { transaction });
return appointments;
}
static async update(id, data, options) {
const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined;
const appointments = await db.appointments.findByPk(id, {}, {transaction});
const updatePayload = {};
if (data.start_time !== undefined) updatePayload.start_time = data.start_time;
if (data.end_time !== undefined) updatePayload.end_time = data.end_time;
if (data.status !== undefined) updatePayload.status = data.status;
updatePayload.updatedById = currentUser.id;
await appointments.update(updatePayload, {transaction});
return appointments;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const appointments = await db.appointments.findAll({
where: {
id: {
[Op.in]: ids,
},
},
transaction,
});
await db.sequelize.transaction(async (transaction) => {
for (const record of appointments) {
await record.update(
{deletedBy: currentUser.id},
{transaction}
);
}
for (const record of appointments) {
await record.destroy({transaction});
}
});
return appointments;
}
static async remove(id, options) {
const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined;
const appointments = await db.appointments.findByPk(id, options);
await appointments.update({
deletedBy: currentUser.id
}, {
transaction,
});
await appointments.destroy({
transaction
});
return appointments;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
const appointments = await db.appointments.findOne(
{ where },
{ transaction },
);
if (!appointments) {
return appointments;
}
const output = appointments.get({plain: true});
return output;
}
static async findAll(filter, options) {
const limit = filter.limit || 0;
let offset = 0;
let where = {};
const currentPage = +filter.page;
const user = (options && options.currentUser) || null;
offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [];
if (filter) {
if (filter.id) {
where = {
...where,
['id']: Utils.uuid(filter.id),
};
}
if (filter.start_timeRange) {
const [start, end] = filter.start_timeRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
start_time: {
...where.start_time,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
start_time: {
...where.start_time,
[Op.lte]: end,
},
};
}
}
if (filter.end_timeRange) {
const [start, end] = filter.end_timeRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
end_time: {
...where.end_time,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
end_time: {
...where.end_time,
[Op.lte]: end,
},
};
}
}
if (filter.active !== undefined) {
where = {
...where,
active: filter.active === true || filter.active === 'true'
};
}
if (filter.status) {
where = {
...where,
status: filter.status,
};
}
if (filter.createdAtRange) {
const [start, end] = filter.createdAtRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.lte]: end,
},
};
}
}
}
const queryOptions = {
where,
include,
distinct: true,
order: filter.field && filter.sort
? [[filter.field, filter.sort]]
: [['createdAt', 'desc']],
transaction: options?.transaction,
logging: console.log
};
if (!options?.countOnly) {
queryOptions.limit = limit ? Number(limit) : undefined;
queryOptions.offset = offset ? Number(offset) : undefined;
}
try {
const { rows, count } = await db.appointments.findAndCountAll(queryOptions);
return {
rows: options?.countOnly ? [] : rows,
count: count
};
} catch (error) {
console.error('Error executing query:', error);
throw error;
}
}
static async findAllAutocomplete(query, limit, offset) {
let where = {};
if (query) {
where = {
[Op.or]: [
{ ['id']: Utils.uuid(query) },
Utils.ilike(
'appointments',
'status',
query,
),
],
};
}
const records = await db.appointments.findAll({
attributes: [ 'id', 'status' ],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
orderBy: [['status', 'ASC']],
});
return records.map((record) => ({
id: record.id,
label: record.status,
}));
}
};

View File

@ -0,0 +1,313 @@
const db = require('../models');
const crypto = require('crypto');
const Utils = require('../utils');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class DoctorsDBApi {
static async create(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const doctors = await db.doctors.create(
{
id: data.id || undefined,
first_name: data.first_name
||
null
,
last_name: data.last_name
||
null
,
specialization: data.specialization
||
null
,
importHash: data.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
},
{ transaction },
);
return doctors;
}
static async bulkImport(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
// Prepare data - wrapping individual data transformations in a map() method
const doctorsData = data.map((item, index) => ({
id: item.id || undefined,
first_name: item.first_name
||
null
,
last_name: item.last_name
||
null
,
specialization: item.specialization
||
null
,
importHash: item.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
createdAt: new Date(Date.now() + index * 1000),
}));
// Bulk create items
const doctors = await db.doctors.bulkCreate(doctorsData, { transaction });
return doctors;
}
static async update(id, data, options) {
const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined;
const doctors = await db.doctors.findByPk(id, {}, {transaction});
const updatePayload = {};
if (data.first_name !== undefined) updatePayload.first_name = data.first_name;
if (data.last_name !== undefined) updatePayload.last_name = data.last_name;
if (data.specialization !== undefined) updatePayload.specialization = data.specialization;
updatePayload.updatedById = currentUser.id;
await doctors.update(updatePayload, {transaction});
return doctors;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const doctors = await db.doctors.findAll({
where: {
id: {
[Op.in]: ids,
},
},
transaction,
});
await db.sequelize.transaction(async (transaction) => {
for (const record of doctors) {
await record.update(
{deletedBy: currentUser.id},
{transaction}
);
}
for (const record of doctors) {
await record.destroy({transaction});
}
});
return doctors;
}
static async remove(id, options) {
const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined;
const doctors = await db.doctors.findByPk(id, options);
await doctors.update({
deletedBy: currentUser.id
}, {
transaction,
});
await doctors.destroy({
transaction
});
return doctors;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
const doctors = await db.doctors.findOne(
{ where },
{ transaction },
);
if (!doctors) {
return doctors;
}
const output = doctors.get({plain: true});
return output;
}
static async findAll(filter, options) {
const limit = filter.limit || 0;
let offset = 0;
let where = {};
const currentPage = +filter.page;
const user = (options && options.currentUser) || null;
offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [];
if (filter) {
if (filter.id) {
where = {
...where,
['id']: Utils.uuid(filter.id),
};
}
if (filter.first_name) {
where = {
...where,
[Op.and]: Utils.ilike(
'doctors',
'first_name',
filter.first_name,
),
};
}
if (filter.last_name) {
where = {
...where,
[Op.and]: Utils.ilike(
'doctors',
'last_name',
filter.last_name,
),
};
}
if (filter.specialization) {
where = {
...where,
[Op.and]: Utils.ilike(
'doctors',
'specialization',
filter.specialization,
),
};
}
if (filter.active !== undefined) {
where = {
...where,
active: filter.active === true || filter.active === 'true'
};
}
if (filter.createdAtRange) {
const [start, end] = filter.createdAtRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.lte]: end,
},
};
}
}
}
const queryOptions = {
where,
include,
distinct: true,
order: filter.field && filter.sort
? [[filter.field, filter.sort]]
: [['createdAt', 'desc']],
transaction: options?.transaction,
logging: console.log
};
if (!options?.countOnly) {
queryOptions.limit = limit ? Number(limit) : undefined;
queryOptions.offset = offset ? Number(offset) : undefined;
}
try {
const { rows, count } = await db.doctors.findAndCountAll(queryOptions);
return {
rows: options?.countOnly ? [] : rows,
count: count
};
} catch (error) {
console.error('Error executing query:', error);
throw error;
}
}
static async findAllAutocomplete(query, limit, offset) {
let where = {};
if (query) {
where = {
[Op.or]: [
{ ['id']: Utils.uuid(query) },
Utils.ilike(
'doctors',
'first_name',
query,
),
],
};
}
const records = await db.doctors.findAll({
attributes: [ 'id', 'first_name' ],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
orderBy: [['first_name', 'ASC']],
});
return records.map((record) => ({
id: record.id,
label: record.first_name,
}));
}
};

View File

@ -0,0 +1,339 @@
const db = require('../models');
const crypto = require('crypto');
const Utils = require('../utils');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class Inventory_itemsDBApi {
static async create(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const inventory_items = await db.inventory_items.create(
{
id: data.id || undefined,
item_name: data.item_name
||
null
,
quantity: data.quantity
||
null
,
expiry_date: data.expiry_date
||
null
,
importHash: data.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
},
{ transaction },
);
return inventory_items;
}
static async bulkImport(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
// Prepare data - wrapping individual data transformations in a map() method
const inventory_itemsData = data.map((item, index) => ({
id: item.id || undefined,
item_name: item.item_name
||
null
,
quantity: item.quantity
||
null
,
expiry_date: item.expiry_date
||
null
,
importHash: item.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
createdAt: new Date(Date.now() + index * 1000),
}));
// Bulk create items
const inventory_items = await db.inventory_items.bulkCreate(inventory_itemsData, { transaction });
return inventory_items;
}
static async update(id, data, options) {
const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined;
const inventory_items = await db.inventory_items.findByPk(id, {}, {transaction});
const updatePayload = {};
if (data.item_name !== undefined) updatePayload.item_name = data.item_name;
if (data.quantity !== undefined) updatePayload.quantity = data.quantity;
if (data.expiry_date !== undefined) updatePayload.expiry_date = data.expiry_date;
updatePayload.updatedById = currentUser.id;
await inventory_items.update(updatePayload, {transaction});
return inventory_items;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const inventory_items = await db.inventory_items.findAll({
where: {
id: {
[Op.in]: ids,
},
},
transaction,
});
await db.sequelize.transaction(async (transaction) => {
for (const record of inventory_items) {
await record.update(
{deletedBy: currentUser.id},
{transaction}
);
}
for (const record of inventory_items) {
await record.destroy({transaction});
}
});
return inventory_items;
}
static async remove(id, options) {
const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined;
const inventory_items = await db.inventory_items.findByPk(id, options);
await inventory_items.update({
deletedBy: currentUser.id
}, {
transaction,
});
await inventory_items.destroy({
transaction
});
return inventory_items;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
const inventory_items = await db.inventory_items.findOne(
{ where },
{ transaction },
);
if (!inventory_items) {
return inventory_items;
}
const output = inventory_items.get({plain: true});
return output;
}
static async findAll(filter, options) {
const limit = filter.limit || 0;
let offset = 0;
let where = {};
const currentPage = +filter.page;
const user = (options && options.currentUser) || null;
offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [];
if (filter) {
if (filter.id) {
where = {
...where,
['id']: Utils.uuid(filter.id),
};
}
if (filter.item_name) {
where = {
...where,
[Op.and]: Utils.ilike(
'inventory_items',
'item_name',
filter.item_name,
),
};
}
if (filter.quantityRange) {
const [start, end] = filter.quantityRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
quantity: {
...where.quantity,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
quantity: {
...where.quantity,
[Op.lte]: end,
},
};
}
}
if (filter.expiry_dateRange) {
const [start, end] = filter.expiry_dateRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
expiry_date: {
...where.expiry_date,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
expiry_date: {
...where.expiry_date,
[Op.lte]: end,
},
};
}
}
if (filter.active !== undefined) {
where = {
...where,
active: filter.active === true || filter.active === 'true'
};
}
if (filter.createdAtRange) {
const [start, end] = filter.createdAtRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.lte]: end,
},
};
}
}
}
const queryOptions = {
where,
include,
distinct: true,
order: filter.field && filter.sort
? [[filter.field, filter.sort]]
: [['createdAt', 'desc']],
transaction: options?.transaction,
logging: console.log
};
if (!options?.countOnly) {
queryOptions.limit = limit ? Number(limit) : undefined;
queryOptions.offset = offset ? Number(offset) : undefined;
}
try {
const { rows, count } = await db.inventory_items.findAndCountAll(queryOptions);
return {
rows: options?.countOnly ? [] : rows,
count: count
};
} catch (error) {
console.error('Error executing query:', error);
throw error;
}
}
static async findAllAutocomplete(query, limit, offset) {
let where = {};
if (query) {
where = {
[Op.or]: [
{ ['id']: Utils.uuid(query) },
Utils.ilike(
'inventory_items',
'item_name',
query,
),
],
};
}
const records = await db.inventory_items.findAll({
attributes: [ 'id', 'item_name' ],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
orderBy: [['item_name', 'ASC']],
});
return records.map((record) => ({
id: record.id,
label: record.item_name,
}));
}
};

View File

@ -0,0 +1,290 @@
const db = require('../models');
const crypto = require('crypto');
const Utils = require('../utils');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class Lab_techniciansDBApi {
static async create(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const lab_technicians = await db.lab_technicians.create(
{
id: data.id || undefined,
first_name: data.first_name
||
null
,
last_name: data.last_name
||
null
,
importHash: data.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
},
{ transaction },
);
return lab_technicians;
}
static async bulkImport(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
// Prepare data - wrapping individual data transformations in a map() method
const lab_techniciansData = data.map((item, index) => ({
id: item.id || undefined,
first_name: item.first_name
||
null
,
last_name: item.last_name
||
null
,
importHash: item.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
createdAt: new Date(Date.now() + index * 1000),
}));
// Bulk create items
const lab_technicians = await db.lab_technicians.bulkCreate(lab_techniciansData, { transaction });
return lab_technicians;
}
static async update(id, data, options) {
const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined;
const lab_technicians = await db.lab_technicians.findByPk(id, {}, {transaction});
const updatePayload = {};
if (data.first_name !== undefined) updatePayload.first_name = data.first_name;
if (data.last_name !== undefined) updatePayload.last_name = data.last_name;
updatePayload.updatedById = currentUser.id;
await lab_technicians.update(updatePayload, {transaction});
return lab_technicians;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const lab_technicians = await db.lab_technicians.findAll({
where: {
id: {
[Op.in]: ids,
},
},
transaction,
});
await db.sequelize.transaction(async (transaction) => {
for (const record of lab_technicians) {
await record.update(
{deletedBy: currentUser.id},
{transaction}
);
}
for (const record of lab_technicians) {
await record.destroy({transaction});
}
});
return lab_technicians;
}
static async remove(id, options) {
const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined;
const lab_technicians = await db.lab_technicians.findByPk(id, options);
await lab_technicians.update({
deletedBy: currentUser.id
}, {
transaction,
});
await lab_technicians.destroy({
transaction
});
return lab_technicians;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
const lab_technicians = await db.lab_technicians.findOne(
{ where },
{ transaction },
);
if (!lab_technicians) {
return lab_technicians;
}
const output = lab_technicians.get({plain: true});
return output;
}
static async findAll(filter, options) {
const limit = filter.limit || 0;
let offset = 0;
let where = {};
const currentPage = +filter.page;
const user = (options && options.currentUser) || null;
offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [];
if (filter) {
if (filter.id) {
where = {
...where,
['id']: Utils.uuid(filter.id),
};
}
if (filter.first_name) {
where = {
...where,
[Op.and]: Utils.ilike(
'lab_technicians',
'first_name',
filter.first_name,
),
};
}
if (filter.last_name) {
where = {
...where,
[Op.and]: Utils.ilike(
'lab_technicians',
'last_name',
filter.last_name,
),
};
}
if (filter.active !== undefined) {
where = {
...where,
active: filter.active === true || filter.active === 'true'
};
}
if (filter.createdAtRange) {
const [start, end] = filter.createdAtRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.lte]: end,
},
};
}
}
}
const queryOptions = {
where,
include,
distinct: true,
order: filter.field && filter.sort
? [[filter.field, filter.sort]]
: [['createdAt', 'desc']],
transaction: options?.transaction,
logging: console.log
};
if (!options?.countOnly) {
queryOptions.limit = limit ? Number(limit) : undefined;
queryOptions.offset = offset ? Number(offset) : undefined;
}
try {
const { rows, count } = await db.lab_technicians.findAndCountAll(queryOptions);
return {
rows: options?.countOnly ? [] : rows,
count: count
};
} catch (error) {
console.error('Error executing query:', error);
throw error;
}
}
static async findAllAutocomplete(query, limit, offset) {
let where = {};
if (query) {
where = {
[Op.or]: [
{ ['id']: Utils.uuid(query) },
Utils.ilike(
'lab_technicians',
'first_name',
query,
),
],
};
}
const records = await db.lab_technicians.findAll({
attributes: [ 'id', 'first_name' ],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
orderBy: [['first_name', 'ASC']],
});
return records.map((record) => ({
id: record.id,
label: record.first_name,
}));
}
};

View File

@ -0,0 +1,290 @@
const db = require('../models');
const crypto = require('crypto');
const Utils = require('../utils');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class Lab_testsDBApi {
static async create(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const lab_tests = await db.lab_tests.create(
{
id: data.id || undefined,
test_name: data.test_name
||
null
,
results: data.results
||
null
,
importHash: data.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
},
{ transaction },
);
return lab_tests;
}
static async bulkImport(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
// Prepare data - wrapping individual data transformations in a map() method
const lab_testsData = data.map((item, index) => ({
id: item.id || undefined,
test_name: item.test_name
||
null
,
results: item.results
||
null
,
importHash: item.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
createdAt: new Date(Date.now() + index * 1000),
}));
// Bulk create items
const lab_tests = await db.lab_tests.bulkCreate(lab_testsData, { transaction });
return lab_tests;
}
static async update(id, data, options) {
const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined;
const lab_tests = await db.lab_tests.findByPk(id, {}, {transaction});
const updatePayload = {};
if (data.test_name !== undefined) updatePayload.test_name = data.test_name;
if (data.results !== undefined) updatePayload.results = data.results;
updatePayload.updatedById = currentUser.id;
await lab_tests.update(updatePayload, {transaction});
return lab_tests;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const lab_tests = await db.lab_tests.findAll({
where: {
id: {
[Op.in]: ids,
},
},
transaction,
});
await db.sequelize.transaction(async (transaction) => {
for (const record of lab_tests) {
await record.update(
{deletedBy: currentUser.id},
{transaction}
);
}
for (const record of lab_tests) {
await record.destroy({transaction});
}
});
return lab_tests;
}
static async remove(id, options) {
const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined;
const lab_tests = await db.lab_tests.findByPk(id, options);
await lab_tests.update({
deletedBy: currentUser.id
}, {
transaction,
});
await lab_tests.destroy({
transaction
});
return lab_tests;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
const lab_tests = await db.lab_tests.findOne(
{ where },
{ transaction },
);
if (!lab_tests) {
return lab_tests;
}
const output = lab_tests.get({plain: true});
return output;
}
static async findAll(filter, options) {
const limit = filter.limit || 0;
let offset = 0;
let where = {};
const currentPage = +filter.page;
const user = (options && options.currentUser) || null;
offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [];
if (filter) {
if (filter.id) {
where = {
...where,
['id']: Utils.uuid(filter.id),
};
}
if (filter.test_name) {
where = {
...where,
[Op.and]: Utils.ilike(
'lab_tests',
'test_name',
filter.test_name,
),
};
}
if (filter.results) {
where = {
...where,
[Op.and]: Utils.ilike(
'lab_tests',
'results',
filter.results,
),
};
}
if (filter.active !== undefined) {
where = {
...where,
active: filter.active === true || filter.active === 'true'
};
}
if (filter.createdAtRange) {
const [start, end] = filter.createdAtRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.lte]: end,
},
};
}
}
}
const queryOptions = {
where,
include,
distinct: true,
order: filter.field && filter.sort
? [[filter.field, filter.sort]]
: [['createdAt', 'desc']],
transaction: options?.transaction,
logging: console.log
};
if (!options?.countOnly) {
queryOptions.limit = limit ? Number(limit) : undefined;
queryOptions.offset = offset ? Number(offset) : undefined;
}
try {
const { rows, count } = await db.lab_tests.findAndCountAll(queryOptions);
return {
rows: options?.countOnly ? [] : rows,
count: count
};
} catch (error) {
console.error('Error executing query:', error);
throw error;
}
}
static async findAllAutocomplete(query, limit, offset) {
let where = {};
if (query) {
where = {
[Op.or]: [
{ ['id']: Utils.uuid(query) },
Utils.ilike(
'lab_tests',
'test_name',
query,
),
],
};
}
const records = await db.lab_tests.findAll({
attributes: [ 'id', 'test_name' ],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
orderBy: [['test_name', 'ASC']],
});
return records.map((record) => ({
id: record.id,
label: record.test_name,
}));
}
};

View File

@ -0,0 +1,290 @@
const db = require('../models');
const crypto = require('crypto');
const Utils = require('../utils');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class Medical_recordsDBApi {
static async create(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const medical_records = await db.medical_records.create(
{
id: data.id || undefined,
record_type: data.record_type
||
null
,
description: data.description
||
null
,
importHash: data.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
},
{ transaction },
);
return medical_records;
}
static async bulkImport(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
// Prepare data - wrapping individual data transformations in a map() method
const medical_recordsData = data.map((item, index) => ({
id: item.id || undefined,
record_type: item.record_type
||
null
,
description: item.description
||
null
,
importHash: item.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
createdAt: new Date(Date.now() + index * 1000),
}));
// Bulk create items
const medical_records = await db.medical_records.bulkCreate(medical_recordsData, { transaction });
return medical_records;
}
static async update(id, data, options) {
const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined;
const medical_records = await db.medical_records.findByPk(id, {}, {transaction});
const updatePayload = {};
if (data.record_type !== undefined) updatePayload.record_type = data.record_type;
if (data.description !== undefined) updatePayload.description = data.description;
updatePayload.updatedById = currentUser.id;
await medical_records.update(updatePayload, {transaction});
return medical_records;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const medical_records = await db.medical_records.findAll({
where: {
id: {
[Op.in]: ids,
},
},
transaction,
});
await db.sequelize.transaction(async (transaction) => {
for (const record of medical_records) {
await record.update(
{deletedBy: currentUser.id},
{transaction}
);
}
for (const record of medical_records) {
await record.destroy({transaction});
}
});
return medical_records;
}
static async remove(id, options) {
const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined;
const medical_records = await db.medical_records.findByPk(id, options);
await medical_records.update({
deletedBy: currentUser.id
}, {
transaction,
});
await medical_records.destroy({
transaction
});
return medical_records;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
const medical_records = await db.medical_records.findOne(
{ where },
{ transaction },
);
if (!medical_records) {
return medical_records;
}
const output = medical_records.get({plain: true});
return output;
}
static async findAll(filter, options) {
const limit = filter.limit || 0;
let offset = 0;
let where = {};
const currentPage = +filter.page;
const user = (options && options.currentUser) || null;
offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [];
if (filter) {
if (filter.id) {
where = {
...where,
['id']: Utils.uuid(filter.id),
};
}
if (filter.record_type) {
where = {
...where,
[Op.and]: Utils.ilike(
'medical_records',
'record_type',
filter.record_type,
),
};
}
if (filter.description) {
where = {
...where,
[Op.and]: Utils.ilike(
'medical_records',
'description',
filter.description,
),
};
}
if (filter.active !== undefined) {
where = {
...where,
active: filter.active === true || filter.active === 'true'
};
}
if (filter.createdAtRange) {
const [start, end] = filter.createdAtRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.lte]: end,
},
};
}
}
}
const queryOptions = {
where,
include,
distinct: true,
order: filter.field && filter.sort
? [[filter.field, filter.sort]]
: [['createdAt', 'desc']],
transaction: options?.transaction,
logging: console.log
};
if (!options?.countOnly) {
queryOptions.limit = limit ? Number(limit) : undefined;
queryOptions.offset = offset ? Number(offset) : undefined;
}
try {
const { rows, count } = await db.medical_records.findAndCountAll(queryOptions);
return {
rows: options?.countOnly ? [] : rows,
count: count
};
} catch (error) {
console.error('Error executing query:', error);
throw error;
}
}
static async findAllAutocomplete(query, limit, offset) {
let where = {};
if (query) {
where = {
[Op.or]: [
{ ['id']: Utils.uuid(query) },
Utils.ilike(
'medical_records',
'record_type',
query,
),
],
};
}
const records = await db.medical_records.findAll({
attributes: [ 'id', 'record_type' ],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
orderBy: [['record_type', 'ASC']],
});
return records.map((record) => ({
id: record.id,
label: record.record_type,
}));
}
};

View File

@ -0,0 +1,326 @@
const db = require('../models');
const crypto = require('crypto');
const Utils = require('../utils');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class PatientsDBApi {
static async create(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const patients = await db.patients.create(
{
id: data.id || undefined,
first_name: data.first_name
||
null
,
last_name: data.last_name
||
null
,
date_of_birth: data.date_of_birth
||
null
,
importHash: data.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
},
{ transaction },
);
return patients;
}
static async bulkImport(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
// Prepare data - wrapping individual data transformations in a map() method
const patientsData = data.map((item, index) => ({
id: item.id || undefined,
first_name: item.first_name
||
null
,
last_name: item.last_name
||
null
,
date_of_birth: item.date_of_birth
||
null
,
importHash: item.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
createdAt: new Date(Date.now() + index * 1000),
}));
// Bulk create items
const patients = await db.patients.bulkCreate(patientsData, { transaction });
return patients;
}
static async update(id, data, options) {
const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined;
const patients = await db.patients.findByPk(id, {}, {transaction});
const updatePayload = {};
if (data.first_name !== undefined) updatePayload.first_name = data.first_name;
if (data.last_name !== undefined) updatePayload.last_name = data.last_name;
if (data.date_of_birth !== undefined) updatePayload.date_of_birth = data.date_of_birth;
updatePayload.updatedById = currentUser.id;
await patients.update(updatePayload, {transaction});
return patients;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const patients = await db.patients.findAll({
where: {
id: {
[Op.in]: ids,
},
},
transaction,
});
await db.sequelize.transaction(async (transaction) => {
for (const record of patients) {
await record.update(
{deletedBy: currentUser.id},
{transaction}
);
}
for (const record of patients) {
await record.destroy({transaction});
}
});
return patients;
}
static async remove(id, options) {
const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined;
const patients = await db.patients.findByPk(id, options);
await patients.update({
deletedBy: currentUser.id
}, {
transaction,
});
await patients.destroy({
transaction
});
return patients;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
const patients = await db.patients.findOne(
{ where },
{ transaction },
);
if (!patients) {
return patients;
}
const output = patients.get({plain: true});
return output;
}
static async findAll(filter, options) {
const limit = filter.limit || 0;
let offset = 0;
let where = {};
const currentPage = +filter.page;
const user = (options && options.currentUser) || null;
offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [];
if (filter) {
if (filter.id) {
where = {
...where,
['id']: Utils.uuid(filter.id),
};
}
if (filter.first_name) {
where = {
...where,
[Op.and]: Utils.ilike(
'patients',
'first_name',
filter.first_name,
),
};
}
if (filter.last_name) {
where = {
...where,
[Op.and]: Utils.ilike(
'patients',
'last_name',
filter.last_name,
),
};
}
if (filter.date_of_birthRange) {
const [start, end] = filter.date_of_birthRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
date_of_birth: {
...where.date_of_birth,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
date_of_birth: {
...where.date_of_birth,
[Op.lte]: end,
},
};
}
}
if (filter.active !== undefined) {
where = {
...where,
active: filter.active === true || filter.active === 'true'
};
}
if (filter.createdAtRange) {
const [start, end] = filter.createdAtRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.lte]: end,
},
};
}
}
}
const queryOptions = {
where,
include,
distinct: true,
order: filter.field && filter.sort
? [[filter.field, filter.sort]]
: [['createdAt', 'desc']],
transaction: options?.transaction,
logging: console.log
};
if (!options?.countOnly) {
queryOptions.limit = limit ? Number(limit) : undefined;
queryOptions.offset = offset ? Number(offset) : undefined;
}
try {
const { rows, count } = await db.patients.findAndCountAll(queryOptions);
return {
rows: options?.countOnly ? [] : rows,
count: count
};
} catch (error) {
console.error('Error executing query:', error);
throw error;
}
}
static async findAllAutocomplete(query, limit, offset) {
let where = {};
if (query) {
where = {
[Op.or]: [
{ ['id']: Utils.uuid(query) },
Utils.ilike(
'patients',
'first_name',
query,
),
],
};
}
const records = await db.patients.findAll({
attributes: [ 'id', 'first_name' ],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
orderBy: [['first_name', 'ASC']],
});
return records.map((record) => ({
id: record.id,
label: record.first_name,
}));
}
};

View File

@ -0,0 +1,290 @@
const db = require('../models');
const crypto = require('crypto');
const Utils = require('../utils');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class PharmacistsDBApi {
static async create(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const pharmacists = await db.pharmacists.create(
{
id: data.id || undefined,
first_name: data.first_name
||
null
,
last_name: data.last_name
||
null
,
importHash: data.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
},
{ transaction },
);
return pharmacists;
}
static async bulkImport(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
// Prepare data - wrapping individual data transformations in a map() method
const pharmacistsData = data.map((item, index) => ({
id: item.id || undefined,
first_name: item.first_name
||
null
,
last_name: item.last_name
||
null
,
importHash: item.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
createdAt: new Date(Date.now() + index * 1000),
}));
// Bulk create items
const pharmacists = await db.pharmacists.bulkCreate(pharmacistsData, { transaction });
return pharmacists;
}
static async update(id, data, options) {
const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined;
const pharmacists = await db.pharmacists.findByPk(id, {}, {transaction});
const updatePayload = {};
if (data.first_name !== undefined) updatePayload.first_name = data.first_name;
if (data.last_name !== undefined) updatePayload.last_name = data.last_name;
updatePayload.updatedById = currentUser.id;
await pharmacists.update(updatePayload, {transaction});
return pharmacists;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const pharmacists = await db.pharmacists.findAll({
where: {
id: {
[Op.in]: ids,
},
},
transaction,
});
await db.sequelize.transaction(async (transaction) => {
for (const record of pharmacists) {
await record.update(
{deletedBy: currentUser.id},
{transaction}
);
}
for (const record of pharmacists) {
await record.destroy({transaction});
}
});
return pharmacists;
}
static async remove(id, options) {
const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined;
const pharmacists = await db.pharmacists.findByPk(id, options);
await pharmacists.update({
deletedBy: currentUser.id
}, {
transaction,
});
await pharmacists.destroy({
transaction
});
return pharmacists;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
const pharmacists = await db.pharmacists.findOne(
{ where },
{ transaction },
);
if (!pharmacists) {
return pharmacists;
}
const output = pharmacists.get({plain: true});
return output;
}
static async findAll(filter, options) {
const limit = filter.limit || 0;
let offset = 0;
let where = {};
const currentPage = +filter.page;
const user = (options && options.currentUser) || null;
offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [];
if (filter) {
if (filter.id) {
where = {
...where,
['id']: Utils.uuid(filter.id),
};
}
if (filter.first_name) {
where = {
...where,
[Op.and]: Utils.ilike(
'pharmacists',
'first_name',
filter.first_name,
),
};
}
if (filter.last_name) {
where = {
...where,
[Op.and]: Utils.ilike(
'pharmacists',
'last_name',
filter.last_name,
),
};
}
if (filter.active !== undefined) {
where = {
...where,
active: filter.active === true || filter.active === 'true'
};
}
if (filter.createdAtRange) {
const [start, end] = filter.createdAtRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.lte]: end,
},
};
}
}
}
const queryOptions = {
where,
include,
distinct: true,
order: filter.field && filter.sort
? [[filter.field, filter.sort]]
: [['createdAt', 'desc']],
transaction: options?.transaction,
logging: console.log
};
if (!options?.countOnly) {
queryOptions.limit = limit ? Number(limit) : undefined;
queryOptions.offset = offset ? Number(offset) : undefined;
}
try {
const { rows, count } = await db.pharmacists.findAndCountAll(queryOptions);
return {
rows: options?.countOnly ? [] : rows,
count: count
};
} catch (error) {
console.error('Error executing query:', error);
throw error;
}
}
static async findAllAutocomplete(query, limit, offset) {
let where = {};
if (query) {
where = {
[Op.or]: [
{ ['id']: Utils.uuid(query) },
Utils.ilike(
'pharmacists',
'first_name',
query,
),
],
};
}
const records = await db.pharmacists.findAll({
attributes: [ 'id', 'first_name' ],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
orderBy: [['first_name', 'ASC']],
});
return records.map((record) => ({
id: record.id,
label: record.first_name,
}));
}
};

View File

@ -0,0 +1,290 @@
const db = require('../models');
const crypto = require('crypto');
const Utils = require('../utils');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class PrescriptionsDBApi {
static async create(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const prescriptions = await db.prescriptions.create(
{
id: data.id || undefined,
medication_name: data.medication_name
||
null
,
dosage: data.dosage
||
null
,
importHash: data.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
},
{ transaction },
);
return prescriptions;
}
static async bulkImport(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
// Prepare data - wrapping individual data transformations in a map() method
const prescriptionsData = data.map((item, index) => ({
id: item.id || undefined,
medication_name: item.medication_name
||
null
,
dosage: item.dosage
||
null
,
importHash: item.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
createdAt: new Date(Date.now() + index * 1000),
}));
// Bulk create items
const prescriptions = await db.prescriptions.bulkCreate(prescriptionsData, { transaction });
return prescriptions;
}
static async update(id, data, options) {
const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined;
const prescriptions = await db.prescriptions.findByPk(id, {}, {transaction});
const updatePayload = {};
if (data.medication_name !== undefined) updatePayload.medication_name = data.medication_name;
if (data.dosage !== undefined) updatePayload.dosage = data.dosage;
updatePayload.updatedById = currentUser.id;
await prescriptions.update(updatePayload, {transaction});
return prescriptions;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const prescriptions = await db.prescriptions.findAll({
where: {
id: {
[Op.in]: ids,
},
},
transaction,
});
await db.sequelize.transaction(async (transaction) => {
for (const record of prescriptions) {
await record.update(
{deletedBy: currentUser.id},
{transaction}
);
}
for (const record of prescriptions) {
await record.destroy({transaction});
}
});
return prescriptions;
}
static async remove(id, options) {
const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined;
const prescriptions = await db.prescriptions.findByPk(id, options);
await prescriptions.update({
deletedBy: currentUser.id
}, {
transaction,
});
await prescriptions.destroy({
transaction
});
return prescriptions;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
const prescriptions = await db.prescriptions.findOne(
{ where },
{ transaction },
);
if (!prescriptions) {
return prescriptions;
}
const output = prescriptions.get({plain: true});
return output;
}
static async findAll(filter, options) {
const limit = filter.limit || 0;
let offset = 0;
let where = {};
const currentPage = +filter.page;
const user = (options && options.currentUser) || null;
offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [];
if (filter) {
if (filter.id) {
where = {
...where,
['id']: Utils.uuid(filter.id),
};
}
if (filter.medication_name) {
where = {
...where,
[Op.and]: Utils.ilike(
'prescriptions',
'medication_name',
filter.medication_name,
),
};
}
if (filter.dosage) {
where = {
...where,
[Op.and]: Utils.ilike(
'prescriptions',
'dosage',
filter.dosage,
),
};
}
if (filter.active !== undefined) {
where = {
...where,
active: filter.active === true || filter.active === 'true'
};
}
if (filter.createdAtRange) {
const [start, end] = filter.createdAtRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.lte]: end,
},
};
}
}
}
const queryOptions = {
where,
include,
distinct: true,
order: filter.field && filter.sort
? [[filter.field, filter.sort]]
: [['createdAt', 'desc']],
transaction: options?.transaction,
logging: console.log
};
if (!options?.countOnly) {
queryOptions.limit = limit ? Number(limit) : undefined;
queryOptions.offset = offset ? Number(offset) : undefined;
}
try {
const { rows, count } = await db.prescriptions.findAndCountAll(queryOptions);
return {
rows: options?.countOnly ? [] : rows,
count: count
};
} catch (error) {
console.error('Error executing query:', error);
throw error;
}
}
static async findAllAutocomplete(query, limit, offset) {
let where = {};
if (query) {
where = {
[Op.or]: [
{ ['id']: Utils.uuid(query) },
Utils.ilike(
'prescriptions',
'medication_name',
query,
),
],
};
}
const records = await db.prescriptions.findAll({
attributes: [ 'id', 'medication_name' ],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
orderBy: [['medication_name', 'ASC']],
});
return records.map((record) => ({
id: record.id,
label: record.medication_name,
}));
}
};

692
backend/src/db/api/users.js Normal file
View File

@ -0,0 +1,692 @@
const db = require('../models');
const crypto = require('crypto');
const Utils = require('../utils');
const bcrypt = require('bcrypt');
const config = require('../../config');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class UsersDBApi {
static async create(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const users = await db.users.create(
{
id: data.data.id || undefined,
firstName: data.data.firstName
||
null,
lastName: data.data.lastName
||
null,
phoneNumber: data.data.phoneNumber
||
null,
email: data.data.email
||
null,
disabled: data.data.disabled
||
false
,
password: data.data.password
||
null,
emailVerified: data.data.emailVerified
||
true
,
emailVerificationToken: data.data.emailVerificationToken
||
null,
emailVerificationTokenExpiresAt: data.data.emailVerificationTokenExpiresAt
||
null,
passwordResetToken: data.data.passwordResetToken
||
null,
passwordResetTokenExpiresAt: data.data.passwordResetTokenExpiresAt
||
null,
provider: data.data.provider
||
null,
importHash: data.data.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
},
{ transaction },
);
return users;
}
static async bulkImport(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
// Prepare data - wrapping individual data transformations in a map() method
const usersData = data.map((item, index) => ({
id: item.id || undefined,
firstName: item.firstName
||
null
,
lastName: item.lastName
||
null
,
phoneNumber: item.phoneNumber
||
null
,
email: item.email
||
null
,
disabled: item.disabled
||
false
,
password: item.password
||
null
,
emailVerified: item.emailVerified
||
false
,
emailVerificationToken: item.emailVerificationToken
||
null
,
emailVerificationTokenExpiresAt: item.emailVerificationTokenExpiresAt
||
null
,
passwordResetToken: item.passwordResetToken
||
null
,
passwordResetTokenExpiresAt: item.passwordResetTokenExpiresAt
||
null
,
provider: item.provider
||
null
,
importHash: item.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
createdAt: new Date(Date.now() + index * 1000),
}));
// Bulk create items
const users = await db.users.bulkCreate(usersData, { transaction });
return users;
}
static async update(id, data, options) {
const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined;
const users = await db.users.findByPk(id, {}, {transaction});
if (!data?.app_role) {
data.app_role = users?.app_role?.id;
}
if (!data?.custom_permissions) {
data.custom_permissions = users?.custom_permissions?.map(item => item.id);
}
if (data.password) {
data.password = bcrypt.hashSync(
data.password,
config.bcrypt.saltRounds,
);
} else {
data.password = users.password;
}
const updatePayload = {};
if (data.firstName !== undefined) updatePayload.firstName = data.firstName;
if (data.lastName !== undefined) updatePayload.lastName = data.lastName;
if (data.phoneNumber !== undefined) updatePayload.phoneNumber = data.phoneNumber;
if (data.email !== undefined) updatePayload.email = data.email;
if (data.disabled !== undefined) updatePayload.disabled = data.disabled;
if (data.password !== undefined) updatePayload.password = data.password;
if (data.emailVerified !== undefined) updatePayload.emailVerified = data.emailVerified;
else updatePayload.emailVerified = true;
if (data.emailVerificationToken !== undefined) updatePayload.emailVerificationToken = data.emailVerificationToken;
if (data.emailVerificationTokenExpiresAt !== undefined) updatePayload.emailVerificationTokenExpiresAt = data.emailVerificationTokenExpiresAt;
if (data.passwordResetToken !== undefined) updatePayload.passwordResetToken = data.passwordResetToken;
if (data.passwordResetTokenExpiresAt !== undefined) updatePayload.passwordResetTokenExpiresAt = data.passwordResetTokenExpiresAt;
if (data.provider !== undefined) updatePayload.provider = data.provider;
updatePayload.updatedById = currentUser.id;
await users.update(updatePayload, {transaction});
return users;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const users = await db.users.findAll({
where: {
id: {
[Op.in]: ids,
},
},
transaction,
});
await db.sequelize.transaction(async (transaction) => {
for (const record of users) {
await record.update(
{deletedBy: currentUser.id},
{transaction}
);
}
for (const record of users) {
await record.destroy({transaction});
}
});
return users;
}
static async remove(id, options) {
const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined;
const users = await db.users.findByPk(id, options);
await users.update({
deletedBy: currentUser.id
}, {
transaction,
});
await users.destroy({
transaction
});
return users;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
const users = await db.users.findOne(
{ where },
{ transaction },
);
if (!users) {
return users;
}
const output = users.get({plain: true});
return output;
}
static async findAll(filter, options) {
const limit = filter.limit || 0;
let offset = 0;
let where = {};
const currentPage = +filter.page;
const user = (options && options.currentUser) || null;
offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [];
if (filter) {
if (filter.id) {
where = {
...where,
['id']: Utils.uuid(filter.id),
};
}
if (filter.firstName) {
where = {
...where,
[Op.and]: Utils.ilike(
'users',
'firstName',
filter.firstName,
),
};
}
if (filter.lastName) {
where = {
...where,
[Op.and]: Utils.ilike(
'users',
'lastName',
filter.lastName,
),
};
}
if (filter.phoneNumber) {
where = {
...where,
[Op.and]: Utils.ilike(
'users',
'phoneNumber',
filter.phoneNumber,
),
};
}
if (filter.email) {
where = {
...where,
[Op.and]: Utils.ilike(
'users',
'email',
filter.email,
),
};
}
if (filter.password) {
where = {
...where,
[Op.and]: Utils.ilike(
'users',
'password',
filter.password,
),
};
}
if (filter.emailVerificationToken) {
where = {
...where,
[Op.and]: Utils.ilike(
'users',
'emailVerificationToken',
filter.emailVerificationToken,
),
};
}
if (filter.passwordResetToken) {
where = {
...where,
[Op.and]: Utils.ilike(
'users',
'passwordResetToken',
filter.passwordResetToken,
),
};
}
if (filter.provider) {
where = {
...where,
[Op.and]: Utils.ilike(
'users',
'provider',
filter.provider,
),
};
}
if (filter.emailVerificationTokenExpiresAtRange) {
const [start, end] = filter.emailVerificationTokenExpiresAtRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
emailVerificationTokenExpiresAt: {
...where.emailVerificationTokenExpiresAt,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
emailVerificationTokenExpiresAt: {
...where.emailVerificationTokenExpiresAt,
[Op.lte]: end,
},
};
}
}
if (filter.passwordResetTokenExpiresAtRange) {
const [start, end] = filter.passwordResetTokenExpiresAtRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
passwordResetTokenExpiresAt: {
...where.passwordResetTokenExpiresAt,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
passwordResetTokenExpiresAt: {
...where.passwordResetTokenExpiresAt,
[Op.lte]: end,
},
};
}
}
if (filter.active !== undefined) {
where = {
...where,
active: filter.active === true || filter.active === 'true'
};
}
if (filter.disabled) {
where = {
...where,
disabled: filter.disabled,
};
}
if (filter.emailVerified) {
where = {
...where,
emailVerified: filter.emailVerified,
};
}
if (filter.createdAtRange) {
const [start, end] = filter.createdAtRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.lte]: end,
},
};
}
}
}
const queryOptions = {
where,
include,
distinct: true,
order: filter.field && filter.sort
? [[filter.field, filter.sort]]
: [['createdAt', 'desc']],
transaction: options?.transaction,
logging: console.log
};
if (!options?.countOnly) {
queryOptions.limit = limit ? Number(limit) : undefined;
queryOptions.offset = offset ? Number(offset) : undefined;
}
try {
const { rows, count } = await db.users.findAndCountAll(queryOptions);
return {
rows: options?.countOnly ? [] : rows,
count: count
};
} catch (error) {
console.error('Error executing query:', error);
throw error;
}
}
static async findAllAutocomplete(query, limit, offset) {
let where = {};
if (query) {
where = {
[Op.or]: [
{ ['id']: Utils.uuid(query) },
Utils.ilike(
'users',
'firstName',
query,
),
],
};
}
const records = await db.users.findAll({
attributes: [ 'id', 'firstName' ],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
orderBy: [['firstName', 'ASC']],
});
return records.map((record) => ({
id: record.id,
label: record.firstName,
}));
}
static async createFromAuth(data, options) {
const transaction = (options && options.transaction) || undefined;
const users = await db.users.create(
{
email: data.email,
firstName: data.firstName,
authenticationUid: data.authenticationUid,
password: data.password,
},
{ transaction },
);
const app_role = await db.roles.findOne({
where: { name: config.roles?.user || "User" },
});
if (app_role?.id) {
await users.setApp_role(app_role?.id || null, {
transaction,
});
}
await users.update(
{
authenticationUid: users.id,
},
{ transaction },
);
delete users.password;
return users;
}
static async updatePassword(id, password, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const users = await db.users.findByPk(id, {
transaction,
});
await users.update(
{
password,
authenticationUid: id,
updatedById: currentUser.id,
},
{ transaction },
);
return users;
}
static async generateEmailVerificationToken(email, options) {
return this._generateToken(['emailVerificationToken', 'emailVerificationTokenExpiresAt'], email, options);
}
static async generatePasswordResetToken(email, options) {
return this._generateToken(['passwordResetToken', 'passwordResetTokenExpiresAt'], email, options);
}
static async findByPasswordResetToken(token, options) {
const transaction = (options && options.transaction) || undefined;
return db.users.findOne(
{
where: {
passwordResetToken: token,
passwordResetTokenExpiresAt: {
[db.Sequelize.Op.gt]: Date.now(),
},
},
},
{ transaction },
);
}
static async findByEmailVerificationToken(token, options) {
const transaction = (options && options.transaction) || undefined;
return db.users.findOne(
{
where: {
emailVerificationToken: token,
emailVerificationTokenExpiresAt: {
[db.Sequelize.Op.gt]: Date.now(),
},
},
},
{ transaction },
);
}
static async markEmailVerified(id, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const users = await db.users.findByPk(id, {
transaction,
});
await users.update(
{
emailVerified: true,
updatedById: currentUser.id,
},
{ transaction },
);
return true;
}
static async _generateToken(keyNames, email, options) {
const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined;
const users = await db.users.findOne(
{
where: { email: email.toLowerCase() },
},
{
transaction,
},
);
const token = crypto
.randomBytes(20)
.toString('hex');
const tokenExpiresAt = Date.now() + 360000;
if(users){
await users.update(
{
[keyNames[0]]: token,
[keyNames[1]]: tokenExpiresAt,
updatedById: currentUser.id,
},
{transaction},
);
}
return token;
}
};

View File

@ -0,0 +1,33 @@
module.exports = {
production: {
dialect: 'postgres',
username: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME,
host: process.env.DB_HOST,
port: process.env.DB_PORT,
logging: console.log,
seederStorage: 'sequelize',
},
development: {
username: 'postgres',
dialect: 'postgres',
password: '',
database: 'db_medease',
host: process.env.DB_HOST || 'localhost',
logging: console.log,
seederStorage: 'sequelize',
},
dev_stage: {
dialect: 'postgres',
username: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME,
host: process.env.DB_HOST,
port: process.env.DB_PORT,
logging: console.log,
seederStorage: 'sequelize',
}
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,68 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function(sequelize, DataTypes) {
const appointments = sequelize.define(
'appointments',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
start_time: {
type: DataTypes.DATE,
},
end_time: {
type: DataTypes.DATE,
},
status: {
type: DataTypes.ENUM,
values: [
"scheduled",
"completed",
"cancelled"
],
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
unique: true,
},
},
{
timestamps: true,
paranoid: true,
freezeTableName: true,
},
);
appointments.associate = (db) => {
db.appointments.belongsTo(db.users, {
as: 'createdBy',
});
db.appointments.belongsTo(db.users, {
as: 'updatedBy',
});
};
return appointments;
};

View File

@ -0,0 +1,58 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function(sequelize, DataTypes) {
const doctors = sequelize.define(
'doctors',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
first_name: {
type: DataTypes.TEXT,
},
last_name: {
type: DataTypes.TEXT,
},
specialization: {
type: DataTypes.TEXT,
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
unique: true,
},
},
{
timestamps: true,
paranoid: true,
freezeTableName: true,
},
);
doctors.associate = (db) => {
db.doctors.belongsTo(db.users, {
as: 'createdBy',
});
db.doctors.belongsTo(db.users, {
as: 'updatedBy',
});
};
return doctors;
};

View File

@ -0,0 +1,38 @@
'use strict';
const fs = require('fs');
const path = require('path');
const Sequelize = require('sequelize');
const basename = path.basename(__filename);
const env = process.env.NODE_ENV || 'development';
const config = require("../db.config")[env];
const db = {};
let sequelize;
console.log(env);
if (config.use_env_variable) {
sequelize = new Sequelize(process.env[config.use_env_variable], config);
} else {
sequelize = new Sequelize(config.database, config.username, config.password, config);
}
fs
.readdirSync(__dirname)
.filter(file => {
return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js');
})
.forEach(file => {
const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes)
db[model.name] = model;
});
Object.keys(db).forEach(modelName => {
if (db[modelName].associate) {
db[modelName].associate(db);
}
});
db.sequelize = sequelize;
db.Sequelize = Sequelize;
module.exports = db;

View File

@ -0,0 +1,58 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function(sequelize, DataTypes) {
const inventory_items = sequelize.define(
'inventory_items',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
item_name: {
type: DataTypes.TEXT,
},
quantity: {
type: DataTypes.INTEGER,
},
expiry_date: {
type: DataTypes.DATE,
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
unique: true,
},
},
{
timestamps: true,
paranoid: true,
freezeTableName: true,
},
);
inventory_items.associate = (db) => {
db.inventory_items.belongsTo(db.users, {
as: 'createdBy',
});
db.inventory_items.belongsTo(db.users, {
as: 'updatedBy',
});
};
return inventory_items;
};

View File

@ -0,0 +1,53 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function(sequelize, DataTypes) {
const lab_technicians = sequelize.define(
'lab_technicians',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
first_name: {
type: DataTypes.TEXT,
},
last_name: {
type: DataTypes.TEXT,
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
unique: true,
},
},
{
timestamps: true,
paranoid: true,
freezeTableName: true,
},
);
lab_technicians.associate = (db) => {
db.lab_technicians.belongsTo(db.users, {
as: 'createdBy',
});
db.lab_technicians.belongsTo(db.users, {
as: 'updatedBy',
});
};
return lab_technicians;
};

View File

@ -0,0 +1,53 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function(sequelize, DataTypes) {
const lab_tests = sequelize.define(
'lab_tests',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
test_name: {
type: DataTypes.TEXT,
},
results: {
type: DataTypes.TEXT,
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
unique: true,
},
},
{
timestamps: true,
paranoid: true,
freezeTableName: true,
},
);
lab_tests.associate = (db) => {
db.lab_tests.belongsTo(db.users, {
as: 'createdBy',
});
db.lab_tests.belongsTo(db.users, {
as: 'updatedBy',
});
};
return lab_tests;
};

View File

@ -0,0 +1,53 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function(sequelize, DataTypes) {
const medical_records = sequelize.define(
'medical_records',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
record_type: {
type: DataTypes.TEXT,
},
description: {
type: DataTypes.TEXT,
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
unique: true,
},
},
{
timestamps: true,
paranoid: true,
freezeTableName: true,
},
);
medical_records.associate = (db) => {
db.medical_records.belongsTo(db.users, {
as: 'createdBy',
});
db.medical_records.belongsTo(db.users, {
as: 'updatedBy',
});
};
return medical_records;
};

View File

@ -0,0 +1,58 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function(sequelize, DataTypes) {
const patients = sequelize.define(
'patients',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
first_name: {
type: DataTypes.TEXT,
},
last_name: {
type: DataTypes.TEXT,
},
date_of_birth: {
type: DataTypes.DATE,
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
unique: true,
},
},
{
timestamps: true,
paranoid: true,
freezeTableName: true,
},
);
patients.associate = (db) => {
db.patients.belongsTo(db.users, {
as: 'createdBy',
});
db.patients.belongsTo(db.users, {
as: 'updatedBy',
});
};
return patients;
};

View File

@ -0,0 +1,53 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function(sequelize, DataTypes) {
const pharmacists = sequelize.define(
'pharmacists',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
first_name: {
type: DataTypes.TEXT,
},
last_name: {
type: DataTypes.TEXT,
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
unique: true,
},
},
{
timestamps: true,
paranoid: true,
freezeTableName: true,
},
);
pharmacists.associate = (db) => {
db.pharmacists.belongsTo(db.users, {
as: 'createdBy',
});
db.pharmacists.belongsTo(db.users, {
as: 'updatedBy',
});
};
return pharmacists;
};

View File

@ -0,0 +1,53 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function(sequelize, DataTypes) {
const prescriptions = sequelize.define(
'prescriptions',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
medication_name: {
type: DataTypes.TEXT,
},
dosage: {
type: DataTypes.TEXT,
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
unique: true,
},
},
{
timestamps: true,
paranoid: true,
freezeTableName: true,
},
);
prescriptions.associate = (db) => {
db.prescriptions.belongsTo(db.users, {
as: 'createdBy',
});
db.prescriptions.belongsTo(db.users, {
as: 'updatedBy',
});
};
return prescriptions;
};

View File

@ -0,0 +1,148 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function(sequelize, DataTypes) {
const users = sequelize.define(
'users',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
firstName: {
type: DataTypes.TEXT,
},
lastName: {
type: DataTypes.TEXT,
},
phoneNumber: {
type: DataTypes.TEXT,
},
email: {
type: DataTypes.TEXT,
},
disabled: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false,
},
password: {
type: DataTypes.TEXT,
},
emailVerified: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false,
},
emailVerificationToken: {
type: DataTypes.TEXT,
},
emailVerificationTokenExpiresAt: {
type: DataTypes.DATE,
},
passwordResetToken: {
type: DataTypes.TEXT,
},
passwordResetTokenExpiresAt: {
type: DataTypes.DATE,
},
provider: {
type: DataTypes.TEXT,
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
unique: true,
},
},
{
timestamps: true,
paranoid: true,
freezeTableName: true,
},
);
users.associate = (db) => {
db.users.belongsTo(db.users, {
as: 'createdBy',
});
db.users.belongsTo(db.users, {
as: 'updatedBy',
});
};
users.beforeCreate((users, options) => {
users = trimStringFields(users);
if (users.provider !== providers.LOCAL && Object.values(providers).indexOf(users.provider) > -1) {
users.emailVerified = true;
if (!users.password) {
const password = crypto
.randomBytes(20)
.toString('hex');
const hashedPassword = bcrypt.hashSync(
password,
config.bcrypt.saltRounds,
);
users.password = hashedPassword
}
}
});
users.beforeUpdate((users, options) => {
users = trimStringFields(users);
});
return users;
};
function trimStringFields(users) {
users.email = users.email.trim();
users.firstName = users.firstName
? users.firstName.trim()
: null;
users.lastName = users.lastName
? users.lastName.trim()
: null;
return users;
}

16
backend/src/db/reset.js Normal file
View File

@ -0,0 +1,16 @@
const db = require('./models');
const {execSync} = require("child_process");
console.log('Resetting Database');
db.sequelize
.sync({ force: true })
.then(() => {
execSync("sequelize db:seed:all");
console.log('OK');
process.exit();
})
.catch((error) => {
console.error(error);
process.exit(1);
});

View File

@ -0,0 +1,66 @@
'use strict';
const bcrypt = require("bcrypt");
const config = require("../../config");
const ids = [
'193bf4b5-9f07-4bd5-9a43-e7e41f3e96af',
'af5a87be-8f9c-4630-902a-37a60b7005ba',
'5bc531ab-611f-41f3-9373-b7cc5d09c93d',
]
module.exports = {
up: async (queryInterface, Sequelize) => {
let admin_hash = bcrypt.hashSync(config.admin_pass, config.bcrypt.saltRounds);
let user_hash = bcrypt.hashSync(config.user_pass, config.bcrypt.saltRounds);
try {
await queryInterface.bulkInsert('users', [
{
id: ids[0],
firstName: 'Admin',
email: config.admin_email,
emailVerified: true,
provider: config.providers.LOCAL,
password: admin_hash,
createdAt: new Date(),
updatedAt: new Date()
},
{
id: ids[1],
firstName: 'John',
email: 'john@doe.com',
emailVerified: true,
provider: config.providers.LOCAL,
password: user_hash,
createdAt: new Date(),
updatedAt: new Date()
},
{
id: ids[2],
firstName: 'Client',
email: 'client@hello.com',
emailVerified: true,
provider: config.providers.LOCAL,
password: user_hash,
createdAt: new Date(),
updatedAt: new Date()
},
]);
} catch (error) {
console.error('Error during bulkInsert:', error);
throw error;
}
},
down: async (queryInterface, Sequelize) => {
try {
await queryInterface.bulkDelete('users', {
id: {
[Sequelize.Op.in]: ids,
},
}, {});
} catch (error) {
console.error('Error during bulkDelete:', error);
throw error;
}
}
}

27
backend/src/db/utils.js Normal file
View File

@ -0,0 +1,27 @@
const validator = require('validator');
const { v4: uuid } = require('uuid');
const Sequelize = require('./models').Sequelize;
module.exports = class Utils {
static uuid(value) {
let id = value;
if (!validator.isUUID(id)) {
id = uuid();
}
return id;
}
static ilike(model, column, value) {
return Sequelize.where(
Sequelize.fn(
'lower',
Sequelize.col(`${model}.${column}`),
),
{
[Sequelize.Op.like]: `%${value}%`.toLowerCase(),
},
);
}
};

23
backend/src/helpers.js Normal file
View File

@ -0,0 +1,23 @@
const jwt = require('jsonwebtoken');
const config = require('./config');
module.exports = class Helpers {
static wrapAsync(fn) {
return function (req, res, next) {
fn(req, res, next).catch(next);
};
}
static commonErrorHandler(error, req, res, next) {
if ([400, 403, 404].includes(error.code)) {
return res.status(error.code).send(error.message);
}
console.error(error);
return res.status(500).send(error.message);
}
static jwtSign(data) {
return jwt.sign(data, config.secret_key, {expiresIn: '6h'});
};
};

143
backend/src/index.js Normal file
View File

@ -0,0 +1,143 @@
const express = require('express');
const cors = require('cors');
const app = express();
const passport = require('passport');
const path = require('path');
const fs = require('fs');
const bodyParser = require('body-parser');
const db = require('./db/models');
const config = require('./config');
const swaggerUI = require('swagger-ui-express');
const swaggerJsDoc = require('swagger-jsdoc');
const authRoutes = require('./routes/auth');
const searchRoutes = require('./routes/search');
const contactFormRoutes = require('./routes/contactForm');
const usersRoutes = require('./routes/users');
const appointmentsRoutes = require('./routes/appointments');
const doctorsRoutes = require('./routes/doctors');
const inventory_itemsRoutes = require('./routes/inventory_items');
const lab_techniciansRoutes = require('./routes/lab_technicians');
const lab_testsRoutes = require('./routes/lab_tests');
const medical_recordsRoutes = require('./routes/medical_records');
const patientsRoutes = require('./routes/patients');
const pharmacistsRoutes = require('./routes/pharmacists');
const prescriptionsRoutes = require('./routes/prescriptions');
const getBaseUrl = (url) => {
if (!url) return '';
return url.endsWith('/api') ? url.slice(0, -4) : url;
};
const options = {
definition: {
openapi: "3.0.0",
info: {
version: "1.0.0",
title: "medease",
description: "medease Online REST API for Testing and Prototyping application. You can perform all major operations with your entities - create, delete and etc.",
},
servers: [
{
url: getBaseUrl(process.env.NEXT_PUBLIC_BACK_API) || config.swaggerUrl,
description: "Development server",
}
],
components: {
securitySchemes: {
bearerAuth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT',
}
},
responses: {
UnauthorizedError: {
description: "Access token is missing or invalid"
}
}
},
security: [{
bearerAuth: []
}]
},
apis: ["./src/routes/*.js"],
};
const specs = swaggerJsDoc(options);
app.use('/api-docs', function (req, res, next) {
swaggerUI.host = getBaseUrl(process.env.NEXT_PUBLIC_BACK_API) || req.get('host');
next()
}, swaggerUI.serve, swaggerUI.setup(specs))
app.use(cors({origin: true}));
require('./auth/auth');
app.use(bodyParser.json());
app.use('/api/auth', authRoutes);
app.enable('trust proxy');
app.use('/api/users', passport.authenticate('jwt', {session: false}), usersRoutes);
app.use('/api/appointments', passport.authenticate('jwt', {session: false}), appointmentsRoutes);
app.use('/api/doctors', passport.authenticate('jwt', {session: false}), doctorsRoutes);
app.use('/api/inventory_items', passport.authenticate('jwt', {session: false}), inventory_itemsRoutes);
app.use('/api/lab_technicians', passport.authenticate('jwt', {session: false}), lab_techniciansRoutes);
app.use('/api/lab_tests', passport.authenticate('jwt', {session: false}), lab_testsRoutes);
app.use('/api/medical_records', passport.authenticate('jwt', {session: false}), medical_recordsRoutes);
app.use('/api/patients', passport.authenticate('jwt', {session: false}), patientsRoutes);
app.use('/api/pharmacists', passport.authenticate('jwt', {session: false}), pharmacistsRoutes);
app.use('/api/prescriptions', passport.authenticate('jwt', {session: false}), prescriptionsRoutes);
app.use('/api/contact-form', contactFormRoutes);
app.use(
'/api/search',
passport.authenticate('jwt', { session: false }),
searchRoutes);
const publicDir = path.join(
__dirname,
'../public',
);
if (fs.existsSync(publicDir)) {
app.use('/', express.static(publicDir));
app.get('*', function(request, response) {
response.sendFile(
path.resolve(publicDir, 'index.html'),
);
});
}
const PORT = process.env.NODE_ENV === 'dev_stage' ? 3000 : 8080;
db.sequelize.sync().then(function () {
app.listen(PORT, () => {
console.log(`Listening on port ${PORT}`);
});
});
module.exports = app;

View File

@ -0,0 +1,11 @@
const util = require('util');
const Multer = require('multer');
const maxSize = 10 * 1024 * 1024;
let processFile = Multer({
storage: Multer.memoryStorage(),
limits: { fileSize: maxSize },
}).single("file");
let processFileMiddleware = util.promisify(processFile);
module.exports = processFileMiddleware;

View File

@ -0,0 +1,408 @@
const express = require('express');
const AppointmentsService = require('../services/appointments');
const AppointmentsDBApi = require('../db/api/appointments');
const wrapAsync = require('../helpers').wrapAsync;
const router = express.Router();
const { parse } = require('json2csv');
/**
* @swagger
* components:
* schemas:
* Appointments:
* type: object
* properties:
*
*/
/**
* @swagger
* tags:
* name: Appointments
* description: The Appointments managing API
*/
/**
* @swagger
* /api/appointments:
* post:
* security:
* - bearerAuth: []
* tags: [Appointments]
* summary: Add new item
* description: Add new item
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated item
* type: object
* $ref: "#/components/schemas/Appointments"
* responses:
* 200:
* description: The item was successfully added
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Appointments"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*/
router.post('/', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer);
await AppointmentsService.create(req.body.data, req.currentUser, true, link.host);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/budgets/bulk-import:
* post:
* security:
* - bearerAuth: []
* tags: [Appointments]
* summary: Bulk import items
* description: Bulk import items
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated items
* type: array
* items:
* $ref: "#/components/schemas/Appointments"
* responses:
* 200:
* description: The items were successfully imported
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Appointments"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*
*/
router.post('/bulk-import', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer);
await AppointmentsService.bulkImport(req, res, true, link.host);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/appointments/{id}:
* put:
* security:
* - bearerAuth: []
* tags: [Appointments]
* summary: Update the data of the selected item
* description: Update the data of the selected item
* parameters:
* - in: path
* name: id
* description: Item ID to update
* required: true
* schema:
* type: string
* requestBody:
* description: Set new item data
* required: true
* content:
* application/json:
* schema:
* properties:
* id:
* description: ID of the updated item
* type: string
* data:
* description: Data of the updated item
* type: object
* $ref: "#/components/schemas/Appointments"
* required:
* - id
* responses:
* 200:
* description: The item data was successfully updated
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Appointments"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.put('/:id', wrapAsync(async (req, res) => {
await AppointmentsService.update(req.body.data, req.body.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/appointments/{id}:
* delete:
* security:
* - bearerAuth: []
* tags: [Appointments]
* summary: Delete the selected item
* description: Delete the selected item
* parameters:
* - in: path
* name: id
* description: Item ID to delete
* required: true
* schema:
* type: string
* responses:
* 200:
* description: The item was successfully deleted
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Appointments"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.delete('/:id', wrapAsync(async (req, res) => {
await AppointmentsService.remove(req.params.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/appointments/deleteByIds:
* post:
* security:
* - bearerAuth: []
* tags: [Appointments]
* summary: Delete the selected item list
* description: Delete the selected item list
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* ids:
* description: IDs of the updated items
* type: array
* responses:
* 200:
* description: The items was successfully deleted
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Appointments"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Items not found
* 500:
* description: Some server error
*/
router.post('/deleteByIds', wrapAsync(async (req, res) => {
await AppointmentsService.deleteByIds(req.body.data, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/appointments:
* get:
* security:
* - bearerAuth: []
* tags: [Appointments]
* summary: Get all appointments
* description: Get all appointments
* responses:
* 200:
* description: Appointments list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Appointments"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/', wrapAsync(async (req, res) => {
const filetype = req.query.filetype
const currentUser = req.currentUser;
const payload = await AppointmentsDBApi.findAll(
req.query, { currentUser }
);
if (filetype && filetype === 'csv') {
const fields = ['id',
'start_time','end_time',
];
const opts = { fields };
try {
const csv = parse(payload.rows, opts);
res.status(200).attachment(csv);
res.send(csv)
} catch (err) {
console.error(err);
}
} else {
res.status(200).send(payload);
}
}));
/**
* @swagger
* /api/appointments/count:
* get:
* security:
* - bearerAuth: []
* tags: [Appointments]
* summary: Count all appointments
* description: Count all appointments
* responses:
* 200:
* description: Appointments count successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Appointments"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/count', wrapAsync(async (req, res) => {
const currentUser = req.currentUser;
const payload = await AppointmentsDBApi.findAll(
req.query,
null,
{ countOnly: true, currentUser }
);
res.status(200).send(payload);
}));
/**
* @swagger
* /api/appointments/autocomplete:
* get:
* security:
* - bearerAuth: []
* tags: [Appointments]
* summary: Find all appointments that match search criteria
* description: Find all appointments that match search criteria
* responses:
* 200:
* description: Appointments list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Appointments"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/autocomplete', async (req, res) => {
const payload = await AppointmentsDBApi.findAllAutocomplete(
req.query.query,
req.query.limit,
req.query.offset,
);
res.status(200).send(payload);
});
/**
* @swagger
* /api/appointments/{id}:
* get:
* security:
* - bearerAuth: []
* tags: [Appointments]
* summary: Get selected item
* description: Get selected item
* parameters:
* - in: path
* name: id
* description: ID of item to get
* required: true
* schema:
* type: string
* responses:
* 200:
* description: Selected item successfully received
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Appointments"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.get('/:id', wrapAsync(async (req, res) => {
const payload = await AppointmentsDBApi.findBy(
{ id: req.params.id },
);
res.status(200).send(payload);
}));
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

200
backend/src/routes/auth.js Normal file
View File

@ -0,0 +1,200 @@
const express = require('express');
const passport = require('passport');
const config = require('../config');
const AuthService = require('../services/auth');
const ForbiddenError = require('../services/notifications/errors/forbidden');
const wrapAsync = require('../helpers').wrapAsync;
const router = express.Router();
/**
* @swagger
* components:
* schemas:
* Auth:
* type: object
* required:
* - email
* - password
* properties:
* email:
* type: string
* default: admin@flatlogic.com
* description: User email
* password:
* type: string
* default: password
* description: User password
*/
/**
* @swagger
* tags:
* name: Auth
* description: Authorization operations
*/
/**
* @swagger
* /api/auth/signin/local:
* post:
* tags: [Auth]
* summary: Logs user into the system
* description: Logs user into the system
* requestBody:
* description: Set valid user email and password
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Auth"
* responses:
* 200:
* description: Successful login
* 400:
* description: Invalid username/password supplied
* x-codegen-request-body-name: body
*/
router.post('/signin/local', wrapAsync(async (req, res) => {
const payload = await AuthService.signin(req.body.email, req.body.password, req,);
res.status(200).send(payload);
}));
/**
* @swagger
* /api/auth/me:
* get:
* security:
* - bearerAuth: []
* tags: [Auth]
* summary: Get current authorized user info
* description: Get current authorized user info
* responses:
* 200:
* description: Successful retrieval of current authorized user data
* 400:
* description: Invalid username/password supplied
* x-codegen-request-body-name: body
*/
router.get('/me', passport.authenticate('jwt', {session: false}), (req, res) => {
if (!req.currentUser || !req.currentUser.id) {
throw new ForbiddenError();
}
const payload = req.currentUser;
delete payload.password;
res.status(200).send(payload);
});
router.put('/password-reset', wrapAsync(async (req, res) => {
const payload = await AuthService.passwordReset(req.body.token, req.body.password, req,);
res.status(200).send(payload);
}));
router.put('/password-update', passport.authenticate('jwt', {session: false}), wrapAsync(async (req, res) => {
const payload = await AuthService.passwordUpdate(req.body.currentPassword, req.body.newPassword, req);
res.status(200).send(payload);
}));
router.post('/send-email-address-verification-email', passport.authenticate('jwt', {session: false}), wrapAsync(async (req, res) => {
if (!req.currentUser) {
throw new ForbiddenError();
}
await AuthService.sendEmailAddressVerificationEmail(req.currentUser.email);
const payload = true;
res.status(200).send(payload);
}));
router.post('/send-password-reset-email', wrapAsync(async (req, res) => {
const link = new URL(req.headers.referer);
await AuthService.sendPasswordResetEmail(req.body.email, 'register', link.host,);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/auth/signup:
* post:
* tags: [Auth]
* summary: Register new user into the system
* description: Register new user into the system
* requestBody:
* description: Set valid user email and password
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Auth"
* responses:
* 200:
* description: New user successfully signed up
* 400:
* description: Invalid username/password supplied
* 500:
* description: Some server error
* x-codegen-request-body-name: body
*/
router.post('/signup', wrapAsync(async (req, res) => {
const link = new URL(req.headers.referer);
const payload = await AuthService.signup(
req.body.email,
req.body.password,
req,
link.host,
)
res.status(200).send(payload);
}));
router.put('/profile', passport.authenticate('jwt', {session: false}), wrapAsync(async (req, res) => {
if (!req.currentUser || !req.currentUser.id) {
throw new ForbiddenError();
}
await AuthService.updateProfile(req.body.profile, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
router.put('/verify-email', wrapAsync(async (req, res) => {
const payload = await AuthService.verifyEmail(req.body.token, req, req.headers.referer)
res.status(200).send(payload);
}));
router.get('/signin/google', (req, res, next) => {
passport.authenticate("google", {scope: ["profile", "email"], state: req.query.app})(req, res, next);
});
router.get('/signin/google/callback', passport.authenticate("google", {failureRedirect: "/login", session: false}),
function (req, res) {
socialRedirect(res, req.query.state, req.user.token, config);
}
);
router.get('/signin/microsoft', (req, res, next) => {
passport.authenticate("microsoft", {
scope: ["https://graph.microsoft.com/user.read openid"],
state: req.query.app
})(req, res, next);
});
router.get('/signin/microsoft/callback', passport.authenticate("microsoft", {
failureRedirect: "/login",
session: false
}),
function (req, res) {
socialRedirect(res, req.query.state, req.user.token, config);
}
);
router.use('/', require('../helpers').commonErrorHandler);
function socialRedirect(res, state, token, config) {
res.redirect(config.uiUrl + "/login?token=" + token);
}
module.exports = router;

View File

@ -0,0 +1,22 @@
const express = require('express');
const router = express.Router();
router.post('/send', async (req, res) => {
try {
const { email, subject, message } = req.body;
if (!email || !subject || !message) {
return res.status(400).json({ error: 'All fields are required' });
}
// Generate the email content
// Send the email
res.status(200).json({ message: 'Email sent successfully' });
} catch (error) {
console.error('Error sending email:', error);
res.status(500).json({ error: 'Error sending email' });
}
});
module.exports = router;

View File

@ -0,0 +1,416 @@
const express = require('express');
const DoctorsService = require('../services/doctors');
const DoctorsDBApi = require('../db/api/doctors');
const wrapAsync = require('../helpers').wrapAsync;
const router = express.Router();
const { parse } = require('json2csv');
/**
* @swagger
* components:
* schemas:
* Doctors:
* type: object
* properties:
* first_name:
* type: string
* default: first_name
* last_name:
* type: string
* default: last_name
* specialization:
* type: string
* default: specialization
*/
/**
* @swagger
* tags:
* name: Doctors
* description: The Doctors managing API
*/
/**
* @swagger
* /api/doctors:
* post:
* security:
* - bearerAuth: []
* tags: [Doctors]
* summary: Add new item
* description: Add new item
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated item
* type: object
* $ref: "#/components/schemas/Doctors"
* responses:
* 200:
* description: The item was successfully added
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Doctors"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*/
router.post('/', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer);
await DoctorsService.create(req.body.data, req.currentUser, true, link.host);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/budgets/bulk-import:
* post:
* security:
* - bearerAuth: []
* tags: [Doctors]
* summary: Bulk import items
* description: Bulk import items
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated items
* type: array
* items:
* $ref: "#/components/schemas/Doctors"
* responses:
* 200:
* description: The items were successfully imported
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Doctors"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*
*/
router.post('/bulk-import', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer);
await DoctorsService.bulkImport(req, res, true, link.host);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/doctors/{id}:
* put:
* security:
* - bearerAuth: []
* tags: [Doctors]
* summary: Update the data of the selected item
* description: Update the data of the selected item
* parameters:
* - in: path
* name: id
* description: Item ID to update
* required: true
* schema:
* type: string
* requestBody:
* description: Set new item data
* required: true
* content:
* application/json:
* schema:
* properties:
* id:
* description: ID of the updated item
* type: string
* data:
* description: Data of the updated item
* type: object
* $ref: "#/components/schemas/Doctors"
* required:
* - id
* responses:
* 200:
* description: The item data was successfully updated
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Doctors"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.put('/:id', wrapAsync(async (req, res) => {
await DoctorsService.update(req.body.data, req.body.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/doctors/{id}:
* delete:
* security:
* - bearerAuth: []
* tags: [Doctors]
* summary: Delete the selected item
* description: Delete the selected item
* parameters:
* - in: path
* name: id
* description: Item ID to delete
* required: true
* schema:
* type: string
* responses:
* 200:
* description: The item was successfully deleted
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Doctors"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.delete('/:id', wrapAsync(async (req, res) => {
await DoctorsService.remove(req.params.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/doctors/deleteByIds:
* post:
* security:
* - bearerAuth: []
* tags: [Doctors]
* summary: Delete the selected item list
* description: Delete the selected item list
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* ids:
* description: IDs of the updated items
* type: array
* responses:
* 200:
* description: The items was successfully deleted
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Doctors"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Items not found
* 500:
* description: Some server error
*/
router.post('/deleteByIds', wrapAsync(async (req, res) => {
await DoctorsService.deleteByIds(req.body.data, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/doctors:
* get:
* security:
* - bearerAuth: []
* tags: [Doctors]
* summary: Get all doctors
* description: Get all doctors
* responses:
* 200:
* description: Doctors list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Doctors"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/', wrapAsync(async (req, res) => {
const filetype = req.query.filetype
const currentUser = req.currentUser;
const payload = await DoctorsDBApi.findAll(
req.query, { currentUser }
);
if (filetype && filetype === 'csv') {
const fields = ['id','first_name','last_name','specialization',
];
const opts = { fields };
try {
const csv = parse(payload.rows, opts);
res.status(200).attachment(csv);
res.send(csv)
} catch (err) {
console.error(err);
}
} else {
res.status(200).send(payload);
}
}));
/**
* @swagger
* /api/doctors/count:
* get:
* security:
* - bearerAuth: []
* tags: [Doctors]
* summary: Count all doctors
* description: Count all doctors
* responses:
* 200:
* description: Doctors count successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Doctors"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/count', wrapAsync(async (req, res) => {
const currentUser = req.currentUser;
const payload = await DoctorsDBApi.findAll(
req.query,
null,
{ countOnly: true, currentUser }
);
res.status(200).send(payload);
}));
/**
* @swagger
* /api/doctors/autocomplete:
* get:
* security:
* - bearerAuth: []
* tags: [Doctors]
* summary: Find all doctors that match search criteria
* description: Find all doctors that match search criteria
* responses:
* 200:
* description: Doctors list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Doctors"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/autocomplete', async (req, res) => {
const payload = await DoctorsDBApi.findAllAutocomplete(
req.query.query,
req.query.limit,
req.query.offset,
);
res.status(200).send(payload);
});
/**
* @swagger
* /api/doctors/{id}:
* get:
* security:
* - bearerAuth: []
* tags: [Doctors]
* summary: Get selected item
* description: Get selected item
* parameters:
* - in: path
* name: id
* description: ID of item to get
* required: true
* schema:
* type: string
* responses:
* 200:
* description: Selected item successfully received
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Doctors"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.get('/:id', wrapAsync(async (req, res) => {
const payload = await DoctorsDBApi.findBy(
{ id: req.params.id },
);
res.status(200).send(payload);
}));
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

View File

@ -0,0 +1,32 @@
const express = require('express');
const config = require('../config');
const path = require('path');
const passport = require('passport');
const services = require('../services/file');
const router = express.Router();
router.get('/download', (req, res) => {
if (process.env.NODE_ENV == "production" || process.env.NEXT_PUBLIC_BACK_API) {
services.downloadGCloud(req, res);
}
else {
services.downloadLocal(req, res);
}
});
router.post('/upload/:table/:field', passport.authenticate('jwt', {session: false}), (req, res) => {
const fileName = `${req.params.table}/${req.params.field}`;
if (process.env.NODE_ENV == "production" || process.env.NEXT_PUBLIC_BACK_API) {
services.uploadGCloud(fileName, req, res);
}
else {
services.uploadLocal(fileName, {
entity: null,
maxFileSize: 10 * 1024 * 1024,
folderIncludesAuthenticationUid: false,
})(req, res);
}
});
module.exports = router;

View File

@ -0,0 +1,416 @@
const express = require('express');
const Inventory_itemsService = require('../services/inventory_items');
const Inventory_itemsDBApi = require('../db/api/inventory_items');
const wrapAsync = require('../helpers').wrapAsync;
const router = express.Router();
const { parse } = require('json2csv');
/**
* @swagger
* components:
* schemas:
* Inventory_items:
* type: object
* properties:
* item_name:
* type: string
* default: item_name
* quantity:
* type: integer
* format: int64
*/
/**
* @swagger
* tags:
* name: Inventory_items
* description: The Inventory_items managing API
*/
/**
* @swagger
* /api/inventory_items:
* post:
* security:
* - bearerAuth: []
* tags: [Inventory_items]
* summary: Add new item
* description: Add new item
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated item
* type: object
* $ref: "#/components/schemas/Inventory_items"
* responses:
* 200:
* description: The item was successfully added
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Inventory_items"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*/
router.post('/', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer);
await Inventory_itemsService.create(req.body.data, req.currentUser, true, link.host);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/budgets/bulk-import:
* post:
* security:
* - bearerAuth: []
* tags: [Inventory_items]
* summary: Bulk import items
* description: Bulk import items
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated items
* type: array
* items:
* $ref: "#/components/schemas/Inventory_items"
* responses:
* 200:
* description: The items were successfully imported
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Inventory_items"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*
*/
router.post('/bulk-import', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer);
await Inventory_itemsService.bulkImport(req, res, true, link.host);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/inventory_items/{id}:
* put:
* security:
* - bearerAuth: []
* tags: [Inventory_items]
* summary: Update the data of the selected item
* description: Update the data of the selected item
* parameters:
* - in: path
* name: id
* description: Item ID to update
* required: true
* schema:
* type: string
* requestBody:
* description: Set new item data
* required: true
* content:
* application/json:
* schema:
* properties:
* id:
* description: ID of the updated item
* type: string
* data:
* description: Data of the updated item
* type: object
* $ref: "#/components/schemas/Inventory_items"
* required:
* - id
* responses:
* 200:
* description: The item data was successfully updated
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Inventory_items"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.put('/:id', wrapAsync(async (req, res) => {
await Inventory_itemsService.update(req.body.data, req.body.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/inventory_items/{id}:
* delete:
* security:
* - bearerAuth: []
* tags: [Inventory_items]
* summary: Delete the selected item
* description: Delete the selected item
* parameters:
* - in: path
* name: id
* description: Item ID to delete
* required: true
* schema:
* type: string
* responses:
* 200:
* description: The item was successfully deleted
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Inventory_items"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.delete('/:id', wrapAsync(async (req, res) => {
await Inventory_itemsService.remove(req.params.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/inventory_items/deleteByIds:
* post:
* security:
* - bearerAuth: []
* tags: [Inventory_items]
* summary: Delete the selected item list
* description: Delete the selected item list
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* ids:
* description: IDs of the updated items
* type: array
* responses:
* 200:
* description: The items was successfully deleted
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Inventory_items"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Items not found
* 500:
* description: Some server error
*/
router.post('/deleteByIds', wrapAsync(async (req, res) => {
await Inventory_itemsService.deleteByIds(req.body.data, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/inventory_items:
* get:
* security:
* - bearerAuth: []
* tags: [Inventory_items]
* summary: Get all inventory_items
* description: Get all inventory_items
* responses:
* 200:
* description: Inventory_items list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Inventory_items"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/', wrapAsync(async (req, res) => {
const filetype = req.query.filetype
const currentUser = req.currentUser;
const payload = await Inventory_itemsDBApi.findAll(
req.query, { currentUser }
);
if (filetype && filetype === 'csv') {
const fields = ['id','item_name',
'quantity',
'expiry_date',
];
const opts = { fields };
try {
const csv = parse(payload.rows, opts);
res.status(200).attachment(csv);
res.send(csv)
} catch (err) {
console.error(err);
}
} else {
res.status(200).send(payload);
}
}));
/**
* @swagger
* /api/inventory_items/count:
* get:
* security:
* - bearerAuth: []
* tags: [Inventory_items]
* summary: Count all inventory_items
* description: Count all inventory_items
* responses:
* 200:
* description: Inventory_items count successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Inventory_items"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/count', wrapAsync(async (req, res) => {
const currentUser = req.currentUser;
const payload = await Inventory_itemsDBApi.findAll(
req.query,
null,
{ countOnly: true, currentUser }
);
res.status(200).send(payload);
}));
/**
* @swagger
* /api/inventory_items/autocomplete:
* get:
* security:
* - bearerAuth: []
* tags: [Inventory_items]
* summary: Find all inventory_items that match search criteria
* description: Find all inventory_items that match search criteria
* responses:
* 200:
* description: Inventory_items list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Inventory_items"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/autocomplete', async (req, res) => {
const payload = await Inventory_itemsDBApi.findAllAutocomplete(
req.query.query,
req.query.limit,
req.query.offset,
);
res.status(200).send(payload);
});
/**
* @swagger
* /api/inventory_items/{id}:
* get:
* security:
* - bearerAuth: []
* tags: [Inventory_items]
* summary: Get selected item
* description: Get selected item
* parameters:
* - in: path
* name: id
* description: ID of item to get
* required: true
* schema:
* type: string
* responses:
* 200:
* description: Selected item successfully received
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Inventory_items"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.get('/:id', wrapAsync(async (req, res) => {
const payload = await Inventory_itemsDBApi.findBy(
{ id: req.params.id },
);
res.status(200).send(payload);
}));
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

View File

@ -0,0 +1,413 @@
const express = require('express');
const Lab_techniciansService = require('../services/lab_technicians');
const Lab_techniciansDBApi = require('../db/api/lab_technicians');
const wrapAsync = require('../helpers').wrapAsync;
const router = express.Router();
const { parse } = require('json2csv');
/**
* @swagger
* components:
* schemas:
* Lab_technicians:
* type: object
* properties:
* first_name:
* type: string
* default: first_name
* last_name:
* type: string
* default: last_name
*/
/**
* @swagger
* tags:
* name: Lab_technicians
* description: The Lab_technicians managing API
*/
/**
* @swagger
* /api/lab_technicians:
* post:
* security:
* - bearerAuth: []
* tags: [Lab_technicians]
* summary: Add new item
* description: Add new item
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated item
* type: object
* $ref: "#/components/schemas/Lab_technicians"
* responses:
* 200:
* description: The item was successfully added
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Lab_technicians"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*/
router.post('/', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer);
await Lab_techniciansService.create(req.body.data, req.currentUser, true, link.host);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/budgets/bulk-import:
* post:
* security:
* - bearerAuth: []
* tags: [Lab_technicians]
* summary: Bulk import items
* description: Bulk import items
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated items
* type: array
* items:
* $ref: "#/components/schemas/Lab_technicians"
* responses:
* 200:
* description: The items were successfully imported
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Lab_technicians"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*
*/
router.post('/bulk-import', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer);
await Lab_techniciansService.bulkImport(req, res, true, link.host);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/lab_technicians/{id}:
* put:
* security:
* - bearerAuth: []
* tags: [Lab_technicians]
* summary: Update the data of the selected item
* description: Update the data of the selected item
* parameters:
* - in: path
* name: id
* description: Item ID to update
* required: true
* schema:
* type: string
* requestBody:
* description: Set new item data
* required: true
* content:
* application/json:
* schema:
* properties:
* id:
* description: ID of the updated item
* type: string
* data:
* description: Data of the updated item
* type: object
* $ref: "#/components/schemas/Lab_technicians"
* required:
* - id
* responses:
* 200:
* description: The item data was successfully updated
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Lab_technicians"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.put('/:id', wrapAsync(async (req, res) => {
await Lab_techniciansService.update(req.body.data, req.body.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/lab_technicians/{id}:
* delete:
* security:
* - bearerAuth: []
* tags: [Lab_technicians]
* summary: Delete the selected item
* description: Delete the selected item
* parameters:
* - in: path
* name: id
* description: Item ID to delete
* required: true
* schema:
* type: string
* responses:
* 200:
* description: The item was successfully deleted
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Lab_technicians"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.delete('/:id', wrapAsync(async (req, res) => {
await Lab_techniciansService.remove(req.params.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/lab_technicians/deleteByIds:
* post:
* security:
* - bearerAuth: []
* tags: [Lab_technicians]
* summary: Delete the selected item list
* description: Delete the selected item list
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* ids:
* description: IDs of the updated items
* type: array
* responses:
* 200:
* description: The items was successfully deleted
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Lab_technicians"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Items not found
* 500:
* description: Some server error
*/
router.post('/deleteByIds', wrapAsync(async (req, res) => {
await Lab_techniciansService.deleteByIds(req.body.data, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/lab_technicians:
* get:
* security:
* - bearerAuth: []
* tags: [Lab_technicians]
* summary: Get all lab_technicians
* description: Get all lab_technicians
* responses:
* 200:
* description: Lab_technicians list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Lab_technicians"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/', wrapAsync(async (req, res) => {
const filetype = req.query.filetype
const currentUser = req.currentUser;
const payload = await Lab_techniciansDBApi.findAll(
req.query, { currentUser }
);
if (filetype && filetype === 'csv') {
const fields = ['id','first_name','last_name',
];
const opts = { fields };
try {
const csv = parse(payload.rows, opts);
res.status(200).attachment(csv);
res.send(csv)
} catch (err) {
console.error(err);
}
} else {
res.status(200).send(payload);
}
}));
/**
* @swagger
* /api/lab_technicians/count:
* get:
* security:
* - bearerAuth: []
* tags: [Lab_technicians]
* summary: Count all lab_technicians
* description: Count all lab_technicians
* responses:
* 200:
* description: Lab_technicians count successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Lab_technicians"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/count', wrapAsync(async (req, res) => {
const currentUser = req.currentUser;
const payload = await Lab_techniciansDBApi.findAll(
req.query,
null,
{ countOnly: true, currentUser }
);
res.status(200).send(payload);
}));
/**
* @swagger
* /api/lab_technicians/autocomplete:
* get:
* security:
* - bearerAuth: []
* tags: [Lab_technicians]
* summary: Find all lab_technicians that match search criteria
* description: Find all lab_technicians that match search criteria
* responses:
* 200:
* description: Lab_technicians list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Lab_technicians"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/autocomplete', async (req, res) => {
const payload = await Lab_techniciansDBApi.findAllAutocomplete(
req.query.query,
req.query.limit,
req.query.offset,
);
res.status(200).send(payload);
});
/**
* @swagger
* /api/lab_technicians/{id}:
* get:
* security:
* - bearerAuth: []
* tags: [Lab_technicians]
* summary: Get selected item
* description: Get selected item
* parameters:
* - in: path
* name: id
* description: ID of item to get
* required: true
* schema:
* type: string
* responses:
* 200:
* description: Selected item successfully received
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Lab_technicians"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.get('/:id', wrapAsync(async (req, res) => {
const payload = await Lab_techniciansDBApi.findBy(
{ id: req.params.id },
);
res.status(200).send(payload);
}));
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

View File

@ -0,0 +1,413 @@
const express = require('express');
const Lab_testsService = require('../services/lab_tests');
const Lab_testsDBApi = require('../db/api/lab_tests');
const wrapAsync = require('../helpers').wrapAsync;
const router = express.Router();
const { parse } = require('json2csv');
/**
* @swagger
* components:
* schemas:
* Lab_tests:
* type: object
* properties:
* test_name:
* type: string
* default: test_name
* results:
* type: string
* default: results
*/
/**
* @swagger
* tags:
* name: Lab_tests
* description: The Lab_tests managing API
*/
/**
* @swagger
* /api/lab_tests:
* post:
* security:
* - bearerAuth: []
* tags: [Lab_tests]
* summary: Add new item
* description: Add new item
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated item
* type: object
* $ref: "#/components/schemas/Lab_tests"
* responses:
* 200:
* description: The item was successfully added
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Lab_tests"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*/
router.post('/', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer);
await Lab_testsService.create(req.body.data, req.currentUser, true, link.host);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/budgets/bulk-import:
* post:
* security:
* - bearerAuth: []
* tags: [Lab_tests]
* summary: Bulk import items
* description: Bulk import items
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated items
* type: array
* items:
* $ref: "#/components/schemas/Lab_tests"
* responses:
* 200:
* description: The items were successfully imported
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Lab_tests"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*
*/
router.post('/bulk-import', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer);
await Lab_testsService.bulkImport(req, res, true, link.host);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/lab_tests/{id}:
* put:
* security:
* - bearerAuth: []
* tags: [Lab_tests]
* summary: Update the data of the selected item
* description: Update the data of the selected item
* parameters:
* - in: path
* name: id
* description: Item ID to update
* required: true
* schema:
* type: string
* requestBody:
* description: Set new item data
* required: true
* content:
* application/json:
* schema:
* properties:
* id:
* description: ID of the updated item
* type: string
* data:
* description: Data of the updated item
* type: object
* $ref: "#/components/schemas/Lab_tests"
* required:
* - id
* responses:
* 200:
* description: The item data was successfully updated
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Lab_tests"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.put('/:id', wrapAsync(async (req, res) => {
await Lab_testsService.update(req.body.data, req.body.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/lab_tests/{id}:
* delete:
* security:
* - bearerAuth: []
* tags: [Lab_tests]
* summary: Delete the selected item
* description: Delete the selected item
* parameters:
* - in: path
* name: id
* description: Item ID to delete
* required: true
* schema:
* type: string
* responses:
* 200:
* description: The item was successfully deleted
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Lab_tests"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.delete('/:id', wrapAsync(async (req, res) => {
await Lab_testsService.remove(req.params.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/lab_tests/deleteByIds:
* post:
* security:
* - bearerAuth: []
* tags: [Lab_tests]
* summary: Delete the selected item list
* description: Delete the selected item list
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* ids:
* description: IDs of the updated items
* type: array
* responses:
* 200:
* description: The items was successfully deleted
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Lab_tests"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Items not found
* 500:
* description: Some server error
*/
router.post('/deleteByIds', wrapAsync(async (req, res) => {
await Lab_testsService.deleteByIds(req.body.data, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/lab_tests:
* get:
* security:
* - bearerAuth: []
* tags: [Lab_tests]
* summary: Get all lab_tests
* description: Get all lab_tests
* responses:
* 200:
* description: Lab_tests list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Lab_tests"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/', wrapAsync(async (req, res) => {
const filetype = req.query.filetype
const currentUser = req.currentUser;
const payload = await Lab_testsDBApi.findAll(
req.query, { currentUser }
);
if (filetype && filetype === 'csv') {
const fields = ['id','test_name','results',
];
const opts = { fields };
try {
const csv = parse(payload.rows, opts);
res.status(200).attachment(csv);
res.send(csv)
} catch (err) {
console.error(err);
}
} else {
res.status(200).send(payload);
}
}));
/**
* @swagger
* /api/lab_tests/count:
* get:
* security:
* - bearerAuth: []
* tags: [Lab_tests]
* summary: Count all lab_tests
* description: Count all lab_tests
* responses:
* 200:
* description: Lab_tests count successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Lab_tests"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/count', wrapAsync(async (req, res) => {
const currentUser = req.currentUser;
const payload = await Lab_testsDBApi.findAll(
req.query,
null,
{ countOnly: true, currentUser }
);
res.status(200).send(payload);
}));
/**
* @swagger
* /api/lab_tests/autocomplete:
* get:
* security:
* - bearerAuth: []
* tags: [Lab_tests]
* summary: Find all lab_tests that match search criteria
* description: Find all lab_tests that match search criteria
* responses:
* 200:
* description: Lab_tests list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Lab_tests"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/autocomplete', async (req, res) => {
const payload = await Lab_testsDBApi.findAllAutocomplete(
req.query.query,
req.query.limit,
req.query.offset,
);
res.status(200).send(payload);
});
/**
* @swagger
* /api/lab_tests/{id}:
* get:
* security:
* - bearerAuth: []
* tags: [Lab_tests]
* summary: Get selected item
* description: Get selected item
* parameters:
* - in: path
* name: id
* description: ID of item to get
* required: true
* schema:
* type: string
* responses:
* 200:
* description: Selected item successfully received
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Lab_tests"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.get('/:id', wrapAsync(async (req, res) => {
const payload = await Lab_testsDBApi.findBy(
{ id: req.params.id },
);
res.status(200).send(payload);
}));
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

View File

@ -0,0 +1,413 @@
const express = require('express');
const Medical_recordsService = require('../services/medical_records');
const Medical_recordsDBApi = require('../db/api/medical_records');
const wrapAsync = require('../helpers').wrapAsync;
const router = express.Router();
const { parse } = require('json2csv');
/**
* @swagger
* components:
* schemas:
* Medical_records:
* type: object
* properties:
* record_type:
* type: string
* default: record_type
* description:
* type: string
* default: description
*/
/**
* @swagger
* tags:
* name: Medical_records
* description: The Medical_records managing API
*/
/**
* @swagger
* /api/medical_records:
* post:
* security:
* - bearerAuth: []
* tags: [Medical_records]
* summary: Add new item
* description: Add new item
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated item
* type: object
* $ref: "#/components/schemas/Medical_records"
* responses:
* 200:
* description: The item was successfully added
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Medical_records"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*/
router.post('/', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer);
await Medical_recordsService.create(req.body.data, req.currentUser, true, link.host);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/budgets/bulk-import:
* post:
* security:
* - bearerAuth: []
* tags: [Medical_records]
* summary: Bulk import items
* description: Bulk import items
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated items
* type: array
* items:
* $ref: "#/components/schemas/Medical_records"
* responses:
* 200:
* description: The items were successfully imported
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Medical_records"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*
*/
router.post('/bulk-import', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer);
await Medical_recordsService.bulkImport(req, res, true, link.host);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/medical_records/{id}:
* put:
* security:
* - bearerAuth: []
* tags: [Medical_records]
* summary: Update the data of the selected item
* description: Update the data of the selected item
* parameters:
* - in: path
* name: id
* description: Item ID to update
* required: true
* schema:
* type: string
* requestBody:
* description: Set new item data
* required: true
* content:
* application/json:
* schema:
* properties:
* id:
* description: ID of the updated item
* type: string
* data:
* description: Data of the updated item
* type: object
* $ref: "#/components/schemas/Medical_records"
* required:
* - id
* responses:
* 200:
* description: The item data was successfully updated
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Medical_records"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.put('/:id', wrapAsync(async (req, res) => {
await Medical_recordsService.update(req.body.data, req.body.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/medical_records/{id}:
* delete:
* security:
* - bearerAuth: []
* tags: [Medical_records]
* summary: Delete the selected item
* description: Delete the selected item
* parameters:
* - in: path
* name: id
* description: Item ID to delete
* required: true
* schema:
* type: string
* responses:
* 200:
* description: The item was successfully deleted
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Medical_records"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.delete('/:id', wrapAsync(async (req, res) => {
await Medical_recordsService.remove(req.params.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/medical_records/deleteByIds:
* post:
* security:
* - bearerAuth: []
* tags: [Medical_records]
* summary: Delete the selected item list
* description: Delete the selected item list
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* ids:
* description: IDs of the updated items
* type: array
* responses:
* 200:
* description: The items was successfully deleted
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Medical_records"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Items not found
* 500:
* description: Some server error
*/
router.post('/deleteByIds', wrapAsync(async (req, res) => {
await Medical_recordsService.deleteByIds(req.body.data, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/medical_records:
* get:
* security:
* - bearerAuth: []
* tags: [Medical_records]
* summary: Get all medical_records
* description: Get all medical_records
* responses:
* 200:
* description: Medical_records list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Medical_records"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/', wrapAsync(async (req, res) => {
const filetype = req.query.filetype
const currentUser = req.currentUser;
const payload = await Medical_recordsDBApi.findAll(
req.query, { currentUser }
);
if (filetype && filetype === 'csv') {
const fields = ['id','record_type','description',
];
const opts = { fields };
try {
const csv = parse(payload.rows, opts);
res.status(200).attachment(csv);
res.send(csv)
} catch (err) {
console.error(err);
}
} else {
res.status(200).send(payload);
}
}));
/**
* @swagger
* /api/medical_records/count:
* get:
* security:
* - bearerAuth: []
* tags: [Medical_records]
* summary: Count all medical_records
* description: Count all medical_records
* responses:
* 200:
* description: Medical_records count successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Medical_records"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/count', wrapAsync(async (req, res) => {
const currentUser = req.currentUser;
const payload = await Medical_recordsDBApi.findAll(
req.query,
null,
{ countOnly: true, currentUser }
);
res.status(200).send(payload);
}));
/**
* @swagger
* /api/medical_records/autocomplete:
* get:
* security:
* - bearerAuth: []
* tags: [Medical_records]
* summary: Find all medical_records that match search criteria
* description: Find all medical_records that match search criteria
* responses:
* 200:
* description: Medical_records list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Medical_records"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/autocomplete', async (req, res) => {
const payload = await Medical_recordsDBApi.findAllAutocomplete(
req.query.query,
req.query.limit,
req.query.offset,
);
res.status(200).send(payload);
});
/**
* @swagger
* /api/medical_records/{id}:
* get:
* security:
* - bearerAuth: []
* tags: [Medical_records]
* summary: Get selected item
* description: Get selected item
* parameters:
* - in: path
* name: id
* description: ID of item to get
* required: true
* schema:
* type: string
* responses:
* 200:
* description: Selected item successfully received
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Medical_records"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.get('/:id', wrapAsync(async (req, res) => {
const payload = await Medical_recordsDBApi.findBy(
{ id: req.params.id },
);
res.status(200).send(payload);
}));
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

View File

@ -0,0 +1,251 @@
const express = require('express');
const db = require('../db/models');
const wrapAsync = require('../helpers').wrapAsync;
const router = express.Router();
const sjs = require('sequelize-json-schema');
const { getWidget, askGpt } = require('../services/openai');
const RolesService = require('../services/roles');
const RolesDBApi = require("../db/api/roles");
/**
* @swagger
* /api/roles/roles-info/{infoId}:
* delete:
* security:
* - bearerAuth: []
* tags: [Roles]
* summary: Remove role information by ID
* description: Remove specific role information by ID
* parameters:
* - in: path
* name: infoId
* description: ID of role information to remove
* required: true
* schema:
* type: string
* - in: query
* name: userId
* description: ID of the user
* required: true
* schema:
* type: string
* - in: query
* name: key
* description: Key of the role information to remove
* required: true
* schema:
* type: string
* responses:
* 200:
* description: Role information successfully removed
* content:
* application/json:
* schema:
* type: object
* properties:
* user:
* type: string
* description: The user information
* 400:
* description: Invalid ID or key supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Role not found
* 500:
* description: Some server error
*/
router.delete(
'/roles-info/:infoId',
wrapAsync(async (req, res) => {
const role = await RolesService.removeRoleInfoById(
req.query.infoId,
req.query.roleId,
req.query.key,
req.currentUser,
);
res.status(200).send(role);
}),
);
/**
* @swagger
* /api/roles/role-info/{roleId}:
* get:
* security:
* - bearerAuth: []
* tags: [Roles]
* summary: Get role information by key
* description: Get specific role information by key
* parameters:
* - in: path
* name: roleId
* description: ID of role to get information for
* required: true
* schema:
* type: string
* - in: query
* name: key
* description: Key of the role information to retrieve
* required: true
* schema:
* type: string
* responses:
* 200:
* description: Role information successfully received
* content:
* application/json:
* schema:
* type: object
* properties:
* info:
* type: string
* description: The role information
* 400:
* description: Invalid ID or key supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Role not found
* 500:
* description: Some server error
*/
router.get(
'/info-by-key',
wrapAsync(async (req, res) => {
const roleId = req.query.roleId;
const key = req.query.key;
const currentUser = req.currentUser;
let info = await RolesService.getRoleInfoByKey(
key,
roleId,
currentUser,
);
const role = await RolesDBApi.findBy({ id: roleId });
if (!role?.role_customization) {
await Promise.all(["pie","bar"].map(async (e)=>{
const schema = await sjs.getSequelizeSchema(db.sequelize, {});
const payload = {
description: `Create some cool ${e} chart`,
modelDefinition: schema.definitions,
};
const widgetId = await getWidget(payload, currentUser?.id, roleId);
if(widgetId){
await RolesService.addRoleInfo(
roleId,
currentUser?.id,
'widgets',
widgetId,
req.currentUser,
);
}
}))
info = await RolesService.getRoleInfoByKey(
key,
roleId,
currentUser,
);
}
res.status(200).send(info);
}),
);
router.post(
'/create_widget',
wrapAsync(async (req, res) => {
const { description, userId, roleId } = req.body;
const currentUser = req.currentUser;
const schema = await sjs.getSequelizeSchema(db.sequelize, {});
const payload = {
description,
modelDefinition: schema.definitions,
};
const widgetId = await getWidget(payload, userId, roleId);
if (widgetId) {
await RolesService.addRoleInfo(
roleId,
userId,
'widgets',
widgetId,
currentUser,
);
return res.status(200).send(widgetId);
} else {
return res.status(400).send(widgetId);
}
}),
);
/**
* @swagger
* /api/openai/ask:
* post:
* security:
* - bearerAuth: []
* tags: [OpenAI]
* summary: Ask a question to ChatGPT
* description: Send a question to OpenAI's ChatGPT and get a response
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* question:
* type: string
* description: The question to ask ChatGPT
* apiKey:
* type: string
* description: OpenAI API key
* responses:
* 200:
* description: Question successfully answered
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* description: Whether the request was successful
* data:
* type: string
* description: The answer from ChatGPT
* 400:
* description: Invalid request
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 500:
* description: Some server error
*/
router.post(
'/ask-gpt',
wrapAsync(async (req, res) => {
const { prompt } = req.body;
if (!prompt) {
return res.status(400).send({
success: false,
error: 'Question and API key are required',
});
}
const response = await askGpt(prompt);
if (response.success) {
return res.status(200).send(response);
} else {
return res.status(500).send(response);
}
}),
);
module.exports = router;

View File

@ -0,0 +1,414 @@
const express = require('express');
const PatientsService = require('../services/patients');
const PatientsDBApi = require('../db/api/patients');
const wrapAsync = require('../helpers').wrapAsync;
const router = express.Router();
const { parse } = require('json2csv');
/**
* @swagger
* components:
* schemas:
* Patients:
* type: object
* properties:
* first_name:
* type: string
* default: first_name
* last_name:
* type: string
* default: last_name
*/
/**
* @swagger
* tags:
* name: Patients
* description: The Patients managing API
*/
/**
* @swagger
* /api/patients:
* post:
* security:
* - bearerAuth: []
* tags: [Patients]
* summary: Add new item
* description: Add new item
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated item
* type: object
* $ref: "#/components/schemas/Patients"
* responses:
* 200:
* description: The item was successfully added
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Patients"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*/
router.post('/', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer);
await PatientsService.create(req.body.data, req.currentUser, true, link.host);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/budgets/bulk-import:
* post:
* security:
* - bearerAuth: []
* tags: [Patients]
* summary: Bulk import items
* description: Bulk import items
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated items
* type: array
* items:
* $ref: "#/components/schemas/Patients"
* responses:
* 200:
* description: The items were successfully imported
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Patients"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*
*/
router.post('/bulk-import', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer);
await PatientsService.bulkImport(req, res, true, link.host);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/patients/{id}:
* put:
* security:
* - bearerAuth: []
* tags: [Patients]
* summary: Update the data of the selected item
* description: Update the data of the selected item
* parameters:
* - in: path
* name: id
* description: Item ID to update
* required: true
* schema:
* type: string
* requestBody:
* description: Set new item data
* required: true
* content:
* application/json:
* schema:
* properties:
* id:
* description: ID of the updated item
* type: string
* data:
* description: Data of the updated item
* type: object
* $ref: "#/components/schemas/Patients"
* required:
* - id
* responses:
* 200:
* description: The item data was successfully updated
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Patients"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.put('/:id', wrapAsync(async (req, res) => {
await PatientsService.update(req.body.data, req.body.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/patients/{id}:
* delete:
* security:
* - bearerAuth: []
* tags: [Patients]
* summary: Delete the selected item
* description: Delete the selected item
* parameters:
* - in: path
* name: id
* description: Item ID to delete
* required: true
* schema:
* type: string
* responses:
* 200:
* description: The item was successfully deleted
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Patients"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.delete('/:id', wrapAsync(async (req, res) => {
await PatientsService.remove(req.params.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/patients/deleteByIds:
* post:
* security:
* - bearerAuth: []
* tags: [Patients]
* summary: Delete the selected item list
* description: Delete the selected item list
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* ids:
* description: IDs of the updated items
* type: array
* responses:
* 200:
* description: The items was successfully deleted
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Patients"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Items not found
* 500:
* description: Some server error
*/
router.post('/deleteByIds', wrapAsync(async (req, res) => {
await PatientsService.deleteByIds(req.body.data, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/patients:
* get:
* security:
* - bearerAuth: []
* tags: [Patients]
* summary: Get all patients
* description: Get all patients
* responses:
* 200:
* description: Patients list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Patients"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/', wrapAsync(async (req, res) => {
const filetype = req.query.filetype
const currentUser = req.currentUser;
const payload = await PatientsDBApi.findAll(
req.query, { currentUser }
);
if (filetype && filetype === 'csv') {
const fields = ['id','first_name','last_name',
'date_of_birth',
];
const opts = { fields };
try {
const csv = parse(payload.rows, opts);
res.status(200).attachment(csv);
res.send(csv)
} catch (err) {
console.error(err);
}
} else {
res.status(200).send(payload);
}
}));
/**
* @swagger
* /api/patients/count:
* get:
* security:
* - bearerAuth: []
* tags: [Patients]
* summary: Count all patients
* description: Count all patients
* responses:
* 200:
* description: Patients count successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Patients"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/count', wrapAsync(async (req, res) => {
const currentUser = req.currentUser;
const payload = await PatientsDBApi.findAll(
req.query,
null,
{ countOnly: true, currentUser }
);
res.status(200).send(payload);
}));
/**
* @swagger
* /api/patients/autocomplete:
* get:
* security:
* - bearerAuth: []
* tags: [Patients]
* summary: Find all patients that match search criteria
* description: Find all patients that match search criteria
* responses:
* 200:
* description: Patients list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Patients"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/autocomplete', async (req, res) => {
const payload = await PatientsDBApi.findAllAutocomplete(
req.query.query,
req.query.limit,
req.query.offset,
);
res.status(200).send(payload);
});
/**
* @swagger
* /api/patients/{id}:
* get:
* security:
* - bearerAuth: []
* tags: [Patients]
* summary: Get selected item
* description: Get selected item
* parameters:
* - in: path
* name: id
* description: ID of item to get
* required: true
* schema:
* type: string
* responses:
* 200:
* description: Selected item successfully received
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Patients"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.get('/:id', wrapAsync(async (req, res) => {
const payload = await PatientsDBApi.findBy(
{ id: req.params.id },
);
res.status(200).send(payload);
}));
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

View File

@ -0,0 +1,104 @@
const express = require('express');
const router = express.Router();
const { pexelsKey, pexelsQuery } = require('../config');
const fetch = require('node-fetch');
const KEY = pexelsKey;
router.get('/image', async (req, res) => {
const headers = {
Authorization: `${KEY}`,
};
const query = pexelsQuery || 'nature';
const orientation = 'portrait';
const perPage = 1;
const url = `https://api.pexels.com/v1/search?query=${query}&orientation=${orientation}&per_page=${perPage}&page=1`;
try {
const response = await fetch(url, { headers });
const data = await response.json();
res.status(200).json(data.photos[0]);
} catch (error) {
res.status(200).json({ error: 'Failed to fetch image' });
}
});
router.get('/video', async (req, res) => {
const headers = {
Authorization: `${KEY}`,
};
const query = pexelsQuery || 'nature';
const orientation = 'portrait';
const perPage = 1;
const url = `https://api.pexels.com/videos/search?query=${query}&orientation=${orientation}&per_page=${perPage}&page=1`;
try {
const response = await fetch(url, { headers });
const data = await response.json();
res.status(200).json(data.videos[0]);
} catch (error) {
res.status(200).json({ error: 'Failed to fetch video' });
}
});
router.get('/multiple-images', async (req, res) => {
const headers = {
Authorization: `${KEY}`,
};
const queries = req.query.queries
? req.query.queries.split(',')
: ['home', 'apple', 'pizza', 'mountains', 'cat'];
const orientation = 'square';
const perPage = 1;
const fallbackImage = {
src: 'https://images.pexels.com/photos/8199252/pexels-photo-8199252.jpeg',
photographer: 'Yan Krukau',
photographer_url: 'https://www.pexels.com/@yankrukov',
};
const fetchFallbackImage = async () => {
try {
const response = await fetch('https://picsum.photos/600');
return {
src: response.url,
photographer: 'Random Picsum',
photographer_url: 'https://picsum.photos/',
};
} catch (error) {
return fallbackImage;
}
};
const fetchImage = async (query) => {
const url = `https://api.pexels.com/v1/search?query=${query}&orientation=${orientation}&per_page=${perPage}&page=1`;
const response = await fetch(url, { headers });
const data = await response.json();
return data.photos[0] || null;
};
const imagePromises = queries.map((query) => fetchImage(query));
const imagesResults = await Promise.allSettled(imagePromises);
const formattedImages = await Promise.all(imagesResults.map(async (result) => {
if (result.status === 'fulfilled' && result.value) {
const image = result.value;
return {
src: image.src?.original || fallbackImage.src,
photographer: image.photographer || fallbackImage.photographer,
photographer_url: image.photographer_url || fallbackImage.photographer_url,
};
} else {
const fallback = await fetchFallbackImage();
return {
src: fallback.src || '',
photographer: fallback.photographer || 'Unknown',
photographer_url: fallback.photographer_url || '',
};
}
}));
res.json(formattedImages);
});
module.exports = router;

View File

@ -0,0 +1,413 @@
const express = require('express');
const PharmacistsService = require('../services/pharmacists');
const PharmacistsDBApi = require('../db/api/pharmacists');
const wrapAsync = require('../helpers').wrapAsync;
const router = express.Router();
const { parse } = require('json2csv');
/**
* @swagger
* components:
* schemas:
* Pharmacists:
* type: object
* properties:
* first_name:
* type: string
* default: first_name
* last_name:
* type: string
* default: last_name
*/
/**
* @swagger
* tags:
* name: Pharmacists
* description: The Pharmacists managing API
*/
/**
* @swagger
* /api/pharmacists:
* post:
* security:
* - bearerAuth: []
* tags: [Pharmacists]
* summary: Add new item
* description: Add new item
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated item
* type: object
* $ref: "#/components/schemas/Pharmacists"
* responses:
* 200:
* description: The item was successfully added
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Pharmacists"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*/
router.post('/', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer);
await PharmacistsService.create(req.body.data, req.currentUser, true, link.host);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/budgets/bulk-import:
* post:
* security:
* - bearerAuth: []
* tags: [Pharmacists]
* summary: Bulk import items
* description: Bulk import items
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated items
* type: array
* items:
* $ref: "#/components/schemas/Pharmacists"
* responses:
* 200:
* description: The items were successfully imported
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Pharmacists"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*
*/
router.post('/bulk-import', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer);
await PharmacistsService.bulkImport(req, res, true, link.host);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/pharmacists/{id}:
* put:
* security:
* - bearerAuth: []
* tags: [Pharmacists]
* summary: Update the data of the selected item
* description: Update the data of the selected item
* parameters:
* - in: path
* name: id
* description: Item ID to update
* required: true
* schema:
* type: string
* requestBody:
* description: Set new item data
* required: true
* content:
* application/json:
* schema:
* properties:
* id:
* description: ID of the updated item
* type: string
* data:
* description: Data of the updated item
* type: object
* $ref: "#/components/schemas/Pharmacists"
* required:
* - id
* responses:
* 200:
* description: The item data was successfully updated
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Pharmacists"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.put('/:id', wrapAsync(async (req, res) => {
await PharmacistsService.update(req.body.data, req.body.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/pharmacists/{id}:
* delete:
* security:
* - bearerAuth: []
* tags: [Pharmacists]
* summary: Delete the selected item
* description: Delete the selected item
* parameters:
* - in: path
* name: id
* description: Item ID to delete
* required: true
* schema:
* type: string
* responses:
* 200:
* description: The item was successfully deleted
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Pharmacists"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.delete('/:id', wrapAsync(async (req, res) => {
await PharmacistsService.remove(req.params.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/pharmacists/deleteByIds:
* post:
* security:
* - bearerAuth: []
* tags: [Pharmacists]
* summary: Delete the selected item list
* description: Delete the selected item list
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* ids:
* description: IDs of the updated items
* type: array
* responses:
* 200:
* description: The items was successfully deleted
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Pharmacists"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Items not found
* 500:
* description: Some server error
*/
router.post('/deleteByIds', wrapAsync(async (req, res) => {
await PharmacistsService.deleteByIds(req.body.data, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/pharmacists:
* get:
* security:
* - bearerAuth: []
* tags: [Pharmacists]
* summary: Get all pharmacists
* description: Get all pharmacists
* responses:
* 200:
* description: Pharmacists list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Pharmacists"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/', wrapAsync(async (req, res) => {
const filetype = req.query.filetype
const currentUser = req.currentUser;
const payload = await PharmacistsDBApi.findAll(
req.query, { currentUser }
);
if (filetype && filetype === 'csv') {
const fields = ['id','first_name','last_name',
];
const opts = { fields };
try {
const csv = parse(payload.rows, opts);
res.status(200).attachment(csv);
res.send(csv)
} catch (err) {
console.error(err);
}
} else {
res.status(200).send(payload);
}
}));
/**
* @swagger
* /api/pharmacists/count:
* get:
* security:
* - bearerAuth: []
* tags: [Pharmacists]
* summary: Count all pharmacists
* description: Count all pharmacists
* responses:
* 200:
* description: Pharmacists count successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Pharmacists"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/count', wrapAsync(async (req, res) => {
const currentUser = req.currentUser;
const payload = await PharmacistsDBApi.findAll(
req.query,
null,
{ countOnly: true, currentUser }
);
res.status(200).send(payload);
}));
/**
* @swagger
* /api/pharmacists/autocomplete:
* get:
* security:
* - bearerAuth: []
* tags: [Pharmacists]
* summary: Find all pharmacists that match search criteria
* description: Find all pharmacists that match search criteria
* responses:
* 200:
* description: Pharmacists list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Pharmacists"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/autocomplete', async (req, res) => {
const payload = await PharmacistsDBApi.findAllAutocomplete(
req.query.query,
req.query.limit,
req.query.offset,
);
res.status(200).send(payload);
});
/**
* @swagger
* /api/pharmacists/{id}:
* get:
* security:
* - bearerAuth: []
* tags: [Pharmacists]
* summary: Get selected item
* description: Get selected item
* parameters:
* - in: path
* name: id
* description: ID of item to get
* required: true
* schema:
* type: string
* responses:
* 200:
* description: Selected item successfully received
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Pharmacists"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.get('/:id', wrapAsync(async (req, res) => {
const payload = await PharmacistsDBApi.findBy(
{ id: req.params.id },
);
res.status(200).send(payload);
}));
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

View File

@ -0,0 +1,413 @@
const express = require('express');
const PrescriptionsService = require('../services/prescriptions');
const PrescriptionsDBApi = require('../db/api/prescriptions');
const wrapAsync = require('../helpers').wrapAsync;
const router = express.Router();
const { parse } = require('json2csv');
/**
* @swagger
* components:
* schemas:
* Prescriptions:
* type: object
* properties:
* medication_name:
* type: string
* default: medication_name
* dosage:
* type: string
* default: dosage
*/
/**
* @swagger
* tags:
* name: Prescriptions
* description: The Prescriptions managing API
*/
/**
* @swagger
* /api/prescriptions:
* post:
* security:
* - bearerAuth: []
* tags: [Prescriptions]
* summary: Add new item
* description: Add new item
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated item
* type: object
* $ref: "#/components/schemas/Prescriptions"
* responses:
* 200:
* description: The item was successfully added
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Prescriptions"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*/
router.post('/', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer);
await PrescriptionsService.create(req.body.data, req.currentUser, true, link.host);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/budgets/bulk-import:
* post:
* security:
* - bearerAuth: []
* tags: [Prescriptions]
* summary: Bulk import items
* description: Bulk import items
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated items
* type: array
* items:
* $ref: "#/components/schemas/Prescriptions"
* responses:
* 200:
* description: The items were successfully imported
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Prescriptions"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*
*/
router.post('/bulk-import', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer);
await PrescriptionsService.bulkImport(req, res, true, link.host);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/prescriptions/{id}:
* put:
* security:
* - bearerAuth: []
* tags: [Prescriptions]
* summary: Update the data of the selected item
* description: Update the data of the selected item
* parameters:
* - in: path
* name: id
* description: Item ID to update
* required: true
* schema:
* type: string
* requestBody:
* description: Set new item data
* required: true
* content:
* application/json:
* schema:
* properties:
* id:
* description: ID of the updated item
* type: string
* data:
* description: Data of the updated item
* type: object
* $ref: "#/components/schemas/Prescriptions"
* required:
* - id
* responses:
* 200:
* description: The item data was successfully updated
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Prescriptions"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.put('/:id', wrapAsync(async (req, res) => {
await PrescriptionsService.update(req.body.data, req.body.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/prescriptions/{id}:
* delete:
* security:
* - bearerAuth: []
* tags: [Prescriptions]
* summary: Delete the selected item
* description: Delete the selected item
* parameters:
* - in: path
* name: id
* description: Item ID to delete
* required: true
* schema:
* type: string
* responses:
* 200:
* description: The item was successfully deleted
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Prescriptions"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.delete('/:id', wrapAsync(async (req, res) => {
await PrescriptionsService.remove(req.params.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/prescriptions/deleteByIds:
* post:
* security:
* - bearerAuth: []
* tags: [Prescriptions]
* summary: Delete the selected item list
* description: Delete the selected item list
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* ids:
* description: IDs of the updated items
* type: array
* responses:
* 200:
* description: The items was successfully deleted
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Prescriptions"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Items not found
* 500:
* description: Some server error
*/
router.post('/deleteByIds', wrapAsync(async (req, res) => {
await PrescriptionsService.deleteByIds(req.body.data, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/prescriptions:
* get:
* security:
* - bearerAuth: []
* tags: [Prescriptions]
* summary: Get all prescriptions
* description: Get all prescriptions
* responses:
* 200:
* description: Prescriptions list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Prescriptions"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/', wrapAsync(async (req, res) => {
const filetype = req.query.filetype
const currentUser = req.currentUser;
const payload = await PrescriptionsDBApi.findAll(
req.query, { currentUser }
);
if (filetype && filetype === 'csv') {
const fields = ['id','medication_name','dosage',
];
const opts = { fields };
try {
const csv = parse(payload.rows, opts);
res.status(200).attachment(csv);
res.send(csv)
} catch (err) {
console.error(err);
}
} else {
res.status(200).send(payload);
}
}));
/**
* @swagger
* /api/prescriptions/count:
* get:
* security:
* - bearerAuth: []
* tags: [Prescriptions]
* summary: Count all prescriptions
* description: Count all prescriptions
* responses:
* 200:
* description: Prescriptions count successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Prescriptions"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/count', wrapAsync(async (req, res) => {
const currentUser = req.currentUser;
const payload = await PrescriptionsDBApi.findAll(
req.query,
null,
{ countOnly: true, currentUser }
);
res.status(200).send(payload);
}));
/**
* @swagger
* /api/prescriptions/autocomplete:
* get:
* security:
* - bearerAuth: []
* tags: [Prescriptions]
* summary: Find all prescriptions that match search criteria
* description: Find all prescriptions that match search criteria
* responses:
* 200:
* description: Prescriptions list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Prescriptions"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/autocomplete', async (req, res) => {
const payload = await PrescriptionsDBApi.findAllAutocomplete(
req.query.query,
req.query.limit,
req.query.offset,
);
res.status(200).send(payload);
});
/**
* @swagger
* /api/prescriptions/{id}:
* get:
* security:
* - bearerAuth: []
* tags: [Prescriptions]
* summary: Get selected item
* description: Get selected item
* parameters:
* - in: path
* name: id
* description: ID of item to get
* required: true
* schema:
* type: string
* responses:
* 200:
* description: Selected item successfully received
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Prescriptions"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.get('/:id', wrapAsync(async (req, res) => {
const payload = await PrescriptionsDBApi.findBy(
{ id: req.params.id },
);
res.status(200).send(payload);
}));
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

View File

@ -0,0 +1,47 @@
const express = require('express');
const SearchService = require('../services/search');
const router = express.Router();
/**
* @swagger
* path:
* /api/search:
* post:
* summary: Search
* description: Search results across multiple tables
* requestBody:
* content:
* application/json:
* schema:
* type: object
* properties:
* searchQuery:
* type: string
* required:
* - searchQuery
* responses:
* 200:
* description: Successful request
* 400:
* description: Invalid request
* 500:
* description: Internal server error
*/
router.post('/', async (req, res) => {
const { searchQuery } = req.body;
if (!searchQuery) {
return res.status(400).json({ error: 'Please enter a search query' });
}
try {
const foundMatches = await SearchService.search(searchQuery, req.currentUser);
res.json(foundMatches);
} catch (error) {
console.error('Internal Server Error', error);
res.status(500).json({ error: 'Internal Server Error' });
}
});
module.exports = router;

421
backend/src/routes/users.js Normal file
View File

@ -0,0 +1,421 @@
const express = require('express');
const UsersService = require('../services/users');
const UsersDBApi = require('../db/api/users');
const wrapAsync = require('../helpers').wrapAsync;
const router = express.Router();
const { parse } = require('json2csv');
/**
* @swagger
* components:
* schemas:
* Users:
* type: object
* properties:
* firstName:
* type: string
* default: firstName
* lastName:
* type: string
* default: lastName
* phoneNumber:
* type: string
* default: phoneNumber
* email:
* type: string
* default: email
*/
/**
* @swagger
* tags:
* name: Users
* description: The Users managing API
*/
/**
* @swagger
* /api/users:
* post:
* security:
* - bearerAuth: []
* tags: [Users]
* summary: Add new item
* description: Add new item
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated item
* type: object
* $ref: "#/components/schemas/Users"
* responses:
* 200:
* description: The item was successfully added
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Users"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*/
router.post('/', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer);
await UsersService.create(req.body.data, req.currentUser, true, link.host);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/budgets/bulk-import:
* post:
* security:
* - bearerAuth: []
* tags: [Users]
* summary: Bulk import items
* description: Bulk import items
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated items
* type: array
* items:
* $ref: "#/components/schemas/Users"
* responses:
* 200:
* description: The items were successfully imported
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Users"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*
*/
router.post('/bulk-import', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer);
await UsersService.bulkImport(req, res, true, link.host);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/users/{id}:
* put:
* security:
* - bearerAuth: []
* tags: [Users]
* summary: Update the data of the selected item
* description: Update the data of the selected item
* parameters:
* - in: path
* name: id
* description: Item ID to update
* required: true
* schema:
* type: string
* requestBody:
* description: Set new item data
* required: true
* content:
* application/json:
* schema:
* properties:
* id:
* description: ID of the updated item
* type: string
* data:
* description: Data of the updated item
* type: object
* $ref: "#/components/schemas/Users"
* required:
* - id
* responses:
* 200:
* description: The item data was successfully updated
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Users"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.put('/:id', wrapAsync(async (req, res) => {
await UsersService.update(req.body.data, req.body.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/users/{id}:
* delete:
* security:
* - bearerAuth: []
* tags: [Users]
* summary: Delete the selected item
* description: Delete the selected item
* parameters:
* - in: path
* name: id
* description: Item ID to delete
* required: true
* schema:
* type: string
* responses:
* 200:
* description: The item was successfully deleted
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Users"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.delete('/:id', wrapAsync(async (req, res) => {
await UsersService.remove(req.params.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/users/deleteByIds:
* post:
* security:
* - bearerAuth: []
* tags: [Users]
* summary: Delete the selected item list
* description: Delete the selected item list
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* ids:
* description: IDs of the updated items
* type: array
* responses:
* 200:
* description: The items was successfully deleted
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Users"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Items not found
* 500:
* description: Some server error
*/
router.post('/deleteByIds', wrapAsync(async (req, res) => {
await UsersService.deleteByIds(req.body.data, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/users:
* get:
* security:
* - bearerAuth: []
* tags: [Users]
* summary: Get all users
* description: Get all users
* responses:
* 200:
* description: Users list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Users"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/', wrapAsync(async (req, res) => {
const filetype = req.query.filetype
const currentUser = req.currentUser;
const payload = await UsersDBApi.findAll(
req.query, { currentUser }
);
if (filetype && filetype === 'csv') {
const fields = ['id','firstName','lastName','phoneNumber','email',
];
const opts = { fields };
try {
const csv = parse(payload.rows, opts);
res.status(200).attachment(csv);
res.send(csv)
} catch (err) {
console.error(err);
}
} else {
res.status(200).send(payload);
}
}));
/**
* @swagger
* /api/users/count:
* get:
* security:
* - bearerAuth: []
* tags: [Users]
* summary: Count all users
* description: Count all users
* responses:
* 200:
* description: Users count successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Users"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/count', wrapAsync(async (req, res) => {
const currentUser = req.currentUser;
const payload = await UsersDBApi.findAll(
req.query,
null,
{ countOnly: true, currentUser }
);
res.status(200).send(payload);
}));
/**
* @swagger
* /api/users/autocomplete:
* get:
* security:
* - bearerAuth: []
* tags: [Users]
* summary: Find all users that match search criteria
* description: Find all users that match search criteria
* responses:
* 200:
* description: Users list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Users"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/autocomplete', async (req, res) => {
const payload = await UsersDBApi.findAllAutocomplete(
req.query.query,
req.query.limit,
req.query.offset,
);
res.status(200).send(payload);
});
/**
* @swagger
* /api/users/{id}:
* get:
* security:
* - bearerAuth: []
* tags: [Users]
* summary: Get selected item
* description: Get selected item
* parameters:
* - in: path
* name: id
* description: ID of item to get
* required: true
* schema:
* type: string
* responses:
* 200:
* description: Selected item successfully received
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Users"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.get('/:id', wrapAsync(async (req, res) => {
const payload = await UsersDBApi.findBy(
{ id: req.params.id },
);
delete payload.password;
res.status(200).send(payload);
}));
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

View File

@ -0,0 +1,131 @@
const db = require('../db/models');
const AppointmentsDBApi = require('../db/api/appointments');
const processFile = require("../middlewares/upload");
const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
const axios = require('axios');
const config = require('../config');
const stream = require('stream');
module.exports = class AppointmentsService {
static async create(data, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await AppointmentsDBApi.create(
data,
{
currentUser,
transaction,
},
);
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
};
static async bulkImport(req, res, sendInvitationEmails = true, host) {
const transaction = await db.sequelize.transaction();
try {
await processFile(req, res);
const bufferStream = new stream.PassThrough();
const results = [];
await bufferStream.end(Buffer.from(req.file.buffer, "utf-8")); // convert Buffer to Stream
await new Promise((resolve, reject) => {
bufferStream
.pipe(csv())
.on('data', (data) => results.push(data))
.on('end', async () => {
console.log('CSV results', results);
resolve();
})
.on('error', (error) => reject(error));
})
await AppointmentsDBApi.bulkImport(results, {
transaction,
ignoreDuplicates: true,
validate: true,
currentUser: req.currentUser
});
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async update(data, id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
let appointments = await AppointmentsDBApi.findBy(
{id},
{transaction},
);
if (!appointments) {
throw new ValidationError(
'appointmentsNotFound',
);
}
const updatedAppointments = await AppointmentsDBApi.update(
id,
data,
{
currentUser,
transaction,
},
);
await transaction.commit();
return updatedAppointments;
} catch (error) {
await transaction.rollback();
throw error;
}
};
static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await AppointmentsDBApi.deleteByIds(ids, {
currentUser,
transaction,
});
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async remove(id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await AppointmentsDBApi.remove(
id,
{
currentUser,
transaction,
},
);
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
};

View File

@ -0,0 +1,257 @@
const UsersDBApi = require('../db/api/users');
const ValidationError = require('./notifications/errors/validation');
const ForbiddenError = require('./notifications/errors/forbidden');
const bcrypt = require('bcrypt');
const config = require('../config');
const helpers = require('../helpers');
class Auth {
static async signup(email, password, options = {}, host) {
const user = await UsersDBApi.findBy({email});
const hashedPassword = await bcrypt.hash(
password,
config.bcrypt.saltRounds,
);
if (user) {
if (user.authenticationUid) {
throw new ValidationError(
'auth.emailAlreadyInUse',
);
}
if (user.disabled) {
throw new ValidationError(
'auth.userDisabled',
);
}
await UsersDBApi.updatePassword(
user.id,
hashedPassword,
options,
);
// Send Email Address Verification Email
const data = {
user: {
id: user.id,
email: user.email
}
};
return helpers.jwtSign(data);
}
const newUser = await UsersDBApi.createFromAuth(
{
firstName: email.split('@')[0],
password: hashedPassword,
email: email,
},
options,
);
// Send Email Address Verification Email
const data = {
user: {
id: newUser.id,
email: newUser.email
}
};
return helpers.jwtSign(data);
}
static async signin(email, password, options = {}) {
const user = await UsersDBApi.findBy({email});
if (!user) {
throw new ValidationError(
'auth.userNotFound',
);
}
if (user.disabled) {
throw new ValidationError(
'auth.userDisabled',
);
}
if (!user.password) {
throw new ValidationError(
'auth.wrongPassword',
);
}
// Set emailVerified param
if (!user.emailVerified) {
throw new ValidationError(
'auth.userNotVerified',
);
}
const passwordsMatch = await bcrypt.compare(
password,
user.password,
);
if (!passwordsMatch) {
throw new ValidationError(
'auth.wrongPassword',
);
}
const data = {
user: {
id: user.id,
email: user.email
}
};
return helpers.jwtSign(data);
}
// sendEmailAddressVerificationEmail
static async sendPasswordResetEmail(email, type = 'register', host) {
let link;
try {
const token = await UsersDBApi.generatePasswordResetToken(
email,
);
link = `${host}/password-reset?token=${token}`;
} catch (error) {
console.error(error);
throw new ValidationError(
'auth.passwordReset.error',
);
}
let passwordResetEmail;
if (type === 'register') {
}
// password Reset Email
return true;
}
static async verifyEmail(token, options = {}) {
const user = await UsersDBApi.findByEmailVerificationToken(
token,
options,
);
if (!user) {
throw new ValidationError(
'auth.emailAddressVerificationEmail.invalidToken',
);
}
return UsersDBApi.markEmailVerified(
user.id,
options,
);
}
static async passwordUpdate(currentPassword, newPassword, options) {
const currentUser = options.currentUser || null;
if (!currentUser) {
throw new ForbiddenError();
}
const currentPasswordMatch = await bcrypt.compare(
currentPassword,
currentUser.password,
);
if (!currentPasswordMatch) {
throw new ValidationError(
'auth.wrongPassword'
)
}
const newPasswordMatch = await bcrypt.compare(
newPassword,
currentUser.password,
);
if (newPasswordMatch) {
throw new ValidationError(
'auth.passwordUpdate.samePassword'
)
}
const hashedPassword = await bcrypt.hash(
newPassword,
config.bcrypt.saltRounds,
);
return UsersDBApi.updatePassword(
currentUser.id,
hashedPassword,
options,
);
}
static async passwordReset(
token,
password,
options = {},
) {
const user = await UsersDBApi.findByPasswordResetToken(
token,
options,
);
if (!user) {
throw new ValidationError(
'auth.passwordReset.invalidToken',
);
}
const hashedPassword = await bcrypt.hash(
password,
config.bcrypt.saltRounds,
);
return UsersDBApi.updatePassword(
user.id,
hashedPassword,
options,
);
}
static async updateProfile(data, currentUser) {
let transaction = await db.sequelize.transaction();
try {
await UsersDBApi.findBy(
{id: currentUser.id},
{transaction},
);
await UsersDBApi.update(
currentUser.id,
data,
{
currentUser,
transaction
},
);
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
}
module.exports = Auth;

View File

@ -0,0 +1,131 @@
const db = require('../db/models');
const DoctorsDBApi = require('../db/api/doctors');
const processFile = require("../middlewares/upload");
const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
const axios = require('axios');
const config = require('../config');
const stream = require('stream');
module.exports = class DoctorsService {
static async create(data, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await DoctorsDBApi.create(
data,
{
currentUser,
transaction,
},
);
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
};
static async bulkImport(req, res, sendInvitationEmails = true, host) {
const transaction = await db.sequelize.transaction();
try {
await processFile(req, res);
const bufferStream = new stream.PassThrough();
const results = [];
await bufferStream.end(Buffer.from(req.file.buffer, "utf-8")); // convert Buffer to Stream
await new Promise((resolve, reject) => {
bufferStream
.pipe(csv())
.on('data', (data) => results.push(data))
.on('end', async () => {
console.log('CSV results', results);
resolve();
})
.on('error', (error) => reject(error));
})
await DoctorsDBApi.bulkImport(results, {
transaction,
ignoreDuplicates: true,
validate: true,
currentUser: req.currentUser
});
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async update(data, id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
let doctors = await DoctorsDBApi.findBy(
{id},
{transaction},
);
if (!doctors) {
throw new ValidationError(
'doctorsNotFound',
);
}
const updatedDoctors = await DoctorsDBApi.update(
id,
data,
{
currentUser,
transaction,
},
);
await transaction.commit();
return updatedDoctors;
} catch (error) {
await transaction.rollback();
throw error;
}
};
static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await DoctorsDBApi.deleteByIds(ids, {
currentUser,
transaction,
});
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async remove(id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await DoctorsDBApi.remove(
id,
{
currentUser,
transaction,
},
);
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
};

View File

@ -0,0 +1,131 @@
const db = require('../db/models');
const Inventory_itemsDBApi = require('../db/api/inventory_items');
const processFile = require("../middlewares/upload");
const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
const axios = require('axios');
const config = require('../config');
const stream = require('stream');
module.exports = class Inventory_itemsService {
static async create(data, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await Inventory_itemsDBApi.create(
data,
{
currentUser,
transaction,
},
);
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
};
static async bulkImport(req, res, sendInvitationEmails = true, host) {
const transaction = await db.sequelize.transaction();
try {
await processFile(req, res);
const bufferStream = new stream.PassThrough();
const results = [];
await bufferStream.end(Buffer.from(req.file.buffer, "utf-8")); // convert Buffer to Stream
await new Promise((resolve, reject) => {
bufferStream
.pipe(csv())
.on('data', (data) => results.push(data))
.on('end', async () => {
console.log('CSV results', results);
resolve();
})
.on('error', (error) => reject(error));
})
await Inventory_itemsDBApi.bulkImport(results, {
transaction,
ignoreDuplicates: true,
validate: true,
currentUser: req.currentUser
});
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async update(data, id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
let inventory_items = await Inventory_itemsDBApi.findBy(
{id},
{transaction},
);
if (!inventory_items) {
throw new ValidationError(
'inventory_itemsNotFound',
);
}
const updatedInventory_items = await Inventory_itemsDBApi.update(
id,
data,
{
currentUser,
transaction,
},
);
await transaction.commit();
return updatedInventory_items;
} catch (error) {
await transaction.rollback();
throw error;
}
};
static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await Inventory_itemsDBApi.deleteByIds(ids, {
currentUser,
transaction,
});
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async remove(id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await Inventory_itemsDBApi.remove(
id,
{
currentUser,
transaction,
},
);
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
};

View File

@ -0,0 +1,131 @@
const db = require('../db/models');
const Lab_techniciansDBApi = require('../db/api/lab_technicians');
const processFile = require("../middlewares/upload");
const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
const axios = require('axios');
const config = require('../config');
const stream = require('stream');
module.exports = class Lab_techniciansService {
static async create(data, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await Lab_techniciansDBApi.create(
data,
{
currentUser,
transaction,
},
);
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
};
static async bulkImport(req, res, sendInvitationEmails = true, host) {
const transaction = await db.sequelize.transaction();
try {
await processFile(req, res);
const bufferStream = new stream.PassThrough();
const results = [];
await bufferStream.end(Buffer.from(req.file.buffer, "utf-8")); // convert Buffer to Stream
await new Promise((resolve, reject) => {
bufferStream
.pipe(csv())
.on('data', (data) => results.push(data))
.on('end', async () => {
console.log('CSV results', results);
resolve();
})
.on('error', (error) => reject(error));
})
await Lab_techniciansDBApi.bulkImport(results, {
transaction,
ignoreDuplicates: true,
validate: true,
currentUser: req.currentUser
});
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async update(data, id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
let lab_technicians = await Lab_techniciansDBApi.findBy(
{id},
{transaction},
);
if (!lab_technicians) {
throw new ValidationError(
'lab_techniciansNotFound',
);
}
const updatedLab_technicians = await Lab_techniciansDBApi.update(
id,
data,
{
currentUser,
transaction,
},
);
await transaction.commit();
return updatedLab_technicians;
} catch (error) {
await transaction.rollback();
throw error;
}
};
static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await Lab_techniciansDBApi.deleteByIds(ids, {
currentUser,
transaction,
});
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async remove(id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await Lab_techniciansDBApi.remove(
id,
{
currentUser,
transaction,
},
);
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
};

View File

@ -0,0 +1,131 @@
const db = require('../db/models');
const Lab_testsDBApi = require('../db/api/lab_tests');
const processFile = require("../middlewares/upload");
const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
const axios = require('axios');
const config = require('../config');
const stream = require('stream');
module.exports = class Lab_testsService {
static async create(data, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await Lab_testsDBApi.create(
data,
{
currentUser,
transaction,
},
);
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
};
static async bulkImport(req, res, sendInvitationEmails = true, host) {
const transaction = await db.sequelize.transaction();
try {
await processFile(req, res);
const bufferStream = new stream.PassThrough();
const results = [];
await bufferStream.end(Buffer.from(req.file.buffer, "utf-8")); // convert Buffer to Stream
await new Promise((resolve, reject) => {
bufferStream
.pipe(csv())
.on('data', (data) => results.push(data))
.on('end', async () => {
console.log('CSV results', results);
resolve();
})
.on('error', (error) => reject(error));
})
await Lab_testsDBApi.bulkImport(results, {
transaction,
ignoreDuplicates: true,
validate: true,
currentUser: req.currentUser
});
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async update(data, id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
let lab_tests = await Lab_testsDBApi.findBy(
{id},
{transaction},
);
if (!lab_tests) {
throw new ValidationError(
'lab_testsNotFound',
);
}
const updatedLab_tests = await Lab_testsDBApi.update(
id,
data,
{
currentUser,
transaction,
},
);
await transaction.commit();
return updatedLab_tests;
} catch (error) {
await transaction.rollback();
throw error;
}
};
static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await Lab_testsDBApi.deleteByIds(ids, {
currentUser,
transaction,
});
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async remove(id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await Lab_testsDBApi.remove(
id,
{
currentUser,
transaction,
},
);
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
};

View File

@ -0,0 +1,131 @@
const db = require('../db/models');
const Medical_recordsDBApi = require('../db/api/medical_records');
const processFile = require("../middlewares/upload");
const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
const axios = require('axios');
const config = require('../config');
const stream = require('stream');
module.exports = class Medical_recordsService {
static async create(data, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await Medical_recordsDBApi.create(
data,
{
currentUser,
transaction,
},
);
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
};
static async bulkImport(req, res, sendInvitationEmails = true, host) {
const transaction = await db.sequelize.transaction();
try {
await processFile(req, res);
const bufferStream = new stream.PassThrough();
const results = [];
await bufferStream.end(Buffer.from(req.file.buffer, "utf-8")); // convert Buffer to Stream
await new Promise((resolve, reject) => {
bufferStream
.pipe(csv())
.on('data', (data) => results.push(data))
.on('end', async () => {
console.log('CSV results', results);
resolve();
})
.on('error', (error) => reject(error));
})
await Medical_recordsDBApi.bulkImport(results, {
transaction,
ignoreDuplicates: true,
validate: true,
currentUser: req.currentUser
});
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async update(data, id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
let medical_records = await Medical_recordsDBApi.findBy(
{id},
{transaction},
);
if (!medical_records) {
throw new ValidationError(
'medical_recordsNotFound',
);
}
const updatedMedical_records = await Medical_recordsDBApi.update(
id,
data,
{
currentUser,
transaction,
},
);
await transaction.commit();
return updatedMedical_records;
} catch (error) {
await transaction.rollback();
throw error;
}
};
static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await Medical_recordsDBApi.deleteByIds(ids, {
currentUser,
transaction,
});
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async remove(id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await Medical_recordsDBApi.remove(
id,
{
currentUser,
transaction,
},
);
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
};

View File

@ -0,0 +1,17 @@
const { getNotification, isNotification } = require('../helpers');
module.exports = class ForbiddenError extends Error {
constructor(messageCode) {
let message;
if (messageCode && isNotification(messageCode)) {
message = getNotification(messageCode);
}
message =
message || getNotification('errors.forbidden.message');
super(message);
this.code = 403;
}
};

View File

@ -0,0 +1,18 @@
const { getNotification, isNotification } = require('../helpers');
module.exports = class ValidationError extends Error {
constructor(messageCode) {
let message;
if (messageCode && isNotification(messageCode)) {
message = getNotification(messageCode);
}
message =
message ||
getNotification('errors.validation.message');
super(message);
this.code = 400;
}
};

View File

@ -0,0 +1,35 @@
const _get = require('lodash/get');
const errors = require('./list');
function format(message, args) {
if (!message) {
return null;
}
return message.replace(/{(\d+)}/g, function (
match,
number,
) {
return typeof args[number] != 'undefined'
? args[number]
: match;
});
}
const isNotification = (key) => {
const message = _get(errors, key);
return !!message;
};
const getNotification = (key, ...args) => {
const message = _get(errors, key);
if (!message) {
return key;
}
return format(message, args);
};
exports.getNotification = getNotification;
exports.isNotification = isNotification;

View File

@ -0,0 +1,104 @@
const errors = {
app: {
title: 'medease',
},
auth: {
userDisabled: 'Your account is disabled',
forbidden: 'Forbidden',
unauthorized: 'Unauthorized',
userNotFound: `Sorry, we don't recognize your credentials`,
wrongPassword: `Sorry, we don't recognize your credentials`,
weakPassword: 'This password is too weak',
emailAlreadyInUse: 'Email is already in use',
invalidEmail: 'Please provide a valid email',
passwordReset: {
invalidToken:
'Password reset link is invalid or has expired',
error: `Email not recognized`,
},
passwordUpdate: {
samePassword: `You can't use the same password. Please create new password`
},
userNotVerified: `Sorry, your email has not been verified yet`,
emailAddressVerificationEmail: {
invalidToken:
'Email verification link is invalid or has expired',
error: `Email not recognized`,
},
},
iam: {
errors: {
userAlreadyExists:
'User with this email already exists',
userNotFound: 'User not found',
disablingHimself: `You can't disable yourself`,
revokingOwnPermission: `You can't revoke your own owner permission`,
deletingHimself: `You can't delete yourself`,
emailRequired: 'Email is required',
},
},
importer: {
errors: {
invalidFileEmpty: 'The file is empty',
invalidFileExcel:
'Only excel (.xlsx) files are allowed',
invalidFileUpload:
'Invalid file. Make sure you are using the last version of the template.',
importHashRequired: 'Import hash is required',
importHashExistent: 'Data has already been imported',
userEmailMissing: 'Some items in the CSV do not have an email',
},
},
errors: {
forbidden: {
message: 'Forbidden',
},
validation: {
message: 'An error occurred',
},
searchQueryRequired: {
message: 'Search query is required',
},
},
emails: {
invitation: {
subject: `You've been invited to {0}`,
body: `
<p>Hello,</p>
<p>You've been invited to {0} set password for your {1} account.</p>
<p><a href='{2}'>{2}</a></p>
<p>Thanks,</p>
<p>Your {0} team</p>
`,
},
emailAddressVerification: {
subject: `Verify your email for {0}`,
body: `
<p>Hello,</p>
<p>Follow this link to verify your email address.</p>
<p><a href='{0}'>{0}</a></p>
<p>If you didn't ask to verify this address, you can ignore this email.</p>
<p>Thanks,</p>
<p>Your {1} team</p>
`,
},
passwordReset: {
subject: `Reset your password for {0}`,
body: `
<p>Hello,</p>
<p>Follow this link to reset your {0} password for your {1} account.</p>
<p><a href='{2}'>{2}</a></p>
<p>If you didn't ask to reset your password, you can ignore this email.</p>
<p>Thanks,</p>
<p>Your {0} team</p>
`,
},
},
};
module.exports = errors;

View File

@ -0,0 +1,51 @@
const axios = require('axios');
const { v4: uuid } = require('uuid');
const RoleService = require('./roles');
const config = require('../config');
module.exports = class OpenAiService {
static async askGpt(prompt) {
if (!config.gpt_key) {
return {
success: false,
error: 'API key is required'
};
}
try {
const response = await axios.post(
'https://api.openai.com/v1/chat/completions',
{
model: 'gpt-4o',
messages: [
{ role: 'user', content: prompt }
]
},
{
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${config.gpt_key}`
}
}
);
if (response.status >= 200 && response.status < 300) {
return {
success: true,
data: response.data.choices[0].message.content
};
} else {
console.error('Error asking question to ChatGPT:', response.data);
return {
success: false,
error: response.data
};
}
} catch (error) {
console.error('Error asking question to ChatGPT:', error.response?.data || error.message);
return {
success: false,
error: error.response?.data || error.message
};
}
}
};

View File

@ -0,0 +1,131 @@
const db = require('../db/models');
const PatientsDBApi = require('../db/api/patients');
const processFile = require("../middlewares/upload");
const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
const axios = require('axios');
const config = require('../config');
const stream = require('stream');
module.exports = class PatientsService {
static async create(data, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await PatientsDBApi.create(
data,
{
currentUser,
transaction,
},
);
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
};
static async bulkImport(req, res, sendInvitationEmails = true, host) {
const transaction = await db.sequelize.transaction();
try {
await processFile(req, res);
const bufferStream = new stream.PassThrough();
const results = [];
await bufferStream.end(Buffer.from(req.file.buffer, "utf-8")); // convert Buffer to Stream
await new Promise((resolve, reject) => {
bufferStream
.pipe(csv())
.on('data', (data) => results.push(data))
.on('end', async () => {
console.log('CSV results', results);
resolve();
})
.on('error', (error) => reject(error));
})
await PatientsDBApi.bulkImport(results, {
transaction,
ignoreDuplicates: true,
validate: true,
currentUser: req.currentUser
});
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async update(data, id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
let patients = await PatientsDBApi.findBy(
{id},
{transaction},
);
if (!patients) {
throw new ValidationError(
'patientsNotFound',
);
}
const updatedPatients = await PatientsDBApi.update(
id,
data,
{
currentUser,
transaction,
},
);
await transaction.commit();
return updatedPatients;
} catch (error) {
await transaction.rollback();
throw error;
}
};
static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await PatientsDBApi.deleteByIds(ids, {
currentUser,
transaction,
});
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async remove(id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await PatientsDBApi.remove(
id,
{
currentUser,
transaction,
},
);
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
};

View File

@ -0,0 +1,131 @@
const db = require('../db/models');
const PharmacistsDBApi = require('../db/api/pharmacists');
const processFile = require("../middlewares/upload");
const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
const axios = require('axios');
const config = require('../config');
const stream = require('stream');
module.exports = class PharmacistsService {
static async create(data, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await PharmacistsDBApi.create(
data,
{
currentUser,
transaction,
},
);
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
};
static async bulkImport(req, res, sendInvitationEmails = true, host) {
const transaction = await db.sequelize.transaction();
try {
await processFile(req, res);
const bufferStream = new stream.PassThrough();
const results = [];
await bufferStream.end(Buffer.from(req.file.buffer, "utf-8")); // convert Buffer to Stream
await new Promise((resolve, reject) => {
bufferStream
.pipe(csv())
.on('data', (data) => results.push(data))
.on('end', async () => {
console.log('CSV results', results);
resolve();
})
.on('error', (error) => reject(error));
})
await PharmacistsDBApi.bulkImport(results, {
transaction,
ignoreDuplicates: true,
validate: true,
currentUser: req.currentUser
});
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async update(data, id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
let pharmacists = await PharmacistsDBApi.findBy(
{id},
{transaction},
);
if (!pharmacists) {
throw new ValidationError(
'pharmacistsNotFound',
);
}
const updatedPharmacists = await PharmacistsDBApi.update(
id,
data,
{
currentUser,
transaction,
},
);
await transaction.commit();
return updatedPharmacists;
} catch (error) {
await transaction.rollback();
throw error;
}
};
static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await PharmacistsDBApi.deleteByIds(ids, {
currentUser,
transaction,
});
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async remove(id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await PharmacistsDBApi.remove(
id,
{
currentUser,
transaction,
},
);
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
};

View File

@ -0,0 +1,131 @@
const db = require('../db/models');
const PrescriptionsDBApi = require('../db/api/prescriptions');
const processFile = require("../middlewares/upload");
const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
const axios = require('axios');
const config = require('../config');
const stream = require('stream');
module.exports = class PrescriptionsService {
static async create(data, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await PrescriptionsDBApi.create(
data,
{
currentUser,
transaction,
},
);
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
};
static async bulkImport(req, res, sendInvitationEmails = true, host) {
const transaction = await db.sequelize.transaction();
try {
await processFile(req, res);
const bufferStream = new stream.PassThrough();
const results = [];
await bufferStream.end(Buffer.from(req.file.buffer, "utf-8")); // convert Buffer to Stream
await new Promise((resolve, reject) => {
bufferStream
.pipe(csv())
.on('data', (data) => results.push(data))
.on('end', async () => {
console.log('CSV results', results);
resolve();
})
.on('error', (error) => reject(error));
})
await PrescriptionsDBApi.bulkImport(results, {
transaction,
ignoreDuplicates: true,
validate: true,
currentUser: req.currentUser
});
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async update(data, id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
let prescriptions = await PrescriptionsDBApi.findBy(
{id},
{transaction},
);
if (!prescriptions) {
throw new ValidationError(
'prescriptionsNotFound',
);
}
const updatedPrescriptions = await PrescriptionsDBApi.update(
id,
data,
{
currentUser,
transaction,
},
);
await transaction.commit();
return updatedPrescriptions;
} catch (error) {
await transaction.rollback();
throw error;
}
};
static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await PrescriptionsDBApi.deleteByIds(ids, {
currentUser,
transaction,
});
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async remove(id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await PrescriptionsDBApi.remove(
id,
{
currentUser,
transaction,
},
);
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
};

View File

@ -0,0 +1,197 @@
const db = require('../db/models');
const ValidationError = require('./notifications/errors/validation');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
/**
* @param {string} permission
* @param {object} currentUser
*/
async function checkPermissions(permission, currentUser) {
if (!currentUser) {
throw new ValidationError('auth.unauthorized');
}
const userPermission = currentUser.custom_permissions.find(
(cp) => cp.name === permission,
);
if (userPermission) {
return true;
}
try {
if (!currentUser.app_role) {
throw new ValidationError('auth.forbidden');
}
const permissions = await currentUser.app_role.getPermissions();
return !!permissions.find((p) => p.name === permission);
} catch (e) {
throw e;
}
}
module.exports = class SearchService {
static async search(searchQuery, currentUser) {
try {
if (!searchQuery) {
throw new ValidationError('iam.errors.searchQueryRequired');
}
const tableColumns = {
"users": [
"firstName",
"lastName",
"phoneNumber",
"email",
],
"doctors": [
"first_name",
"last_name",
"specialization",
],
"inventory_items": [
"item_name",
],
"lab_technicians": [
"first_name",
"last_name",
],
"lab_tests": [
"test_name",
"results",
],
"medical_records": [
"record_type",
"description",
],
"patients": [
"first_name",
"last_name",
],
"pharmacists": [
"first_name",
"last_name",
],
"prescriptions": [
"medication_name",
"dosage",
],
};
const columnsInt = {
"inventory_items": [
"quantity",
],
};
let allFoundRecords = [];
for (const tableName in tableColumns) {
if (tableColumns.hasOwnProperty(tableName)) {
const attributesToSearch = tableColumns[tableName];
const attributesIntToSearch = columnsInt[tableName] || [];
const whereCondition = {
[Op.or]: [
...attributesToSearch.map(attribute => ({
[attribute]: {
[Op.iLike] : `%${searchQuery}%`,
},
})),
...attributesIntToSearch.map(attribute => (
Sequelize.where(
Sequelize.cast(Sequelize.col(`${tableName}.${attribute}`), 'varchar'),
{ [Op.iLike]: `%${searchQuery}%` }
)
)),
],
};
const hasPermission = await checkPermissions(`READ_${tableName.toUpperCase()}`, currentUser);
if (!hasPermission) {
continue;
}
const foundRecords = await db[tableName].findAll({
where: whereCondition,
attributes: [...tableColumns[tableName], 'id', ...attributesIntToSearch],
});
const modifiedRecords = foundRecords.map((record) => {
const matchAttribute = [];
for (const attribute of attributesToSearch) {
if (record[attribute]?.toLowerCase()?.includes(searchQuery.toLowerCase())) {
matchAttribute.push(attribute);
}
}
for (const attribute of attributesIntToSearch) {
const castedValue = String(record[attribute]);
if (castedValue && castedValue.toLowerCase().includes(searchQuery.toLowerCase())) {
matchAttribute.push(attribute);
}
}
return {
...record.get(),
matchAttribute,
tableName,
};
});
allFoundRecords = allFoundRecords.concat(modifiedRecords);
}
}
return allFoundRecords;
} catch (error) {
throw error;
}
}
}

View File

@ -0,0 +1,163 @@
const db = require('../db/models');
const UsersDBApi = require('../db/api/users');
const processFile = require("../middlewares/upload");
const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
const axios = require('axios');
const config = require('../config');
const stream = require('stream');
const AuthService = require('./auth');
module.exports = class UsersService {
static async create(data, currentUser, sendInvitationEmails = true, host) {
let transaction = await db.sequelize.transaction();
let email = data.email;
let emailsToInvite = [];
try {
if (email) {
let user = await UsersDBApi.findBy({email}, {transaction});
if (user) {
throw new ValidationError(
'iam.errors.userAlreadyExists',
);
} else {
await UsersDBApi.create(
{data},
{
currentUser,
transaction,
},
);
emailsToInvite.push(email);
}
} else {
throw new ValidationError('iam.errors.emailRequired')
}
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
if (emailsToInvite && emailsToInvite.length) {
if (!sendInvitationEmails) return;
AuthService.sendPasswordResetEmail(email, 'invitation', host);
}
}
static async bulkImport(req, res, sendInvitationEmails = true, host) {
const transaction = await db.sequelize.transaction();
let emailsToInvite = [];
try {
await processFile(req, res);
const bufferStream = new stream.PassThrough();
const results = [];
await bufferStream.end(Buffer.from(req.file.buffer, "utf-8")); // convert Buffer to Stream
await new Promise((resolve, reject) => {
bufferStream
.pipe(csv())
.on('data', (data) => results.push(data))
.on('end', () => {
console.log('results csv', results);
resolve();
})
.on('error', (error) => reject(error));
});
const hasAllEmails = results.every((result) => result.email);
if (!hasAllEmails) {
throw new ValidationError('importer.errors.userEmailMissing');
}
await UsersDBApi.bulkImport(results, {
transaction,
ignoreDuplicates: true,
validate: true,
currentUser: req.currentUser
});
emailsToInvite = results.map((result) => result.email);
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
if (emailsToInvite && emailsToInvite.length && !sendInvitationEmails) {
emailsToInvite.forEach((email) => {
AuthService.sendPasswordResetEmail(email, 'invitation', host);
});
}
}
static async update(data, id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
let users = await UsersDBApi.findBy(
{id},
{transaction},
);
if (!users) {
throw new ValidationError(
'iam.errors.userNotFound',
);
}
const updatedUser = await UsersDBApi.update(
id,
data,
{
currentUser,
transaction,
},
);
await transaction.commit();
return updatedUser;
} catch (error) {
await transaction.rollback();
throw error;
}
};
static async remove(id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
if (currentUser.id === id) {
throw new ValidationError(
'iam.errors.deletingHimself',
);
}
// if (currentUser.app_role?.name !== config.roles.admin) {
// throw new ValidationError(
// 'errors.forbidden.message',
// );
// }
await UsersDBApi.remove(
id,
{
currentUser,
transaction,
},
);
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
};

45
backend/watcher.js Normal file
View File

@ -0,0 +1,45 @@
const chokidar = require('chokidar');
const { exec } = require('child_process');
const nodemon = require('nodemon');
const migrationsWatcher = chokidar.watch('./src/db/migrations', {
persistent: true,
ignoreInitial: true
});
migrationsWatcher.on('add', (filePath) => {
console.log(`[DEBUG] New migration file: ${filePath}`);
exec('npm run db:migrate', (error, stdout, stderr) => {
console.log(stdout);
if (error) {
console.error(stderr);
}
});
});
const seedersWatcher = chokidar.watch('./src/db/seeders', {
persistent: true,
ignoreInitial: true
});
seedersWatcher.on('add', (filePath) => {
console.log(`[DEBUG] New seed file: ${filePath}`);
exec('npm run db:seed', (error, stdout, stderr) => {
console.log(stdout);
if (error) {
console.error(stderr);
}
});
});
nodemon({
script: './src/index.js',
ignore: ['./src/db/migrations', './src/db/seeders'],
delay: '500'
});
nodemon.on('start', () => {
console.log('Nodemon started');
});
nodemon.on('restart', (files) => {
console.log('Nodemon restarted due changes in:', files);
});

4470
backend/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

14
cloudbuild.yaml Normal file
View File

@ -0,0 +1,14 @@
steps:
- name: 'gcr.io/cloud-builders/docker'
entrypoint: 'bash'
args: ['-c', 'docker pull gcr.io/fldemo-315215/medease-32565-dev:latest || exit 0']
- name: 'gcr.io/cloud-builders/docker'
args: [
'build',
'-t', 'gcr.io/fldemo-315215/medease-32565-dev:latest',
'--file', 'Dockerfile.dev',
'--cache-from', 'gcr.io/fldemo-315215/medease-32565-dev:latest',
'.'
]
images: ['gcr.io/fldemo-315215/medease-32565-dev:latest']
logsBucket: 'gs://fldemo-315215-cloudbuild-logs'

Some files were not shown because too many files have changed in this diff Show More