diff --git a/.github/workflows/api-deploy-nonprod.yml b/.github/workflows/api-deploy-nonprod.yml index ff3547c..f9ea162 100644 --- a/.github/workflows/api-deploy-nonprod.yml +++ b/.github/workflows/api-deploy-nonprod.yml @@ -90,5 +90,5 @@ jobs: steps: - name: Deploy to Coolify 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 }}' diff --git a/.github/workflows/api-deploy-prod.yml b/.github/workflows/api-deploy-prod.yml index 63ff524..783a64b 100644 --- a/.github/workflows/api-deploy-prod.yml +++ b/.github/workflows/api-deploy-prod.yml @@ -90,5 +90,5 @@ jobs: steps: - name: Deploy to Coolify 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 }}' diff --git a/.github/workflows/web-container.yml b/.github/workflows/web-container.yml index 61aeca7..36a5f04 100644 --- a/.github/workflows/web-container.yml +++ b/.github/workflows/web-container.yml @@ -16,7 +16,7 @@ on: - web/** jobs: determine-workflow: - runs-on: 'ubuntu-latest' + runs-on: ['self-hosted','pi'] outputs: workflow_type: ${{ steps.workflow.outputs.workflow_type }} workflow_envs: ${{ steps.workflow.outputs.workflow_envs }} diff --git a/.github/workflows/web-deploy-nonprod.yml b/.github/workflows/web-deploy-nonprod.yml index 61fcb6b..2bc288f 100644 --- a/.github/workflows/web-deploy-nonprod.yml +++ b/.github/workflows/web-deploy-nonprod.yml @@ -90,5 +90,5 @@ jobs: steps: - name: Deploy to Coolify 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 }}' diff --git a/.github/workflows/web-deploy-prod.yml b/.github/workflows/web-deploy-prod.yml index 6c87dcc..f379846 100644 --- a/.github/workflows/web-deploy-prod.yml +++ b/.github/workflows/web-deploy-prod.yml @@ -90,5 +90,5 @@ jobs: steps: - name: Deploy to Coolify 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 }}' diff --git a/.gitignore b/.gitignore index d7de12f..721604b 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,6 @@ dist-ssr *.njsproj *.sln *.sw? + +api/package-lock.json +web/package-lock.json \ No newline at end of file diff --git a/README.md b/README.md index c8b6ec3..a5febe9 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/api/Dockerfile b/api/Dockerfile index b6db0c2..d95b891 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -4,7 +4,7 @@ WORKDIR /app COPY ./package*.json ./ -RUN npm ci +RUN npm cache clean --force && npm install --no-audit --no-fund COPY . ./ diff --git a/api/middleware/auth.js b/api/middleware/auth.js new file mode 100644 index 0000000..4adff15 --- /dev/null +++ b/api/middleware/auth.js @@ -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(); +}; diff --git a/api/package.json b/api/package.json index 4ead34c..2cb8d17 100644 --- a/api/package.json +++ b/api/package.json @@ -11,8 +11,9 @@ "dependencies": { "cors": "^2.8.5", "dotenv": "^16.5.0", - "express": "^5.1.0", - "pg": "^8.16.2" + "express": "^5.2.0", + "pg": "^8.17.1", + "yup": "^1.7.1" }, "devDependencies": { "nodemon": "^3.1.10" diff --git a/api/server.js b/api/server.js index 1efdecc..c21522d 100644 --- a/api/server.js +++ b/api/server.js @@ -1,11 +1,11 @@ import express from 'express'; import cors from 'cors'; import dotenv from 'dotenv'; -import userRouter from './services/user/index.js'; -import departmentRouter from './services/department/index.js'; -import pool from './services/postgres/postgresServices.js'; +import { medicationRouter } from './services/medications/index.js'; +import { shiftsRouter } from './services/shifts/index.js'; +import { shiftRunQuery } from './services/shiftConnection.js'; +import { validateMedicationApiKey, validateShiftSyncApiKey } from './middleware/auth.js'; dotenv.config(); -import { routes } from './router/routes.js'; const app = express(); @@ -19,18 +19,16 @@ const corsOptions = { app.use(cors(corsOptions)); app.use(express.json({ limit: '10mb' })); -app.use('/api', routes); - -app.get('*route', (req, res) => { - res.send("Hello from ShiftSync"); -}); app.use(express.json()); const apiRouter = express.Router(); -apiRouter.use('/user', userRouter); -apiRouter.use('/department', departmentRouter); +// ParamyxRx Router (/api/medication) +apiRouter.use('/medication', validateMedicationApiKey, medicationRouter); + +// ShiftSync Router (/api/shifts) +apiRouter.use('/shifts', validateShiftSyncApiKey, shiftsRouter); app.get("/api", (req, res) => { res.json('Welcome to Shift Sync API'); @@ -38,17 +36,23 @@ app.get("/api", (req, res) => { app.use('/api', apiRouter); -app.get('/db-health', async (req, res) => { +app.get('/api/db-health', async (req, res) => { try { - console.log('in'); - const result = await pool.query('SELECT NOW()'); - console.log('after result'); + const result = await shiftRunQuery('SELECT NOW()'); res.json({ connected: true, time: result.rows[0].now }); } catch (err) { console.error(err); 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, () => { console.log('Server Started on port 5172'); diff --git a/api/services/index.js b/api/services/index.js index 1853820..2f84338 100644 --- a/api/services/index.js +++ b/api/services/index.js @@ -1,7 +1,7 @@ import { - userDataOperations -} from './operations/userData.js'; + medicationOperations +} from './operations/medications.js'; export const databaseServices = { - ...userDataOperations + ...medicationOperations } \ No newline at end of file diff --git a/api/services/medications/index.js b/api/services/medications/index.js new file mode 100644 index 0000000..80f526f --- /dev/null +++ b/api/services/medications/index.js @@ -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); + } +}); \ No newline at end of file diff --git a/api/services/operations/helpers/medications.js b/api/services/operations/helpers/medications.js new file mode 100644 index 0000000..3edf033 --- /dev/null +++ b/api/services/operations/helpers/medications.js @@ -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; + } +}; \ No newline at end of file diff --git a/api/services/operations/medications.js b/api/services/operations/medications.js new file mode 100644 index 0000000..b55455a --- /dev/null +++ b/api/services/operations/medications.js @@ -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 +} \ No newline at end of file diff --git a/api/services/paramyxConnection.js b/api/services/paramyxConnection.js new file mode 100644 index 0000000..bf1188e --- /dev/null +++ b/api/services/paramyxConnection.js @@ -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(); + } +} \ No newline at end of file diff --git a/api/services/shiftConnection.js b/api/services/shiftConnection.js new file mode 100644 index 0000000..7bba6bc --- /dev/null +++ b/api/services/shiftConnection.js @@ -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(); + } +} \ No newline at end of file diff --git a/api/services/shifts/index.js b/api/services/shifts/index.js new file mode 100644 index 0000000..dcf519f --- /dev/null +++ b/api/services/shifts/index.js @@ -0,0 +1,3 @@ +import express from 'express'; + +export const shiftsRouter = express.Router(); \ No newline at end of file diff --git a/api/services/validations/medications.js b/api/services/validations/medications.js new file mode 100644 index 0000000..844cc15 --- /dev/null +++ b/api/services/validations/medications.js @@ -0,0 +1,5 @@ +import * as Yup from 'yup'; + +export const fullMedicationInformationSchema = Yup.object().shape({ + drugId: Yup.string().required("drugId is required"), +}); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index c3e5595..12cb1b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -90,29 +90,33 @@ } }, "node_modules/body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz", + "integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", - "debug": "^4.4.0", + "debug": "^4.4.3", "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", + "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" + "raw-body": "^3.0.1", + "type-is": "^2.0.1" }, "engines": { "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -347,9 +351,9 @@ } }, "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -464,18 +468,19 @@ } }, "node_modules/express": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", "dependencies": { "accepts": "^2.0.0", - "body-parser": "^2.2.0", + "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", + "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", @@ -684,31 +689,39 @@ } }, "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "license": "MIT", "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/ignore-by-default": { @@ -796,9 +809,9 @@ "license": "MIT" }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "dev": true, "license": "MIT" }, @@ -1037,9 +1050,9 @@ "license": "MIT" }, "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -1061,18 +1074,18 @@ } }, "node_modules/raw-body": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", - "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.6.3", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.10" } }, "node_modules/readdirp": { @@ -1305,9 +1318,9 @@ } }, "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "license": "MIT", "engines": { "node": ">= 0.8" diff --git a/web/Dockerfile b/web/Dockerfile index 21b3560..7fa96cb 100644 --- a/web/Dockerfile +++ b/web/Dockerfile @@ -4,7 +4,7 @@ WORKDIR /app COPY ./package*.json ./ -RUN npm ci +RUN npm cache clean --force && npm install --no-audit --no-fund COPY . ./ diff --git a/web/package.json b/web/package.json index bba0a49..acaf7fb 100644 --- a/web/package.json +++ b/web/package.json @@ -16,7 +16,7 @@ "@emotion/styled": "^11.14.0", "@mui/icons-material": "^7.1.0", "@mui/material": "^7.1.0", - "axios": "^1.10.0", + "axios": "^1.13.2", "react": "^19.1.0", "react-dom": "^19.1.0", "react-router-dom": "^7.6.0", @@ -32,6 +32,6 @@ "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.19", "globals": "^16.0.0", - "vite": "^6.3.5" + "vite": "^6.4.1" } }