Merge branch 'main' into feature/settings-page
This commit is contained in:
commit
3a23f60488
22 changed files with 387 additions and 75 deletions
2
.github/workflows/api-deploy-nonprod.yml
vendored
2
.github/workflows/api-deploy-nonprod.yml
vendored
|
|
@ -90,5 +90,5 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- name: Deploy to Coolify
|
- name: Deploy to Coolify
|
||||||
run: |
|
run: |
|
||||||
curl --request GET '${{ secrets.COOLIFY_WEBHOOK_API }}' --header 'Authorization: Bearer ${{ secrets.COOLIFY_TOKEN }}'
|
curl '${{ secrets.COOLIFY_WEBHOOK_API }}' --header 'Authorization: Bearer ${{ secrets.COOLIFY_TOKEN }}'
|
||||||
|
|
||||||
|
|
|
||||||
2
.github/workflows/api-deploy-prod.yml
vendored
2
.github/workflows/api-deploy-prod.yml
vendored
|
|
@ -90,5 +90,5 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- name: Deploy to Coolify
|
- name: Deploy to Coolify
|
||||||
run: |
|
run: |
|
||||||
curl --request GET '${{ secrets.COOLIFY_WEBHOOK_API }}' --header 'Authorization: Bearer ${{ secrets.COOLIFY_TOKEN }}'
|
curl '${{ secrets.COOLIFY_WEBHOOK_API }}' --header 'Authorization: Bearer ${{ secrets.COOLIFY_TOKEN }}'
|
||||||
|
|
||||||
|
|
|
||||||
2
.github/workflows/web-container.yml
vendored
2
.github/workflows/web-container.yml
vendored
|
|
@ -16,7 +16,7 @@ on:
|
||||||
- web/**
|
- web/**
|
||||||
jobs:
|
jobs:
|
||||||
determine-workflow:
|
determine-workflow:
|
||||||
runs-on: 'ubuntu-latest'
|
runs-on: ['self-hosted','pi']
|
||||||
outputs:
|
outputs:
|
||||||
workflow_type: ${{ steps.workflow.outputs.workflow_type }}
|
workflow_type: ${{ steps.workflow.outputs.workflow_type }}
|
||||||
workflow_envs: ${{ steps.workflow.outputs.workflow_envs }}
|
workflow_envs: ${{ steps.workflow.outputs.workflow_envs }}
|
||||||
|
|
|
||||||
2
.github/workflows/web-deploy-nonprod.yml
vendored
2
.github/workflows/web-deploy-nonprod.yml
vendored
|
|
@ -90,5 +90,5 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- name: Deploy to Coolify
|
- name: Deploy to Coolify
|
||||||
run: |
|
run: |
|
||||||
curl --request GET '${{ secrets.COOLIFY_WEBHOOK_WEB }}' --header 'Authorization: Bearer ${{ secrets.COOLIFY_TOKEN }}'
|
curl '${{ secrets.COOLIFY_WEBHOOK_WEB }}' --header 'Authorization: Bearer ${{ secrets.COOLIFY_TOKEN }}'
|
||||||
|
|
||||||
|
|
|
||||||
2
.github/workflows/web-deploy-prod.yml
vendored
2
.github/workflows/web-deploy-prod.yml
vendored
|
|
@ -90,5 +90,5 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- name: Deploy to Coolify
|
- name: Deploy to Coolify
|
||||||
run: |
|
run: |
|
||||||
curl --request GET '${{ secrets.COOLIFY_WEBHOOK_WEB }}' --header 'Authorization: Bearer ${{ secrets.COOLIFY_TOKEN }}'
|
curl '${{ secrets.COOLIFY_WEBHOOK_WEB }}' --header 'Authorization: Bearer ${{ secrets.COOLIFY_TOKEN }}'
|
||||||
|
|
||||||
|
|
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -24,3 +24,6 @@ dist-ssr
|
||||||
*.njsproj
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
|
|
||||||
|
api/package-lock.json
|
||||||
|
web/package-lock.json
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Welcome to ShiftSync 👋
|
# Welcome to ShiftSync
|
||||||
|
|
||||||
This is a new project, directed to create a website and app that is new, innovative, and works efficiently to schedule first responders/employees to shifts. The website and app is directed toward volunteer agencies specifically in both Fire Department and EMS fields. The website and app is currently in development and will hope to reach alpha by end of 2025.
|
This is a new project, directed to create a website and app that is new, innovative, and works efficiently to schedule first responders/employees to shifts. The website and app is directed toward volunteer agencies specifically in both Fire Department and EMS fields. The website and app is currently in development and will hope to reach alpha by end of 2025.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ WORKDIR /app
|
||||||
|
|
||||||
COPY ./package*.json ./
|
COPY ./package*.json ./
|
||||||
|
|
||||||
RUN npm ci
|
RUN npm cache clean --force && npm install --no-audit --no-fund
|
||||||
|
|
||||||
COPY . ./
|
COPY . ./
|
||||||
|
|
||||||
|
|
|
||||||
22
api/middleware/auth.js
Normal file
22
api/middleware/auth.js
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
export const validateMedicationApiKey = (req, res, next) => {
|
||||||
|
const authHeader = req.headers['authorization'];
|
||||||
|
const token = authHeader && authHeader.split(' ')[1];
|
||||||
|
|
||||||
|
if (!token || token !== process.env.MEDICATION_API_KEY) {
|
||||||
|
console.log('MEDICATION - User entered an Invalid token: ', token);
|
||||||
|
return res.status(401).json({ error: 'Unauthorized - Invalid API Key' });
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const validateShiftSyncApiKey = (req, res, next) => {
|
||||||
|
const authHeader = req.headers['authorization'];
|
||||||
|
const token = authHeader && authHeader.split(' ')[1];
|
||||||
|
|
||||||
|
if (!token || token !== process.env.SHIFTSYNC_API_KEY) {
|
||||||
|
console.log('SHIFT - User entered an Invalid token: ', token);
|
||||||
|
return res.status(401).json({ error: 'Unauthorized - Invalid API Key' });
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
|
@ -11,8 +11,9 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^16.5.0",
|
"dotenv": "^16.5.0",
|
||||||
"express": "^5.1.0",
|
"express": "^5.2.0",
|
||||||
"pg": "^8.16.2"
|
"pg": "^8.17.1",
|
||||||
|
"yup": "^1.7.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"nodemon": "^3.1.10"
|
"nodemon": "^3.1.10"
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import cors from 'cors';
|
import cors from 'cors';
|
||||||
import dotenv from 'dotenv';
|
import dotenv from 'dotenv';
|
||||||
import userRouter from './services/user/index.js';
|
import { medicationRouter } from './services/medications/index.js';
|
||||||
import departmentRouter from './services/department/index.js';
|
import { shiftsRouter } from './services/shifts/index.js';
|
||||||
import pool from './services/postgres/postgresServices.js';
|
import { shiftRunQuery } from './services/shiftConnection.js';
|
||||||
|
import { validateMedicationApiKey, validateShiftSyncApiKey } from './middleware/auth.js';
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
import { routes } from './router/routes.js';
|
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
|
|
@ -19,18 +19,16 @@ const corsOptions = {
|
||||||
|
|
||||||
app.use(cors(corsOptions));
|
app.use(cors(corsOptions));
|
||||||
app.use(express.json({ limit: '10mb' }));
|
app.use(express.json({ limit: '10mb' }));
|
||||||
app.use('/api', routes);
|
|
||||||
|
|
||||||
app.get('*route', (req, res) => {
|
|
||||||
res.send("Hello from ShiftSync");
|
|
||||||
});
|
|
||||||
|
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
|
|
||||||
const apiRouter = express.Router();
|
const apiRouter = express.Router();
|
||||||
|
|
||||||
apiRouter.use('/user', userRouter);
|
// ParamyxRx Router (/api/medication)
|
||||||
apiRouter.use('/department', departmentRouter);
|
apiRouter.use('/medication', validateMedicationApiKey, medicationRouter);
|
||||||
|
|
||||||
|
// ShiftSync Router (/api/shifts)
|
||||||
|
apiRouter.use('/shifts', validateShiftSyncApiKey, shiftsRouter);
|
||||||
|
|
||||||
app.get("/api", (req, res) => {
|
app.get("/api", (req, res) => {
|
||||||
res.json('Welcome to Shift Sync API');
|
res.json('Welcome to Shift Sync API');
|
||||||
|
|
@ -38,17 +36,23 @@ app.get("/api", (req, res) => {
|
||||||
|
|
||||||
app.use('/api', apiRouter);
|
app.use('/api', apiRouter);
|
||||||
|
|
||||||
app.get('/db-health', async (req, res) => {
|
app.get('/api/db-health', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
console.log('in');
|
const result = await shiftRunQuery('SELECT NOW()');
|
||||||
const result = await pool.query('SELECT NOW()');
|
|
||||||
console.log('after result');
|
|
||||||
res.json({ connected: true, time: result.rows[0].now });
|
res.json({ connected: true, time: result.rows[0].now });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
res.status(500).json({ connected: false, error: err.message });
|
res.status(500).json({ connected: false, error: err.message });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
app.get('/api/version', async (req, res) => {
|
||||||
|
try {
|
||||||
|
res.json('1.0.1');
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
res.status(500).json({ connected: false, error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
app.listen(5172, () => {
|
app.listen(5172, () => {
|
||||||
console.log('Server Started on port 5172');
|
console.log('Server Started on port 5172');
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import {
|
import {
|
||||||
userDataOperations
|
medicationOperations
|
||||||
} from './operations/userData.js';
|
} from './operations/medications.js';
|
||||||
|
|
||||||
export const databaseServices = {
|
export const databaseServices = {
|
||||||
...userDataOperations
|
...medicationOperations
|
||||||
}
|
}
|
||||||
47
api/services/medications/index.js
Normal file
47
api/services/medications/index.js
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
import express from 'express';
|
||||||
|
import { databaseServices } from '../index.js';
|
||||||
|
import { fullMedicationInformationSchema } from '../validations/medications.js';
|
||||||
|
|
||||||
|
export const medicationRouter = express.Router();
|
||||||
|
|
||||||
|
medicationRouter.get('/', async (req, res) => {
|
||||||
|
let data;
|
||||||
|
try {
|
||||||
|
data = await databaseServices.getMedications();
|
||||||
|
res.status(200);
|
||||||
|
} catch (err) {
|
||||||
|
data = { Error: err?.message };
|
||||||
|
res.status(500);
|
||||||
|
} finally {
|
||||||
|
res.send(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
medicationRouter.get('/base/', async (req, res) => {
|
||||||
|
let data;
|
||||||
|
try {
|
||||||
|
data = await databaseServices.getBaseMedications();
|
||||||
|
res.status(200);
|
||||||
|
} catch (err) {
|
||||||
|
data = { Error: err?.message };
|
||||||
|
res.status(500);
|
||||||
|
} finally {
|
||||||
|
res.send(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
medicationRouter.post('/full/', async (req, res) => {
|
||||||
|
let data;
|
||||||
|
const body = req?.body;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fullMedicationInformationSchema.validate(body);
|
||||||
|
data = await databaseServices.getFullMedicationInformation(body);
|
||||||
|
res.status(200);
|
||||||
|
} catch (err) {
|
||||||
|
data = { Error: err?.message };
|
||||||
|
res.status(500);
|
||||||
|
} finally {
|
||||||
|
res.send(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
129
api/services/operations/helpers/medications.js
Normal file
129
api/services/operations/helpers/medications.js
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
import { paramyxRunQuery } from '../../paramyxConnection.js';
|
||||||
|
|
||||||
|
export const medicationHelpers = {
|
||||||
|
getBaseMedications: async () => {
|
||||||
|
const [medList, medRoutes] = await Promise.all([
|
||||||
|
paramyxRunQuery('SELECT * FROM medications'),
|
||||||
|
paramyxRunQuery('SELECT * FROM medication_routes')
|
||||||
|
]);
|
||||||
|
|
||||||
|
const medMap = {};
|
||||||
|
|
||||||
|
medList.forEach((med) => {
|
||||||
|
medMap[med.id] = {
|
||||||
|
id: med.id,
|
||||||
|
generic: med.generic,
|
||||||
|
trade: med.trade,
|
||||||
|
system: med.system,
|
||||||
|
adult: {
|
||||||
|
hasIV: false,
|
||||||
|
hasIO: false,
|
||||||
|
hasIM: false,
|
||||||
|
hasIN: false,
|
||||||
|
hasPO: false,
|
||||||
|
hasSL: false,
|
||||||
|
hasPR: false,
|
||||||
|
hasNEB: false,
|
||||||
|
hasET: false,
|
||||||
|
hasSGA: false
|
||||||
|
},
|
||||||
|
pediatric: {
|
||||||
|
hasIV: false,
|
||||||
|
hasIO: false,
|
||||||
|
hasIM: false,
|
||||||
|
hasIN: false,
|
||||||
|
hasPO: false,
|
||||||
|
hasSL: false,
|
||||||
|
hasPR: false,
|
||||||
|
hasNEB: false,
|
||||||
|
hasET: false,
|
||||||
|
hasSGA: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
medRoutes.forEach((route) => {
|
||||||
|
const medId = route.medication_id;
|
||||||
|
|
||||||
|
if (medMap[medId]) {
|
||||||
|
const target = route.is_adult ? medMap[medId].adult : medMap[medId].pediatric;
|
||||||
|
|
||||||
|
target.hasIV = !!route.intravenous;
|
||||||
|
target.hasIO = !!route.intraosseous;
|
||||||
|
target.hasIM = !!route.intramuscular;
|
||||||
|
target.hasIN = !!route.intranasal;
|
||||||
|
target.hasPO = !!route.oral;
|
||||||
|
target.hasSL = !!route.sublingual;
|
||||||
|
target.hasPR = !!route.rectal;
|
||||||
|
target.hasNEB = !!route.nebulizer;
|
||||||
|
target.hasET = !!route.endotracheal;
|
||||||
|
target.hasSGA = !!route.supraglottic;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const fullMedList = Object.values(medMap);
|
||||||
|
|
||||||
|
return fullMedList;
|
||||||
|
},
|
||||||
|
getFullMedicationInformation: async (drugId) => {
|
||||||
|
const [medInformation, medRoutes] = await Promise.all([
|
||||||
|
paramyxRunQuery(
|
||||||
|
`select
|
||||||
|
m.id,
|
||||||
|
m.generic,
|
||||||
|
m.trade,
|
||||||
|
m.system,
|
||||||
|
mi.class,
|
||||||
|
mi.indications,
|
||||||
|
mi.contraindications,
|
||||||
|
mi.precautions,
|
||||||
|
mi.adverse,
|
||||||
|
mi.onset,
|
||||||
|
mi.duration,
|
||||||
|
mi.tip,
|
||||||
|
mi.action
|
||||||
|
from medications m inner join medication_information mi on m.id = mi.id where m.id = $1;`,
|
||||||
|
[drugId]
|
||||||
|
),
|
||||||
|
paramyxRunQuery(`select * from medication_routes mr where medication_id = $1;`, [drugId])
|
||||||
|
]);
|
||||||
|
|
||||||
|
const fullMedicationInformation = {
|
||||||
|
...medInformation[0]
|
||||||
|
};
|
||||||
|
|
||||||
|
medRoutes?.forEach((row) => {
|
||||||
|
if (row?.is_adult) {
|
||||||
|
fullMedicationInformation.adult = {
|
||||||
|
totalMax: row.total_max,
|
||||||
|
iv: row.intravenous,
|
||||||
|
io: row.intraosseous,
|
||||||
|
im: row.intramuscular,
|
||||||
|
in: row.intranasal,
|
||||||
|
po: row.oral,
|
||||||
|
sl: row.sublingual,
|
||||||
|
pr: row.rectal,
|
||||||
|
neb: row.nebulizer,
|
||||||
|
et: row.endotracheal,
|
||||||
|
sga: row.supraglottic
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fullMedicationInformation.pediatric = {
|
||||||
|
totalMax: row.total_max,
|
||||||
|
iv: row.intravenous,
|
||||||
|
io: row.intraosseous,
|
||||||
|
im: row.intramuscular,
|
||||||
|
in: row.intranasal,
|
||||||
|
po: row.oral,
|
||||||
|
sl: row.sublingual,
|
||||||
|
pr: row.rectal,
|
||||||
|
neb: row.nebulizer,
|
||||||
|
et: row.endotracheal,
|
||||||
|
sga: row.supraglottic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return fullMedicationInformation;
|
||||||
|
}
|
||||||
|
};
|
||||||
39
api/services/operations/medications.js
Normal file
39
api/services/operations/medications.js
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { paramyxRunQuery } from '../paramyxConnection.js';
|
||||||
|
import { medicationHelpers } from './helpers/medications.js'
|
||||||
|
|
||||||
|
const getMedications = async () => {
|
||||||
|
try {
|
||||||
|
const dataResp = await paramyxRunQuery('SELECT * FROM medications');
|
||||||
|
return dataResp;
|
||||||
|
} catch (err) {
|
||||||
|
console.log('GET MEDICATIONS ERROR: ', err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getBaseMedications = async () => {
|
||||||
|
try {
|
||||||
|
const dataResp = medicationHelpers.getBaseMedications();
|
||||||
|
return dataResp;
|
||||||
|
} catch (err) {
|
||||||
|
console.log('GET BASE MEDICATIONS ERROR: ', err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getFullMedicationInformation = async (drug) => {
|
||||||
|
const { drugId } = drug;
|
||||||
|
try {
|
||||||
|
const dataResp = medicationHelpers.getFullMedicationInformation(drugId);
|
||||||
|
return dataResp;
|
||||||
|
} catch (err) {
|
||||||
|
console.log('GET FULL MEDICATION INFORMATION ERROR: ', err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const medicationOperations = {
|
||||||
|
getMedications,
|
||||||
|
getBaseMedications,
|
||||||
|
getFullMedicationInformation
|
||||||
|
}
|
||||||
23
api/services/paramyxConnection.js
Normal file
23
api/services/paramyxConnection.js
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { Pool } from 'pg';
|
||||||
|
import 'dotenv/config';
|
||||||
|
|
||||||
|
const poolCreds = {
|
||||||
|
host: process.env.POSTGRES_HOST,
|
||||||
|
database: process.env.POSTGRES_DB_PARAMYXRX,
|
||||||
|
port: process.env.POSTGRES_PORT,
|
||||||
|
user: process.env.POSTGRES_USER,
|
||||||
|
password: process.env.POSTGRES_PASSWORD
|
||||||
|
};
|
||||||
|
|
||||||
|
const pool = new Pool(poolCreds);
|
||||||
|
|
||||||
|
export const paramyxRunQuery = async (query, params = []) => {
|
||||||
|
const client = await pool.connect();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const pgQueryResponse = await client.query(query, params);
|
||||||
|
return pgQueryResponse?.rows || [];
|
||||||
|
} finally {
|
||||||
|
client.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
23
api/services/shiftConnection.js
Normal file
23
api/services/shiftConnection.js
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { Pool } from 'pg';
|
||||||
|
import 'dotenv/config';
|
||||||
|
|
||||||
|
const poolCreds = {
|
||||||
|
host: process.env.POSTGRES_HOST,
|
||||||
|
database: process.env.POSTGRES_DB_SHIFT,
|
||||||
|
port: process.env.POSTGRES_PORT,
|
||||||
|
user: process.env.POSTGRES_USER,
|
||||||
|
password: process.env.POSTGRES_PASSWORD
|
||||||
|
};
|
||||||
|
|
||||||
|
const pool = new Pool(poolCreds);
|
||||||
|
|
||||||
|
export const shiftRunQuery = async (query, params = []) => {
|
||||||
|
const client = await pool.connect();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const pgQueryResponse = await client.query(query, params);
|
||||||
|
return pgQueryResponse?.rows || [];
|
||||||
|
} finally {
|
||||||
|
client.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
3
api/services/shifts/index.js
Normal file
3
api/services/shifts/index.js
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
import express from 'express';
|
||||||
|
|
||||||
|
export const shiftsRouter = express.Router();
|
||||||
5
api/services/validations/medications.js
Normal file
5
api/services/validations/medications.js
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
|
export const fullMedicationInformationSchema = Yup.object().shape({
|
||||||
|
drugId: Yup.string().required("drugId is required"),
|
||||||
|
});
|
||||||
103
package-lock.json
generated
103
package-lock.json
generated
|
|
@ -90,29 +90,33 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/body-parser": {
|
"node_modules/body-parser": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz",
|
||||||
"integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
|
"integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bytes": "^3.1.2",
|
"bytes": "^3.1.2",
|
||||||
"content-type": "^1.0.5",
|
"content-type": "^1.0.5",
|
||||||
"debug": "^4.4.0",
|
"debug": "^4.4.3",
|
||||||
"http-errors": "^2.0.0",
|
"http-errors": "^2.0.0",
|
||||||
"iconv-lite": "^0.6.3",
|
"iconv-lite": "^0.7.0",
|
||||||
"on-finished": "^2.4.1",
|
"on-finished": "^2.4.1",
|
||||||
"qs": "^6.14.0",
|
"qs": "^6.14.0",
|
||||||
"raw-body": "^3.0.0",
|
"raw-body": "^3.0.1",
|
||||||
"type-is": "^2.0.0"
|
"type-is": "^2.0.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/express"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/brace-expansion": {
|
"node_modules/brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -347,9 +351,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.4.1",
|
"version": "4.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||||
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
|
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ms": "^2.1.3"
|
"ms": "^2.1.3"
|
||||||
|
|
@ -464,18 +468,19 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/express": {
|
"node_modules/express": {
|
||||||
"version": "5.1.0",
|
"version": "5.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
|
||||||
"integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
|
"integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"accepts": "^2.0.0",
|
"accepts": "^2.0.0",
|
||||||
"body-parser": "^2.2.0",
|
"body-parser": "^2.2.1",
|
||||||
"content-disposition": "^1.0.0",
|
"content-disposition": "^1.0.0",
|
||||||
"content-type": "^1.0.5",
|
"content-type": "^1.0.5",
|
||||||
"cookie": "^0.7.1",
|
"cookie": "^0.7.1",
|
||||||
"cookie-signature": "^1.2.1",
|
"cookie-signature": "^1.2.1",
|
||||||
"debug": "^4.4.0",
|
"debug": "^4.4.0",
|
||||||
|
"depd": "^2.0.0",
|
||||||
"encodeurl": "^2.0.0",
|
"encodeurl": "^2.0.0",
|
||||||
"escape-html": "^1.0.3",
|
"escape-html": "^1.0.3",
|
||||||
"etag": "^1.8.1",
|
"etag": "^1.8.1",
|
||||||
|
|
@ -684,31 +689,39 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/http-errors": {
|
"node_modules/http-errors": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
|
||||||
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
|
"integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"depd": "2.0.0",
|
"depd": "~2.0.0",
|
||||||
"inherits": "2.0.4",
|
"inherits": "~2.0.4",
|
||||||
"setprototypeof": "1.2.0",
|
"setprototypeof": "~1.2.0",
|
||||||
"statuses": "2.0.1",
|
"statuses": "~2.0.2",
|
||||||
"toidentifier": "1.0.1"
|
"toidentifier": "~1.0.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/express"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/iconv-lite": {
|
"node_modules/iconv-lite": {
|
||||||
"version": "0.6.3",
|
"version": "0.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz",
|
||||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
"integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/express"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ignore-by-default": {
|
"node_modules/ignore-by-default": {
|
||||||
|
|
@ -796,9 +809,9 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/lodash": {
|
"node_modules/lodash": {
|
||||||
"version": "4.17.21",
|
"version": "4.17.23",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
|
||||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
|
@ -1037,9 +1050,9 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/qs": {
|
"node_modules/qs": {
|
||||||
"version": "6.14.0",
|
"version": "6.14.1",
|
||||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz",
|
||||||
"integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
|
"integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==",
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"side-channel": "^1.1.0"
|
"side-channel": "^1.1.0"
|
||||||
|
|
@ -1061,18 +1074,18 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/raw-body": {
|
"node_modules/raw-body": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
|
||||||
"integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==",
|
"integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bytes": "3.1.2",
|
"bytes": "~3.1.2",
|
||||||
"http-errors": "2.0.0",
|
"http-errors": "~2.0.1",
|
||||||
"iconv-lite": "0.6.3",
|
"iconv-lite": "~0.7.0",
|
||||||
"unpipe": "1.0.0"
|
"unpipe": "~1.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.8"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/readdirp": {
|
"node_modules/readdirp": {
|
||||||
|
|
@ -1305,9 +1318,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/statuses": {
|
"node_modules/statuses": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
|
||||||
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
|
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ WORKDIR /app
|
||||||
|
|
||||||
COPY ./package*.json ./
|
COPY ./package*.json ./
|
||||||
|
|
||||||
RUN npm ci
|
RUN npm cache clean --force && npm install --no-audit --no-fund
|
||||||
|
|
||||||
COPY . ./
|
COPY . ./
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
"@emotion/styled": "^11.14.0",
|
"@emotion/styled": "^11.14.0",
|
||||||
"@mui/icons-material": "^7.1.0",
|
"@mui/icons-material": "^7.1.0",
|
||||||
"@mui/material": "^7.1.0",
|
"@mui/material": "^7.1.0",
|
||||||
"axios": "^1.10.0",
|
"axios": "^1.13.2",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"react-router-dom": "^7.6.0",
|
"react-router-dom": "^7.6.0",
|
||||||
|
|
@ -32,6 +32,6 @@
|
||||||
"eslint-plugin-react-hooks": "^5.2.0",
|
"eslint-plugin-react-hooks": "^5.2.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.19",
|
"eslint-plugin-react-refresh": "^0.4.19",
|
||||||
"globals": "^16.0.0",
|
"globals": "^16.0.0",
|
||||||
"vite": "^6.3.5"
|
"vite": "^6.4.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue