diff --git a/apps/backend/.eslintrc.js b/apps/backend/.eslintrc.cjs similarity index 100% rename from apps/backend/.eslintrc.js rename to apps/backend/.eslintrc.cjs diff --git a/apps/backend/package.json b/apps/backend/package.json index a1b420a2..a436e2e6 100644 --- a/apps/backend/package.json +++ b/apps/backend/package.json @@ -4,30 +4,35 @@ "dependencies": { "@aws-sdk/client-cognito-identity-provider": "^3.496.0", "@aws-sdk/client-s3": "^3.496.0", + "archiver": "^7.0.0", "aws-sdk-mock": "^5.1.0", "axios": "^1.6.0", "body-parser": "^1.19.0", "cors": "^2.8.5", + "csv-writer": "^1.6.0", "express": "^4.17.1", "express-async-errors": "^3.1.1", "express-fileupload": "^1.2.0", + "file-type": "^21.0.0", "helmet": "^7.1.0", "join-images": "^1.1.5", "lodash": "^4.17.21", "loglevel": "^1.7.1", "mongodb-memory-server": "^7.4.0", - "mongoose": "^6.0.6", + "mongoose": "^6.13.8", "mongoose-encryption": "^2.1.0", "node-2fa": "^2.0.2", "omit-deep-lodash": "^1.1.5", "pad": "^3.2.0", "pdf2pic": "^3.1.3", + "sharp": "^0.34.3", "supertest": "^6.1.3", "twilio": "^3.71.1" }, "devDependencies": { "@3dp4me/types": "workspace:*", "@smithy/types": "^4.1.0", + "@types/archiver": "^6.0.0", "@types/body-parser": "^1.19.5", "@types/cors": "^2.8.17", "@types/express": "^4.17.21", @@ -45,6 +50,7 @@ "webpack": "^5.89.0", "webpack-cli": "^5.1.4" }, + "exports": "./build/index.js", "jest": { "testEnvironment": "node", "testTimeout": 60000, @@ -54,13 +60,13 @@ ] }, "license": "MIT", - "main": "index.js", "scripts": { - "build": "webpack", + "build": "webpack --config webpack.prod.js", "clean": "rimraf .turbo build dist node_modules", "lint": "eslint --fix src/**/*.ts", "lint:check": "eslint src/**/*.ts", - "start": "rm -rf ./dist && tsc && doppler run -- node ./dist/src/index.js", + "start": "rm -rf ./dist && webpack --config webpack.dev.js && doppler run -- node ./build/bundle.js", "test": "cross-env S3_BUCKET_NAME=test jest --runInBand --forceExit" - } + }, + "type": "module" } diff --git a/apps/backend/scripts/.gitignore b/apps/backend/scripts/.gitignore new file mode 100644 index 00000000..387f11a1 --- /dev/null +++ b/apps/backend/scripts/.gitignore @@ -0,0 +1,4 @@ +apps/backend/scripts/patients/ +apps/backend/scripts/step_exports/ +apps/backend/scripts/exports/ +apps/backend/scripts/patients.csv \ No newline at end of file diff --git a/apps/backend/scripts/dataextraction.ts b/apps/backend/scripts/dataextraction.ts new file mode 100644 index 00000000..5782c3c9 --- /dev/null +++ b/apps/backend/scripts/dataextraction.ts @@ -0,0 +1,708 @@ +/* + * Combined Export Script - Steps 1, 2, and 3 + * STEP 1: Generate CSV of all basic patient info + * STEP 2: Generate CSV of all steps with string-convertible data: String, MultilineString, + * Number, Date, Phone, RadioButton, MultiSelect, Tags + * STEP 3: Export media files (File, Audio, Photo, Signature) from S3 to local filesystem (functions related to S3 are in awsS3Helpers.ts) + * STEP 4: Package everything into a ZIP file + * + * String CSV excludes: Header, Divider, File, Audio, Photo, Signature, Map + */ + +import fs from 'fs'; +import path from 'path'; +import mongoose from 'mongoose'; +import { createObjectCsvWriter } from 'csv-writer'; +import { initDB } from '../src/utils/initDb'; +import { PatientModel } from '../src/models/Patient'; +import { StepModel } from '../src/models/Metadata'; +// import { FieldType } from '@3dp4me/types'; +import { downloadFile, fileExistsInS3, downloadAndSaveFileWithTypeDetection, sanitizeFilename } from '../src/utils/aws/awsS3Helpers'; +import archiver from 'archiver'; +import { fileTypeFromBuffer } from 'file-type'; +// Update the import to include Field +import { Field, FieldType } from '@3dp4me/types'; +// mongoose.set('strictQuery', false); (for deprecation) + +const EXPORT_DIR = path.join(__dirname, 'step_exports'); +const MEDIA_EXPORT_DIR = path.join(__dirname, 'patients'); +const ZIP_OUTPUT_DIR = path.join(__dirname, 'exports'); + +const INCLUDED_TYPES = [ + FieldType.STRING, + FieldType.MULTILINE_STRING, + FieldType.NUMBER, + FieldType.DATE, + FieldType.PHONE, + FieldType.RADIO_BUTTON, + FieldType.MULTI_SELECT, + FieldType.TAGS, + FieldType.MAP, +]; + +const IGNORED_FIELD_TYPES = [ + FieldType.FILE, + FieldType.AUDIO, + FieldType.PHOTO, + FieldType.SIGNATURE, + FieldType.DIVIDER, + FieldType.HEADER, +]; + +const MEDIA_FIELD_TYPES = [ + FieldType.FILE, + FieldType.AUDIO, + FieldType.PHOTO, + // FieldType.SIGNATURE, (not media, stored as an array of points on a canvas in mongo. generate an image of this signature and save it) +]; + +// What to export? +interface ExportOptions { + includeDeleted?: boolean; + includeHidden?: boolean; + zipFilename?: string; +} + +async function createZipArchive(zipFilename: string): Promise { + console.log('\n=== STEP 4: Creating ZIP Archive ==='); + + if (!fs.existsSync(ZIP_OUTPUT_DIR)) { + fs.mkdirSync(ZIP_OUTPUT_DIR, { recursive: true }); + } + + const zipPath = path.join(ZIP_OUTPUT_DIR, zipFilename); + const output = fs.createWriteStream(zipPath); + const archive = archiver('zip', { + zlib: { level: 9 } // Maximum compression + }); + + return new Promise((resolve, reject) => { + output.on('close', () => { + const sizeInMB = (archive.pointer() / 1024 / 1024).toFixed(2); + console.log(`ZIP archive created: ${zipPath}`); + console.log(`Archive size: ${sizeInMB} MB`); + resolve(zipPath); + }); + + // Good practice to catch warnings (ie stat failures and other non-blocking errors) + archive.on('warning', (err) => { + if (err.code === 'ENOENT') { + // Log warning for missing files but don't fail + console.warn('Archive warning - file not found:', err.message); + } else { + // Reject promise for other types of warnings as they indicate real issues + console.error('Archive warning (treating as error):', err); + reject(err); + } + }); + + archive.on('error', (err) => { + console.error('Error creating ZIP archive:', err); + reject(err); + }); + + archive.pipe(output); + + // Add patients.csv if it exists + const patientsCsvPath = path.join(__dirname, 'patients.csv'); + if (fs.existsSync(patientsCsvPath)) { + archive.file(patientsCsvPath, { name: 'patients.csv' }); + console.log('Added patients.csv to archive'); + } + + // Add step CSV files if directory exists + if (fs.existsSync(EXPORT_DIR)) { + archive.directory(EXPORT_DIR, 'step_csvs'); + console.log('Added step CSV files to archive'); + } + + // Add media files if directory exists + if (fs.existsSync(MEDIA_EXPORT_DIR)) { + archive.directory(MEDIA_EXPORT_DIR, 'patients'); + console.log('Added media files to archive'); + } + + archive.finalize(); + }); +} + +// STEP 1: Generate patient CSV (makes patients.csv) +async function generatePatientCSV() { + console.log('\n=== STEP 1: Generating Patient CSV ==='); + + const patients = await PatientModel.find(); + const patientRecords = patients.map(p => { + const obj = p.toObject(); + + return { + ...obj, + dateCreated: obj.dateCreated?.toISOString(), + lastEdited: obj.lastEdited?.toISOString(), + }; + }); + + const csvWriter = createObjectCsvWriter({ + path: path.join(__dirname, 'patients.csv'), + header: [ + { id: 'dateCreated', title: 'Date Created' }, + { id: 'orderId', title: 'Order ID' }, + { id: 'lastEdited', title: 'Last Edited' }, + { id: 'lastEditedBy', title: 'Last Edited By' }, + { id: 'status', title: 'Status' }, + { id: 'phoneNumber', title: 'Phone Number' }, + { id: 'orderYear', title: 'Order Year' }, + { id: 'firstName', title: 'First Name' }, + { id: 'fathersName', title: 'Father\'s Name' }, + { id: 'grandfathersName', title: 'Grandfather\'s Name' }, + { id: 'familyName', title: 'Family Name' }, + ] + }); + + await csvWriter.writeRecords(patientRecords); + console.log(`Generated patients.csv with ${patientRecords.length} records`); +} + +// STEP 2: Generate step CSVs (makes step_csvs/*.csv) +// Helper function to get filtered step definitions (moved to global scope) +async function getSteps(options: ExportOptions): Promise { + const { includeDeleted = false, includeHidden = false } = options; + + // Build query filter based on options + const stepFilter: any = {}; + if (!includeDeleted) { + stepFilter.isDeleted = { $ne: true }; + } + if (!includeHidden) { + stepFilter.isHidden = { $ne: true }; + } + + // Get step definitions based on filter + const stepDefinitions = await StepModel.find(stepFilter).lean(); + console.log(`Found ${stepDefinitions.length} step definitions`); + + return stepDefinitions; +} + +async function generateStepCSVs(options: ExportOptions = {}) { + const { includeDeleted = false, includeHidden = false } = options; // default not include hidden or deleted, can be changed + + console.log('\n=== STEP 2: Generating Step CSVs ==='); + console.log(`Options: includeDeleted=${includeDeleted}, includeHidden=${includeHidden}`); + + if (!fs.existsSync(EXPORT_DIR)) fs.mkdirSync(EXPORT_DIR); + + // Get step definitions using the global getSteps function + const stepDefinitions = await getSteps(options); + + const patients = await PatientModel.find().lean(); + console.log(`Found ${patients.length} patients`); + + for (const stepDef of stepDefinitions) { + const stepKey = stepDef.key; + console.log(`Processing step: ${stepKey}`); + + // Get the mongoose model for this step + let StepDataModel; + try { + StepDataModel = mongoose.model(stepKey); + } catch (error) { + console.log(`No model found for step ${stepKey}, skipping`); + continue; + } + + // Check if this step has field groups + const hasFieldGroups = stepDef.fields.some((field: any) => + field.fieldType === FieldType.FIELD_GROUP && Array.isArray(field.subFields) + ); + + if (hasFieldGroups) { + // Process steps with field groups - create individual CSVs per patient + console.log(`Step ${stepKey} has field groups, creating individual CSVs per patient`); + + for (const patient of patients) { + const stepDoc = await StepDataModel.findOne({ patientId: patient._id }); + if (!stepDoc) continue; + + const patientDir = path.join(EXPORT_DIR, patient.orderId); + if (!fs.existsSync(patientDir)) fs.mkdirSync(patientDir, { recursive: true }); + + const records = []; + + // Get field group data (stored as arrays in the document) + const fieldGroupData = new Map(); + + // Collect all field group arrays + for (const field of stepDef.fields) { + if (field.fieldType === FieldType.FIELD_GROUP && Array.isArray(field.subFields)) { + const groupData = stepDoc[field.key]; + if (Array.isArray(groupData)) { + fieldGroupData.set(field.key, groupData); + } + } + } + + // Find the maximum number of entries across all field groups for this patient + const maxEntries = Math.max(...Array.from(fieldGroupData.values()).map(arr => arr.length), 0); + + // Create a record for each entry in the field groups + for (let i = 0; i < maxEntries; i++) { + const row: Record = { + entryNumber: i + 1, + }; + + // Process each field in the step definition + for (const field of stepDef.fields) { + // Skip hidden or deleted fields if not including them + if (!includeHidden && field.isHidden) continue; + if (!includeDeleted && field.isDeleted) continue; + + if (IGNORED_FIELD_TYPES.includes(field.fieldType)) continue; + + if (field.fieldType === FieldType.FIELD_GROUP && Array.isArray(field.subFields)) { + // Handle field groups (nested fields) + const groupData = fieldGroupData.get(field.key); + if (groupData && groupData[i]) { + for (const subField of field.subFields) { + // Skip hidden or deleted subfields if not including them + if (!includeHidden && subField.isHidden) continue; + if (!includeDeleted && subField.isDeleted) continue; + + if (INCLUDED_TYPES.includes(subField.fieldType)) { + row[subField.key] = formatField(groupData[i][subField.key], subField, 'EN'); + } + } + } + } else if (INCLUDED_TYPES.includes(field.fieldType)) { + // Handle regular fields (non-field-group fields) + row[field.key] = formatField(stepDoc[field.key], field, 'EN'); + } + } + + records.push(row); + } + + if (records.length > 0) { + // Create a mapping of field keys to their display names + const fieldDisplayNames = new Map(); + // Add entry number display name + fieldDisplayNames.set('entryNumber', 'Entry Number'); + + // Map field keys to display names from step definition + for (const field of stepDef.fields) { + if (!includeHidden && field.isHidden) continue; + if (!includeDeleted && field.isDeleted) continue; + if (IGNORED_FIELD_TYPES.includes(field.fieldType)) continue; + + if (field.fieldType === FieldType.FIELD_GROUP && Array.isArray(field.subFields)) { + for (const subField of field.subFields) { + if (!includeHidden && subField.isHidden) continue; + if (!includeDeleted && subField.isDeleted) continue; + if (INCLUDED_TYPES.includes(subField.fieldType)) { + fieldDisplayNames.set(subField.key, subField.displayName?.EN || subField.key); + } + } + } else if (INCLUDED_TYPES.includes(field.fieldType)) { + fieldDisplayNames.set(field.key, field.displayName?.EN || field.key); + } + } + + const csvWriter = createObjectCsvWriter({ + path: path.join(patientDir, `${stepKey}.csv`), + header: Object.keys(records[0]).map(key => ({ + id: key, + title: fieldDisplayNames.get(key) || key + })), + }); + + await csvWriter.writeRecords(records); + console.log(`Wrote ${records.length} records to ${patientDir}/${stepKey}.csv`); + } + } + } else { + // Process steps without field groups - maintain original behavior (one CSV per step) + console.log(`Step ${stepKey} has no field groups, creating single CSV for all patients`); + + const records = []; + + for (const patient of patients) { + const stepDoc = await StepDataModel.findOne({ patientId: patient._id }); + if (!stepDoc) continue; + + const row: Record = { + patientId: patient.orderId, + }; + + // Process each field in the step definition + for (const field of stepDef.fields) { + // Skip hidden or deleted fields if not including them + if (!includeHidden && field.isHidden) continue; + if (!includeDeleted && field.isDeleted) continue; + + if (IGNORED_FIELD_TYPES.includes(field.fieldType)) continue; + + if (INCLUDED_TYPES.includes(field.fieldType)) { + // Handle regular fields (no field groups in this step) + row[field.key] = formatField(stepDoc[field.key], field, 'EN'); + } + } + + records.push(row); + } + + if (records.length > 0) { + // Create a mapping of field keys to their display names + const fieldDisplayNames = new Map(); + // Add patient ID display name + fieldDisplayNames.set('patientId', 'Patient ID'); + + // Map field keys to display names from step definition + for (const field of stepDef.fields) { + if (!includeHidden && field.isHidden) continue; + if (!includeDeleted && field.isDeleted) continue; + if (IGNORED_FIELD_TYPES.includes(field.fieldType)) continue; + + if (INCLUDED_TYPES.includes(field.fieldType)) { + fieldDisplayNames.set(field.key, field.displayName?.EN || field.key); + } + } + + const csvWriter = createObjectCsvWriter({ + path: path.join(EXPORT_DIR, `${stepKey}.csv`), + header: Object.keys(records[0]).map(key => ({ + id: key, + title: fieldDisplayNames.get(key) || key + })), + }); + + await csvWriter.writeRecords(records); + console.log(`Wrote ${records.length} records to ${stepKey}.csv`); + } else { + console.log(`No records found for step ${stepKey}, skip`); + } + } + } +} + +// STEP 3: Export media files (makes patients/*/*.jpg) +async function exportStepMedia(options: ExportOptions = {}) { + const { includeDeleted = false, includeHidden = false } = options; + + console.log('\n=== STEP 3: Exporting Media Files ==='); + console.log(`Options: includeDeleted=${includeDeleted}, includeHidden=${includeHidden}`); + + if (!fs.existsSync(MEDIA_EXPORT_DIR)) fs.mkdirSync(MEDIA_EXPORT_DIR); + + const stepDefinitions = await getSteps(options); + + const patients = await PatientModel.find(); + console.log(`Found ${patients.length} patients`); + + let totalFilesDownloaded = 0; + + for (const stepDef of stepDefinitions) { + const stepKey = stepDef.key; + console.log(`Processing step: ${stepKey}`); + + // Get the mongoose model for this step + let StepDataModel; + try { + StepDataModel = mongoose.model(stepKey); + } catch (error) { + console.log(`No model found for step ${stepKey}, skip`); + continue; + } + + for (const patient of patients) { + const stepDoc = await StepDataModel.findOne({ patientId: patient._id }); + if (!stepDoc) continue; + + const patientDir = path.join(MEDIA_EXPORT_DIR, patient.orderId); + const stepDir = path.join(patientDir, stepKey); + + // Track if we actually downloaded any files for this step + let hasDownloadedFiles = false; + + // Process regular fields + for (const field of stepDef.fields) { + // Check if field should be included based on options + if (!includeHidden && field.isHidden) continue; + if (!includeDeleted && field.isDeleted) continue; + + if (MEDIA_FIELD_TYPES.includes(field.fieldType)) { + const fileData = stepDoc[field.key]; + if (Array.isArray(fileData)) { + // Handle array of files + for (const file of fileData) { + if (file && file.filename) { + const s3Key = `${patient._id}/${stepKey}/${field.key}/${file.filename}`; + const fileExists = await fileExistsInS3(s3Key); + + if (fileExists) { + // Create directory only when we have actual files + if (!hasDownloadedFiles) { + fs.mkdirSync(patientDir, { recursive: true }); + fs.mkdirSync(stepDir, { recursive: true }); + hasDownloadedFiles = true; + } + + const sanitizedFilename = sanitizeFilename(file.filename); + const localPath = path.join(stepDir, sanitizedFilename); + const success = await downloadAndSaveFileWithTypeDetection(s3Key, localPath, file.filename); + + if (success) { + console.log(`Downloaded: ${localPath}`); + totalFilesDownloaded++; + } + } + } + } + } else if (fileData && fileData.filename) { + // Handle single file + const result = await downloadSingleFile( + patient, + stepKey, + field.key, + fileData, + patientDir, + stepDir, + hasDownloadedFiles + ); + + if (result.success) { + totalFilesDownloaded++; + } + hasDownloadedFiles = result.hasDownloadedFiles; + } + } + + } + } + } + + console.log(`Media export completed. Downloaded ${totalFilesDownloaded} files.`); +} + +// Function to format field values based on type +function formatField(value: any, field: Field, lang: 'EN' | 'AR' = 'EN'): any { + if (!value) return ''; + + const fieldType = field.fieldType; + + if (fieldType === FieldType.DATE) return new Date(value).toISOString(); + + if (fieldType === FieldType.MULTI_SELECT || fieldType === FieldType.TAGS || fieldType === FieldType.RADIO_BUTTON) { + // Handle array values (MULTI_SELECT, TAGS) + if (Array.isArray(value)) { + const resolvedValues = value.map(val => resolveFieldOption(field, val, lang)).filter(Boolean); + return resolvedValues.join(', '); + } + // RADIO_BUTTON: single ID + return resolveFieldOption(field, value, lang) || value; + } + + if (fieldType === FieldType.MAP) { + // Format MAP data as "lat,lng" + if (value && typeof value === 'object') { + const lat = value.lat || value.latitude; + const lng = value.lng || value.longitude; + if (lat !== undefined && lng !== undefined) { + return `${lat},${lng}`; + } + } + return value; + } + + return value; +} + +// Helper function to resolve field option IDs to human-readable text +function resolveFieldOption(field: Field, value: any, lang: 'EN' | 'AR' = 'EN'): string | null { + if (!field.options || !Array.isArray(field.options) || !value) { + return null; + } + + // Find the option that matches the value (ID) + const valStr = value.toString(); + const matchingOption = field.options.find(opt => opt._id?.toString() === valStr); + + if (matchingOption?.Question?.[lang]) { + return matchingOption.Question[lang]; + } + + return null; +} + +async function downloadSingleFile( + patient: any, + stepKey: string, + fieldKey: string, + fileData: any, + patientDir: string, + stepDir: string, + hasDownloadedFiles: boolean +): Promise<{ success: boolean; hasDownloadedFiles: boolean }> { + if (!fileData || !fileData.filename) { + return { success: false, hasDownloadedFiles }; + } + + const s3Key = `${patient._id}/${stepKey}/${fieldKey}/${fileData.filename}`; + const fileExists = await fileExistsInS3(s3Key); + + if (!fileExists) { + return { success: false, hasDownloadedFiles }; + } + + // Create directory only when we have actual files + if (!hasDownloadedFiles) { + fs.mkdirSync(patientDir, { recursive: true }); + fs.mkdirSync(stepDir, { recursive: true }); + hasDownloadedFiles = true; + } + + const sanitizedFilename = sanitizeFilename(fileData.filename); + const localPath = path.join(stepDir, sanitizedFilename); + const downloadSuccess = await downloadAndSaveFileWithTypeDetection(s3Key, localPath, fileData.filename); + + if (downloadSuccess) { + console.log(`Downloaded: ${localPath}`); + } + + return { success: downloadSuccess, hasDownloadedFiles }; +} + +// Combined export function - now returns a ReadableStream +export async function runCombinedExport(options: ExportOptions = {}, shouldDisconnect = false) { + const { + includeDeleted = false, + includeHidden = false, + zipFilename = `3dp4me_export_${new Date().toISOString().slice(0, 19).replace(/[:-]/g, '')}.zip` + } = options; + + await initDB(); + console.log('Connected to DB'); + console.log('Export Configuration:', { includeDeleted, includeHidden, zipFilename }); + + try { + // Generate CSV data in memory + await generatePatientCSV(); + await generateStepCSVs({ includeDeleted, includeHidden }); + await exportStepMedia({ includeDeleted, includeHidden }); + + // Create zip stream instead of file + const zipStream = await createZipStream(); + + console.log('\nExport stream created successfully'); + + return zipStream; + } catch (error) { + console.error('Error during export process:', error); + throw error; + } finally { + if (shouldDisconnect) { + await mongoose.disconnect(); + console.log('Disconnected from DB'); + } + } +} + +// New function to create a zip stream instead of a file +async function createZipStream(): Promise { + console.log('\n=== STEP 4: Creating ZIP Stream ==='); + + const archive = archiver('zip', { + zlib: { level: 9 } // Maximum compression + }); + + // Handle archiver events + archive.on('warning', (err) => { + if (err.code === 'ENOENT') { + console.warn('Archive warning - file not found:', err.message); + } else { + console.error('Archive warning (treating as error):', err); + throw err; + } + }); + + archive.on('error', (err) => { + console.error('Error creating ZIP archive:', err); + throw err; + }); + + // Add patients.csv if it exists + const patientsCsvPath = path.join(__dirname, 'patients.csv'); + if (fs.existsSync(patientsCsvPath)) { + archive.file(patientsCsvPath, { name: 'patients.csv' }); + console.log('Added patients.csv to archive'); + } + + // Add step CSV files if directory exists + if (fs.existsSync(EXPORT_DIR)) { + archive.directory(EXPORT_DIR, 'step_csvs'); + console.log('Added step CSV files to archive'); + } + + // Add media files if directory exists + if (fs.existsSync(MEDIA_EXPORT_DIR)) { + archive.directory(MEDIA_EXPORT_DIR, 'patients'); + console.log('Added media files to archive'); + } + + // Finalize the archive + archive.finalize(); + + return archive; +} + +// Main function for command line usage +async function main() { + const includeDeleted = process.argv.includes('--include-deleted'); + const includeHidden = process.argv.includes('--include-hidden'); + + const zipFilenameArg = process.argv.find(arg => arg.startsWith('--zip-filename=')); + const customZipFilename = zipFilenameArg ? zipFilenameArg.split('=')[1] : undefined; + + await runCombinedExport({ + includeDeleted, + includeHidden, + zipFilename: customZipFilename + }, true); +} + + + +// Replace the detectFileTypeFromBuffer function +async function detectFileTypeFromBuffer(buffer: Buffer): Promise { + try { + const result = await fileTypeFromBuffer(buffer); + return result?.ext || null; + } catch (error) { + console.error('Error detecting file type:', error); // defaults to .png + return null; + } +} + +// Function to add the proper extension to a filename, default to .png if no type is detected +function addProperExtension(originalFilename: string, detectedType: string | null): string { + // If file already has an extension, keep it + const hasExtension = path.extname(originalFilename).length > 0; + if (hasExtension) { + return originalFilename; + } + + // If we detected a type, add the extension + if (detectedType) { + return `${originalFilename}.${detectedType}`; + } + + // Default fallback - most files are images + return `${originalFilename}.png`; +} + + +// Run if called directly +if (require.main === module) { + main().catch(err => { + console.error('Error in main function:', err); + process.exit(1); + }); +} \ No newline at end of file diff --git a/apps/backend/scripts/patients.csv b/apps/backend/scripts/patients.csv new file mode 100644 index 00000000..55cf41a4 --- /dev/null +++ b/apps/backend/scripts/patients.csv @@ -0,0 +1,23 @@ +Date Created,Order ID,Last Edited,Last Edited By,Status,Phone Number,Order Year,First Name,Father's Name,Grandfather's Name,Family Name +2024-01-14T23:09:29.937Z,B00001,2024-01-14T23:09:29.937Z,Matthew Walowski,Active,,2024,Matthew,Logan,,Walowski +2024-01-14T23:12:14.650Z,B00002,2024-01-14T23:12:14.650Z,Matthew Walowski,Active,,2024,Matthew2,,, +2024-01-14T23:12:21.090Z,B00003,2024-01-14T23:12:21.090Z,Matthew Walowski,Active,,2024,Matthew3,,,W +2024-01-14T23:12:31.388Z,B00004,2024-01-14T23:12:31.388Z,Matthew Walowski,Active,,2024,Matthew4,,,W +2024-01-14T23:12:37.329Z,B00005,2024-01-14T23:12:37.329Z,Matthew Walowski,Active,,2024,Matthew5,,,W +2024-01-14T23:12:42.255Z,B00006,2025-03-02T15:54:29.612Z,Matthew Walowski,Active,,2024,Matthew5,,,W +2024-01-14T23:12:47.610Z,B00007,2024-02-04T15:08:03.548Z,Matthew Walowski,Active,,2024,Matthew7,,,W +2024-01-14T23:12:52.605Z,B00008,2024-01-14T23:33:10.243Z,Matthew Walowski,Archived,,2024,Matthew8,,,W +2024-01-14T23:12:56.508Z,B00009,2025-03-02T15:54:56.316Z,Matthew Walowski,Active,,2024,Matthew9,,,W +2024-01-14T23:13:02.968Z,B00010,2024-01-14T23:13:02.968Z,Matthew Walowski,Active,,2024,Matthew10,,,W +2024-01-14T23:13:07.209Z,B00011,2024-02-04T15:11:13.513Z,Matthew Walowski,Active,,2024,Matthew11,,,W +2024-01-14T23:13:15.565Z,B00012,2025-03-01T18:57:42.190Z,Matthew Walowski,Archived,,2024,Matthew12,,,W +2024-01-14T23:13:24.724Z,B00013,2024-04-13T14:51:20.257Z,Matthew Walowski,Active,,2024,Matthew13,,,W +2024-01-14T23:13:30.324Z,B00014,2025-03-02T16:14:56.990Z,ISJHH000 None,Active,,2024,I CHANGE IT!,,,W +2024-01-14T23:13:37.680Z,B00015,2024-01-15T02:17:19.069Z,Matthew Walowski,Active,,2024,Matthew15,,,W +2024-01-15T02:24:00.253Z,B00016,2024-01-15T02:24:00.253Z,Matthew Walowski,Active,,2024,Test,test,asdf,asdf +2024-01-28T19:39:50.020Z,B00017,2025-01-04T18:00:04.715Z,Matthew Walowski,Active,,2024,Tester,,,Patient +2024-04-06T14:27:36.175Z,B00018,2024-12-26T16:25:54.213Z,Matthew Walowski,Active,,2024,Tester,,,Patient +2025-02-02T19:33:51.405Z,C00001,2025-03-01T17:09:39.089Z,Matthew Walowski,Archived,,2025,NewOne,,,No +2025-03-02T16:27:11.134Z,C00002,2025-03-02T16:27:11.134Z,ISJHH000 None,Active,,2025,Should Have Gaza,,,f +2025-03-02T16:27:39.496Z,C00003,2025-06-01T14:54:43.032Z,Matthew Walowski,Active,,2025,NO TAGS,rrgda,, +2025-06-01T14:55:05.990Z,C00004,2025-09-14T13:25:03.021Z,Matthew Walowski,Active,,2025,New Patient,,,I TEST diff --git a/apps/backend/scripts/patients/B00006/__root_patient_storage/1740849421919.png b/apps/backend/scripts/patients/B00006/__root_patient_storage/1740849421919.png new file mode 100644 index 00000000..921d615c Binary files /dev/null and b/apps/backend/scripts/patients/B00006/__root_patient_storage/1740849421919.png differ diff --git a/apps/backend/scripts/patients/B00007/__root_patient_storage/bio.jpeg b/apps/backend/scripts/patients/B00007/__root_patient_storage/bio.jpeg new file mode 100644 index 00000000..8a18955d Binary files /dev/null and b/apps/backend/scripts/patients/B00007/__root_patient_storage/bio.jpeg differ diff --git a/apps/backend/scripts/patients/B00009/survey/1738202688348.png b/apps/backend/scripts/patients/B00009/survey/1738202688348.png new file mode 100644 index 00000000..f68fee06 Binary files /dev/null and b/apps/backend/scripts/patients/B00009/survey/1738202688348.png differ diff --git a/apps/backend/scripts/patients/B00011/__root_patient_storage/pin.png b/apps/backend/scripts/patients/B00011/__root_patient_storage/pin.png new file mode 100644 index 00000000..207cb229 --- /dev/null +++ b/apps/backend/scripts/patients/B00011/__root_patient_storage/pin.png @@ -0,0 +1 @@ +{"success":false,"message":"An error occurred: The \"chunk\" argument must be of type string or an instance of Buffer or Uint8Array. Received an instance of ReadableStream"} \ No newline at end of file diff --git a/apps/backend/scripts/patients/B00011/__root_patient_storage/pin__1_.png b/apps/backend/scripts/patients/B00011/__root_patient_storage/pin__1_.png new file mode 100644 index 00000000..cb32dbe1 Binary files /dev/null and b/apps/backend/scripts/patients/B00011/__root_patient_storage/pin__1_.png differ diff --git a/apps/backend/scripts/patients/B00012/__root_patient_storage/bio.jpeg b/apps/backend/scripts/patients/B00012/__root_patient_storage/bio.jpeg new file mode 100644 index 00000000..8a18955d Binary files /dev/null and b/apps/backend/scripts/patients/B00012/__root_patient_storage/bio.jpeg differ diff --git a/apps/backend/scripts/patients/B00012/survey/1738202110630.png b/apps/backend/scripts/patients/B00012/survey/1738202110630.png new file mode 100644 index 00000000..ac9d568e Binary files /dev/null and b/apps/backend/scripts/patients/B00012/survey/1738202110630.png differ diff --git a/apps/backend/scripts/patients/B00012/survey/1738202182483.png b/apps/backend/scripts/patients/B00012/survey/1738202182483.png new file mode 100644 index 00000000..4b7689c2 Binary files /dev/null and b/apps/backend/scripts/patients/B00012/survey/1738202182483.png differ diff --git a/apps/backend/scripts/patients/B00012/survey/1738202664034.png b/apps/backend/scripts/patients/B00012/survey/1738202664034.png new file mode 100644 index 00000000..d8212672 Binary files /dev/null and b/apps/backend/scripts/patients/B00012/survey/1738202664034.png differ diff --git a/apps/backend/scripts/patients/B00014/__root_patient_storage/pin.png b/apps/backend/scripts/patients/B00014/__root_patient_storage/pin.png new file mode 100644 index 00000000..cb32dbe1 Binary files /dev/null and b/apps/backend/scripts/patients/B00014/__root_patient_storage/pin.png differ diff --git a/apps/backend/scripts/patients/B00015/survey/1705274649356.png b/apps/backend/scripts/patients/B00015/survey/1705274649356.png new file mode 100644 index 00000000..6545dc60 Binary files /dev/null and b/apps/backend/scripts/patients/B00015/survey/1705274649356.png differ diff --git a/apps/backend/scripts/patients/B00015/survey/1705274694849.png b/apps/backend/scripts/patients/B00015/survey/1705274694849.png new file mode 100644 index 00000000..ec88ded9 Binary files /dev/null and b/apps/backend/scripts/patients/B00015/survey/1705274694849.png differ diff --git a/apps/backend/scripts/patients/B00015/survey/interview_g05f56.mp3 b/apps/backend/scripts/patients/B00015/survey/interview_g05f56.mp3 new file mode 100644 index 00000000..4bd075b5 Binary files /dev/null and b/apps/backend/scripts/patients/B00015/survey/interview_g05f56.mp3 differ diff --git a/apps/backend/scripts/patients/B00017/__root_patient_storage/0021-60b6dcae9243de00fe6f6efa12e1e8b9.jpg b/apps/backend/scripts/patients/B00017/__root_patient_storage/0021-60b6dcae9243de00fe6f6efa12e1e8b9.jpg new file mode 100644 index 00000000..17090ccc Binary files /dev/null and b/apps/backend/scripts/patients/B00017/__root_patient_storage/0021-60b6dcae9243de00fe6f6efa12e1e8b9.jpg differ diff --git a/apps/backend/scripts/patients/B00017/__root_patient_storage/1706998334982.png b/apps/backend/scripts/patients/B00017/__root_patient_storage/1706998334982.png new file mode 100644 index 00000000..9c058a2d Binary files /dev/null and b/apps/backend/scripts/patients/B00017/__root_patient_storage/1706998334982.png differ diff --git a/apps/backend/scripts/patients/B00017/__root_patient_storage/1706998907579.png b/apps/backend/scripts/patients/B00017/__root_patient_storage/1706998907579.png new file mode 100644 index 00000000..e8fca645 Binary files /dev/null and b/apps/backend/scripts/patients/B00017/__root_patient_storage/1706998907579.png differ diff --git a/apps/backend/scripts/patients/B00017/__root_patient_storage/1706998939667.png b/apps/backend/scripts/patients/B00017/__root_patient_storage/1706998939667.png new file mode 100644 index 00000000..1d40d818 Binary files /dev/null and b/apps/backend/scripts/patients/B00017/__root_patient_storage/1706998939667.png differ diff --git a/apps/backend/scripts/patients/B00017/__root_patient_storage/1706998994622.png b/apps/backend/scripts/patients/B00017/__root_patient_storage/1706998994622.png new file mode 100644 index 00000000..c5f1bccd Binary files /dev/null and b/apps/backend/scripts/patients/B00017/__root_patient_storage/1706998994622.png differ diff --git a/apps/backend/scripts/patients/B00017/__root_patient_storage/1707058529134.png b/apps/backend/scripts/patients/B00017/__root_patient_storage/1707058529134.png new file mode 100644 index 00000000..d46a9bc8 Binary files /dev/null and b/apps/backend/scripts/patients/B00017/__root_patient_storage/1707058529134.png differ diff --git a/apps/backend/scripts/patients/B00017/__root_patient_storage/bio.jpeg b/apps/backend/scripts/patients/B00017/__root_patient_storage/bio.jpeg new file mode 100644 index 00000000..8a18955d Binary files /dev/null and b/apps/backend/scripts/patients/B00017/__root_patient_storage/bio.jpeg differ diff --git a/apps/backend/scripts/patients/B00017/__root_patient_storage/image_from_ios.jpg b/apps/backend/scripts/patients/B00017/__root_patient_storage/image_from_ios.jpg new file mode 100644 index 00000000..3b0d8004 Binary files /dev/null and b/apps/backend/scripts/patients/B00017/__root_patient_storage/image_from_ios.jpg differ diff --git a/apps/backend/scripts/patients/B00017/__root_patient_storage/pin__1_.png b/apps/backend/scripts/patients/B00017/__root_patient_storage/pin__1_.png new file mode 100644 index 00000000..cb32dbe1 Binary files /dev/null and b/apps/backend/scripts/patients/B00017/__root_patient_storage/pin__1_.png differ diff --git a/apps/backend/scripts/patients/B00017/survey/1706970951044.png b/apps/backend/scripts/patients/B00017/survey/1706970951044.png new file mode 100644 index 00000000..313b68b2 Binary files /dev/null and b/apps/backend/scripts/patients/B00017/survey/1706970951044.png differ diff --git a/apps/backend/scripts/patients/B00017/survey/1707058511406.png b/apps/backend/scripts/patients/B00017/survey/1707058511406.png new file mode 100644 index 00000000..8157ebcf Binary files /dev/null and b/apps/backend/scripts/patients/B00017/survey/1707058511406.png differ diff --git a/apps/backend/scripts/patients/B00017/survey/auth0-svgrepo-com.svg b/apps/backend/scripts/patients/B00017/survey/auth0-svgrepo-com.svg new file mode 100644 index 00000000..e02b0da7 --- /dev/null +++ b/apps/backend/scripts/patients/B00017/survey/auth0-svgrepo-com.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/apps/backend/scripts/patients/B00017/survey/bio.jpeg b/apps/backend/scripts/patients/B00017/survey/bio.jpeg new file mode 100644 index 00000000..8a18955d Binary files /dev/null and b/apps/backend/scripts/patients/B00017/survey/bio.jpeg differ diff --git a/apps/backend/scripts/patients/B00017/survey/pin.png b/apps/backend/scripts/patients/B00017/survey/pin.png new file mode 100644 index 00000000..207cb229 --- /dev/null +++ b/apps/backend/scripts/patients/B00017/survey/pin.png @@ -0,0 +1 @@ +{"success":false,"message":"An error occurred: The \"chunk\" argument must be of type string or an instance of Buffer or Uint8Array. Received an instance of ReadableStream"} \ No newline at end of file diff --git a/apps/backend/scripts/patients/B00017/survey/pin__1_.png b/apps/backend/scripts/patients/B00017/survey/pin__1_.png new file mode 100644 index 00000000..cb32dbe1 Binary files /dev/null and b/apps/backend/scripts/patients/B00017/survey/pin__1_.png differ diff --git a/apps/backend/scripts/patients/B00018/__root_patient_storage/1713016902654.png b/apps/backend/scripts/patients/B00018/__root_patient_storage/1713016902654.png new file mode 100644 index 00000000..dcf9cb79 Binary files /dev/null and b/apps/backend/scripts/patients/B00018/__root_patient_storage/1713016902654.png differ diff --git a/apps/backend/scripts/patients/B00018/__root_patient_storage/1713016936597.png b/apps/backend/scripts/patients/B00018/__root_patient_storage/1713016936597.png new file mode 100644 index 00000000..b636f248 Binary files /dev/null and b/apps/backend/scripts/patients/B00018/__root_patient_storage/1713016936597.png differ diff --git a/apps/backend/scripts/patients/C00001/survey/interview_8gaxwfd.mp3 b/apps/backend/scripts/patients/C00001/survey/interview_8gaxwfd.mp3 new file mode 100644 index 00000000..4cc36b16 Binary files /dev/null and b/apps/backend/scripts/patients/C00001/survey/interview_8gaxwfd.mp3 differ diff --git a/apps/backend/scripts/patients/C00003/__root_patient_storage/1748788099852.png b/apps/backend/scripts/patients/C00003/__root_patient_storage/1748788099852.png new file mode 100644 index 00000000..238599f1 Binary files /dev/null and b/apps/backend/scripts/patients/C00003/__root_patient_storage/1748788099852.png differ diff --git a/apps/backend/scripts/patients/C00003/__root_patient_storage/1748788970699.png b/apps/backend/scripts/patients/C00003/__root_patient_storage/1748788970699.png new file mode 100644 index 00000000..33ceda75 Binary files /dev/null and b/apps/backend/scripts/patients/C00003/__root_patient_storage/1748788970699.png differ diff --git a/apps/backend/scripts/patients/C00003/__root_patient_storage/1748789644846.png b/apps/backend/scripts/patients/C00003/__root_patient_storage/1748789644846.png new file mode 100644 index 00000000..c7c61d65 Binary files /dev/null and b/apps/backend/scripts/patients/C00003/__root_patient_storage/1748789644846.png differ diff --git a/apps/backend/scripts/patients/C00003/__root_patient_storage/1748789669577.png b/apps/backend/scripts/patients/C00003/__root_patient_storage/1748789669577.png new file mode 100644 index 00000000..f5b9853e Binary files /dev/null and b/apps/backend/scripts/patients/C00003/__root_patient_storage/1748789669577.png differ diff --git a/apps/backend/scripts/patients/C00003/interview/audio_vps7p2.mp3 b/apps/backend/scripts/patients/C00003/interview/audio_vps7p2.mp3 new file mode 100644 index 00000000..38e72ab6 Binary files /dev/null and b/apps/backend/scripts/patients/C00003/interview/audio_vps7p2.mp3 differ diff --git a/apps/backend/scripts/patients/C00004/__root_patient_storage/1748789714643.png b/apps/backend/scripts/patients/C00004/__root_patient_storage/1748789714643.png new file mode 100644 index 00000000..264c9269 Binary files /dev/null and b/apps/backend/scripts/patients/C00004/__root_patient_storage/1748789714643.png differ diff --git a/apps/backend/scripts/step_exports/B00009/interview.csv b/apps/backend/scripts/step_exports/B00009/interview.csv new file mode 100644 index 00000000..cf818bc8 --- /dev/null +++ b/apps/backend/scripts/step_exports/B00009/interview.csv @@ -0,0 +1,2 @@ +Entry Number,Tester,Where are you?,What is the date?,Describe +1,,,2025-01-30T02:05:10.432Z,asdf diff --git a/apps/backend/scripts/step_exports/B00012/interview.csv b/apps/backend/scripts/step_exports/B00012/interview.csv new file mode 100644 index 00000000..3b58f9d7 --- /dev/null +++ b/apps/backend/scripts/step_exports/B00012/interview.csv @@ -0,0 +1,2 @@ +Entry Number,Tester,Where are you?,What is the date?,Describe +1,,,2024-12-31T06:00:00.000Z, diff --git a/apps/backend/scripts/step_exports/C00001/interview.csv b/apps/backend/scripts/step_exports/C00001/interview.csv new file mode 100644 index 00000000..697b6b6d --- /dev/null +++ b/apps/backend/scripts/step_exports/C00001/interview.csv @@ -0,0 +1,4 @@ +Entry Number,ShortText,LongText,A Numf,DATE,PHONNEY,Tester,Where are you?,What is the date?,Describe +1,sdfda,asdfa,234,2025-01-29T06:00:00.000Z,+96283294857823,,,2025-03-07T06:00:00.000Z,Test 3 +2,This is a new field,oLd feil,34,2025-02-11T06:00:00.000Z,+18476024294,,,2025-02-06T06:00:00.000Z,Test 4 +3,,,,2025-02-08T19:12:50.169Z,,,,, diff --git a/apps/backend/scripts/step_exports/C00003/interview.csv b/apps/backend/scripts/step_exports/C00003/interview.csv new file mode 100644 index 00000000..ee5be117 --- /dev/null +++ b/apps/backend/scripts/step_exports/C00003/interview.csv @@ -0,0 +1,2 @@ +Entry Number,ShortText,LongText,A Numf,DATE,PHONNEY,Tester +1,,,,2025-05-03T13:43:43.544Z,, diff --git a/apps/backend/scripts/step_exports/C00003/psychoSocialSupport.csv b/apps/backend/scripts/step_exports/C00003/psychoSocialSupport.csv new file mode 100644 index 00000000..154699c3 --- /dev/null +++ b/apps/backend/scripts/step_exports/C00003/psychoSocialSupport.csv @@ -0,0 +1,3 @@ +Entry Number,Evaluation done by:,Initial Evaluation Date,Initial Evaluation,Main Goal,Total planned sessions,Sessions Plan,Recommendations,Summary,Final Evaluation,Date,Done By,Visit Summary +1,sdf,2025-03-07T06:00:00.000Z,adfasdfasdfasdfasdfasdfa,sdfasdfede,,,,,,2025-03-08T15:24:33.967Z,,mn +2,sdf,2025-03-07T06:00:00.000Z,adfasdfasdfasdfasdfasdfa,sdfasdfede,,,,,,2025-03-28T05:00:00.000Z, nn,hb diff --git a/apps/backend/scripts/step_exports/C00004/interview.csv b/apps/backend/scripts/step_exports/C00004/interview.csv new file mode 100644 index 00000000..bc10796f --- /dev/null +++ b/apps/backend/scripts/step_exports/C00004/interview.csv @@ -0,0 +1,3 @@ +Entry Number,ShortText,LongText,A Numf,DATE,PHONNEY,Tester +1,TABLE VALUE A,TABLE VALUE B,,2025-09-10T05:00:00.000Z,+9628471234567, +2,TABLE VALUE A,TABLE VALUE B,,2025-09-17T05:00:00.000Z,+962283902490238, diff --git a/apps/backend/scripts/step_exports/__root_patient_storage.csv b/apps/backend/scripts/step_exports/__root_patient_storage.csv new file mode 100644 index 00000000..cea580db --- /dev/null +++ b/apps/backend/scripts/step_exports/__root_patient_storage.csv @@ -0,0 +1,12 @@ +Patient ID,Patient Tags +B00006,Gaza +B00007, +B00009,Jordan +B00011, +B00012, +B00014,"Jordan, Gaza" +B00017, +B00018, +C00002,Gaza +C00003, +C00004,"Jordan, Gaza" diff --git a/apps/backend/scripts/step_exports/anotherStep.csv b/apps/backend/scripts/step_exports/anotherStep.csv new file mode 100644 index 00000000..042bdd8c --- /dev/null +++ b/apps/backend/scripts/step_exports/anotherStep.csv @@ -0,0 +1,3 @@ +Patient ID +B00012 +B00013 diff --git a/apps/backend/scripts/step_exports/survey.csv b/apps/backend/scripts/step_exports/survey.csv new file mode 100644 index 00000000..23aa9530 --- /dev/null +++ b/apps/backend/scripts/step_exports/survey.csv @@ -0,0 +1,9 @@ +Patient ID,How did you hear about us?,Child birth order in family,Is the Child in school?,Cause of Hearing Loss,Other causes of Hearing Loss,Consequences of hearing loss,Other issues parents see from hearing loss,Does the patient have another disability?,"If yes, please list and describe other disabilities.",Number of additional Disabled People in House,Number of Retired Household Members,Number of Military or Police in Household,Overall Number of Working People in Household,Number of Cars in Household ,Number of loans if any,Type of Insurance,Do you receive any social security benefits?,Household Income Range,Notes,Location on Map,Next +B00009,,,,,,,,,,,,,,,,,,,,, +B00012,,,,,,,,,,,,,,,,,,,,"41.9383828,-87.6505608", +B00013,asdfasdfsdf,,,,,,,,,,,,,,,,,,,, +B00015,test,1 2 3 4,Yes,63e9fc13c51ce88a3aea9894,NOPE,63e5cf2dc51ce88a3ae9749f,aa,No,no,28,,,,,,Private,,,,, +B00017,,,,,,,,,,,,,,,,,,,notes here,"41.9161993,-87.6737569", +B00018,sdsdsdfasdf,sf,Yes,,,,,,,,,4,,,,,,,,, +C00001,qefa,,,,,,,,,,,,,,,,,,,, +C00004,,,Yes,Relative marriage,,,,,,,,,,,,,,,,"31.95891068638918,35.936005883789065", diff --git a/apps/backend/app.ts b/apps/backend/src/app.ts similarity index 80% rename from apps/backend/app.ts rename to apps/backend/src/app.ts index d92340e3..cd291d0d 100644 --- a/apps/backend/app.ts +++ b/apps/backend/src/app.ts @@ -1,6 +1,6 @@ import "express-async-errors" import path from "path" -import { router } from "./src/routes" +import { router } from "./routes" import log from "loglevel" import express, { NextFunction } from 'express' @@ -8,15 +8,15 @@ import fileUpload from "express-fileupload" import cors from "cors" import bodyParser from "body-parser" -import { requireAuthentication } from './src/middleware/authentication'; -import { initDB } from './src/utils/initDb'; +import { requireAuthentication } from './middleware/authentication'; +import { initDB } from './utils/initDb'; import { setResponseHeaders, configureHelment, -} from './src/middleware/responses' -import { logRequest } from './src/middleware/logging'; -import { ENV_TEST } from './src/utils/constants'; -import { errorHandler } from "./src/utils/errorHandler" +} from './middleware/responses' +import { logRequest } from './middleware/logging'; +import { ENV_TEST } from './utils/constants'; +import { errorHandler } from "./utils/errorHandler" import { Request, Response } from 'express'; const app = express(); diff --git a/apps/backend/src/index.ts b/apps/backend/src/index.ts index b3d19e11..cff990d7 100644 --- a/apps/backend/src/index.ts +++ b/apps/backend/src/index.ts @@ -1,9 +1,8 @@ - /** * Module dependencies. */ -import app from '../app'; +import app from './app'; import http from 'http'; /** diff --git a/apps/backend/src/models/Metadata.ts b/apps/backend/src/models/Metadata.ts index 68affe3d..3ddc7198 100644 --- a/apps/backend/src/models/Metadata.ts +++ b/apps/backend/src/models/Metadata.ts @@ -143,6 +143,7 @@ const stepSchema = new mongoose.Schema({ }, }, isHidden: { type: Boolean, required: false, default: false }, + isDeleted: { type: Boolean, required: false, default: false }, }) diff --git a/apps/backend/src/routes/api/export.ts b/apps/backend/src/routes/api/export.ts new file mode 100644 index 00000000..dcc84e53 --- /dev/null +++ b/apps/backend/src/routes/api/export.ts @@ -0,0 +1,34 @@ +import express, { Response } from 'express'; +import { AuthenticatedRequest } from '../../middleware/types'; +import { runCombinedExport } from '../../../scripts/dataextraction'; +import errorWrap from '../../utils/errorWrap'; +import { requireAdmin } from '../../middleware/authentication'; +import { queryParamToBool } from '../../utils/request'; + +export const router = express.Router(); + +router.get( + '/download', + requireAdmin as any, + errorWrap(async (req: AuthenticatedRequest, res: Response) => { + // Extract query parameters + const includeDeleted = queryParamToBool(req.query.includeDeleted ?? 'false'); + const includeHidden = queryParamToBool(req.query.includeHidden ?? 'false'); + + // Get the zip stream directly + const zipStream = await runCombinedExport({ + includeDeleted, + includeHidden, + }); + + // Set appropriate headers + const filename = `3dp4me_export_${new Date().toISOString().slice(0, 19).replace(/[:-]/g, '')}.zip`; + res.setHeader('Content-Type', 'application/zip'); + res.setHeader('Content-Disposition', `attachment; filename="${filename}"`); + + // Pipe the stream directly to the response + zipStream.pipe(res); + }), +); + +export default router; \ No newline at end of file diff --git a/apps/backend/src/routes/api/index.ts b/apps/backend/src/routes/api/index.ts index 2737d2bb..326e4af3 100644 --- a/apps/backend/src/routes/api/index.ts +++ b/apps/backend/src/routes/api/index.ts @@ -1,14 +1,23 @@ import express from 'express'; +import patients from './patients'; +import steps from './steps'; +import metadata from './metadata'; +import users from './users'; +import roles from './roles'; +import publicRoutes from './public'; +import exportRoutes from './export'; + export const router = express.Router(); // Put all routes here -router.use('/patients', require('./patients')); -router.use('/stages', require('./steps')); -router.use('/metadata', require('./metadata')); -router.use('/users', require('./users')); -router.use('/roles', require('./roles')); -router.use('/public', require('./public')); +router.use('/patients', patients); +router.use('/stages', steps); +router.use('/metadata', metadata); +router.use('/users', users); +router.use('/roles', roles); +router.use('/public', publicRoutes); +router.use('/export', exportRoutes); // for export button // Disable the Twilio stuff for now // router.use('/messages', require('./messages')); diff --git a/apps/backend/src/routes/api/messages.js b/apps/backend/src/routes/api/messages.js index 4e9455a3..b496732c 100644 --- a/apps/backend/src/routes/api/messages.js +++ b/apps/backend/src/routes/api/messages.js @@ -1,5 +1,5 @@ -const express = require('express'); -const { MessagingResponse } = require('twilio').twiml; +import express from 'express'; +import { MessagingResponse } from 'twilio'; const router = express.Router(); const accountSid = process.env.ACCOUNT_SID; diff --git a/apps/backend/src/routes/api/metadata.ts b/apps/backend/src/routes/api/metadata.ts index 4bbc797d..b9d83016 100644 --- a/apps/backend/src/routes/api/metadata.ts +++ b/apps/backend/src/routes/api/metadata.ts @@ -1,8 +1,7 @@ -import { Router, Request, Response } from 'express'; - -import mongoose = require('mongoose'); -import log = require('loglevel'); +import { Router, Response } from 'express'; +import mongoose from 'mongoose'; +import log from 'loglevel'; import { requireAdmin } from '../../middleware/authentication'; import { sendResponse } from '../../utils/response'; import { @@ -112,4 +111,4 @@ router.delete( }), ); -module.exports = router; +export default router; \ No newline at end of file diff --git a/apps/backend/src/routes/api/patients.ts b/apps/backend/src/routes/api/patients.ts index d4ae5984..cdac81fc 100644 --- a/apps/backend/src/routes/api/patients.ts +++ b/apps/backend/src/routes/api/patients.ts @@ -462,4 +462,4 @@ const updatePatientStepData = async (patientId: string, StepModel: typeof mongoo return patientStepData.save(); }; -module.exports = router; +export default router; \ No newline at end of file diff --git a/apps/backend/src/routes/api/public.ts b/apps/backend/src/routes/api/public.ts index 6793bb2f..267ee99c 100644 --- a/apps/backend/src/routes/api/public.ts +++ b/apps/backend/src/routes/api/public.ts @@ -55,4 +55,4 @@ const fileFromRequest = (req: AuthenticatedRequest): Nullish => { if (!stream) throw new Error(`No read stream for ${objectKey}`); - const webstream = stream.transformToWebStream() - return Readable.fromWeb(webstream as any) + // Handle AWS SDK v3 stream properly + if (stream instanceof Readable) { + return stream; + } + + // Convert AWS SDK stream to Node.js Readable stream using the working method + const webStream = stream.transformToWebStream(); + const reader = webStream.getReader(); + + return new Readable({ + async read() { + try { + const { done, value } = await reader.read(); + if (done) { + this.push(null); // End the stream + } else { + this.push(Buffer.from(value)); + } + } catch (error) { + this.emit('error', error); + } + } + }); }; export const deleteFile = async (filePath: string) => { @@ -117,4 +142,135 @@ function getS3(credentials: typeof S3_CREDENTIALS, region: string) { }); return s3; -} \ No newline at end of file +} + +export const fileExistsInS3 = async (s3Key: string): Promise => { + try { + await downloadFile(s3Key); + return true; + } catch (error) { + return false; + } +}; + + +// Function to download and save a file from Step3 with type detection (uses package to detect type, defaults to .png if no type is detected) + +// Step 1: Download file from S3 to local temporary path using streaming +export const downloadFileToLocal = async ( + s3Key: string, + localPath: string +): Promise => { + const s3Stream = await downloadFile(s3Key); + const writeStream = fs.createWriteStream(localPath); + + return new Promise((resolve, reject) => { + s3Stream.pipe(writeStream) + .on('finish', () => { + console.log(`Downloaded to temporary location: ${localPath}`); + resolve(); + }) + .on('error', (error) => { + reject(error); + }); + }); +}; + +// Step 2: Determine file type from saved file on disk +const detectFileTypeFromFile = async (filePath: string): Promise => { + try { + const fileTypeResult = await fileTypeFromFile(filePath); + return fileTypeResult?.ext || null; + } catch (error) { + console.error('Error detecting file type:', error); + return null; + } +}; + +// Step 3: Rename file with proper extension if needed +const renameFileWithProperExtension = async ( + currentPath: string, + originalFilename: string, + detectedType: string | null +): Promise => { + const properFilename = addProperExtension(originalFilename, detectedType); + const properLocalPath = path.join(path.dirname(currentPath), sanitizeFilename(properFilename)); + + // Only rename if the path is different + if (path.resolve(properLocalPath) !== path.resolve(currentPath)) { + fs.renameSync(currentPath, properLocalPath); + console.log(`Renamed file to: ${properLocalPath}`); + } + + return properLocalPath; +}; + +// Main function that orchestrates the three steps +export const downloadAndSaveFileWithTypeDetection = async ( + s3Key: string, + localPath: string, + originalFilename: string +): Promise => { + try { + // Create directory if it doesn't exist + const dir = path.dirname(localPath); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + + // Step 1: Download to temporary location + const tempPath = `${localPath}.tmp`; + await downloadFileToLocal(s3Key, tempPath); + + // Step 2: Detect file type from saved file + const detectedType = await detectFileTypeFromFile(tempPath); + + // Step 3: Rename with proper extension + const finalPath = await renameFileWithProperExtension(tempPath, originalFilename, detectedType); + + if (detectedType) { + console.log(`Downloaded with detected type '${detectedType}': ${finalPath}`); + } else { + console.log(`No type detected, kept original name: ${finalPath}`); + } + + return true; + } catch (error) { + console.error('Error in downloadAndSaveFileWithTypeDetection:', error); + return false; + } +}; + +export const sanitizeFilename = (filename: string): string => { + const ext = path.extname(filename); + const name = path.basename(filename, ext); + const sanitizedName = name.replace(/[^a-z0-9.-]/gi, '_').toLowerCase(); + return sanitizedName + ext; +}; + +const detectFileTypeFromBuffer = async (buffer: Buffer): Promise => { + try { + const result = await fileTypeFromBuffer(buffer); + return result?.ext || null; + } catch (error) { + console.error('Error detecting file type:', error); // defaults to .png + return null; + } +}; + +// Function to add the proper extension to a filename, default to .png if no type is detected +const addProperExtension = (originalFilename: string, detectedType: string | null): string => { + // If file already has an extension, keep it + const hasExtension = path.extname(originalFilename).length > 0; + if (hasExtension) { + return originalFilename; + } + + // If we detected a type, add the extension + if (detectedType) { + return `${originalFilename}.${detectedType}`; + } + + // Default fallback - most files are images + return `${originalFilename}.png`; +}; \ No newline at end of file diff --git a/apps/backend/src/utils/initDb.ts b/apps/backend/src/utils/initDb.ts index 07a7c82f..8e55add6 100644 --- a/apps/backend/src/utils/initDb.ts +++ b/apps/backend/src/utils/initDb.ts @@ -2,6 +2,7 @@ import { Field, FieldType, PatientTagsField, + PatientTagSyria, ReservedStep, RootStep, RootStepFieldKeys, @@ -16,7 +17,6 @@ import encrypt from 'mongoose-encryption' import { StepModel } from '../models/Metadata' import { fileSchema } from '../schemas/fileSchema' import { signatureSchema } from '../schemas/signatureSchema' -import { PatientTagSyria } from '@3dp4me/types'; /** * Initalizes and connects to the DB. Should be called at app startup. @@ -47,17 +47,17 @@ const clearModels = async () => { // Migrations for root step const initReservedSteps = async () => { - log.info("Initializing the reserved step") + log.info('Initializing the reserved step') const rootStep = await StepModel.findOne({ key: ReservedStep.Root }).lean() if (!rootStep) { - log.info("Creating the reserved step") + log.info('Creating the reserved step') return StepModel.create(RootStep) } // Older version missing the tag field const tagField = rootStep.fields.find((f) => f.key === RootStepFieldKeys.Tags) if (!tagField) { - log.info("Tags is missing from reserved step, adding it") + log.info('Tags is missing from reserved step, adding it') return StepModel.updateOne( { key: ReservedStep.Root }, { $push: { fields: PatientTagsField } } @@ -67,17 +67,17 @@ const initReservedSteps = async () => { // Older version missing the syria option const syriaOption = tagField.options.find((o) => o.Question.EN === PatientTagSyria.Question.EN) if (!syriaOption) { - log.info("Syria is missing from tag options, adding it") + log.info('Syria is missing from tag options, adding it') return StepModel.updateOne( - { + { key: ReservedStep.Root, - "fields.key": RootStepFieldKeys.Tags + 'fields.key': RootStepFieldKeys.Tags, }, - { $push: { "fields.$.options": PatientTagSyria } } + { $push: { 'fields.$.options': PatientTagSyria } } ) } - log.info("Reserved step is up to date") + log.info('Reserved step is up to date') return null } diff --git a/apps/backend/tsconfig.json b/apps/backend/tsconfig.json index d422b029..88d8e945 100644 --- a/apps/backend/tsconfig.json +++ b/apps/backend/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "module": "commonjs", + "module": "ESNext", "esModuleInterop": true, "allowSyntheticDefaultImports": true, "target": "ESNext", diff --git a/apps/backend/webpack.config.js b/apps/backend/webpack.config.js deleted file mode 100644 index 9b089e4a..00000000 --- a/apps/backend/webpack.config.js +++ /dev/null @@ -1,27 +0,0 @@ -module.exports = { - entry: "./src/index.ts", - target: 'node', - module: { - rules: [ - { - test: /\.tsx?$/, - use: 'ts-loader', - exclude: /node_modules/, - }, - { - test: /\.node$/, - use: 'node-loader', - }, - ], - }, - node: { - __dirname: false, - }, - resolve: { - extensions: ['.tsx', '.ts', '.js', '.node'], - }, - output: { - filename: 'bundle.js', - path: __dirname + '/build', - }, -}; diff --git a/apps/backend/webpack.dev.js b/apps/backend/webpack.dev.js new file mode 100644 index 00000000..954664c4 --- /dev/null +++ b/apps/backend/webpack.dev.js @@ -0,0 +1,43 @@ +// webpack.config.js +import { ExpirationStatus } from '@aws-sdk/client-s3'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +export default { + entry: "./src/index.ts", + mode: 'development', + target: 'node', + module: { + rules: [ + { + test: /\.tsx?$/, + use: 'ts-loader', + exclude: /node_modules/, + }, + { + test: /\.node$/, + use: 'node-loader', + }, + ], + }, + node: { + __dirname: true, + }, + resolve: { + extensions: ['.tsx', '.ts', '.js', '.node'], + }, + output: { + path: path.resolve(__dirname, 'build'), + filename: 'bundle.js', + module: true, + }, + experiments: { + outputModule: true, + }, + externals: { + sharp: 'module sharp' + } +}; diff --git a/apps/backend/webpack.prod.js b/apps/backend/webpack.prod.js new file mode 100644 index 00000000..5aebd0a8 --- /dev/null +++ b/apps/backend/webpack.prod.js @@ -0,0 +1,43 @@ +// webpack.config.js +import { ExpirationStatus } from '@aws-sdk/client-s3'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +export default { + entry: "./src/index.ts", + mode: 'production', + target: 'node', + module: { + rules: [ + { + test: /\.tsx?$/, + use: 'ts-loader', + exclude: /node_modules/, + }, + { + test: /\.node$/, + use: 'node-loader', + }, + ], + }, + node: { + __dirname: true, + }, + resolve: { + extensions: ['.tsx', '.ts', '.js', '.node'], + }, + output: { + path: path.resolve(__dirname, 'build'), + filename: 'bundle.js', + module: true, + }, + experiments: { + outputModule: true, + }, + externals: { + sharp: 'module sharp' + } +}; diff --git a/apps/frontend/src/api/api.ts b/apps/frontend/src/api/api.ts index a394f431..8e76abec 100644 --- a/apps/frontend/src/api/api.ts +++ b/apps/frontend/src/api/api.ts @@ -321,3 +321,12 @@ export const getSelf = async (): Promise> => { return res.data } + +export const downloadAllPatientData = async (includeDeleted: boolean, includeHidden: boolean) => { + const res = await instance.get('/export/download', { + params: { includeDeleted, includeHidden }, + responseType: 'blob', + }) + + fileDownload(res.data, '3dp4me_export.zip') +} diff --git a/apps/frontend/src/components/ExportButton/ExportButton.tsx b/apps/frontend/src/components/ExportButton/ExportButton.tsx new file mode 100644 index 00000000..38eba5f3 --- /dev/null +++ b/apps/frontend/src/components/ExportButton/ExportButton.tsx @@ -0,0 +1,102 @@ +import { + Button, + Checkbox, + CircularProgress, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + FormControlLabel, +} from '@mui/material' +import React, { useState } from 'react' + +import { downloadAllPatientData } from '../../api/api' +import { useTranslations } from '../../hooks/useTranslations' + +interface ExportButtonProps { + onExportComplete?: () => void + onExportError?: (error: Error) => void +} + +const ExportButton: React.FC = ({ onExportComplete, onExportError }) => { + const translations = useTranslations()[0] + const [open, setOpen] = useState(false) + const [includeDeleted, setIncludeDeleted] = useState(false) + const [includeHidden, setIncludeHidden] = useState(false) + const [loading, setLoading] = useState(false) + + const handleDownload = async () => { + setLoading(true) + try { + await downloadAllPatientData(includeDeleted, includeHidden) + setOpen(false) + onExportComplete?.() + } catch (error) { + console.error('Export failed:', error) + onExportError?.(error as Error) + } finally { + setLoading(false) + } + } + + return ( + <> + + + setOpen(false)} + aria-labelledby="export-dialog-title" + > + + {translations.exportOptionsTitle} + + + setIncludeDeleted(e.target.checked)} + disabled={loading} + /> + } + label={translations.exportIncludeDeleted} + /> + setIncludeHidden(e.target.checked)} + disabled={loading} + /> + } + label={translations.exportIncludeHidden} + /> + + + + + + + + ) +} + +export default ExportButton diff --git a/apps/frontend/src/components/Fields/SignatureField.tsx b/apps/frontend/src/components/Fields/SignatureField.tsx index 9baa2dec..25b2fffe 100644 --- a/apps/frontend/src/components/Fields/SignatureField.tsx +++ b/apps/frontend/src/components/Fields/SignatureField.tsx @@ -42,7 +42,7 @@ const SignatureField = ({ onChange(`${fieldId}.signatureCanvasWidth`, data.width) onChange(`${fieldId}.signatureCanvasHeight`, data.height) - if (!!documentURL) { + if (documentURL) { onChange(`${fieldId}.documentURL.EN`, documentURL.EN) onChange(`${fieldId}.documentURL.AR`, documentURL.AR) } diff --git a/apps/frontend/src/components/Navbar/Navbar.tsx b/apps/frontend/src/components/Navbar/Navbar.tsx index c0030c56..d73b70a0 100644 --- a/apps/frontend/src/components/Navbar/Navbar.tsx +++ b/apps/frontend/src/components/Navbar/Navbar.tsx @@ -11,6 +11,7 @@ import { useTranslations } from '../../hooks/useTranslations' import { Context } from '../../store/Store' import { Routes } from '../../utils/constants' import AccountDropdown from '../AccountDropdown/AccountDropdown' +import ExportButton from '../ExportButton/ExportButton' export interface NavbarProps { username: string @@ -141,6 +142,11 @@ const Navbar = ({ username, userEmail }: NavbarProps) => { {renderLinks()} + alert('Export successful!')} + onExportError={(err) => alert(`Export failed: ${err.message}`)} + /> +
diff --git a/apps/frontend/src/pages/PatientDetail/PatientDetail.tsx b/apps/frontend/src/pages/PatientDetail/PatientDetail.tsx index 8858c890..2400eb66 100644 --- a/apps/frontend/src/pages/PatientDetail/PatientDetail.tsx +++ b/apps/frontend/src/pages/PatientDetail/PatientDetail.tsx @@ -206,22 +206,26 @@ const PatientDetail = () => { onUploadProfilePicture={onUploadProfilePicture} /> - setManagePatientModalOpen(true)} - /> + setManagePatientModalOpen(true)} + /> -
- - {generateStepContent()} -
+
+ + {generateStepContent()} +
) } -export default PatientDetail \ No newline at end of file +export default PatientDetail diff --git a/apps/frontend/src/translations.json b/apps/frontend/src/translations.json index 96492774..18fd4a6b 100644 --- a/apps/frontend/src/translations.json +++ b/apps/frontend/src/translations.json @@ -1,5 +1,12 @@ { "EN": { + "exportPatientData": "Export Patient Data", + "exportOptionsTitle": "Export Options", + "exportIncludeDeleted": "Include Deleted Steps", + "exportIncludeHidden": "Include Hidden Fields", + "exportAsZip": "Export as ZIP", + "cancel": "Cancel", + "exporting": "Exporting...", "patient2FA": { "patientLogin": "Patient Login", "patientID": "Patient ID:", @@ -265,6 +272,13 @@ } }, "AR": { + "exportPatientData": "تصدير بيانات المرضى", + "exportOptionsTitle": "خيارات التصدير", + "exportIncludeDeleted": "تضمين الخطوات المحذوفة", + "exportIncludeHidden": "تضمين الحقول المخفية", + "exportAsZip": "تصدير كملف ZIP", + "cancel": "يلغي", + "exporting": "جارٍ التصدير...", "patient2FA": { "patientLogin": "تسجيل دخول المريض", "patientID": "هوية المريض:", diff --git a/packages/types/src/models/field.ts b/packages/types/src/models/field.ts index f4bc5c8a..018a2539 100644 --- a/packages/types/src/models/field.ts +++ b/packages/types/src/models/field.ts @@ -1,5 +1,4 @@ import { Nullish, Unsaved } from '../utils' - import { File } from './file' import { MapPoint } from './map' import { Signature } from './signature' diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 072e9bbc..4a36476b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,9 @@ importers: '@aws-sdk/client-s3': specifier: ^3.496.0 version: 3.735.0 + archiver: + specifier: ^7.0.0 + version: 7.0.1 aws-sdk-mock: specifier: ^5.1.0 version: 5.9.0 @@ -44,6 +47,9 @@ importers: cors: specifier: ^2.8.5 version: 2.8.5 + csv-writer: + specifier: ^1.6.0 + version: 1.6.0 express: specifier: ^4.17.1 version: 4.21.2 @@ -53,12 +59,15 @@ importers: express-fileupload: specifier: ^1.2.0 version: 1.5.1 + file-type: + specifier: ^21.0.0 + version: 21.0.0 helmet: specifier: ^7.1.0 version: 7.2.0 join-images: specifier: ^1.1.5 - version: 1.1.5(sharp@0.32.6) + version: 1.1.5(sharp@0.34.3) lodash: specifier: ^4.17.21 version: 4.17.21 @@ -69,7 +78,7 @@ importers: specifier: ^7.4.0 version: 7.6.3 mongoose: - specifier: ^6.0.6 + specifier: ^6.13.8 version: 6.13.8 mongoose-encryption: specifier: ^2.1.0 @@ -86,6 +95,9 @@ importers: pdf2pic: specifier: ^3.1.3 version: 3.1.3 + sharp: + specifier: ^0.34.3 + version: 0.34.3 supertest: specifier: ^6.1.3 version: 6.3.4 @@ -99,6 +111,9 @@ importers: '@smithy/types': specifier: ^4.1.0 version: 4.1.0 + '@types/archiver': + specifier: ^6.0.0 + version: 6.0.3 '@types/body-parser': specifier: ^1.19.5 version: 1.19.5 @@ -1784,6 +1799,9 @@ packages: '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + '@borewit/text-codec@0.1.1': + resolution: {integrity: sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==} + '@changesets/apply-release-plan@7.0.8': resolution: {integrity: sha512-qjMUj4DYQ1Z6qHawsn7S71SujrExJ+nceyKKyI9iB+M5p9lCL55afuEd6uLBPRpLGWQwkwvWegDHtwHJb1UjpA==} @@ -1942,9 +1960,13 @@ packages: '@effect/schema@0.69.0': resolution: {integrity: sha512-dqVnriWqM8TT8d+5vgg1pH4ZOXfs7tQBVAY5N7PALzIOlZamsBKQCdwvMMqremjSkKITckF8/cIj1eQZHQeg7Q==} + deprecated: this package has been merged into the main effect package peerDependencies: effect: ^3.5.7 + '@emnapi/runtime@1.5.0': + resolution: {integrity: sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==} + '@emotion/babel-plugin@11.13.5': resolution: {integrity: sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==} @@ -2045,6 +2067,128 @@ packages: resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} deprecated: Use @eslint/object-schema instead + '@img/sharp-darwin-arm64@0.34.3': + resolution: {integrity: sha512-ryFMfvxxpQRsgZJqBd4wsttYQbCxsJksrv9Lw/v798JcQ8+w84mBWuXwl+TT0WJ/WrYOLaYpwQXi3sA9nTIaIg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.3': + resolution: {integrity: sha512-yHpJYynROAj12TA6qil58hmPmAwxKKC7reUqtGLzsOHfP7/rniNGTL8tjWX6L3CTV4+5P4ypcS7Pp+7OB+8ihA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.0': + resolution: {integrity: sha512-sBZmpwmxqwlqG9ueWFXtockhsxefaV6O84BMOrhtg/YqbTaRdqDE7hxraVE3y6gVM4eExmfzW4a8el9ArLeEiQ==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.0': + resolution: {integrity: sha512-M64XVuL94OgiNHa5/m2YvEQI5q2cl9d/wk0qFTDVXcYzi43lxuiFTftMR1tOnFQovVXNZJ5TURSDK2pNe9Yzqg==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.0': + resolution: {integrity: sha512-RXwd0CgG+uPRX5YYrkzKyalt2OJYRiJQ8ED/fi1tq9WQW2jsQIn0tqrlR5l5dr/rjqq6AHAxURhj2DVjyQWSOA==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linux-arm@1.2.0': + resolution: {integrity: sha512-mWd2uWvDtL/nvIzThLq3fr2nnGfyr/XMXlq8ZJ9WMR6PXijHlC3ksp0IpuhK6bougvQrchUAfzRLnbsen0Cqvw==} + cpu: [arm] + os: [linux] + + '@img/sharp-libvips-linux-ppc64@1.2.0': + resolution: {integrity: sha512-Xod/7KaDDHkYu2phxxfeEPXfVXFKx70EAFZ0qyUdOjCcxbjqyJOEUpDe6RIyaunGxT34Anf9ue/wuWOqBW2WcQ==} + cpu: [ppc64] + os: [linux] + + '@img/sharp-libvips-linux-s390x@1.2.0': + resolution: {integrity: sha512-eMKfzDxLGT8mnmPJTNMcjfO33fLiTDsrMlUVcp6b96ETbnJmd4uvZxVJSKPQfS+odwfVaGifhsB07J1LynFehw==} + cpu: [s390x] + os: [linux] + + '@img/sharp-libvips-linux-x64@1.2.0': + resolution: {integrity: sha512-ZW3FPWIc7K1sH9E3nxIGB3y3dZkpJlMnkk7z5tu1nSkBoCgw2nSRTFHI5pB/3CQaJM0pdzMF3paf9ckKMSE9Tg==} + cpu: [x64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.0': + resolution: {integrity: sha512-UG+LqQJbf5VJ8NWJ5Z3tdIe/HXjuIdo4JeVNADXBFuG7z9zjoegpzzGIyV5zQKi4zaJjnAd2+g2nna8TZvuW9Q==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-x64@1.2.0': + resolution: {integrity: sha512-SRYOLR7CXPgNze8akZwjoGBoN1ThNZoqpOgfnOxmWsklTGVfJiGJoC/Lod7aNMGA1jSsKWM1+HRX43OP6p9+6Q==} + cpu: [x64] + os: [linux] + + '@img/sharp-linux-arm64@0.34.3': + resolution: {integrity: sha512-QdrKe3EvQrqwkDrtuTIjI0bu6YEJHTgEeqdzI3uWJOH6G1O8Nl1iEeVYRGdj1h5I21CqxSvQp1Yv7xeU3ZewbA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linux-arm@0.34.3': + resolution: {integrity: sha512-oBK9l+h6KBN0i3dC8rYntLiVfW8D8wH+NPNT3O/WBHeW0OQWCjfWksLUaPidsrDKpJgXp3G3/hkmhptAW0I3+A==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + + '@img/sharp-linux-ppc64@0.34.3': + resolution: {integrity: sha512-GLtbLQMCNC5nxuImPR2+RgrviwKwVql28FWZIW1zWruy6zLgA5/x2ZXk3mxj58X/tszVF69KK0Is83V8YgWhLA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + + '@img/sharp-linux-s390x@0.34.3': + resolution: {integrity: sha512-3gahT+A6c4cdc2edhsLHmIOXMb17ltffJlxR0aC2VPZfwKoTGZec6u5GrFgdR7ciJSsHT27BD3TIuGcuRT0KmQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + + '@img/sharp-linux-x64@0.34.3': + resolution: {integrity: sha512-8kYso8d806ypnSq3/Ly0QEw90V5ZoHh10yH0HnrzOCr6DKAPI6QVHvwleqMkVQ0m+fc7EH8ah0BB0QPuWY6zJQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-linuxmusl-arm64@0.34.3': + resolution: {integrity: sha512-vAjbHDlr4izEiXM1OTggpCcPg9tn4YriK5vAjowJsHwdBIdx0fYRsURkxLG2RLm9gyBq66gwtWI8Gx0/ov+JKQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linuxmusl-x64@0.34.3': + resolution: {integrity: sha512-gCWUn9547K5bwvOn9l5XGAEjVTTRji4aPTqLzGXHvIr6bIDZKNTA34seMPgM0WmSf+RYBH411VavCejp3PkOeQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-wasm32@0.34.3': + resolution: {integrity: sha512-+CyRcpagHMGteySaWos8IbnXcHgfDn7pO2fiC2slJxvNq9gDipYBN42/RagzctVRKgxATmfqOSulgZv5e1RdMg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.3': + resolution: {integrity: sha512-MjnHPnbqMXNC2UgeLJtX4XqoVHHlZNd+nPt1kRPmj63wURegwBhZlApELdtxM2OIZDRv/DFtLcNhVbd1z8GYXQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.3': + resolution: {integrity: sha512-xuCdhH44WxuXgOM714hn4amodJMZl3OEvf0GVTm0BEyMeA2to+8HEdRPShH0SLYptJY1uBw+SCFP9WVQi1Q/cw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.3': + resolution: {integrity: sha512-OWwz05d++TxzLEv4VnsTz5CmZ6mI6S05sfQGEMrNrQcOEERbX46332IvE7pO/EUiw7jUrrS40z/M7kPyjfl04g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -3014,6 +3158,13 @@ packages: peerDependencies: react: ^18 || ^19 + '@tokenizer/inflate@0.2.7': + resolution: {integrity: sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==} + engines: {node: '>=18'} + + '@tokenizer/token@0.3.0': + resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} + '@tootallnate/once@1.1.2': resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==} engines: {node: '>= 6'} @@ -3034,6 +3185,9 @@ packages: '@tsconfig/node16@1.0.4': resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + '@types/archiver@6.0.3': + resolution: {integrity: sha512-a6wUll6k3zX6qs5KlxIggs1P1JcYJaTCx2gnlr+f0S1yd2DoaEwoIK10HmBaLnZwWneBz+JBm0dwcZu0zECBcQ==} + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -3228,6 +3382,9 @@ packages: '@types/react@18.3.18': resolution: {integrity: sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==} + '@types/readdir-glob@1.1.5': + resolution: {integrity: sha512-raiuEPUYqXu+nvtY2Pe8s8FEmZ3x5yAH4VkLdihcPdalvsHltomrRC9BzuStrJ9yk06470hS0Crw0f1pXqD+Hg==} + '@types/resolve@1.17.1': resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==} @@ -3643,10 +3800,18 @@ packages: resolution: {integrity: sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==} engines: {node: '>= 10'} + archiver-utils@5.0.2: + resolution: {integrity: sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==} + engines: {node: '>= 14'} + archiver@5.3.2: resolution: {integrity: sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==} engines: {node: '>= 10'} + archiver@7.0.1: + resolution: {integrity: sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==} + engines: {node: '>= 14'} + arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} @@ -3887,28 +4052,6 @@ packages: bare-events@2.5.4: resolution: {integrity: sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==} - bare-fs@4.0.1: - resolution: {integrity: sha512-ilQs4fm/l9eMfWY2dY0WCIUplSUp7U0CT1vrqMg1MUdeZl4fypu5UP0XcDBK5WBQPJAKP1b7XEodISmekH/CEg==} - engines: {bare: '>=1.7.0'} - - bare-os@3.4.0: - resolution: {integrity: sha512-9Ous7UlnKbe3fMi7Y+qh0DwAup6A1JkYgPnjvMDNOlmnxNRQvQ/7Nst+OnUQKzk0iAT0m9BisbDVp9gCv8+ETA==} - engines: {bare: '>=1.6.0'} - - bare-path@3.0.0: - resolution: {integrity: sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==} - - bare-stream@2.6.4: - resolution: {integrity: sha512-G6i3A74FjNq4nVrrSTUz5h3vgXzBJnjmWAVlBWaZETkgu+LgKd7AiyOml3EDJY1AHlIbBHKDXE+TUT53Ff8OaA==} - peerDependencies: - bare-buffer: '*' - bare-events: '*' - peerDependenciesMeta: - bare-buffer: - optional: true - bare-events: - optional: true - base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -4017,6 +4160,7 @@ packages: bson@6.10.1: resolution: {integrity: sha512-P92xmHDQjSKPLHqFxefqMxASNq/aWJMEZugpCjf+AF/pgcUpMMQCg7t7+ewko0/u8AapvF3luf/FoehddEK+sA==} engines: {node: '>=16.20.1'} + deprecated: a critical bug affecting only useBigInt64=true deserialization usage is fixed in bson@6.10.3 btoa@1.2.1: resolution: {integrity: sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==} @@ -4032,6 +4176,10 @@ packages: buffer-crc32@0.2.13: resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + buffer-crc32@1.0.0: + resolution: {integrity: sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==} + engines: {node: '>=8.0.0'} + buffer-equal-constant-time@1.0.1: resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} @@ -4051,6 +4199,9 @@ packages: buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + buffers@0.1.1: resolution: {integrity: sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==} engines: {node: '>=0.2.0'} @@ -4175,9 +4326,6 @@ packages: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} - chownr@1.1.4: - resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} - chrome-launcher@0.15.2: resolution: {integrity: sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==} engines: {node: '>=12.13.0'} @@ -4325,6 +4473,10 @@ packages: resolution: {integrity: sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==} engines: {node: '>= 10'} + compress-commons@6.0.2: + resolution: {integrity: sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==} + engines: {node: '>= 14'} + compressible@2.0.18: resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} engines: {node: '>= 0.6'} @@ -4436,6 +4588,10 @@ packages: resolution: {integrity: sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==} engines: {node: '>= 10'} + crc32-stream@6.0.0: + resolution: {integrity: sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==} + engines: {node: '>= 14'} + create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} @@ -4603,6 +4759,9 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + csv-writer@1.6.0: + resolution: {integrity: sha512-NOx7YDFWEsM/fTRAJjRpPp8t+MKRVvniAg9wQlUKx20MFrPs73WLJhFf5iteqrxNYnsy924K3Iroh3yNHeYd2g==} + cwebp-bin@7.0.1: resolution: {integrity: sha512-Ko5ADY74/dbfd8xG0+f+MUP9UKjCe1TG4ehpW0E5y4YlPdwDJlGrSzSR4/Yonxpm9QmZE1RratkIxFlKeyo3FA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -4677,10 +4836,6 @@ packages: resolution: {integrity: sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==} engines: {node: '>=4'} - decompress-response@6.0.0: - resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} - engines: {node: '>=10'} - decompress-tar@4.1.1: resolution: {integrity: sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==} engines: {node: '>=4'} @@ -4704,10 +4859,6 @@ packages: dedent@0.7.0: resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} - deep-extend@0.6.0: - resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} - engines: {node: '>=4.0.0'} - deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -4766,8 +4917,8 @@ packages: engines: {node: '>=0.10'} hasBin: true - detect-libc@2.0.3: - resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} + detect-libc@2.0.4: + resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} engines: {node: '>=8'} detect-newline@3.1.0: @@ -5355,10 +5506,6 @@ packages: resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} engines: {node: '>= 0.8.0'} - expand-template@2.0.3: - resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} - engines: {node: '>=6'} - expect@27.5.1: resolution: {integrity: sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} @@ -5463,6 +5610,9 @@ packages: fd-slicer@1.1.0: resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + fflate@0.8.2: + resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -5481,6 +5631,10 @@ packages: resolution: {integrity: sha512-UssQP5ZgIOKelfsaB5CuGAL+Y+q7EmONuiwF3N5HAH0t27rvrttgi6Ra9k/+DVaY9UF6+ybxu5pOXLUdA8N7Vg==} engines: {node: '>=8'} + file-type@21.0.0: + resolution: {integrity: sha512-ek5xNX2YBYlXhiUXui3D/BXa3LdqPmoLJ7rqEx2bKJ7EAUEfmXgW0Das7Dc6Nr9MvqaOnIqiPV0mZk/r/UpNAg==} + engines: {node: '>=20'} + file-type@3.9.0: resolution: {integrity: sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==} engines: {node: '>=0.10.0'} @@ -5626,6 +5780,7 @@ packages: formidable@2.1.2: resolution: {integrity: sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==} + deprecated: 'ACTION REQUIRED: SWITCH TO v3 - v1 and v2 are VULNERABLE! v1 is DEPRECATED FOR OVER 2 YEARS! Use formidable@latest or try formidable-mini for fresh projects' forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} @@ -5759,9 +5914,6 @@ packages: git-hooks-list@1.0.3: resolution: {integrity: sha512-Y7wLWcrLUXwk2noSka166byGCvhMtDRpgHdzCno1UQv/n/Hegp++a2xBWJL1lJarnKD3SWaljD+0z1ztqxuKyQ==} - github-from-package@0.0.0: - resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} - glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -5829,6 +5981,7 @@ packages: gm@1.25.0: resolution: {integrity: sha512-4kKdWXTtgQ4biIo7hZA396HT062nDVVHPjQcurNZ3o/voYN+o5FUC5kOwuORbpExp3XbTJ3SU7iRipiIhQtovw==} engines: {node: '>=14'} + deprecated: The gm module has been sunset. Please migrate to an alternative. https://github.com/aheckmann/gm?tab=readme-ov-file#2025-02-24-this-project-is-not-maintained gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} @@ -5851,6 +6004,7 @@ packages: graphql@14.0.0: resolution: {integrity: sha512-HGVcnO6B25YZcSt6ZsH6/N+XkYuPA7yMqJmlJ4JWxWlS4Tr8SHI56R1Ocs8Eor7V7joEZPRXPDH8RRdll1w44Q==} engines: {node: 6.x || 8.x || >= 10.x} + deprecated: 'No longer supported; please update to a newer version. Details: https://github.com/graphql/graphql-js#version-support' gzip-size@6.0.0: resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==} @@ -7269,10 +7423,6 @@ packages: resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} engines: {node: '>=4'} - mimic-response@3.1.0: - resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} - engines: {node: '>=10'} - mini-css-extract-plugin@2.9.2: resolution: {integrity: sha512-GJuACcS//jtq4kCtd5ii/M0SZf7OZRH+BxdqXZHaJfb8TJiVl+NgQRPwiYt2EuqeSkNydn/7vP+bcE27C5mb9w==} engines: {node: '>= 12.13.0'} @@ -7312,9 +7462,6 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} - mkdirp-classic@0.5.3: - resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} - mkdirp@0.5.6: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true @@ -7412,9 +7559,6 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - napi-build-utils@2.0.0: - resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} - natural-compare-lite@1.4.0: resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} @@ -7451,16 +7595,9 @@ packages: node-2fa@2.0.3: resolution: {integrity: sha512-PQldrOhjuoZyoydMvMSctllPN1ZPZ1/NwkEcgYwY9faVqE/OymxR+3awPpbWZxm6acLKqvmNqQmdqTsqYyflFw==} - node-abi@3.73.0: - resolution: {integrity: sha512-z8iYzQGBu35ZkTQ9mtR8RqugJZ9RCLn8fv3d7LsgDBzOijGQP3RdKTX4LA7LXw03ZhU5z0l4xfhIMgSES31+cg==} - engines: {node: '>=10'} - node-abort-controller@3.1.1: resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} - node-addon-api@6.1.0: - resolution: {integrity: sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==} - node-addon-api@7.1.1: resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} @@ -8399,11 +8536,6 @@ packages: resolution: {integrity: sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==} engines: {node: ^10 || ^12 || >=14} - prebuild-install@7.1.3: - resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} - engines: {node: '>=10'} - hasBin: true - prelude-ls@1.1.2: resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==} engines: {node: '>= 0.8.0'} @@ -8455,6 +8587,10 @@ packages: process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + promise-polyfill@6.1.0: resolution: {integrity: sha512-g0LWaH0gFsxovsU7R5LrrhHhWAWiHRnh1GPrhXnPgYsDkIqjRYUYSZEsej/wtleDrz5xVSIDbeKfidztp2XHFQ==} @@ -8562,10 +8698,6 @@ packages: resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} engines: {node: '>= 0.8'} - rc@1.2.8: - resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} - hasBin: true - react-app-env@1.2.3: resolution: {integrity: sha512-GmBiEvnHjJuSYBeEeSw/Kqp9r+tQDADnVe87z6yruJ5vohhH5x78WwnPnv7Lo/ygUDqddb7S0W+axOE1xvrbzQ==} hasBin: true @@ -8775,6 +8907,10 @@ packages: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} + readable-stream@4.7.0: + resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + readdir-glob@1.1.3: resolution: {integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==} @@ -9141,6 +9277,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.7.2: + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + engines: {node: '>=10'} + hasBin: true + send@0.19.0: resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} engines: {node: '>= 0.8.0'} @@ -9199,9 +9340,9 @@ packages: shallowequal@1.1.0: resolution: {integrity: sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==} - sharp@0.32.6: - resolution: {integrity: sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==} - engines: {node: '>=14.15.0'} + sharp@0.34.3: + resolution: {integrity: sha512-eX2IQ6nFohW4DbvHIOLRB3MHFpYqaqvXd3Tp5e/T/dSH83fxaNJQRvDMhASmkNTsNTVF2/OOopzRCt7xokgPfg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} shebang-command@1.2.0: resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} @@ -9252,12 +9393,6 @@ packages: signature_pad@2.3.2: resolution: {integrity: sha512-peYXLxOsIY6MES2TrRLDiNg2T++8gGbpP2yaC+6Ohtxr+a2dzoaqWosWDY9sWqTAAk6E/TyQO+LJw9zQwyu5kA==} - simple-concat@1.0.1: - resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} - - simple-get@4.0.1: - resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} - simple-swizzle@0.2.2: resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} @@ -9349,6 +9484,7 @@ packages: source-map@0.8.0-beta.0: resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} engines: {node: '>= 8'} + deprecated: The work that was done in this beta branch won't be included in future versions sourcemap-codec@1.4.8: resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} @@ -9528,10 +9664,6 @@ packages: resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} engines: {node: '>=6'} - strip-json-comments@2.0.1: - resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} - engines: {node: '>=0.10.0'} - strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -9543,6 +9675,10 @@ packages: strnum@1.0.5: resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} + strtok3@10.3.4: + resolution: {integrity: sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==} + engines: {node: '>=18'} + style-inject@0.3.0: resolution: {integrity: sha512-IezA2qp+vcdlhJaVm5SOdPPTUu0FCEqfNSli2vRuSIBbu5Nq5UvygTk/VzeCqfLz2Atj3dVII5QBKGZRZ0edzw==} @@ -9643,12 +9779,6 @@ packages: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} - tar-fs@2.1.2: - resolution: {integrity: sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==} - - tar-fs@3.0.8: - resolution: {integrity: sha512-ZoROL70jptorGAlgAYiLoBLItEKw/fUxg9BSYK/dF/GAGYFJOJJJMvjPAKDJraCXFwadD456FCuvLWgfhMsPwg==} - tar-stream@1.6.2: resolution: {integrity: sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==} engines: {node: '>= 0.8.0'} @@ -9781,6 +9911,10 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} + token-types@6.1.1: + resolution: {integrity: sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==} + engines: {node: '>=14.16'} + tough-cookie@4.1.4: resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} engines: {node: '>=6'} @@ -9976,6 +10110,10 @@ packages: engines: {node: '>=14.17'} hasBin: true + uint8array-extras@1.5.0: + resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==} + engines: {node: '>=18'} + ulid@2.3.0: resolution: {integrity: sha512-keqHubrlpvT6G2wH0OEfSW4mquYRcbe/J8NMmveoQOjUqmo+hXtO+ORCpWhdbZ7k72UtY61BL7haGxW6enBnjw==} hasBin: true @@ -10556,6 +10694,10 @@ packages: resolution: {integrity: sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==} engines: {node: '>= 10'} + zip-stream@6.0.1: + resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} + engines: {node: '>= 14'} + snapshots: '@alloc/quick-lru@5.2.0': {} @@ -13161,6 +13303,8 @@ snapshots: '@bcoe/v8-coverage@0.2.3': {} + '@borewit/text-codec@0.1.1': {} + '@changesets/apply-release-plan@7.0.8': dependencies: '@changesets/config': 3.0.5 @@ -13394,6 +13538,11 @@ snapshots: effect: 3.5.7 fast-check: 3.20.0 + '@emnapi/runtime@1.5.0': + dependencies: + tslib: 2.8.1 + optional: true + '@emotion/babel-plugin@11.13.5': dependencies: '@babel/helper-module-imports': 7.25.9 @@ -13544,6 +13693,92 @@ snapshots: '@humanwhocodes/object-schema@2.0.3': {} + '@img/sharp-darwin-arm64@0.34.3': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.0 + optional: true + + '@img/sharp-darwin-x64@0.34.3': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.0 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.0': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.0': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.0': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.0': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.0': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.0': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.0': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.0': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.0': + optional: true + + '@img/sharp-linux-arm64@0.34.3': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.0 + optional: true + + '@img/sharp-linux-arm@0.34.3': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.0 + optional: true + + '@img/sharp-linux-ppc64@0.34.3': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.0 + optional: true + + '@img/sharp-linux-s390x@0.34.3': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.0 + optional: true + + '@img/sharp-linux-x64@0.34.3': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.0 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.3': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.0 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.3': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.0 + optional: true + + '@img/sharp-wasm32@0.34.3': + dependencies: + '@emnapi/runtime': 1.5.0 + optional: true + + '@img/sharp-win32-arm64@0.34.3': + optional: true + + '@img/sharp-win32-ia32@0.34.3': + optional: true + + '@img/sharp-win32-x64@0.34.3': + optional: true + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -14128,7 +14363,7 @@ snapshots: metro-config: 0.81.0 metro-core: 0.81.0 readline: 1.3.0 - semver: 7.6.3 + semver: 7.7.2 transitivePeerDependencies: - '@babel/core' - '@babel/preset-env' @@ -14802,6 +15037,16 @@ snapshots: '@tanstack/query-core': 5.65.0 react: 18.3.1 + '@tokenizer/inflate@0.2.7': + dependencies: + debug: 4.4.0 + fflate: 0.8.2 + token-types: 6.1.1 + transitivePeerDependencies: + - supports-color + + '@tokenizer/token@0.3.0': {} + '@tootallnate/once@1.1.2': {} '@trysound/sax@0.2.0': {} @@ -14814,6 +15059,10 @@ snapshots: '@tsconfig/node16@1.0.4': {} + '@types/archiver@6.0.3': + dependencies: + '@types/readdir-glob': 1.1.5 + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.26.7 @@ -15055,6 +15304,10 @@ snapshots: '@types/prop-types': 15.7.14 csstype: 3.1.3 + '@types/readdir-glob@1.1.5': + dependencies: + '@types/node': 20.17.16 + '@types/resolve@1.17.1': dependencies: '@types/node': 20.17.16 @@ -15386,12 +15639,12 @@ snapshots: '@webpack-cli/configtest@2.1.1(webpack-cli@5.1.4)(webpack@5.97.1)': dependencies: webpack: 5.97.1(webpack-cli@5.1.4) - webpack-cli: 5.1.4(webpack-dev-server@4.15.2)(webpack@5.97.1) + webpack-cli: 5.1.4(webpack@5.97.1) '@webpack-cli/info@2.0.2(webpack-cli@5.1.4)(webpack@5.97.1)': dependencies: webpack: 5.97.1(webpack-cli@5.1.4) - webpack-cli: 5.1.4(webpack-dev-server@4.15.2)(webpack@5.97.1) + webpack-cli: 5.1.4(webpack@5.97.1) '@webpack-cli/serve@2.0.5(webpack-cli@5.1.4)(webpack-dev-server@4.15.2)(webpack@5.97.1)': dependencies: @@ -15560,6 +15813,16 @@ snapshots: normalize-path: 3.0.0 readable-stream: 3.6.2 + archiver-utils@5.0.2: + dependencies: + glob: 10.4.5 + graceful-fs: 4.2.11 + is-stream: 2.0.1 + lazystream: 1.0.1 + lodash: 4.17.21 + normalize-path: 3.0.0 + readable-stream: 4.7.0 + archiver@5.3.2: dependencies: archiver-utils: 2.1.0 @@ -15570,6 +15833,16 @@ snapshots: tar-stream: 2.2.0 zip-stream: 4.1.1 + archiver@7.0.1: + dependencies: + archiver-utils: 5.0.2 + async: 3.2.6 + buffer-crc32: 1.0.0 + readable-stream: 4.7.0 + readdir-glob: 1.1.3 + tar-stream: 3.1.7 + zip-stream: 6.0.1 + arg@4.1.3: {} arg@5.0.2: {} @@ -15941,30 +16214,6 @@ snapshots: bare-events@2.5.4: optional: true - bare-fs@4.0.1: - dependencies: - bare-events: 2.5.4 - bare-path: 3.0.0 - bare-stream: 2.6.4(bare-events@2.5.4) - transitivePeerDependencies: - - bare-buffer - optional: true - - bare-os@3.4.0: - optional: true - - bare-path@3.0.0: - dependencies: - bare-os: 3.4.0 - optional: true - - bare-stream@2.6.4(bare-events@2.5.4): - dependencies: - streamx: 2.21.1 - optionalDependencies: - bare-events: 2.5.4 - optional: true - base64-js@1.5.1: {} batch@0.6.1: {} @@ -16124,6 +16373,8 @@ snapshots: buffer-crc32@0.2.13: {} + buffer-crc32@1.0.0: {} + buffer-equal-constant-time@1.0.1: {} buffer-fill@1.0.0: @@ -16144,6 +16395,11 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + buffers@0.1.1: {} builtin-modules@3.3.0: {} @@ -16273,8 +16529,6 @@ snapshots: dependencies: readdirp: 4.1.1 - chownr@1.1.4: {} - chrome-launcher@0.15.2: dependencies: '@types/node': 20.17.16 @@ -16417,6 +16671,14 @@ snapshots: normalize-path: 3.0.0 readable-stream: 3.6.2 + compress-commons@6.0.2: + dependencies: + crc-32: 1.2.2 + crc32-stream: 6.0.0 + is-stream: 2.0.1 + normalize-path: 3.0.0 + readable-stream: 4.7.0 + compressible@2.0.18: dependencies: mime-db: 1.53.0 @@ -16542,6 +16804,11 @@ snapshots: crc-32: 1.2.2 readable-stream: 3.6.2 + crc32-stream@6.0.0: + dependencies: + crc-32: 1.2.2 + readable-stream: 4.7.0 + create-require@1.1.1: {} cross-env@3.2.4: @@ -16725,6 +16992,8 @@ snapshots: csstype@3.1.3: {} + csv-writer@1.6.0: {} + cwebp-bin@7.0.1: dependencies: bin-build: 3.0.0 @@ -16788,10 +17057,6 @@ snapshots: mimic-response: 1.0.1 optional: true - decompress-response@6.0.0: - dependencies: - mimic-response: 3.1.0 - decompress-tar@4.1.1: dependencies: file-type: 5.2.0 @@ -16837,8 +17102,6 @@ snapshots: dedent@0.7.0: {} - deep-extend@0.6.0: {} - deep-is@0.1.4: {} deepmerge@4.3.1: {} @@ -16882,7 +17145,7 @@ snapshots: detect-libc@1.0.3: optional: true - detect-libc@2.0.3: {} + detect-libc@2.0.4: {} detect-newline@3.1.0: {} @@ -17683,8 +17946,6 @@ snapshots: exit@0.1.2: {} - expand-template@2.0.3: {} - expect@27.5.1: dependencies: '@jest/types': 27.5.1 @@ -17829,6 +18090,8 @@ snapshots: dependencies: pend: 1.2.0 + fflate@0.8.2: {} + file-entry-cache@6.0.1: dependencies: flat-cache: 3.2.0 @@ -17844,6 +18107,15 @@ snapshots: file-type@12.4.2: {} + file-type@21.0.0: + dependencies: + '@tokenizer/inflate': 0.2.7 + strtok3: 10.3.4 + token-types: 6.1.1 + uint8array-extras: 1.5.0 + transitivePeerDependencies: + - supports-color + file-type@3.9.0: optional: true @@ -18159,8 +18431,6 @@ snapshots: git-hooks-list@1.0.3: {} - github-from-package@0.0.0: {} - glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -19495,10 +19765,10 @@ snapshots: jmespath@0.16.0: {} - join-images@1.1.5(sharp@0.32.6): + join-images@1.1.5(sharp@0.34.3): dependencies: is-plain-obj: 3.0.0 - sharp: 0.32.6 + sharp: 0.34.3 tslib: 2.8.1 js-cookie@2.2.1: {} @@ -20162,8 +20432,6 @@ snapshots: mimic-response@1.0.1: optional: true - mimic-response@3.1.0: {} - mini-css-extract-plugin@2.9.2(webpack@5.97.1): dependencies: schema-utils: 4.3.0 @@ -20198,8 +20466,6 @@ snapshots: minipass@7.1.2: {} - mkdirp-classic@0.5.3: {} - mkdirp@0.5.6: dependencies: minimist: 1.2.8 @@ -20333,8 +20599,6 @@ snapshots: nanoid@3.3.8: {} - napi-build-utils@2.0.0: {} - natural-compare-lite@1.4.0: {} natural-compare@1.4.0: {} @@ -20383,14 +20647,8 @@ snapshots: thirty-two: 1.0.2 tslib: 2.8.1 - node-abi@3.73.0: - dependencies: - semver: 7.6.3 - node-abort-controller@3.1.1: {} - node-addon-api@6.1.0: {} - node-addon-api@7.1.1: optional: true @@ -21324,21 +21582,6 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - prebuild-install@7.1.3: - dependencies: - detect-libc: 2.0.3 - expand-template: 2.0.3 - github-from-package: 0.0.0 - minimist: 1.2.8 - mkdirp-classic: 0.5.3 - napi-build-utils: 2.0.0 - node-abi: 3.73.0 - pump: 3.0.2 - rc: 1.2.8 - simple-get: 4.0.1 - tar-fs: 2.1.2 - tunnel-agent: 0.6.0 - prelude-ls@1.1.2: {} prelude-ls@1.2.1: {} @@ -21385,6 +21628,8 @@ snapshots: process-nextick-args@2.0.1: {} + process@0.11.10: {} + promise-polyfill@6.1.0: {} promise.series@0.2.0: {} @@ -21424,6 +21669,7 @@ snapshots: dependencies: end-of-stream: 1.4.4 once: 1.4.0 + optional: true punycode@1.3.2: {} @@ -21484,13 +21730,6 @@ snapshots: iconv-lite: 0.4.24 unpipe: 1.0.0 - rc@1.2.8: - dependencies: - deep-extend: 0.6.0 - ini: 1.3.8 - minimist: 1.2.8 - strip-json-comments: 2.0.1 - react-app-env@1.2.3: dependencies: cross-env: 3.2.4 @@ -21649,7 +21888,7 @@ snapshots: react-refresh: 0.14.2 regenerator-runtime: 0.13.11 scheduler: 0.24.0-canary-efb381bbf-20230505 - semver: 7.6.3 + semver: 7.7.2 stacktrace-parser: 0.1.10 whatwg-fetch: 3.6.20 ws: 6.2.3 @@ -21881,6 +22120,14 @@ snapshots: string_decoder: 1.3.0 util-deprecate: 1.0.2 + readable-stream@4.7.0: + dependencies: + abort-controller: 3.0.0 + buffer: 6.0.3 + events: 3.3.0 + process: 0.11.10 + string_decoder: 1.3.0 + readdir-glob@1.1.3: dependencies: minimatch: 5.1.6 @@ -22266,6 +22513,8 @@ snapshots: semver@7.6.3: {} + semver@7.7.2: {} + send@0.19.0: dependencies: debug: 2.6.9 @@ -22355,18 +22604,34 @@ snapshots: shallowequal@1.1.0: {} - sharp@0.32.6: + sharp@0.34.3: dependencies: color: 4.2.3 - detect-libc: 2.0.3 - node-addon-api: 6.1.0 - prebuild-install: 7.1.3 - semver: 7.6.3 - simple-get: 4.0.1 - tar-fs: 3.0.8 - tunnel-agent: 0.6.0 - transitivePeerDependencies: - - bare-buffer + detect-libc: 2.0.4 + semver: 7.7.2 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.3 + '@img/sharp-darwin-x64': 0.34.3 + '@img/sharp-libvips-darwin-arm64': 1.2.0 + '@img/sharp-libvips-darwin-x64': 1.2.0 + '@img/sharp-libvips-linux-arm': 1.2.0 + '@img/sharp-libvips-linux-arm64': 1.2.0 + '@img/sharp-libvips-linux-ppc64': 1.2.0 + '@img/sharp-libvips-linux-s390x': 1.2.0 + '@img/sharp-libvips-linux-x64': 1.2.0 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.0 + '@img/sharp-libvips-linuxmusl-x64': 1.2.0 + '@img/sharp-linux-arm': 0.34.3 + '@img/sharp-linux-arm64': 0.34.3 + '@img/sharp-linux-ppc64': 0.34.3 + '@img/sharp-linux-s390x': 0.34.3 + '@img/sharp-linux-x64': 0.34.3 + '@img/sharp-linuxmusl-arm64': 0.34.3 + '@img/sharp-linuxmusl-x64': 0.34.3 + '@img/sharp-wasm32': 0.34.3 + '@img/sharp-win32-arm64': 0.34.3 + '@img/sharp-win32-ia32': 0.34.3 + '@img/sharp-win32-x64': 0.34.3 shebang-command@1.2.0: dependencies: @@ -22418,14 +22683,6 @@ snapshots: signature_pad@2.3.2: {} - simple-concat@1.0.1: {} - - simple-get@4.0.1: - dependencies: - decompress-response: 6.0.0 - once: 1.4.0 - simple-concat: 1.0.1 - simple-swizzle@0.2.2: dependencies: is-arrayish: 0.3.2 @@ -22756,8 +23013,6 @@ snapshots: strip-final-newline@2.0.0: {} - strip-json-comments@2.0.1: {} - strip-json-comments@3.1.1: {} strip-outer@1.0.1: @@ -22767,6 +23022,10 @@ snapshots: strnum@1.0.5: {} + strtok3@10.3.4: + dependencies: + '@tokenizer/token': 0.3.0 + style-inject@0.3.0: {} style-loader@3.3.4(webpack@5.97.1): @@ -22936,23 +23195,6 @@ snapshots: tapable@2.2.1: {} - tar-fs@2.1.2: - dependencies: - chownr: 1.1.4 - mkdirp-classic: 0.5.3 - pump: 3.0.2 - tar-stream: 2.2.0 - - tar-fs@3.0.8: - dependencies: - pump: 3.0.2 - tar-stream: 3.1.7 - optionalDependencies: - bare-fs: 4.0.1 - bare-path: 3.0.0 - transitivePeerDependencies: - - bare-buffer - tar-stream@1.6.2: dependencies: bl: 1.2.3 @@ -23086,6 +23328,12 @@ snapshots: toidentifier@1.0.1: {} + token-types@6.1.1: + dependencies: + '@borewit/text-codec': 0.1.1 + '@tokenizer/token': 0.3.0 + ieee754: 1.2.1 + tough-cookie@4.1.4: dependencies: psl: 1.15.0 @@ -23181,6 +23429,7 @@ snapshots: tunnel-agent@0.6.0: dependencies: safe-buffer: 5.2.1 + optional: true turbo-darwin-64@1.13.4: optional: true @@ -23304,6 +23553,8 @@ snapshots: typescript@5.7.3: {} + uint8array-extras@1.5.0: {} + ulid@2.3.0: {} unbox-primitive@1.1.0: @@ -23652,7 +23903,7 @@ snapshots: watchpack: 2.4.2 webpack-sources: 3.2.3 optionalDependencies: - webpack-cli: 5.1.4(webpack-dev-server@4.15.2)(webpack@5.97.1) + webpack-cli: 5.1.4(webpack@5.97.1) transitivePeerDependencies: - '@swc/core' - esbuild @@ -24023,3 +24274,9 @@ snapshots: archiver-utils: 3.0.4 compress-commons: 4.1.2 readable-stream: 3.6.2 + + zip-stream@6.0.1: + dependencies: + archiver-utils: 5.0.2 + compress-commons: 6.0.2 + readable-stream: 4.7.0