Upgrade expressjs (#1633)

* upgrade expressjs

* migration

* reenable test

* extend timeout on download test

* fixup! migration

* move empty body test from controller to validator

* enusre not empty

* extract validation function

* fixup! reenable test

* remove thin controllers

* disable e2e test of project file download
This commit is contained in:
Alex Christoffer Rasmussen
2025-06-12 15:02:35 +02:00
committed by Carlos Valente
parent 90870ecfb6
commit 6f3ab274bd
39 changed files with 590 additions and 841 deletions
+11 -11
View File
@@ -6,18 +6,18 @@
"exports": "./src/index.js",
"dependencies": {
"@googleapis/sheets": "^5.0.5",
"cookie": "^1.0.2",
"cookie-parser": "^1.4.7",
"cors": "^2.8.5",
"cookie": "1.0.2",
"cookie-parser": "1.4.7",
"cors": "2.8.5",
"dotenv": "^16.0.1",
"express": "^4.21.1",
"express-static-gzip": "^2.2.0",
"express-validator": "^7.2.0",
"express": "5.1.0",
"express-static-gzip": "3.0.0",
"express-validator": "7.2.1",
"multer": "2.0.1",
"fast-equals": "^5.0.1",
"google-auth-library": "^9.4.2",
"got": "^14.4.5",
"lowdb": "^7.0.1",
"multer": "^1.4.5-lts.1",
"ontime-utils": "workspace:*",
"osc-min": "2.1.2",
"sanitize-filename": "^1.6.3",
@@ -26,10 +26,10 @@
"xlsx": "^0.18.5"
},
"devDependencies": {
"@types/cookie-parser": "^1.4.8",
"@types/cors": "^2.8.17",
"@types/express": "^4.17.17",
"@types/multer": "^1.4.11",
"@types/cookie-parser": "1.4.9",
"@types/cors": "2.8.19",
"@types/express": "5.0.3",
"@types/multer": "1.4.13",
"@types/node": "catalog:",
"@types/websocket": "^1.0.5",
"@types/ws": "^8.5.10",
@@ -1,41 +0,0 @@
import { defaultCss } from '../../user/styles/bundledCss.js';
import type { Request, Response } from 'express';
import { readCssFile, writeCssFile } from './assets.service.js';
/**
* Exposes the contents of the cssOverride.css file
*/
export async function getCssOverride(_req: Request, res: Response) {
try {
const data = await readCssFile();
res.status(200).send(data);
} catch (error) {
res.status(500).send({ message: error });
}
}
/**
* Allows modifying the cssOverride.css file
*/
export async function postCssOverride(req: Request, res: Response) {
const { css } = req.body;
try {
await writeCssFile(css);
res.status(204).send();
} catch (error) {
res.status(500).send({ message: error });
}
}
/**
* Restores the default cssOverride.css file
*/
export async function restoreCss(_req: Request, res: Response) {
try {
await writeCssFile(defaultCss);
res.status(200).send(defaultCss);
} catch (error) {
res.status(500).send({ message: error });
}
}
@@ -1,10 +1,40 @@
import express from 'express';
import { getCssOverride, postCssOverride, restoreCss } from './assets.controller.js';
import type { Request, Response } from 'express';
import type { ErrorResponse } from 'ontime-types';
import { validatePostCss } from './assets.validation.js';
import { readCssFile, writeCssFile } from './assets.service.js';
import { getErrorMessage } from 'ontime-utils';
import { defaultCss } from '../../user/styles/bundledCss.js';
export const router = express.Router();
router.get('/css', getCssOverride);
router.post('/css', validatePostCss, postCssOverride);
router.post('/css/restore', restoreCss);
router.get('/css', async (_req: Request, res: Response<string | ErrorResponse>) => {
try {
const data = await readCssFile();
res.status(200).send(data);
} catch (error) {
const message = getErrorMessage(error);
res.status(500).send({ message });
}
});
router.post('/css', validatePostCss, async (req: Request, res: Response<never | ErrorResponse>) => {
const { css } = req.body;
try {
await writeCssFile(css);
res.status(204).send();
} catch (error) {
const message = getErrorMessage(error);
res.status(500).send({ message });
}
});
router.post('/css/restore', async (_req: Request, res: Response<string | ErrorResponse>) => {
try {
await writeCssFile(defaultCss);
res.status(200).send(defaultCss);
} catch (error) {
const message = getErrorMessage(error);
res.status(500).send({ message });
}
});
@@ -1,12 +1,4 @@
import { Request, Response, NextFunction } from 'express';
import { body, validationResult } from 'express-validator';
import { body } from 'express-validator';
import { requestValidationFunction } from '../validation-utils/validationFunction.js';
export const validatePostCss = [
body('css').isString().trim(),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
next();
},
];
export const validatePostCss = [body('css').isString().trim(), requestValidationFunction];
@@ -12,7 +12,7 @@ import * as automationService from './automation.service.js';
import { parseOutput } from './automation.validation.js';
export function getAutomationSettings(_req: Request, res: Response<AutomationSettings>) {
res.json(automationDao.getAutomationSettings());
res.status(200).json(automationDao.getAutomationSettings());
}
export async function postAutomationSettings(req: Request, res: Response<AutomationSettings | ErrorResponse>) {
@@ -12,7 +12,6 @@ import {
testOutput,
} from './automation.controller.js';
import {
paramContainsId,
validateAutomationSettings,
validateAutomation,
validateAutomationPatch,
@@ -20,6 +19,7 @@ import {
validateTrigger,
validateTriggerPatch,
} from './automation.validation.js';
import { paramsWithId } from '../validation-utils/validationFunction.js';
export const router = express.Router();
@@ -28,10 +28,10 @@ router.post('/', validateAutomationSettings, postAutomationSettings);
router.post('/trigger', validateTrigger, postTrigger);
router.put('/trigger/:id', validateTriggerPatch, putTrigger);
router.delete('/trigger/:id', paramContainsId, deleteTrigger);
router.delete('/trigger/:id', paramsWithId, deleteTrigger);
router.post('/automation', validateAutomation, postAutomation);
router.put('/automation/:id', validateAutomationPatch, editAutomation);
router.delete('/automation/:id', paramContainsId, deleteAutomation);
router.delete('/automation/:id', paramsWithId, deleteAutomation);
router.post('/test', validateTestPayload, testOutput);
@@ -10,22 +10,12 @@ import {
} from 'ontime-types';
import { parseUserTime } from 'ontime-utils';
import type { Request, Response, NextFunction } from 'express';
import { body, oneOf, param, validationResult } from 'express-validator';
import { body, oneOf, param } from 'express-validator';
import * as assert from '../../utils/assert.js';
import { isFilterOperator, isFilterRule, isOntimeActionAction } from './automation.utils.js';
export const paramContainsId = [
param('id').isString().notEmpty(),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
next();
},
];
import { requestValidationFunction } from '../validation-utils/validationFunction.js';
export const validateAutomationSettings = [
body('enabledAutomations').isBoolean(),
@@ -37,57 +27,33 @@ export const validateAutomationSettings = [
body('triggers.*.automationId').optional().isString().trim(),
body('automations').optional().custom(parseAutomation),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
next();
},
requestValidationFunction,
];
export const validateTrigger = [
body('title').isString().trim(),
body('title').isString().trim().notEmpty(),
body('trigger').isIn(timerLifecycleValues),
body('automationId').isString().trim(),
body('automationId').isString().trim().notEmpty(),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
next();
},
requestValidationFunction,
];
export const validateTriggerPatch = [
param('id').isString().notEmpty(),
body('title').optional().isString().trim(),
body('title').optional().isString().trim().notEmpty(),
body('trigger').optional().isIn(timerLifecycleValues),
body('automationId').optional().isString().trim(),
body('automationId').optional().isString().trim().notEmpty(),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
next();
},
requestValidationFunction,
];
export const validateAutomation = [
body().custom(parseAutomation),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
next();
},
];
export const validateAutomation = [body().custom(parseAutomation), requestValidationFunction];
export const validateAutomationPatch = [
param('id').isString().notEmpty(),
body().custom(parseAutomation),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
next();
},
requestValidationFunction,
];
/**
@@ -162,11 +128,7 @@ export const validateTestPayload = [
body('visible').if(body('type').equals('ontime')).optional().isString().trim(),
body('secondarySource').if(body('type').equals('ontime')).optional().isString().trim(),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
next();
},
requestValidationFunction,
];
/**
@@ -13,7 +13,7 @@ export const router = express.Router();
router.get('/', async (_req: Request, res: Response<CustomFields>) => {
const customFields = getProjectCustomFields();
res.json(customFields);
res.status(200).json(customFields);
});
router.post('/', validateCustomField, async (req: Request, res: Response<CustomFields | ErrorResponse>) => {
@@ -1,7 +1,7 @@
import { isAlphanumericWithSpace } from 'ontime-utils';
import type { Request, Response, NextFunction } from 'express';
import { body, param, validationResult } from 'express-validator';
import { body, param } from 'express-validator';
import { requestValidationFunction } from '../validation-utils/validationFunction.js';
export const validateCustomField = [
body('label')
@@ -14,11 +14,7 @@ export const validateCustomField = [
body('type').isIn(['string', 'image']),
body('colour').isString().trim(),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
next();
},
requestValidationFunction,
];
export const validateEditCustomField = [
@@ -33,19 +29,7 @@ export const validateEditCustomField = [
body('type').isIn(['string', 'image']),
body('colour').isString().trim(),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
next();
},
requestValidationFunction,
];
export const validateDeleteCustomField = [
param('key').isString().notEmpty(),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
next();
},
];
export const validateDeleteCustomField = [param('key').isString().notEmpty(), requestValidationFunction];
+16 -8
View File
@@ -106,7 +106,8 @@ export async function projectDownload(req: Request, res: Response) {
const { filename } = req.body;
const pathToFile = doesProjectExist(filename);
if (!pathToFile) {
return res.status(404).send({ message: `Project ${filename} not found.` });
res.status(404).send({ message: `Project ${filename} not found.` });
return;
}
res.download(pathToFile, filename, (error) => {
@@ -138,7 +139,8 @@ export async function postProjectFile(req: Request, res: Response<MessageRespons
} catch (error) {
const message = getErrorMessage(error);
if (message.startsWith('Project file')) {
return res.status(403).send({ message });
res.status(403).send({ message });
return;
}
res.status(400).send({ message });
}
@@ -195,7 +197,8 @@ export async function loadProject(req: Request, res: Response<MessageResponse |
} catch (error) {
const message = getErrorMessage(error);
if (message.startsWith('Project file')) {
return res.status(403).send({ message });
res.status(403).send({ message });
return;
}
res.status(500).send({ message });
}
@@ -214,7 +217,8 @@ export async function loadDemo(_req: Request, res: Response<MessageResponse | Er
} catch (error) {
const message = getErrorMessage(error);
if (message.startsWith('Project file')) {
return res.status(403).send({ message });
res.status(403).send({ message });
return;
}
res.status(500).send({ message });
}
@@ -245,7 +249,8 @@ export async function duplicateProjectFile(req: Request, res: Response<MessageRe
} catch (error) {
const message = getErrorMessage(error);
if (message.startsWith('Project file')) {
return res.status(403).send({ message });
res.status(403).send({ message });
return;
}
res.status(500).send({ message });
@@ -275,7 +280,8 @@ export async function renameProjectFile(req: Request, res: Response<MessageRespo
} catch (error) {
const message = getErrorMessage(error);
if (message.startsWith('Project file')) {
return res.status(403).send({ message });
res.status(403).send({ message });
return;
}
res.status(500).send({ message });
@@ -303,10 +309,12 @@ export async function deleteProjectFile(req: Request, res: Response<MessageRespo
} catch (error) {
const message = getErrorMessage(error);
if (message === 'Cannot delete currently loaded project') {
return res.status(403).send({ message });
res.status(403).send({ message });
return;
}
if (message === 'Project file not found') {
return res.status(404).send({ message });
res.status(404).send({ message });
return;
}
res.status(500).send({ message });
+10 -49
View File
@@ -1,12 +1,13 @@
import type { Request, Response, NextFunction } from 'express';
import { body, param, validationResult } from 'express-validator';
import { body, param } from 'express-validator';
import sanitize from 'sanitize-filename';
import { ensureJsonExtension } from '../../utils/fileManagement.js';
import { requestValidationFunction } from '../validation-utils/validationFunction.js';
/**
* @description Validates request for a new project.
*/
export const validateNewProject = [
body().notEmpty().withMessage('No object found in request'),
body('filename').optional().isString().trim(),
body('title').optional().isString().trim(),
body('description').optional().isString().trim(),
@@ -18,11 +19,7 @@ export const validateNewProject = [
body('endMessage').optional().isString().trim(),
body('custom').optional().isArray(),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
next();
},
requestValidationFunction,
];
/**
@@ -40,25 +37,14 @@ export const validateQuickProject = [
body('viewSettings.freezeEnd').optional().isBoolean(),
body('viewSettings.endMessage').optional().isString().trim(),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
next();
},
requestValidationFunction,
];
/**
* @description Validates request for pathing data in the project.
*/
export const validatePatchProject = [
// Custom validator to ensure the body is not empty
(req: Request, res: Response, next: NextFunction) => {
if (Object.keys(req.body).length === 0) {
return res.status(422).json({ errors: [{ msg: 'Request body cannot be empty' }] });
}
next();
},
body().notEmpty().withMessage('No object found in request'),
body('rundowns').isObject().optional({ nullable: false }),
body('project').isObject().optional({ nullable: false }),
body('settings').isObject().optional({ nullable: false }),
@@ -68,11 +54,7 @@ export const validatePatchProject = [
body('osc').isObject().optional({ nullable: false }),
body('http').isObject().optional({ nullable: false }),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
next();
},
requestValidationFunction,
];
/**
@@ -88,14 +70,7 @@ export const validateNewFilenameBody = [
.withMessage('Filename was empty or contained only invalid characters')
.customSanitizer((input: string) => ensureJsonExtension(input)),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() });
}
next();
},
requestValidationFunction,
];
/**
@@ -111,14 +86,7 @@ export const validateFilenameBody = [
.withMessage('Filename was empty or contained only invalid characters')
.customSanitizer((input: string) => ensureJsonExtension(input)),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() });
}
next();
},
requestValidationFunction,
];
/**
@@ -134,12 +102,5 @@ export const validateFilenameParam = [
.withMessage('Filename was empty or contained only invalid characters')
.customSanitizer((input: string) => ensureJsonExtension(input)),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() });
}
next();
},
requestValidationFunction,
];
@@ -1,45 +0,0 @@
/**
* This module encapsulates logic related to
* Google Sheets
*/
import type { Request, Response } from 'express';
import { generateRundownPreview, listWorksheets, saveExcelFile } from './excel.service.js';
import { CustomFields, Rundown } from 'ontime-types';
export async function postExcel(req: Request, res: Response) {
try {
// file has been validated by middleware
const filePath = (req.file as Express.Multer.File).path;
await saveExcelFile(filePath);
res.status(200).send();
} catch (error) {
res.status(500).send({ message: String(error) });
}
}
export async function getWorksheets(req: Request, res: Response) {
try {
const names = listWorksheets();
res.status(200).send(names);
} catch (error) {
res.status(500).send({ message: String(error) });
}
}
/**
* parses an Excel spreadsheet
* @returns parsed result
*/
export async function previewExcel(
req: Request,
res: Response<{ rundown: Rundown; customFields: CustomFields } | { message: string }>,
) {
try {
const { options } = req.body;
const data = generateRundownPreview(options);
res.status(200).send(data);
} catch (error) {
res.status(500).send({ message: String(error) });
}
}
+36 -8
View File
@@ -1,14 +1,42 @@
/**
* This is a feature specific router for integration with Excel
*/
import express from 'express';
import type { Request, Response } from 'express';
import { uploadExcel } from './excel.middleware.js';
import { getWorksheets, postExcel, previewExcel } from './excel.controller.js';
import { validateFileExists, validateImportMapOptions } from './excel.validation.js';
import { CustomFields, ErrorResponse, Rundown } from 'ontime-types';
import { generateRundownPreview, listWorksheets, saveExcelFile } from './excel.service.js';
export const router = express.Router();
router.post('/upload', uploadExcel, validateFileExists, postExcel);
router.get('/worksheets', getWorksheets);
router.post('/preview', validateImportMapOptions, previewExcel);
router.post('/upload', uploadExcel, validateFileExists, async (req: Request, res: Response<never | ErrorResponse>) => {
try {
// file has been validated by middleware
const filePath = (req.file as Express.Multer.File).path;
await saveExcelFile(filePath);
res.status(201).send();
} catch (error) {
res.status(500).send({ message: String(error) });
}
});
router.get('/worksheets', (_req: Request, res: Response<string[] | ErrorResponse>) => {
try {
const names = listWorksheets();
res.status(200).send(names);
} catch (error) {
res.status(500).send({ message: String(error) });
}
});
router.post(
'/preview',
validateImportMapOptions,
(req: Request, res: Response<{ rundown: Rundown; customFields: CustomFields } | ErrorResponse>) => {
try {
const { options } = req.body;
const data = generateRundownPreview(options);
res.status(200).send(data);
} catch (error) {
res.status(500).send({ message: String(error) });
}
},
);
@@ -1,16 +1,12 @@
import { isImportMap } from 'ontime-utils';
import { body, validationResult } from 'express-validator';
import type { NextFunction, Request, Response } from 'express';
import { body } from 'express-validator';
import {
requestValidationFunction,
requestValidationFunctionWithFile,
} from '../validation-utils/validationFunction.js';
export const validateFileExists = [
(req: Request, res: Response, next: NextFunction) => {
if (!req.file) {
return res.status(422).json({ errors: 'File not found' });
}
next();
},
];
export const validateFileExists = [requestValidationFunctionWithFile];
export const validateImportMapOptions = [
body('options')
@@ -19,9 +15,5 @@ export const validateImportMapOptions = [
return isImportMap(content);
}),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
next();
},
requestValidationFunction,
];
+2 -2
View File
@@ -31,6 +31,6 @@ appRouter.use('/report', reportRouter);
appRouter.use('/assets', assetsRouter);
//we don't want to redirect to react index when using api routes
appRouter.all('/*', (_req, res) => {
res.status(404).send();
appRouter.all('/*splat', (_req, res) => {
res.status(404).send('data path not found');
});
@@ -1,40 +0,0 @@
import { ErrorResponse, ProjectData } from 'ontime-types';
import { getErrorMessage } from 'ontime-utils';
import type { Request, Response } from 'express';
import { removeUndefined } from '../../utils/parserUtils.js';
import { failEmptyObjects } from '../../utils/routerUtils.js';
import { editCurrentProjectData } from '../../services/project-service/ProjectService.js';
import * as projectDao from './projectData.dao.js';
export function getProjectData(_req: Request, res: Response<ProjectData>) {
res.json(projectDao.getProjectData());
}
export async function postProjectData(req: Request, res: Response<ProjectData | ErrorResponse>) {
if (failEmptyObjects(req.body, res)) {
return;
}
try {
const newData: Partial<ProjectData> = removeUndefined({
title: req.body?.title,
description: req.body?.description,
publicUrl: req.body?.publicUrl,
publicInfo: req.body?.publicInfo,
backstageUrl: req.body?.backstageUrl,
backstageInfo: req.body?.backstageInfo,
endMessage: req.body?.endMessage,
projectLogo: req.body?.projectLogo,
custom: req.body?.custom,
});
const updatedData = await editCurrentProjectData(newData);
res.status(200).send(updatedData);
} catch (error) {
const message = getErrorMessage(error);
res.status(400).send({ message });
}
}
@@ -1,12 +1,42 @@
import express from 'express';
import type { Request, Response } from 'express';
import type { ErrorResponse, ProjectData } from 'ontime-types';
import { getErrorMessage } from 'ontime-utils';
import { getProjectData, postProjectData } from './projectData.controller.js';
import { projectSanitiser } from './projectData.validation.js';
import { uploadImageFile } from '../db/db.middleware.js';
import { postProjectLogo } from '../db/db.controller.js';
import * as projectDao from './projectData.dao.js';
import { removeUndefined } from '../../utils/parserUtils.js';
import { editCurrentProjectData } from '../../services/project-service/ProjectService.js';
export const router = express.Router();
router.get('/', getProjectData);
router.post('/', projectSanitiser, postProjectData);
router.get('/', (_req: Request, res: Response<ProjectData>) => {
res.status(200).json(projectDao.getProjectData());
});
router.post('/', projectSanitiser, async (req: Request, res: Response<ProjectData | ErrorResponse>) => {
try {
const newData: Partial<ProjectData> = removeUndefined({
title: req.body?.title,
description: req.body?.description,
publicUrl: req.body?.publicUrl,
publicInfo: req.body?.publicInfo,
backstageUrl: req.body?.backstageUrl,
backstageInfo: req.body?.backstageInfo,
endMessage: req.body?.endMessage,
projectLogo: req.body?.projectLogo,
custom: req.body?.custom,
});
const updatedData = await editCurrentProjectData(newData);
res.status(200).send(updatedData);
} catch (error) {
const message = getErrorMessage(error);
res.status(400).send({ message });
}
});
router.post('/upload', uploadImageFile, postProjectLogo);
@@ -1,7 +1,8 @@
import { Request, Response, NextFunction } from 'express';
import { body, validationResult } from 'express-validator';
import { body } from 'express-validator';
import { requestValidationFunction } from '../validation-utils/validationFunction.js';
export const projectSanitiser = [
body().notEmpty().withMessage('No object found in request'),
body('title').optional().isString().trim(),
body('description').optional().isString().trim(),
body('publicUrl').optional().isString().trim(),
@@ -9,14 +10,10 @@ export const projectSanitiser = [
body('backstageUrl').optional().isString().trim(),
body('backstageInfo').optional().isString().trim(),
body('endMessage').optional().isString().trim(),
body('projectLogo').optional({ nullable: true }).isString().trim(),
body('projectLogo').optional({ nullable: true }).isString().trim().isBase64(),
body('custom').optional().isArray(),
body('custom.*.title').optional().isString().trim().notEmpty(),
body('custom.*.value').optional().isString().trim().notEmpty(),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
next();
},
requestValidationFunction,
];
@@ -1,18 +0,0 @@
import type { Request, Response } from 'express';
import type { OntimeReport } from 'ontime-types';
import * as report from './report.service.js';
export function getAll(_req: Request, res: Response<OntimeReport>) {
res.json(report.generate());
}
export function deleteAll(_req: Request, res: Response<OntimeReport>) {
report.clear();
res.status(200).send();
}
export function deleteWithId(req: Request, res: Response<OntimeReport>) {
const { eventId } = req.params;
report.clear(eventId);
res.status(200).send();
}
@@ -1,10 +1,21 @@
import express from 'express';
import { getAll, deleteWithId, deleteAll } from './report.controller.js';
import { paramsMustHaveEntryId } from '../rundown/rundown.validation.js';
import type { Request, Response } from 'express';
import { paramsWithId } from '../validation-utils/validationFunction.js';
import * as report from './report.service.js';
export const router = express.Router();
router.get('/', getAll);
router.get('/', (_req: Request, res: Response) => {
res.status(200).json(report.generate());
});
router.delete('/all', deleteAll);
router.delete('/:eventId', paramsMustHaveEntryId, deleteWithId);
router.delete('/all', (_req: Request, res: Response) => {
report.clear();
res.status(204).send();
});
router.delete('/:id', paramsWithId, (req: Request, res: Response) => {
const { id } = req.params;
report.clear(id);
res.status(204).send();
});
@@ -19,7 +19,6 @@ import {
ungroupEntries,
} from './rundown.service.js';
import {
paramsMustHaveEntryId,
rundownArrayOfIds,
rundownBatchPutValidator,
rundownPostValidator,
@@ -27,6 +26,7 @@ import {
rundownReorderValidator,
rundownSwapValidator,
} from './rundown.validation.js';
import { paramsWithId } from '../validation-utils/validationFunction.js';
export const router = express.Router();
@@ -100,11 +100,11 @@ router.patch('/swap', rundownSwapValidator, async (req: Request, res: Response<R
});
router.patch(
'/applydelay/:entryId',
paramsMustHaveEntryId,
'/applydelay/:id',
paramsWithId,
async (req: Request, res: Response<Rundown | ErrorResponse>) => {
try {
const newRundown = await applyDelay(req.params.entryId);
const newRundown = await applyDelay(req.params.id);
res.status(200).send(newRundown);
} catch (error) {
const message = getErrorMessage(error);
@@ -113,9 +113,9 @@ router.patch(
},
);
router.post('/clone/:entryId', paramsMustHaveEntryId, async (req: Request, res: Response<Rundown | ErrorResponse>) => {
router.post('/clone/:id', paramsWithId, async (req: Request, res: Response<Rundown | ErrorResponse>) => {
try {
const newRundown = await cloneEntry(req.params.entryId);
const newRundown = await cloneEntry(req.params.id);
res.status(200).send(newRundown);
} catch (error) {
const message = getErrorMessage(error);
@@ -134,11 +134,11 @@ router.post('/group', rundownArrayOfIds, async (req: Request, res: Response<Rund
});
router.post(
'/ungroup/:entryId',
paramsMustHaveEntryId,
'/ungroup/:id',
paramsWithId,
async (req: Request, res: Response<Rundown | ErrorResponse>) => {
try {
const newRundown = await ungroupEntries(req.params.entryId);
const newRundown = await ungroupEntries(req.params.id);
res.status(200).send(newRundown);
} catch (error) {
const message = getErrorMessage(error);
@@ -1,38 +1,22 @@
import { body, param, validationResult } from 'express-validator';
import type { Request, Response, NextFunction } from 'express';
import { body, param } from 'express-validator';
import { requestValidationFunction } from '../validation-utils/validationFunction.js';
export const rundownPostValidator = [
body('type').isString().isIn(['event', 'delay', 'block']),
body('after').optional().isString(),
body('before').optional().isString(),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
next();
},
requestValidationFunction,
];
export const rundownPutValidator = [
body('id').isString().notEmpty(),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
next();
},
];
export const rundownPutValidator = [body('id').isString().notEmpty(), requestValidationFunction];
export const rundownBatchPutValidator = [
body('data').isObject(),
body('ids').isArray().notEmpty(),
body('ids.*').isString(),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
next();
},
requestValidationFunction,
];
export const rundownReorderValidator = [
@@ -40,41 +24,21 @@ export const rundownReorderValidator = [
body('destinationId').isString().notEmpty(),
body('order').isIn(['before', 'after', 'insert']),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
next();
},
requestValidationFunction,
];
export const rundownSwapValidator = [
body('from').isString().notEmpty(),
body('to').isString().notEmpty(),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
next();
},
requestValidationFunction,
];
export const paramsMustHaveEntryId = [
param('entryId').isString().notEmpty(),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
next();
},
];
export const paramsMustHaveEntryId = [param('entryId').isString().notEmpty(), requestValidationFunction];
export const rundownArrayOfIds = [
body('ids').isArray().notEmpty(),
body('ids.*').isString(),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
next();
},
requestValidationFunction,
];
@@ -1,41 +0,0 @@
import { getErrorMessage } from 'ontime-utils';
import { ErrorResponse, GetInfo, GetUrl, SessionStats } from 'ontime-types';
import type { Request, Response } from 'express';
import * as sessionService from './session.service.js';
export async function getSessionStats(_req: Request, res: Response<SessionStats | ErrorResponse>) {
try {
const stats = await sessionService.getSessionStats();
res.status(200).send(stats);
} catch (error) {
const message = getErrorMessage(error);
res.status(500).send({ message });
}
}
export async function getInfo(_req: Request, res: Response<GetInfo | ErrorResponse>) {
try {
const info = await sessionService.getInfo();
res.status(200).send(info);
} catch (error) {
const message = getErrorMessage(error);
res.status(500).send({ message });
}
}
export async function generateUrl(req: Request, res: Response<GetUrl | ErrorResponse>) {
try {
const url = sessionService.generateAuthenticatedUrl(
req.body.baseUrl,
req.body.path,
req.body.lock,
req.body.authenticate,
);
res.status(200).send({ url: url.toString() });
} catch (error) {
const message = getErrorMessage(error);
res.status(500).send({ message });
}
}
@@ -1,10 +1,43 @@
import express from 'express';
import { getInfo, getSessionStats, generateUrl } from './session.controller.js';
import type { Request, Response } from 'express';
import { getErrorMessage } from 'ontime-utils';
import type { ErrorResponse, GetInfo, GetUrl, SessionStats } from 'ontime-types';
import { validateGenerateUrl } from './session.validation.js';
import * as sessionService from './session.service.js';
export const router = express.Router();
router.get('/', getSessionStats);
router.get('/info', getInfo);
router.post('/url', validateGenerateUrl, generateUrl);
router.get('/', async (_req: Request, res: Response<SessionStats | ErrorResponse>) => {
try {
const stats = await sessionService.getSessionStats();
res.status(200).send(stats);
} catch (error) {
const message = getErrorMessage(error);
res.status(500).send({ message });
}
});
router.get('/info', async (_req: Request, res: Response<GetInfo | ErrorResponse>) => {
try {
const info = await sessionService.getInfo();
res.status(200).send(info);
} catch (error) {
const message = getErrorMessage(error);
res.status(500).send({ message });
}
});
router.post('/url', validateGenerateUrl, (req: Request, res: Response<GetUrl | ErrorResponse>) => {
try {
const url = sessionService.generateAuthenticatedUrl(
req.body.baseUrl,
req.body.path,
req.body.lock,
req.body.authenticate,
);
res.status(200).send({ url: url.toString() });
} catch (error) {
const message = getErrorMessage(error);
res.status(500).send({ message });
}
});
@@ -1,5 +1,6 @@
import type { Request, Response, NextFunction } from 'express';
import { body, validationResult } from 'express-validator';
import { body } from 'express-validator';
import { requestValidationFunction } from '../validation-utils/validationFunction.js';
export const validateGenerateUrl = [
body('baseUrl').isString().trim().notEmpty(),
@@ -7,9 +8,5 @@ export const validateGenerateUrl = [
body('lock').isBoolean(),
body('authenticate').isBoolean(),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
next();
},
requestValidationFunction,
];
@@ -4,7 +4,6 @@ import { getErrorMessage, obfuscate } from 'ontime-utils';
import type { Request, Response } from 'express';
import { isDocker } from '../../setup/environment.js';
import { failEmptyObjects } from '../../utils/routerUtils.js';
import { getDataProvider } from '../../classes/data-provider/DataProvider.js';
import * as appState from '../../services/app-state-service/AppStateService.js';
@@ -25,9 +24,6 @@ export async function getSettings(_req: Request, res: Response<Settings>) {
}
export async function postSettings(req: Request, res: Response<Settings | ErrorResponse>) {
if (failEmptyObjects(req.body, res)) {
return;
}
try {
const settings = getDataProvider().getSettings();
const editorKey = extractPin(req.body?.editorKey, settings.editorKey);
@@ -35,13 +31,15 @@ export async function postSettings(req: Request, res: Response<Settings | ErrorR
const serverPort = Number(req.body?.serverPort);
//TODO: should this not be part of the validator?
if (isNaN(serverPort)) {
return res.status(400).send({ message: `Invalid value found for server port: ${req.body?.serverPort}` });
res.status(400).send({ message: `Invalid value found for server port: ${req.body?.serverPort}` });
return;
}
const hasChangedPort = settings.serverPort !== serverPort;
if (isDocker && hasChangedPort) {
return res.status(403).json({ message: 'Can`t change port when running inside docker' });
res.status(403).json({ message: 'Can`t change port when running inside docker' });
return;
}
let timeFormat = settings.timeFormat;
@@ -1,31 +1,21 @@
import { body, validationResult } from 'express-validator';
import type { Request, Response, NextFunction } from 'express';
import { body } from 'express-validator';
import { requestValidationFunction } from '../validation-utils/validationFunction.js';
/**
* @description Validates object for POST /ontime/settings/welcomedialog
*/
export const validateWelcomeDialog = [
body('show').isBoolean(),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
next();
},
];
export const validateWelcomeDialog = [body('show').isBoolean(), requestValidationFunction];
/**
* @description Validates object for POST /ontime/settings
*/
export const validateSettings = [
body().notEmpty().withMessage('No object found in request'),
body('editorKey').isString().isLength({ min: 0, max: 4 }).optional({ nullable: true }),
body('operatorKey').isString().isLength({ min: 0, max: 4 }).optional({ nullable: true }),
body('timeFormat').isString().isIn(['12', '24']),
body('language').isString(),
body('serverPort').isPort().optional(),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
next();
},
requestValidationFunction,
];
@@ -2,6 +2,7 @@ import { isImportMap } from 'ontime-utils';
import { body, param, validationResult } from 'express-validator';
import { NextFunction, Request, Response } from 'express';
import { requestValidationFunction } from '../validation-utils/validationFunction.js';
export const validateRequestConnection = [
param('sheetId')
@@ -14,27 +15,23 @@ export const validateRequestConnection = [
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
if (!errors.isEmpty()) {
res.status(422).json({ errors: errors.array() });
return;
}
// check that the file exists
if (!req.file) {
return res.status(422).json({ errors: 'File not found' });
res.status(422).json({ errors: 'File not found' });
return;
}
next();
},
];
export const validateSheetId = [
param('sheetId').isString(),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
next();
},
];
export const validateSheetId = [param('sheetId').isString().trim().notEmpty(), requestValidationFunction];
export const validateSheetOptions = [
param('sheetId').isString(),
param('sheetId').isString().trim().notEmpty(),
body('options')
.isObject()
.custom((content) => {
@@ -42,9 +39,5 @@ export const validateSheetOptions = [
return isValid;
}),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
next();
},
requestValidationFunction,
];
@@ -1,30 +0,0 @@
import type { ErrorResponse, URLPreset } from 'ontime-types';
import { getErrorMessage } from 'ontime-utils';
import type { Request, Response } from 'express';
import { failIsNotArray } from '../../utils/routerUtils.js';
import { getDataProvider } from '../../classes/data-provider/DataProvider.js';
export async function getUrlPresets(_req: Request, res: Response<URLPreset[]>) {
const presets = getDataProvider().getUrlPresets();
res.status(200).send(presets as URLPreset[]);
}
export async function postUrlPresets(req: Request, res: Response<URLPreset[] | ErrorResponse>) {
if (failIsNotArray(req.body, res)) {
return;
}
try {
const newPresets: URLPreset[] = req.body.map((preset: URLPreset) => ({
enabled: preset.enabled,
alias: preset.alias,
pathAndParams: preset.pathAndParams,
}));
await getDataProvider().setUrlPresets(newPresets);
res.status(200).send(newPresets);
} catch (error) {
const message = getErrorMessage(error);
res.status(400).send({ message });
}
}
@@ -1,8 +1,28 @@
import express from 'express';
import { getUrlPresets, postUrlPresets } from './urlPresets.controller.js';
import type { Request, Response } from 'express';
import type { ErrorResponse, URLPreset } from 'ontime-types';
import { getErrorMessage } from 'ontime-utils';
import { validateUrlPresets } from './urlPresets.validation.js';
import { getDataProvider } from '../../classes/data-provider/DataProvider.js';
export const router = express.Router();
router.get('/', getUrlPresets);
router.post('/', validateUrlPresets, postUrlPresets);
router.get('/', (_req: Request, res: Response<URLPreset[]>) => {
const presets = getDataProvider().getUrlPresets();
res.status(200).send(presets as URLPreset[]);
});
router.post('/', validateUrlPresets, async (req: Request, res: Response<URLPreset[] | ErrorResponse>) => {
try {
const newPresets: URLPreset[] = req.body.map((preset: URLPreset) => ({
enabled: preset.enabled,
alias: preset.alias,
pathAndParams: preset.pathAndParams,
}));
await getDataProvider().setUrlPresets(newPresets);
res.status(200).send(newPresets);
} catch (error) {
const message = getErrorMessage(error);
res.status(400).send({ message });
}
});
@@ -1,18 +1,14 @@
import { body, validationResult } from 'express-validator';
import { Request, Response, NextFunction } from 'express';
import { body } from 'express-validator';
import { requestValidationFunction } from '../validation-utils/validationFunction.js';
/**
* validate array of URL preset objects
*/
export const validateUrlPresets = [
body().isArray(),
body().isArray().withMessage('No array found in request'),
body('*.enabled').isBoolean(),
body('*.alias').isString().trim(),
body('*.pathAndParams').isString().trim(),
body('*.alias').isString().trim().notEmpty(),
body('*.pathAndParams').isString().trim().notEmpty(),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
next();
},
requestValidationFunction,
];
@@ -0,0 +1,34 @@
import type { Request, Response, NextFunction } from 'express';
import { param, validationResult } from 'express-validator';
/**
* Runs validation and any error are sent with status 422
*/
export function requestValidationFunction(req: Request, res: Response, next: NextFunction) {
const errors = validationResult(req);
if (!errors.isEmpty()) {
res.status(422).json({ errors: errors.array() });
return;
}
next();
}
/**
* Runs validation and any error are sent with status 422
* Also checks for the presses of a `file` in the body
*/
export function requestValidationFunctionWithFile(req: Request, res: Response, next: NextFunction) {
const errors = validationResult(req);
if (!errors.isEmpty()) {
res.status(422).json({ errors: errors.array() });
return;
}
// check that the file exists
if (!req.file) {
res.status(422).json({ errors: 'File not found' });
return;
}
next();
}
export const paramsWithId = [param('id').isString().trim().notEmpty(), requestValidationFunction];
@@ -1,20 +1,16 @@
import { check, validationResult } from 'express-validator';
import { Request, Response, NextFunction } from 'express';
import { body } from 'express-validator';
import { requestValidationFunction } from '../validation-utils/validationFunction.js';
/**
* @description Validates object for POST /ontime/views
*/
export const validateViewSettings = [
check('dangerColor').isString().trim().withMessage('dangerColor value must be string'),
check('endMessage').isString().trim().withMessage('endMessage value must be string'),
check('freezeEnd').isBoolean().withMessage('freezeEnd value must be boolean'),
check('normalColor').isString().trim().withMessage('normalColor value must be string'),
check('overrideStyles').isBoolean().withMessage('overrideStyles value must be boolean'),
check('warningColor').isString().trim().withMessage('warningColor value must be string'),
body('dangerColor').isString().trim().withMessage('dangerColor value must be string'),
body('endMessage').isString().trim().withMessage('endMessage value must be string'),
body('freezeEnd').isBoolean().withMessage('freezeEnd value must be boolean'),
body('normalColor').isString().trim().withMessage('normalColor value must be string'),
body('overrideStyles').isBoolean().withMessage('overrideStyles value must be boolean'),
body('warningColor').isString().trim().withMessage('warningColor value must be string'),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
next();
},
requestValidationFunction,
];
@@ -5,7 +5,7 @@
*
*/
import { LogOrigin } from 'ontime-types';
import { ErrorResponse, LogOrigin } from 'ontime-types';
import express, { type Request, type Response } from 'express';
@@ -27,10 +27,11 @@ integrationRouter.get('/', (_req: Request, res: Response<{ message: string }>) =
/**
* All calls are sent to the dispatcher
*/
integrationRouter.get('/*', (req: Request, res: Response) => {
integrationRouter.get('/*splat', (req: Request, res: Response<ErrorResponse | { payload: unknown }>) => {
let action = req.path.substring(1);
if (!action) {
return res.status(400).json({ error: 'No action found' });
res.status(400).json({ message: 'No action found' });
return;
}
try {
+2 -2
View File
@@ -76,7 +76,7 @@ app.disable('x-powered-by');
// Implement middleware
app.use(cors()); // setup cors for all routes
app.options('*', cors()); // enable pre-flight cors
app.options('*splat', cors()); // enable pre-flight cors
app.use(bodyParser);
app.use(cookieParser());
@@ -97,7 +97,7 @@ app.use(`${prefix}/user`, express.static(publicDir.userDir));
// Base route for static files
app.use(`${prefix}`, authenticateAndRedirect, compressedStatic);
app.use(`${prefix}/*`, authenticateAndRedirect, compressedStatic);
app.use(`${prefix}/*splat`, authenticateAndRedirect, compressedStatic);
// Implement catch all
app.use((_error, response) => {
-39
View File
@@ -1,39 +0,0 @@
import type { Response } from 'express';
import { isEmptyObject } from './parserUtils.js';
/**
* @description initial checks for an empty of malformed request object
* @param obj
* @param res
*/
export const failEmptyObjects = (obj: object, res: Response): boolean => {
try {
if (isEmptyObject(obj)) {
res.status(400).send('No object found in request');
return true;
}
} catch (error) {
res.status(400).send(error);
return true;
}
return false;
};
/**
* @description initial checks for an empty of malformed request object
* @param obj
* @param res
*/
export const failIsNotArray = (obj: object, res: Response): boolean => {
try {
if (!Array.isArray(obj)) {
res.status(400).send('No array found in request');
return true;
}
} catch (error) {
res.status(400).send(error);
return true;
}
return false;
};
+13 -12
View File
@@ -1,9 +1,10 @@
import { test, expect } from '@playwright/test';
import { randomUUID } from 'crypto';
import { readFile } from 'fs/promises';
import { readFile, unlink } from 'fs/promises';
const fileToUpload = 'e2e/tests/fixtures/e2e-test-db.json';
const fileToDownload = 'e2e/tests/fixtures/tmp/e2e-test-db.json';
const fileToDownload = 'e2e/tests/fixtures/tmp/';
test('project file upload', async ({ page }) => {
await page.goto('http://localhost:4001/editor');
@@ -35,30 +36,30 @@ test('project file upload', async ({ page }) => {
await expect(thirdTitle).toHaveValue('Lithuania');
});
//TODO: this works when testing locally, but not in github actions
test.fixme('project file download', async ({ page }) => {
await page.goto('http://localhost:4001/editor');
await page.getByRole('button', { name: 'toggle settings' }).click();
await page.getByRole('button', { name: 'Manage projects' }).click();
// workaround to download
// https://playwright.dev/docs/api/class-download
const downloadPromise = page.waitForEvent('download');
await page.goto('http://localhost:4001/editor/?settings=project__manage');
await page
.getByRole('row', { name: /.*currently loaded/i })
.getByLabel('Options')
.click();
// workaround to download
// https://playwright.dev/docs/api/class-download
const downloadPromise = page.waitForEvent('download', { timeout: 10_000 });
await page.getByRole('menuitem', { name: 'Download' }).click();
const download = await downloadPromise;
// Wait for the download process to complete and save the downloaded file somewhere.
await download.saveAs(fileToDownload);
const uniqFileToDownload = fileToDownload + randomUUID() + '.json';
await download.saveAs(uniqFileToDownload);
expect(download.failure()).toMatchObject({});
const original = JSON.parse(await readFile(fileToUpload, { encoding: 'utf-8' }));
const fromServer = JSON.parse(await readFile(fileToDownload, { encoding: 'utf-8' }));
const fromServer = JSON.parse(await readFile(uniqFileToDownload, { encoding: 'utf-8' }));
await unlink(uniqFileToDownload);
// when a file is parsed, the server will write the version number to the project file
original.settings.version = 'not-important';
+22 -3
View File
@@ -21,6 +21,24 @@
"bab4a",
"d3eb1"
],
"flatOrder": [
"32d31",
"21cd2",
"0b371",
"3cd28",
"e457f",
"01e85",
"1c420",
"b7737",
"d3a80",
"8276c",
"2340b",
"cb90b",
"503c4",
"5e965",
"bab4a",
"d3eb1"
],
"entries": {
"32d31": {
"type": "event",
@@ -469,7 +487,8 @@
"publicInfo": "Rehearsal Schedule - Turin 2022",
"backstageUrl": "www.github.com/cpvalente/ontime",
"backstageInfo": "Rehearsal Schedule - Turin 2022\nAll performers to wear full costumes for 1st rehearsal",
"projectLogo": null
"projectLogo": null,
"custom": []
},
"settings": {
"app": "ontime",
@@ -489,12 +508,12 @@
"warningColor": "#FFAB33"
},
"customFields": {
"song": {
"Song": {
"label": "Song",
"type": "string",
"colour": "#339E4E"
},
"artist": {
"Artist": {
"label": "Artist",
"type": "string",
"colour": "#3E75E8"
+202 -236
View File
@@ -289,26 +289,26 @@ importers:
specifier: ^5.0.5
version: 5.0.5(encoding@0.1.13)
cookie:
specifier: ^1.0.2
specifier: 1.0.2
version: 1.0.2
cookie-parser:
specifier: ^1.4.7
specifier: 1.4.7
version: 1.4.7
cors:
specifier: ^2.8.5
specifier: 2.8.5
version: 2.8.5
dotenv:
specifier: ^16.0.1
version: 16.3.1
express:
specifier: ^4.21.1
version: 4.21.2
specifier: 5.1.0
version: 5.1.0
express-static-gzip:
specifier: ^2.2.0
version: 2.2.0
specifier: 3.0.0
version: 3.0.0
express-validator:
specifier: ^7.2.0
version: 7.2.0
specifier: 7.2.1
version: 7.2.1
fast-equals:
specifier: ^5.0.1
version: 5.0.1
@@ -322,8 +322,8 @@ importers:
specifier: ^7.0.1
version: 7.0.1
multer:
specifier: ^1.4.5-lts.1
version: 1.4.5-lts.1
specifier: 2.0.1
version: 2.0.1
ontime-utils:
specifier: workspace:*
version: link:../../packages/utils
@@ -344,17 +344,17 @@ importers:
version: 0.18.5
devDependencies:
'@types/cookie-parser':
specifier: ^1.4.8
version: 1.4.8(@types/express@4.17.17)
specifier: 1.4.9
version: 1.4.9(@types/express@5.0.3)
'@types/cors':
specifier: ^2.8.17
version: 2.8.17
specifier: 2.8.19
version: 2.8.19
'@types/express':
specifier: ^4.17.17
version: 4.17.17
specifier: 5.0.3
version: 5.0.3
'@types/multer':
specifier: ^1.4.11
version: 1.4.11
specifier: 1.4.13
version: 1.4.13
'@types/node':
specifier: 'catalog:'
version: 22.15.26
@@ -2207,13 +2207,13 @@ packages:
'@types/connect@3.4.35':
resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==}
'@types/cookie-parser@1.4.8':
resolution: {integrity: sha512-l37JqFrOJ9yQfRQkljb41l0xVphc7kg5JTjjr+pLRZ0IyZ49V4BQ8vbF4Ut2C2e+WH4al3xD3ZwYwIUfnbT4NQ==}
'@types/cookie-parser@1.4.9':
resolution: {integrity: sha512-tGZiZ2Gtc4m3wIdLkZ8mkj1T6CEHb35+VApbL2T14Dew8HA7c+04dmKqsKRNC+8RJPm16JEK0tFSwdZqubfc4g==}
peerDependencies:
'@types/express': '*'
'@types/cors@2.8.17':
resolution: {integrity: sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==}
'@types/cors@2.8.19':
resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==}
'@types/debug@4.1.12':
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
@@ -2227,11 +2227,11 @@ packages:
'@types/estree@1.0.7':
resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==}
'@types/express-serve-static-core@4.17.33':
resolution: {integrity: sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==}
'@types/express-serve-static-core@5.0.6':
resolution: {integrity: sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==}
'@types/express@4.17.17':
resolution: {integrity: sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==}
'@types/express@5.0.3':
resolution: {integrity: sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==}
'@types/fs-extra@9.0.13':
resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==}
@@ -2251,14 +2251,17 @@ packages:
'@types/lodash@4.14.191':
resolution: {integrity: sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==}
'@types/mime@1.3.5':
resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
'@types/mime@3.0.1':
resolution: {integrity: sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==}
'@types/ms@0.7.34':
resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==}
'@types/multer@1.4.11':
resolution: {integrity: sha512-svK240gr6LVWvv3YGyhLlA+6LRRWA4mnGIU7RcNmgjBYFl6665wcXrRfxGp5tEPVHUNm5FMcmq7too9bxCwX/w==}
'@types/multer@1.4.13':
resolution: {integrity: sha512-bhhdtPw7JqCiEfC9Jimx5LqX9BDIPJEh2q/fQ4bqbBPtyEZYr3cvF22NwG0DmPZNYA0CAf2CnqDB4KIGGpJcaw==}
'@types/node@22.15.26':
resolution: {integrity: sha512-lgISkNrqdQ5DAzjBhnDNGKDuXDNo7/1V4FhNzsKREhWLZTOELQAptuAnJMzHtUl1qyEBBy9lNBKQ9WjyiSloTw==}
@@ -2296,6 +2299,9 @@ packages:
'@types/semver@7.5.5':
resolution: {integrity: sha512-+d+WYC1BxJ6yVOgUgzK8gWvp5qF8ssV5r4nsDcZWKRWcDQLQ619tvWAxJQYGgBrO1MnLJC7a5GtiYsAoQ47dJg==}
'@types/send@0.17.5':
resolution: {integrity: sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==}
'@types/serve-static@1.15.0':
resolution: {integrity: sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==}
@@ -2451,8 +2457,8 @@ packages:
abbrev@1.1.1:
resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
accepts@1.3.8:
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
accepts@2.0.0:
resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
engines: {node: '>= 0.6'}
acorn-globals@7.0.1:
@@ -2571,9 +2577,6 @@ packages:
resolution: {integrity: sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ==}
engines: {node: '>=10'}
array-flatten@1.1.1:
resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==}
array-includes@3.1.6:
resolution: {integrity: sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==}
engines: {node: '>= 0.4'}
@@ -2651,9 +2654,9 @@ packages:
bluebird@3.7.2:
resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==}
body-parser@1.20.3:
resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==}
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
body-parser@2.2.0:
resolution: {integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==}
engines: {node: '>=18'}
boolean@3.2.0:
resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==}
@@ -2741,10 +2744,6 @@ packages:
resolution: {integrity: sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==}
engines: {node: '>=8'}
call-bind-apply-helpers@1.0.1:
resolution: {integrity: sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==}
engines: {node: '>= 0.4'}
call-bind-apply-helpers@1.0.2:
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
engines: {node: '>= 0.4'}
@@ -2881,9 +2880,9 @@ packages:
concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
concat-stream@1.6.2:
resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==}
engines: {'0': node >= 0.8}
concat-stream@2.0.0:
resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==}
engines: {'0': node >= 6.0}
config-file-ts@0.2.4:
resolution: {integrity: sha512-cKSW0BfrSaAUnxpgvpXPLaaW/umg4bqg4k3GO1JqlRfpx+d5W0GDXznCMkWotJQek5Mmz1MJVChQnz3IVaeMZQ==}
@@ -2891,8 +2890,8 @@ packages:
config-file-ts@0.2.8-rc1:
resolution: {integrity: sha512-GtNECbVI82bT4RiDIzBSVuTKoSHufnU7Ce7/42bkWZJZFLjmDF2WBpVsvRkhKCfKBnTBb3qZrBwPpFBU/Myvhg==}
content-disposition@0.5.4:
resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
content-disposition@1.0.0:
resolution: {integrity: sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==}
engines: {node: '>= 0.6'}
content-type@1.0.5:
@@ -2912,9 +2911,9 @@ packages:
cookie-signature@1.0.6:
resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==}
cookie@0.7.1:
resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==}
engines: {node: '>= 0.6'}
cookie-signature@1.2.2:
resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==}
engines: {node: '>=6.6.0'}
cookie@0.7.2:
resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
@@ -2998,14 +2997,6 @@ packages:
resolution: {integrity: sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==}
engines: {node: '>=12'}
debug@2.6.9:
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
debug@4.3.7:
resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==}
engines: {node: '>=6.0'}
@@ -3065,10 +3056,6 @@ packages:
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
engines: {node: '>= 0.8'}
destroy@1.2.0:
resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==}
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
detect-libc@2.0.4:
resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==}
engines: {node: '>=8'}
@@ -3182,10 +3169,6 @@ packages:
emoji-regex@9.2.2:
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
encodeurl@1.0.2:
resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==}
engines: {node: '>= 0.8'}
encodeurl@2.0.0:
resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
engines: {node: '>= 0.8'}
@@ -3225,10 +3208,6 @@ packages:
es-module-lexer@1.7.0:
resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
es-object-atoms@1.0.0:
resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==}
engines: {node: '>= 0.4'}
es-object-atoms@1.1.1:
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
engines: {node: '>= 0.4'}
@@ -3410,16 +3389,16 @@ packages:
exponential-backoff@3.1.2:
resolution: {integrity: sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA==}
express-static-gzip@2.2.0:
resolution: {integrity: sha512-4ZQ0pHX0CAauxmzry2/8XFLM6aZA4NBvg9QezSlsEO1zLnl7vMFa48/WIcjzdfOiEUS4S1npPPKP2NHHYAp6qg==}
express-static-gzip@3.0.0:
resolution: {integrity: sha512-36O10S0asHl3QojOBQQ0ZjXNtElmhgPS6erSUCCZymXkB/CK1mnGqOj4BTJN+FYRDIzVFnzo3wLFCZJvAk6rQQ==}
express-validator@7.2.0:
resolution: {integrity: sha512-I2ByKD8panjtr8Y05l21Wph9xk7kk64UMyvJCl/fFM/3CTJq8isXYPLeKW/aZBCdb/LYNv63PwhY8khw8VWocA==}
express-validator@7.2.1:
resolution: {integrity: sha512-CjNE6aakfpuwGaHQZ3m8ltCG2Qvivd7RHtVMS/6nVxOM7xVGqr4bhflsm4+N5FP5zI7Zxp+Hae+9RE+o8e3ZOQ==}
engines: {node: '>= 8.0.0'}
express@4.21.2:
resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==}
engines: {node: '>= 0.10.0'}
express@5.1.0:
resolution: {integrity: sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==}
engines: {node: '>= 18'}
extend@3.0.2:
resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
@@ -3478,8 +3457,8 @@ packages:
resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
engines: {node: '>=8'}
finalhandler@1.3.1:
resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==}
finalhandler@2.1.0:
resolution: {integrity: sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==}
engines: {node: '>= 0.8'}
find-root@1.1.0:
@@ -3546,9 +3525,9 @@ packages:
framesync@6.1.2:
resolution: {integrity: sha512-jBTqhX6KaQVDyus8muwZbBeGGP0XgujBRbQ7gM7BRdS3CadCZIHiawyzYLnafYcvZIh5j8WE7cxZKFn7dXhu9g==}
fresh@0.5.2:
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
engines: {node: '>= 0.6'}
fresh@2.0.0:
resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==}
engines: {node: '>= 0.8'}
fs-constants@1.0.0:
resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
@@ -3615,10 +3594,6 @@ packages:
get-intrinsic@1.2.2:
resolution: {integrity: sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==}
get-intrinsic@1.2.6:
resolution: {integrity: sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==}
engines: {node: '>= 0.4'}
get-intrinsic@1.2.7:
resolution: {integrity: sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==}
engines: {node: '>= 0.4'}
@@ -3827,10 +3802,6 @@ packages:
engines: {node: ^8.11.2 || >=10}
os: [darwin]
iconv-lite@0.4.24:
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
engines: {node: '>=0.10.0'}
iconv-lite@0.6.3:
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
engines: {node: '>=0.10.0'}
@@ -3959,6 +3930,9 @@ packages:
is-potential-custom-element-name@1.0.1:
resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
is-promise@4.0.0:
resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==}
is-regex@1.1.4:
resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==}
engines: {node: '>= 0.4'}
@@ -4198,17 +4172,18 @@ packages:
resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
engines: {node: '>= 0.6'}
merge-descriptors@1.0.3:
resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==}
media-typer@1.1.0:
resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==}
engines: {node: '>= 0.8'}
merge-descriptors@2.0.0:
resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==}
engines: {node: '>=18'}
merge2@1.4.1:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'}
methods@1.1.2:
resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==}
engines: {node: '>= 0.6'}
micromatch@4.0.5:
resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==}
engines: {node: '>=8.6'}
@@ -4217,14 +4192,17 @@ packages:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'}
mime-db@1.54.0:
resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==}
engines: {node: '>= 0.6'}
mime-types@2.1.35:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
mime@1.6.0:
resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
engines: {node: '>=4'}
hasBin: true
mime-types@3.0.1:
resolution: {integrity: sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==}
engines: {node: '>= 0.6'}
mime@2.6.0:
resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==}
@@ -4322,15 +4300,12 @@ packages:
engines: {node: '>=10'}
hasBin: true
ms@2.0.0:
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
multer@1.4.5-lts.1:
resolution: {integrity: sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==}
engines: {node: '>= 6.0.0'}
multer@2.0.1:
resolution: {integrity: sha512-Ug8bXeTIUlxurg8xLTEskKShvcKDZALo1THEX5E41pYCD2sCVub5/kIRIGqWNoqV6szyLyQKV6mD4QUrWE5GCQ==}
engines: {node: '>= 10.16.0'}
nanoid@3.3.11:
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
@@ -4349,6 +4324,10 @@ packages:
resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
engines: {node: '>= 0.6'}
negotiator@1.0.0:
resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
engines: {node: '>= 0.6'}
no-case@3.0.4:
resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
@@ -4519,8 +4498,9 @@ packages:
resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
engines: {node: '>=16 || 14 >=14.18'}
path-to-regexp@0.1.12:
resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==}
path-to-regexp@8.2.0:
resolution: {integrity: sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==}
engines: {node: '>=16'}
path-type@4.0.0:
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
@@ -4639,8 +4619,8 @@ packages:
resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==}
engines: {node: '>=0.6'}
qs@6.13.0:
resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==}
qs@6.14.0:
resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==}
engines: {node: '>=0.6'}
querystringify@2.2.0:
@@ -4657,8 +4637,8 @@ packages:
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
engines: {node: '>= 0.6'}
raw-body@2.5.2:
resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==}
raw-body@3.0.0:
resolution: {integrity: sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==}
engines: {node: '>= 0.8'}
react-clientside-effect@1.2.6:
@@ -4867,6 +4847,10 @@ packages:
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
router@2.2.0:
resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==}
engines: {node: '>= 18'}
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
@@ -4919,17 +4903,17 @@ packages:
engines: {node: '>=10'}
hasBin: true
send@0.19.0:
resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==}
engines: {node: '>= 0.8.0'}
send@1.2.0:
resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==}
engines: {node: '>= 18'}
serialize-error@7.0.1:
resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==}
engines: {node: '>=10'}
serve-static@1.16.2:
resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==}
engines: {node: '>= 0.8.0'}
serve-static@2.2.0:
resolution: {integrity: sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==}
engines: {node: '>= 18'}
server-timing@3.3.3:
resolution: {integrity: sha512-TP0xWAca4oM8H/PSdeaGgp2qm+HrZ2cWCRcMXS2t500a7Wum/hSojlpTW43VZsIUSVNlKPFGDknH34IqF+mbBg==}
@@ -5313,6 +5297,10 @@ packages:
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
engines: {node: '>= 0.6'}
type-is@2.0.1:
resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==}
engines: {node: '>= 0.6'}
typed-array-length@1.0.4:
resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==}
@@ -5414,10 +5402,6 @@ packages:
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
utils-merge@1.0.1:
resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
engines: {node: '>= 0.4.0'}
uuid@9.0.1:
resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==}
hasBin: true
@@ -7600,11 +7584,11 @@ snapshots:
dependencies:
'@types/node': 22.15.26
'@types/cookie-parser@1.4.8(@types/express@4.17.17)':
'@types/cookie-parser@1.4.9(@types/express@5.0.3)':
dependencies:
'@types/express': 4.17.17
'@types/express': 5.0.3
'@types/cors@2.8.17':
'@types/cors@2.8.19':
dependencies:
'@types/node': 22.15.26
@@ -7618,17 +7602,17 @@ snapshots:
'@types/estree@1.0.7': {}
'@types/express-serve-static-core@4.17.33':
'@types/express-serve-static-core@5.0.6':
dependencies:
'@types/node': 22.15.26
'@types/qs': 6.9.7
'@types/range-parser': 1.2.4
'@types/send': 0.17.5
'@types/express@4.17.17':
'@types/express@5.0.3':
dependencies:
'@types/body-parser': 1.19.2
'@types/express-serve-static-core': 4.17.33
'@types/qs': 6.9.7
'@types/express-serve-static-core': 5.0.6
'@types/serve-static': 1.15.0
'@types/fs-extra@9.0.13':
@@ -7649,13 +7633,15 @@ snapshots:
'@types/lodash@4.14.191': {}
'@types/mime@1.3.5': {}
'@types/mime@3.0.1': {}
'@types/ms@0.7.34': {}
'@types/multer@1.4.11':
'@types/multer@1.4.13':
dependencies:
'@types/express': 4.17.17
'@types/express': 5.0.3
'@types/node@22.15.26':
dependencies:
@@ -7695,6 +7681,11 @@ snapshots:
'@types/semver@7.5.5': {}
'@types/send@0.17.5':
dependencies:
'@types/mime': 1.3.5
'@types/node': 22.15.26
'@types/serve-static@1.15.0':
dependencies:
'@types/mime': 3.0.1
@@ -7904,10 +7895,10 @@ snapshots:
abbrev@1.1.1: {}
accepts@1.3.8:
accepts@2.0.0:
dependencies:
mime-types: 2.1.35
negotiator: 0.6.3
mime-types: 3.0.1
negotiator: 1.0.0
acorn-globals@7.0.1:
dependencies:
@@ -8104,8 +8095,6 @@ snapshots:
dependencies:
tslib: 2.6.2
array-flatten@1.1.1: {}
array-includes@3.1.6:
dependencies:
call-bind: 1.0.2
@@ -8185,20 +8174,17 @@ snapshots:
bluebird@3.7.2: {}
body-parser@1.20.3:
body-parser@2.2.0:
dependencies:
bytes: 3.1.2
content-type: 1.0.5
debug: 2.6.9
depd: 2.0.0
destroy: 1.2.0
debug: 4.4.1
http-errors: 2.0.0
iconv-lite: 0.4.24
iconv-lite: 0.6.3
on-finished: 2.4.1
qs: 6.13.0
raw-body: 2.5.2
type-is: 1.6.18
unpipe: 1.0.0
qs: 6.14.0
raw-body: 3.0.0
type-is: 2.0.1
transitivePeerDependencies:
- supports-color
@@ -8357,11 +8343,6 @@ snapshots:
normalize-url: 6.1.0
responselike: 2.0.1
call-bind-apply-helpers@1.0.1:
dependencies:
es-errors: 1.3.0
function-bind: 1.1.2
call-bind-apply-helpers@1.0.2:
dependencies:
es-errors: 1.3.0
@@ -8374,7 +8355,7 @@ snapshots:
call-bound@1.0.3:
dependencies:
call-bind-apply-helpers: 1.0.1
call-bind-apply-helpers: 1.0.2
get-intrinsic: 1.2.7
callsites@3.1.0: {}
@@ -8500,11 +8481,11 @@ snapshots:
concat-map@0.0.1: {}
concat-stream@1.6.2:
concat-stream@2.0.0:
dependencies:
buffer-from: 1.1.2
inherits: 2.0.4
readable-stream: 2.3.8
readable-stream: 3.6.2
typedarray: 0.0.6
config-file-ts@0.2.4:
@@ -8517,7 +8498,7 @@ snapshots:
glob: 10.4.5
typescript: 5.5.3
content-disposition@0.5.4:
content-disposition@1.0.0:
dependencies:
safe-buffer: 5.2.1
@@ -8534,7 +8515,7 @@ snapshots:
cookie-signature@1.0.6: {}
cookie@0.7.1: {}
cookie-signature@1.2.2: {}
cookie@0.7.2: {}
@@ -8625,10 +8606,6 @@ snapshots:
whatwg-url: 11.0.0
optional: true
debug@2.6.9:
dependencies:
ms: 2.0.0
debug@4.3.7:
dependencies:
ms: 2.1.3
@@ -8672,8 +8649,6 @@ snapshots:
depd@2.0.0: {}
destroy@1.2.0: {}
detect-libc@2.0.4: {}
detect-node-es@1.1.0: {}
@@ -8838,8 +8813,6 @@ snapshots:
emoji-regex@9.2.2: {}
encodeurl@1.0.2: {}
encodeurl@2.0.0: {}
encoding@0.1.13:
@@ -8903,10 +8876,6 @@ snapshots:
es-module-lexer@1.7.0: {}
es-object-atoms@1.0.0:
dependencies:
es-errors: 1.3.0
es-object-atoms@1.1.1:
dependencies:
es-errors: 1.3.0
@@ -9179,50 +9148,47 @@ snapshots:
exponential-backoff@3.1.2: {}
express-static-gzip@2.2.0:
express-static-gzip@3.0.0:
dependencies:
mime-types: 3.0.1
parseurl: 1.3.3
serve-static: 1.16.2
serve-static: 2.2.0
transitivePeerDependencies:
- supports-color
express-validator@7.2.0:
express-validator@7.2.1:
dependencies:
lodash: 4.17.21
validator: 13.12.0
express@4.21.2:
express@5.1.0:
dependencies:
accepts: 1.3.8
array-flatten: 1.1.1
body-parser: 1.20.3
content-disposition: 0.5.4
accepts: 2.0.0
body-parser: 2.2.0
content-disposition: 1.0.0
content-type: 1.0.5
cookie: 0.7.1
cookie-signature: 1.0.6
debug: 2.6.9
depd: 2.0.0
cookie: 0.7.2
cookie-signature: 1.2.2
debug: 4.4.1
encodeurl: 2.0.0
escape-html: 1.0.3
etag: 1.8.1
finalhandler: 1.3.1
fresh: 0.5.2
finalhandler: 2.1.0
fresh: 2.0.0
http-errors: 2.0.0
merge-descriptors: 1.0.3
methods: 1.1.2
merge-descriptors: 2.0.0
mime-types: 3.0.1
on-finished: 2.4.1
once: 1.4.0
parseurl: 1.3.3
path-to-regexp: 0.1.12
proxy-addr: 2.0.7
qs: 6.13.0
qs: 6.14.0
range-parser: 1.2.1
safe-buffer: 5.2.1
send: 0.19.0
serve-static: 1.16.2
setprototypeof: 1.2.0
router: 2.2.0
send: 1.2.0
serve-static: 2.2.0
statuses: 2.0.1
type-is: 1.6.18
utils-merge: 1.0.1
type-is: 2.0.1
vary: 1.1.2
transitivePeerDependencies:
- supports-color
@@ -9284,15 +9250,14 @@ snapshots:
dependencies:
to-regex-range: 5.0.1
finalhandler@1.3.1:
finalhandler@2.1.0:
dependencies:
debug: 2.6.9
debug: 4.4.1
encodeurl: 2.0.0
escape-html: 1.0.3
on-finished: 2.4.1
parseurl: 1.3.3
statuses: 2.0.1
unpipe: 1.0.0
transitivePeerDependencies:
- supports-color
@@ -9351,7 +9316,7 @@ snapshots:
dependencies:
tslib: 2.4.0
fresh@0.5.2: {}
fresh@2.0.0: {}
fs-constants@1.0.0: {}
@@ -9432,19 +9397,6 @@ snapshots:
has-symbols: 1.0.3
hasown: 2.0.0
get-intrinsic@1.2.6:
dependencies:
call-bind-apply-helpers: 1.0.1
dunder-proto: 1.0.1
es-define-property: 1.0.1
es-errors: 1.3.0
es-object-atoms: 1.0.0
function-bind: 1.1.2
gopd: 1.2.0
has-symbols: 1.1.0
hasown: 2.0.2
math-intrinsics: 1.1.0
get-intrinsic@1.2.7:
dependencies:
call-bind-apply-helpers: 1.0.2
@@ -9738,10 +9690,6 @@ snapshots:
node-addon-api: 1.7.2
optional: true
iconv-lite@0.4.24:
dependencies:
safer-buffer: 2.1.2
iconv-lite@0.6.3:
dependencies:
safer-buffer: 2.1.2
@@ -9851,6 +9799,8 @@ snapshots:
is-potential-custom-element-name@1.0.1:
optional: true
is-promise@4.0.0: {}
is-regex@1.1.4:
dependencies:
call-bind: 1.0.2
@@ -10117,12 +10067,12 @@ snapshots:
media-typer@0.3.0: {}
merge-descriptors@1.0.3: {}
media-typer@1.1.0: {}
merge-descriptors@2.0.0: {}
merge2@1.4.1: {}
methods@1.1.2: {}
micromatch@4.0.5:
dependencies:
braces: 3.0.2
@@ -10130,11 +10080,15 @@ snapshots:
mime-db@1.52.0: {}
mime-db@1.54.0: {}
mime-types@2.1.35:
dependencies:
mime-db: 1.52.0
mime@1.6.0: {}
mime-types@3.0.1:
dependencies:
mime-db: 1.54.0
mime@2.6.0: {}
@@ -10215,15 +10169,13 @@ snapshots:
mkdirp@1.0.4: {}
ms@2.0.0: {}
ms@2.1.3: {}
multer@1.4.5-lts.1:
multer@2.0.1:
dependencies:
append-field: 1.0.0
busboy: 1.6.0
concat-stream: 1.6.2
concat-stream: 2.0.0
mkdirp: 0.5.6
object-assign: 4.1.1
type-is: 1.6.18
@@ -10237,6 +10189,8 @@ snapshots:
negotiator@0.6.3: {}
negotiator@1.0.0: {}
no-case@3.0.4:
dependencies:
lower-case: 2.0.2
@@ -10405,7 +10359,7 @@ snapshots:
lru-cache: 10.4.3
minipass: 7.1.2
path-to-regexp@0.1.12: {}
path-to-regexp@8.2.0: {}
path-type@4.0.0: {}
@@ -10499,7 +10453,7 @@ snapshots:
dependencies:
side-channel: 1.0.4
qs@6.13.0:
qs@6.14.0:
dependencies:
side-channel: 1.1.0
@@ -10512,11 +10466,11 @@ snapshots:
range-parser@1.2.1: {}
raw-body@2.5.2:
raw-body@3.0.0:
dependencies:
bytes: 3.1.2
http-errors: 2.0.0
iconv-lite: 0.4.24
iconv-lite: 0.6.3
unpipe: 1.0.0
react-clientside-effect@1.2.6(react@18.3.1):
@@ -10753,6 +10707,16 @@ snapshots:
'@rollup/rollup-win32-x64-msvc': 4.41.1
fsevents: 2.3.3
router@2.2.0:
dependencies:
debug: 4.4.1
depd: 2.0.0
is-promise: 4.0.0
parseurl: 1.3.3
path-to-regexp: 8.2.0
transitivePeerDependencies:
- supports-color
run-parallel@1.2.0:
dependencies:
queue-microtask: 1.2.3
@@ -10803,17 +10767,15 @@ snapshots:
semver@7.6.2: {}
send@0.19.0:
send@1.2.0:
dependencies:
debug: 2.6.9
depd: 2.0.0
destroy: 1.2.0
encodeurl: 1.0.2
debug: 4.4.1
encodeurl: 2.0.0
escape-html: 1.0.3
etag: 1.8.1
fresh: 0.5.2
fresh: 2.0.0
http-errors: 2.0.0
mime: 1.6.0
mime-types: 3.0.1
ms: 2.1.3
on-finished: 2.4.1
range-parser: 1.2.1
@@ -10826,12 +10788,12 @@ snapshots:
type-fest: 0.13.1
optional: true
serve-static@1.16.2:
serve-static@2.2.0:
dependencies:
encodeurl: 2.0.0
escape-html: 1.0.3
parseurl: 1.3.3
send: 0.19.0
send: 1.2.0
transitivePeerDependencies:
- supports-color
@@ -10868,14 +10830,14 @@ snapshots:
dependencies:
call-bound: 1.0.3
es-errors: 1.3.0
get-intrinsic: 1.2.6
get-intrinsic: 1.2.7
object-inspect: 1.13.3
side-channel-weakmap@1.0.2:
dependencies:
call-bound: 1.0.3
es-errors: 1.3.0
get-intrinsic: 1.2.6
get-intrinsic: 1.2.7
object-inspect: 1.13.3
side-channel-map: 1.0.1
@@ -11218,6 +11180,12 @@ snapshots:
media-typer: 0.3.0
mime-types: 2.1.35
type-is@2.0.1:
dependencies:
content-type: 1.0.5
media-typer: 1.1.0
mime-types: 3.0.1
typed-array-length@1.0.4:
dependencies:
call-bind: 1.0.2
@@ -11311,8 +11279,6 @@ snapshots:
util-deprecate@1.0.2: {}
utils-merge@1.0.1: {}
uuid@9.0.1: {}
validator@13.12.0: {}