diff --git a/backend-node/src/app.ts b/backend-node/src/app.ts index 6339213e..3c8974d4 100644 --- a/backend-node/src/app.ts +++ b/backend-node/src/app.ts @@ -52,14 +52,20 @@ import { BatchSchedulerService } from "./services/batchSchedulerService"; const app = express(); // 기본 미들웨어 -app.use(helmet({ - contentSecurityPolicy: { - directives: { - ...helmet.contentSecurityPolicy.getDefaultDirectives(), - "frame-ancestors": ["'self'", "http://localhost:9771", "http://localhost:3000"], // 프론트엔드 도메인 허용 +app.use( + helmet({ + contentSecurityPolicy: { + directives: { + ...helmet.contentSecurityPolicy.getDefaultDirectives(), + "frame-ancestors": [ + "'self'", + "http://localhost:9771", + "http://localhost:3000", + ], // 프론트엔드 도메인 허용 + }, }, - }, -})); + }) +); app.use(compression()); app.use(express.json({ limit: "10mb" })); app.use(express.urlencoded({ extended: true, limit: "10mb" })); @@ -104,7 +110,7 @@ app.use( // Rate Limiting (개발 환경에서는 완화) const limiter = rateLimit({ windowMs: 1 * 60 * 1000, // 1분 - max: config.nodeEnv === "development" ? 10000 : 100, // 개발환경에서는 10000으로 증가, 운영환경에서는 100 + max: config.nodeEnv === "development" ? 10000 : 10000, // 개발환경에서는 10000으로 증가, 운영환경에서는 100 message: { error: "너무 많은 요청이 발생했습니다. 잠시 후 다시 시도해주세요.", }, @@ -191,7 +197,7 @@ app.listen(PORT, HOST, async () => { logger.info(`📊 Environment: ${config.nodeEnv}`); logger.info(`🔗 Health check: http://${HOST}:${PORT}/health`); logger.info(`🌐 External access: http://39.117.244.52:${PORT}/health`); - + // 배치 스케줄러 초기화 try { await BatchSchedulerService.initialize(); diff --git a/backend-node/src/services/enhancedDataflowControlService.ts b/backend-node/src/services/enhancedDataflowControlService.ts index 6aae33da..862feda0 100644 --- a/backend-node/src/services/enhancedDataflowControlService.ts +++ b/backend-node/src/services/enhancedDataflowControlService.ts @@ -11,15 +11,15 @@ import { import { MultiConnectionQueryService } from "./multiConnectionQueryService"; import { logger } from "../utils/logger"; -export interface EnhancedControlAction extends ControlAction { - // 🆕 기본 ControlAction 속성들 (상속됨) - id?: number; - actionType?: string; +export interface EnhancedControlAction + extends Omit { + // 🆕 기본 ControlAction 속성들 (일부 재정의) + id: string; // ControlAction과 호환성을 위해 string 타입 유지 fromTable: string; - // 🆕 추가 속성들 - conditions?: ControlCondition[]; - fieldMappings?: any[]; + // 🆕 추가 속성들 (선택적으로 재정의) + conditions: ControlCondition[]; // 필수 속성으로 변경 + fieldMappings: any[]; // 필수 속성으로 변경 // 🆕 UPDATE 액션 관련 필드 updateConditions?: UpdateCondition[]; @@ -166,16 +166,16 @@ export class EnhancedDataflowControlService extends DataflowControlService { let actionResult: any; // 커넥션 ID 추출 - const sourceConnectionId = enhancedAction.fromConnection?.connectionId || enhancedAction.fromConnection?.id || 0; - const targetConnectionId = enhancedAction.toConnection?.connectionId || enhancedAction.toConnection?.id || 0; + const sourceConnectionId = enhancedAction.fromConnection?.id || 0; + const targetConnectionId = enhancedAction.toConnection?.id || 0; switch (enhancedAction.actionType) { case "insert": - actionResult = await this.executeMultiConnectionInsert( + actionResult = await this.executeEnhancedMultiConnectionInsert( enhancedAction, sourceData, enhancedAction.fromTable, - enhancedAction.targetTable, + enhancedAction.targetTable || enhancedAction.fromTable, sourceConnectionId, targetConnectionId, null @@ -183,11 +183,11 @@ export class EnhancedDataflowControlService extends DataflowControlService { break; case "update": - actionResult = await this.executeMultiConnectionUpdate( + actionResult = await this.executeEnhancedMultiConnectionUpdate( enhancedAction, sourceData, enhancedAction.fromTable, - enhancedAction.targetTable, + enhancedAction.targetTable || enhancedAction.fromTable, sourceConnectionId, targetConnectionId, null @@ -195,11 +195,11 @@ export class EnhancedDataflowControlService extends DataflowControlService { break; case "delete": - actionResult = await this.executeMultiConnectionDelete( + actionResult = await this.executeEnhancedMultiConnectionDelete( enhancedAction, sourceData, enhancedAction.fromTable, - enhancedAction.targetTable, + enhancedAction.targetTable || enhancedAction.fromTable, sourceConnectionId, targetConnectionId, null @@ -247,8 +247,8 @@ export class EnhancedDataflowControlService extends DataflowControlService { /** * 🆕 다중 커넥션 INSERT 실행 */ - async executeMultiConnectionInsert( - action: EnhancedControlAction, + async executeEnhancedMultiConnectionInsert( + action: ControlAction, sourceData: Record, sourceTable: string, targetTable: string, @@ -257,16 +257,17 @@ export class EnhancedDataflowControlService extends DataflowControlService { multiConnService: any ): Promise { try { - logger.info(`다중 커넥션 INSERT 실행: action=${action.action}`); + const enhancedAction = action as EnhancedControlAction; + logger.info(`다중 커넥션 INSERT 실행: action=${action.id}`); // 커넥션 ID 결정 - const fromConnId = fromConnectionId || action.fromConnection?.connectionId || 0; - const toConnId = toConnectionId || action.toConnection?.connectionId || 0; + const fromConnId = fromConnectionId || action.fromConnection?.id || 0; + const toConnId = toConnectionId || action.toConnection?.id || 0; // FROM 테이블에서 소스 데이터 조회 (조건이 있는 경우) let fromData = sourceData; if ( - action.fromTable && + enhancedAction.fromTable && action.conditions && action.conditions.length > 0 ) { @@ -277,7 +278,7 @@ export class EnhancedDataflowControlService extends DataflowControlService { const fromResults = await this.multiConnectionService.fetchDataFromConnection( fromConnId, - action.fromTable, + enhancedAction.fromTable, queryConditions ); @@ -302,7 +303,7 @@ export class EnhancedDataflowControlService extends DataflowControlService { const insertResult = await this.multiConnectionService.insertDataToConnection( toConnId, - action.targetTable, + action.targetTable || enhancedAction.fromTable, mappedData ); @@ -317,8 +318,8 @@ export class EnhancedDataflowControlService extends DataflowControlService { /** * 🆕 다중 커넥션 UPDATE 실행 */ - async executeMultiConnectionUpdate( - action: EnhancedControlAction, + async executeEnhancedMultiConnectionUpdate( + action: ControlAction, sourceData: Record, sourceTable: string, targetTable: string, @@ -327,26 +328,30 @@ export class EnhancedDataflowControlService extends DataflowControlService { multiConnService: any ): Promise { try { - logger.info(`다중 커넥션 UPDATE 실행: action=${action.action}`); + const enhancedAction = action as EnhancedControlAction; + logger.info(`다중 커넥션 UPDATE 실행: action=${action.id}`); // 커넥션 ID 결정 - const fromConnId = fromConnectionId || action.fromConnection?.connectionId || 0; - const toConnId = toConnectionId || action.toConnection?.connectionId || 0; + const fromConnId = fromConnectionId || action.fromConnection?.id || 0; + const toConnId = toConnectionId || action.toConnection?.id || 0; // UPDATE 조건 확인 - if (!action.updateConditions || action.updateConditions.length === 0) { + if ( + !enhancedAction.updateConditions || + enhancedAction.updateConditions.length === 0 + ) { throw new Error("UPDATE 작업에는 업데이트 조건이 필요합니다."); } // FROM 테이블에서 업데이트 조건 확인 const updateConditions = this.buildUpdateConditions( - action.updateConditions, + enhancedAction.updateConditions, sourceData ); const fromResults = await this.multiConnectionService.fetchDataFromConnection( fromConnId, - action.fromTable || action.targetTable, + enhancedAction.fromTable || action.targetTable || "default_table", updateConditions ); @@ -360,13 +365,13 @@ export class EnhancedDataflowControlService extends DataflowControlService { // 업데이트 필드 매핑 적용 const updateData = this.applyUpdateFieldMappings( - action.updateFields || [], + enhancedAction.updateFields || [], fromResults[0] ); // WHERE 조건 구성 (TO 테이블 대상) const whereConditions = this.buildWhereConditions( - action.updateFields || [], + enhancedAction.updateFields || [], fromResults[0] ); @@ -374,7 +379,7 @@ export class EnhancedDataflowControlService extends DataflowControlService { const updateResult = await this.multiConnectionService.updateDataToConnection( toConnId, - action.targetTable, + action.targetTable || enhancedAction.fromTable, updateData, whereConditions ); @@ -390,8 +395,8 @@ export class EnhancedDataflowControlService extends DataflowControlService { /** * 🆕 다중 커넥션 DELETE 실행 */ - async executeMultiConnectionDelete( - action: EnhancedControlAction, + async executeEnhancedMultiConnectionDelete( + action: ControlAction, sourceData: Record, sourceTable: string, targetTable: string, @@ -400,28 +405,30 @@ export class EnhancedDataflowControlService extends DataflowControlService { multiConnService: any ): Promise { try { - logger.info(`다중 커넥션 DELETE 실행: action=${action.action}`); + const enhancedAction = action as EnhancedControlAction; + logger.info(`다중 커넥션 DELETE 실행: action=${action.id}`); // 커넥션 ID 결정 - const fromConnId = - fromConnectionId || action.fromConnection?.connectionId || 0; - const toConnId = - toConnectionId || action.toConnection?.connectionId || 0; + const fromConnId = fromConnectionId || action.fromConnection?.id || 0; + const toConnId = toConnectionId || action.toConnection?.id || 0; // DELETE 조건 확인 - if (!action.deleteConditions || action.deleteConditions.length === 0) { + if ( + !enhancedAction.deleteConditions || + enhancedAction.deleteConditions.length === 0 + ) { throw new Error("DELETE 작업에는 삭제 조건이 필요합니다."); } // FROM 테이블에서 삭제 트리거 조건 확인 const deleteConditions = this.buildDeleteConditions( - action.deleteConditions, + enhancedAction.deleteConditions, sourceData ); const fromResults = await this.multiConnectionService.fetchDataFromConnection( fromConnId, - action.fromTable || action.targetTable, + enhancedAction.fromTable || action.targetTable || "default_table", deleteConditions ); @@ -432,7 +439,7 @@ export class EnhancedDataflowControlService extends DataflowControlService { // WHERE 조건 구성 (TO 테이블 대상) const whereConditions = this.buildDeleteWhereConditions( - action.deleteWhereConditions || [], + enhancedAction.deleteWhereConditions || [], fromResults[0] ); @@ -441,14 +448,14 @@ export class EnhancedDataflowControlService extends DataflowControlService { } // 안전장치 적용 - const maxDeleteCount = action.maxDeleteCount || 100; + const maxDeleteCount = enhancedAction.maxDeleteCount || 100; // Dry Run 실행 (선택사항) - if (action.dryRunFirst) { + if (enhancedAction.dryRunFirst) { const countResult = await this.multiConnectionService.fetchDataFromConnection( toConnId, - action.targetTable, + action.targetTable || enhancedAction.fromTable, whereConditions ); @@ -465,13 +472,13 @@ export class EnhancedDataflowControlService extends DataflowControlService { const deleteResult = await this.multiConnectionService.deleteDataFromConnection( toConnId, - action.targetTable, + action.targetTable || enhancedAction.fromTable, whereConditions, maxDeleteCount ); // 삭제 로그 기록 (선택사항) - if (action.logAllDeletes) { + if (enhancedAction.logAllDeletes) { logger.info( `삭제 실행 로그: ${JSON.stringify({ action: action.id, diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 4e699d12..7db705eb 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -39,7 +39,9 @@ "clsx": "^2.1.1", "cmdk": "^1.1.1", "date-fns": "^4.1.0", + "docx-preview": "^0.3.6", "lucide-react": "^0.525.0", + "mammoth": "^1.11.0", "next": "15.4.4", "react": "19.1.0", "react-day-picker": "^9.9.0", @@ -47,8 +49,10 @@ "react-hook-form": "^7.62.0", "react-hot-toast": "^2.6.0", "react-window": "^2.1.0", + "sheetjs-style": "^0.15.8", "sonner": "^2.0.7", "tailwind-merge": "^3.3.1", + "xlsx": "^0.18.5", "zod": "^4.1.5" }, "devDependencies": { @@ -3351,6 +3355,15 @@ "win32" ] }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.11", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz", + "integrity": "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/@xyflow/react": { "version": "12.8.5", "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.8.5.tgz", @@ -3406,6 +3419,22 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/adler-32": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.2.0.tgz", + "integrity": "sha512-/vUqU/UY4MVeFsg+SsK6c+/05RZXIHZMGJA+PX5JyWI0ZRcBpupnRuPLU/NXXoFwMYCPCoxIfElM2eS+DUXCqQ==", + "license": "Apache-2.0", + "dependencies": { + "exit-on-epipe": "~1.0.1", + "printj": "~1.1.0" + }, + "bin": { + "adler32": "bin/adler32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -3705,6 +3734,32 @@ "dev": true, "license": "MIT" }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bluebird": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", + "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==", + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -3837,6 +3892,28 @@ ], "license": "CC-BY-4.0" }, + "node_modules/cfb": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz", + "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "crc-32": "~1.2.0" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/cfb/node_modules/adler-32": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz", + "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -3939,6 +4016,28 @@ "react-dom": "^18 || ^19 || ^19.0.0-rc" } }, + "node_modules/codepage": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.14.0.tgz", + "integrity": "sha512-iz3zJLhlrg37/gYRWgEPkaFTtzmnEv1h+r7NgZum2lFElYQPi0/5bnmuDfODHxfp0INEfnRqyfyeIJDbb7ahRw==", + "license": "Apache-2.0", + "dependencies": { + "commander": "~2.14.1", + "exit-on-epipe": "~1.0.1" + }, + "bin": { + "codepage": "bin/codepage.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/codepage/node_modules/commander": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.14.1.tgz", + "integrity": "sha512-+YR16o3rK53SmWHU3rEM3tPAh2rwb1yPcQX5irVn7mb0gXbwuCCrnkbV5+PBfETdfg1vui07nM6PCG1zndcjQw==", + "license": "MIT" + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -3971,6 +4070,12 @@ "node": ">= 0.8" } }, + "node_modules/commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "license": "MIT" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -3995,6 +4100,24 @@ "node": "^14.18.0 || >=16.10.0" } }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -4308,6 +4431,12 @@ "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", "license": "MIT" }, + "node_modules/dingbat-to-unicode": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dingbat-to-unicode/-/dingbat-to-unicode-1.0.1.tgz", + "integrity": "sha512-98l0sW87ZT58pU4i61wa2OHwxbiYSbuxsCBozaVnYX2iCnr3bLM3fIes1/ej7h1YdOKuKt/MLs706TVnALA65w==", + "license": "BSD-2-Clause" + }, "node_modules/doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -4321,6 +4450,15 @@ "node": ">=0.10.0" } }, + "node_modules/docx-preview": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/docx-preview/-/docx-preview-0.3.6.tgz", + "integrity": "sha512-gKVPE18hlpfuhQHiptsw1rbOwzQeGSwK10/w7hv1ZMEqHmjtCuTpz6AUMfu1twIPGxgpcsMXThKI6B6WsP3L1w==", + "license": "Apache-2.0", + "dependencies": { + "jszip": ">=3.0.0" + } + }, "node_modules/dotenv": { "version": "16.6.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", @@ -4334,6 +4472,15 @@ "url": "https://dotenvx.com" } }, + "node_modules/duck": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/duck/-/duck-0.1.12.tgz", + "integrity": "sha512-wkctla1O6VfP89gQ+J/yDesM0S7B7XLXjKGzXxMDVFg7uEn706niAtyYovKbyq1oT9YwDcly721/iUWoc8MVRg==", + "license": "BSD", + "dependencies": { + "underscore": "^1.13.1" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -5049,6 +5196,15 @@ "node": ">=0.10.0" } }, + "node_modules/exit-on-epipe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz", + "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/exsolve": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz", @@ -5263,6 +5419,15 @@ "node": ">= 6" } }, + "node_modules/frac": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", + "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -5577,6 +5742,12 @@ "node": ">= 4" } }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -5604,6 +5775,12 @@ "node": ">=0.8.19" } }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -6132,6 +6309,18 @@ "node": ">=4.0" } }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -6176,6 +6365,15 @@ "node": ">= 0.8.0" } }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/lightningcss": { "version": "1.30.1", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", @@ -6451,6 +6649,17 @@ "loose-envify": "cli.js" } }, + "node_modules/lop": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/lop/-/lop-0.4.2.tgz", + "integrity": "sha512-RefILVDQ4DKoRZsJ4Pj22TxE3omDO47yFpkIBoDKzkqPRISs5U1cnAdg/5583YPkWPaLIYHOKRMQSvjFsO26cw==", + "license": "BSD-2-Clause", + "dependencies": { + "duck": "^0.1.12", + "option": "~0.2.1", + "underscore": "^1.13.1" + } + }, "node_modules/lucide-react": { "version": "0.525.0", "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.525.0.tgz", @@ -6470,6 +6679,39 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/mammoth": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/mammoth/-/mammoth-1.11.0.tgz", + "integrity": "sha512-BcEqqY/BOwIcI1iR5tqyVlqc3KIaMRa4egSoK83YAVrBf6+yqdAAbtUcFDCWX8Zef8/fgNZ6rl4VUv+vVX8ddQ==", + "license": "BSD-2-Clause", + "dependencies": { + "@xmldom/xmldom": "^0.8.6", + "argparse": "~1.0.3", + "base64-js": "^1.5.1", + "bluebird": "~3.4.0", + "dingbat-to-unicode": "^1.0.1", + "jszip": "^3.7.1", + "lop": "^0.4.2", + "path-is-absolute": "^1.0.0", + "underscore": "^1.13.1", + "xmlbuilder": "^10.0.0" + }, + "bin": { + "mammoth": "bin/mammoth" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/mammoth/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -6855,6 +7097,12 @@ "dev": true, "license": "MIT" }, + "node_modules/option": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/option/-/option-0.2.4.tgz", + "integrity": "sha512-pkEqbDyl8ou5cpq+VsnQbe/WlEy5qS7xPzMS1U55OCG9KPvwFD46zDbxQIj3egJSFc3D+XhYOPUzz49zQAVy7A==", + "license": "BSD-2-Clause" + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -6923,6 +7171,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -6946,6 +7200,15 @@ "node": ">=8" } }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -7173,6 +7436,18 @@ } } }, + "node_modules/printj": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz", + "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==", + "license": "Apache-2.0", + "bin": { + "printj": "bin/printj.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/prisma": { "version": "6.16.2", "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.16.2.tgz", @@ -7199,6 +7474,12 @@ } } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -7437,6 +7718,27 @@ "react-dom": "^18.0.0 || ^19.0.0" } }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, "node_modules/readdirp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", @@ -7591,6 +7893,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, "node_modules/safe-push-apply": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", @@ -7694,6 +8002,12 @@ "node": ">= 0.4" } }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, "node_modules/sharp": { "version": "0.34.4", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.4.tgz", @@ -7760,6 +8074,28 @@ "node": ">=8" } }, + "node_modules/sheetjs-style": { + "version": "0.15.8", + "resolved": "https://registry.npmjs.org/sheetjs-style/-/sheetjs-style-0.15.8.tgz", + "integrity": "sha512-/wRiwnq5ck7aO+zLBs+u5JqQK4agUTIGCS0nxgaMjFl6XdlVaaB/RNJcP6S6Efj3+RYbSZuAoyqmSnbzxfT7Kg==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.2.0", + "cfb": "^1.1.4", + "codepage": "~1.14.0", + "commander": "~2.17.1", + "crc-32": "~1.2.0", + "exit-on-epipe": "~1.0.1", + "ssf": "~0.10.3", + "wmf": "~1.0.1" + }, + "bin": { + "xlsx": "bin/xlsx.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", @@ -7855,6 +8191,27 @@ "node": ">=0.10.0" } }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "license": "BSD-3-Clause" + }, + "node_modules/ssf": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.10.3.tgz", + "integrity": "sha512-pRuUdW0WwyB2doSqqjWyzwCD6PkfxpHAHdZp39K3dp/Hq7f+xfMwNAWIi16DyrRg4gg9c/RvLYkJTSawTPTm1w==", + "license": "Apache-2.0", + "dependencies": { + "frac": "~1.1.2" + }, + "bin": { + "ssf": "bin/ssf.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/stable-hash": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", @@ -7876,6 +8233,15 @@ "node": ">= 0.4" } }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/string.prototype.includes": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", @@ -8359,6 +8725,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/underscore": { + "version": "1.13.7", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", + "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==", + "license": "MIT" + }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", @@ -8463,6 +8835,12 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -8568,6 +8946,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/wmf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz", + "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/word": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz", + "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -8578,6 +8974,66 @@ "node": ">=0.10.0" } }, + "node_modules/xlsx": { + "version": "0.18.5", + "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz", + "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "cfb": "~1.2.1", + "codepage": "~1.15.0", + "crc-32": "~1.2.1", + "ssf": "~0.11.2", + "wmf": "~1.0.1", + "word": "~0.3.0" + }, + "bin": { + "xlsx": "bin/xlsx.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/xlsx/node_modules/adler-32": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz", + "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/xlsx/node_modules/codepage": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz", + "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/xlsx/node_modules/ssf": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz", + "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==", + "license": "Apache-2.0", + "dependencies": { + "frac": "~1.1.2" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/xmlbuilder": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-10.1.1.tgz", + "integrity": "sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg==", + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, "node_modules/yallist": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",