From 251a362717d1d38a0a2b88ce0d122e2d877deac1 Mon Sep 17 00:00:00 2001 From: laxmanhalaki Date: Wed, 21 Jan 2026 19:05:16 +0530 Subject: [PATCH] changed to typescript and added missed tables --- package-lock.json | 5292 +++++++++++++---- package.json | 59 +- scripts/migrate.ts | 41 + server.js | 156 - src/common/config/auth.js | 34 - src/common/config/auth.ts | 31 + .../config/{constants.js => constants.ts} | 98 +- .../config/{database.js => database.ts} | 16 +- src/common/middleware/auth.js | 100 - src/common/middleware/auth.ts | 99 + src/common/middleware/errorHandler.js | 76 - src/common/middleware/errorHandler.ts | 78 + src/common/middleware/roleCheck.js | 47 - src/common/middleware/roleCheck.ts | 46 + src/common/middleware/upload.ts | 96 + src/common/utils/logger.js | 67 - src/common/utils/logger.ts | 71 + src/database/models/AiSummary.ts | 57 + src/database/models/Application.js | 124 - src/database/models/Application.ts | 189 + src/database/models/ApplicationProgress.ts | 65 + .../models/ApplicationStatusHistory.ts | 61 + src/database/models/Area.ts | 81 + src/database/models/AreaManager.ts | 67 + .../models/{AuditLog.js => AuditLog.ts} | 25 +- ...ionalChange.js => ConstitutionalChange.ts} | 29 +- src/database/models/Dealer.ts | 89 + src/database/models/DealerCode.ts | 53 + src/database/models/District.ts | 53 + src/database/models/DistrictManager.ts | 67 + src/database/models/Document.js | 64 - src/database/models/Document.ts | 104 + src/database/models/DocumentVersion.ts | 56 + src/database/models/EmailTemplate.ts | 53 + src/database/models/EorChecklist.ts | 62 + src/database/models/EorChecklistItem.ts | 65 + src/database/models/ExitFeedback.ts | 76 + src/database/models/FddAssignment.ts | 51 + src/database/models/FddReport.ts | 70 + .../{FinancePayment.js => FinancePayment.ts} | 28 +- src/database/models/FnF.js | 72 - src/database/models/FnF.ts | 103 + .../models/{FnFLineItem.js => FnFLineItem.ts} | 20 +- src/database/models/Interview.ts | 62 + src/database/models/InterviewEvaluation.ts | 62 + src/database/models/InterviewFeedback.ts | 46 + src/database/models/InterviewParticipant.ts | 51 + src/database/models/KTMatrixScore.ts | 61 + src/database/models/LoaAcknowledgement.ts | 56 + src/database/models/LoaApproval.ts | 66 + src/database/models/LoaDocumentGenerated.ts | 57 + src/database/models/LoaRequest.ts | 68 + src/database/models/LoiAcknowledgement.ts | 56 + src/database/models/LoiApproval.ts | 66 + src/database/models/LoiDocumentGenerated.ts | 57 + src/database/models/LoiRequest.ts | 68 + .../{Notification.js => Notification.ts} | 20 +- src/database/models/Opportunity.ts | 116 + src/database/models/Outlet.js | 104 - src/database/models/Outlet.ts | 125 + src/database/models/Permission.ts | 61 + src/database/models/Questionnaire.ts | 37 + src/database/models/QuestionnaireQuestion.ts | 62 + src/database/models/QuestionnaireResponse.ts | 66 + src/database/models/QuestionnaireScore.ts | 56 + src/database/models/Region.js | 44 - src/database/models/Region.ts | 84 + src/database/models/RegionManager.ts | 67 + ...ocationRequest.js => RelocationRequest.ts} | 32 +- src/database/models/Resignation.js | 110 - src/database/models/Resignation.ts | 132 + src/database/models/Role.ts | 59 + src/database/models/RolePermission.ts | 77 + src/database/models/SLABreach.ts | 56 + ...LAConfiguration.js => SLAConfiguration.ts} | 19 +- ...lationConfig.js => SLAEscalationConfig.ts} | 19 +- .../models/{SLAReminder.js => SLAReminder.ts} | 18 +- src/database/models/SLATracking.ts | 76 + src/database/models/SecurityDeposit.ts | 75 + src/database/models/State.ts | 58 + src/database/models/TerminationRequest.ts | 71 + src/database/models/User.js | 89 - src/database/models/User.ts | 160 + src/database/models/UserRole.ts | 109 + src/database/models/WorkNoteAttachment.ts | 46 + src/database/models/WorkNoteTag.ts | 41 + ...wStageConfig.js => WorkflowStageConfig.ts} | 20 +- src/database/models/Worknote.js | 52 - src/database/models/Worknote.ts | 78 + src/database/models/Zone.js | 49 - src/database/models/Zone.ts | 75 + src/database/models/ZoneManager.ts | 67 + src/database/models/index.js | 55 - src/database/models/index.ts | 203 + src/modules/admin/admin.controller.ts | 169 + src/modules/admin/admin.routes.ts | 27 + .../assessment/assessment.controller.ts | 160 + src/modules/assessment/assessment.routes.ts | 20 + ...{auth.controller.js => auth.controller.ts} | 44 +- src/modules/auth/auth.routes.js | 15 - src/modules/auth/auth.routes.ts | 15 + .../collaboration/collaboration.controller.js | 84 - .../collaboration/collaboration.controller.ts | 117 + .../collaboration/collaboration.routes.js | 14 - .../collaboration/collaboration.routes.ts | 16 + .../communication/communication.controller.ts | 37 + .../communication/communication.routes.ts | 17 + src/modules/dealer/dealer.controller.ts | 71 + src/modules/dealer/dealer.routes.ts | 12 + src/modules/eor/eor.controller.ts | 76 + src/modules/eor/eor.routes.ts | 13 + src/modules/fdd/fdd.controller.ts | 69 + src/modules/fdd/fdd.routes.ts | 12 + src/modules/loa/loa.controller.ts | 91 + src/modules/loa/loa.routes.ts | 13 + src/modules/loi/loi.controller.ts | 91 + src/modules/loi/loi.routes.ts | 13 + src/modules/master/master.controller.js | 121 - src/modules/master/master.controller.ts | 286 + src/modules/master/master.routes.js | 28 - src/modules/master/master.routes.ts | 43 + ...let.controller.js => outlet.controller.ts} | 24 +- src/modules/master/outlet.routes.js | 16 - src/modules/master/outlet.routes.ts | 16 + .../onboarding/onboarding.controller.js | 136 - .../onboarding/onboarding.controller.ts | 182 + src/modules/onboarding/onboarding.routes.js | 15 - src/modules/onboarding/onboarding.routes.ts | 15 + .../opportunity/opportunity.controller.ts | 109 + src/modules/opportunity/opportunity.routes.ts | 14 + ...roller.js => constitutional.controller.ts} | 38 +- ...controller.js => relocation.controller.ts} | 78 +- ...ontroller.js => resignation.controller.ts} | 90 +- .../self-service/resignation.routes.js | 13 - .../self-service/resignation.routes.ts | 13 + .../self-service/self-service.routes.js | 26 - .../self-service/self-service.routes.ts | 26 + ...controller.js => settlement.controller.ts} | 16 +- src/modules/settlement/settlement.routes.js | 17 - src/modules/settlement/settlement.routes.ts | 17 + src/modules/sla/sla.controller.ts | 26 + src/modules/sla/sla.routes.ts | 11 + src/server.ts | 184 + src/types/auth.types.ts | 7 + src/types/common.types.ts | 12 + src/types/constants.types.ts | 41 + src/types/express.types.ts | 15 + tsconfig.json | 39 + 148 files changed, 11531 insertions(+), 2994 deletions(-) create mode 100644 scripts/migrate.ts delete mode 100644 server.js delete mode 100644 src/common/config/auth.js create mode 100644 src/common/config/auth.ts rename src/common/config/{constants.js => constants.ts} (78%) rename src/common/config/{database.js => database.ts} (73%) delete mode 100644 src/common/middleware/auth.js create mode 100644 src/common/middleware/auth.ts delete mode 100644 src/common/middleware/errorHandler.js create mode 100644 src/common/middleware/errorHandler.ts delete mode 100644 src/common/middleware/roleCheck.js create mode 100644 src/common/middleware/roleCheck.ts create mode 100644 src/common/middleware/upload.ts delete mode 100644 src/common/utils/logger.js create mode 100644 src/common/utils/logger.ts create mode 100644 src/database/models/AiSummary.ts delete mode 100644 src/database/models/Application.js create mode 100644 src/database/models/Application.ts create mode 100644 src/database/models/ApplicationProgress.ts create mode 100644 src/database/models/ApplicationStatusHistory.ts create mode 100644 src/database/models/Area.ts create mode 100644 src/database/models/AreaManager.ts rename src/database/models/{AuditLog.js => AuditLog.ts} (64%) rename src/database/models/{ConstitutionalChange.js => ConstitutionalChange.ts} (65%) create mode 100644 src/database/models/Dealer.ts create mode 100644 src/database/models/DealerCode.ts create mode 100644 src/database/models/District.ts create mode 100644 src/database/models/DistrictManager.ts delete mode 100644 src/database/models/Document.js create mode 100644 src/database/models/Document.ts create mode 100644 src/database/models/DocumentVersion.ts create mode 100644 src/database/models/EmailTemplate.ts create mode 100644 src/database/models/EorChecklist.ts create mode 100644 src/database/models/EorChecklistItem.ts create mode 100644 src/database/models/ExitFeedback.ts create mode 100644 src/database/models/FddAssignment.ts create mode 100644 src/database/models/FddReport.ts rename src/database/models/{FinancePayment.js => FinancePayment.ts} (63%) delete mode 100644 src/database/models/FnF.js create mode 100644 src/database/models/FnF.ts rename src/database/models/{FnFLineItem.js => FnFLineItem.ts} (72%) create mode 100644 src/database/models/Interview.ts create mode 100644 src/database/models/InterviewEvaluation.ts create mode 100644 src/database/models/InterviewFeedback.ts create mode 100644 src/database/models/InterviewParticipant.ts create mode 100644 src/database/models/KTMatrixScore.ts create mode 100644 src/database/models/LoaAcknowledgement.ts create mode 100644 src/database/models/LoaApproval.ts create mode 100644 src/database/models/LoaDocumentGenerated.ts create mode 100644 src/database/models/LoaRequest.ts create mode 100644 src/database/models/LoiAcknowledgement.ts create mode 100644 src/database/models/LoiApproval.ts create mode 100644 src/database/models/LoiDocumentGenerated.ts create mode 100644 src/database/models/LoiRequest.ts rename src/database/models/{Notification.js => Notification.ts} (68%) create mode 100644 src/database/models/Opportunity.ts delete mode 100644 src/database/models/Outlet.js create mode 100644 src/database/models/Outlet.ts create mode 100644 src/database/models/Permission.ts create mode 100644 src/database/models/Questionnaire.ts create mode 100644 src/database/models/QuestionnaireQuestion.ts create mode 100644 src/database/models/QuestionnaireResponse.ts create mode 100644 src/database/models/QuestionnaireScore.ts delete mode 100644 src/database/models/Region.js create mode 100644 src/database/models/Region.ts create mode 100644 src/database/models/RegionManager.ts rename src/database/models/{RelocationRequest.js => RelocationRequest.ts} (68%) delete mode 100644 src/database/models/Resignation.js create mode 100644 src/database/models/Resignation.ts create mode 100644 src/database/models/Role.ts create mode 100644 src/database/models/RolePermission.ts create mode 100644 src/database/models/SLABreach.ts rename src/database/models/{SLAConfiguration.js => SLAConfiguration.ts} (67%) rename src/database/models/{SLAEscalationConfig.js => SLAEscalationConfig.ts} (65%) rename src/database/models/{SLAReminder.js => SLAReminder.ts} (66%) create mode 100644 src/database/models/SLATracking.ts create mode 100644 src/database/models/SecurityDeposit.ts create mode 100644 src/database/models/State.ts create mode 100644 src/database/models/TerminationRequest.ts delete mode 100644 src/database/models/User.js create mode 100644 src/database/models/User.ts create mode 100644 src/database/models/UserRole.ts create mode 100644 src/database/models/WorkNoteAttachment.ts create mode 100644 src/database/models/WorkNoteTag.ts rename src/database/models/{WorkflowStageConfig.js => WorkflowStageConfig.ts} (62%) delete mode 100644 src/database/models/Worknote.js create mode 100644 src/database/models/Worknote.ts delete mode 100644 src/database/models/Zone.js create mode 100644 src/database/models/Zone.ts create mode 100644 src/database/models/ZoneManager.ts delete mode 100644 src/database/models/index.js create mode 100644 src/database/models/index.ts create mode 100644 src/modules/admin/admin.controller.ts create mode 100644 src/modules/admin/admin.routes.ts create mode 100644 src/modules/assessment/assessment.controller.ts create mode 100644 src/modules/assessment/assessment.routes.ts rename src/modules/auth/{auth.controller.js => auth.controller.ts} (84%) delete mode 100644 src/modules/auth/auth.routes.js create mode 100644 src/modules/auth/auth.routes.ts delete mode 100644 src/modules/collaboration/collaboration.controller.js create mode 100644 src/modules/collaboration/collaboration.controller.ts delete mode 100644 src/modules/collaboration/collaboration.routes.js create mode 100644 src/modules/collaboration/collaboration.routes.ts create mode 100644 src/modules/communication/communication.controller.ts create mode 100644 src/modules/communication/communication.routes.ts create mode 100644 src/modules/dealer/dealer.controller.ts create mode 100644 src/modules/dealer/dealer.routes.ts create mode 100644 src/modules/eor/eor.controller.ts create mode 100644 src/modules/eor/eor.routes.ts create mode 100644 src/modules/fdd/fdd.controller.ts create mode 100644 src/modules/fdd/fdd.routes.ts create mode 100644 src/modules/loa/loa.controller.ts create mode 100644 src/modules/loa/loa.routes.ts create mode 100644 src/modules/loi/loi.controller.ts create mode 100644 src/modules/loi/loi.routes.ts delete mode 100644 src/modules/master/master.controller.js create mode 100644 src/modules/master/master.controller.ts delete mode 100644 src/modules/master/master.routes.js create mode 100644 src/modules/master/master.routes.ts rename src/modules/master/{outlet.controller.js => outlet.controller.ts} (86%) delete mode 100644 src/modules/master/outlet.routes.js create mode 100644 src/modules/master/outlet.routes.ts delete mode 100644 src/modules/onboarding/onboarding.controller.js create mode 100644 src/modules/onboarding/onboarding.controller.ts delete mode 100644 src/modules/onboarding/onboarding.routes.js create mode 100644 src/modules/onboarding/onboarding.routes.ts create mode 100644 src/modules/opportunity/opportunity.controller.ts create mode 100644 src/modules/opportunity/opportunity.routes.ts rename src/modules/self-service/{constitutional.controller.js => constitutional.controller.ts} (79%) rename src/modules/self-service/{relocation.controller.js => relocation.controller.ts} (74%) rename src/modules/self-service/{resignation.controller.js => resignation.controller.ts} (81%) delete mode 100644 src/modules/self-service/resignation.routes.js create mode 100644 src/modules/self-service/resignation.routes.ts delete mode 100644 src/modules/self-service/self-service.routes.js create mode 100644 src/modules/self-service/self-service.routes.ts rename src/modules/settlement/{settlement.controller.js => settlement.controller.ts} (84%) delete mode 100644 src/modules/settlement/settlement.routes.js create mode 100644 src/modules/settlement/settlement.routes.ts create mode 100644 src/modules/sla/sla.controller.ts create mode 100644 src/modules/sla/sla.routes.ts create mode 100644 src/server.ts create mode 100644 src/types/auth.types.ts create mode 100644 src/types/common.types.ts create mode 100644 src/types/constants.types.ts create mode 100644 src/types/express.types.ts create mode 100644 tsconfig.json diff --git a/package-lock.json b/package-lock.json index ef58e5a..1de4c7f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,33 +9,794 @@ "version": "1.0.0", "license": "PROPRIETARY", "dependencies": { - "bcryptjs": "^2.4.3", - "compression": "^1.7.4", + "bcryptjs": "^3.0.3", + "compression": "^1.8.1", "cors": "^2.8.5", - "dotenv": "^16.3.1", - "express": "^4.18.2", - "express-rate-limit": "^7.1.5", - "express-validator": "^7.0.1", - "helmet": "^7.1.0", - "jsonwebtoken": "^9.0.2", - "multer": "^1.4.5-lts.1", - "nodemailer": "^6.9.7", - "pg": "^8.11.3", + "dotenv": "^17.2.3", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "express-validator": "^7.3.1", + "helmet": "^8.1.0", + "jsonwebtoken": "^9.0.3", + "multer": "^2.0.2", + "nodemailer": "^7.0.12", + "pg": "^8.17.2", "pg-hstore": "^2.3.4", - "sequelize": "^6.35.2", - "uuid": "^9.0.1", - "winston": "^3.11.0" + "sequelize": "^6.37.7", + "uuid": "^13.0.0", + "winston": "^3.19.0" }, "devDependencies": { - "jest": "^29.7.0", + "@types/bcryptjs": "^2.4.6", + "@types/compression": "^1.8.1", + "@types/cors": "^2.8.19", + "@types/express": "^5.0.6", + "@types/jest": "^30.0.0", + "@types/jsonwebtoken": "^9.0.10", + "@types/multer": "^2.0.0", + "@types/node": "^25.0.9", + "@types/nodemailer": "^7.0.5", + "@types/pg": "^8.16.0", + "@types/supertest": "^6.0.3", + "@types/uuid": "^10.0.0", + "@types/validator": "^13.15.10", + "jest": "^30.2.0", "nodemon": "^3.0.2", - "supertest": "^6.3.3" + "supertest": "^7.2.2", + "ts-node": "^10.9.2", + "tsx": "^4.21.0", + "typescript": "^5.9.3" }, "engines": { "node": ">=18.0.0", "npm": ">=9.0.0" } }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sesv2": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sesv2/-/client-sesv2-3.972.0.tgz", + "integrity": "sha512-tFa5HTfas9cm+dRnuQcSLz67rkpslP2Vjy8iW4G+wjTip6BM7jy1CJbRcNiXh8NQj5lDtR0Uqg/7zS5G0vmEBQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.972.0", + "@aws-sdk/credential-provider-node": "3.972.0", + "@aws-sdk/middleware-host-header": "3.972.0", + "@aws-sdk/middleware-logger": "3.972.0", + "@aws-sdk/middleware-recursion-detection": "3.972.0", + "@aws-sdk/middleware-user-agent": "3.972.0", + "@aws-sdk/region-config-resolver": "3.972.0", + "@aws-sdk/signature-v4-multi-region": "3.972.0", + "@aws-sdk/types": "3.972.0", + "@aws-sdk/util-endpoints": "3.972.0", + "@aws-sdk/util-user-agent-browser": "3.972.0", + "@aws-sdk/util-user-agent-node": "3.972.0", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.20.6", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.7", + "@smithy/middleware-retry": "^4.4.23", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.10.8", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.22", + "@smithy/util-defaults-mode-node": "^4.2.25", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.972.0.tgz", + "integrity": "sha512-5qw6qLiRE4SUiz0hWy878dSR13tSVhbTWhsvFT8mGHe37NRRiaobm5MA2sWD0deRAuO98djSiV+dhWXa1xIFNw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.972.0", + "@aws-sdk/middleware-host-header": "3.972.0", + "@aws-sdk/middleware-logger": "3.972.0", + "@aws-sdk/middleware-recursion-detection": "3.972.0", + "@aws-sdk/middleware-user-agent": "3.972.0", + "@aws-sdk/region-config-resolver": "3.972.0", + "@aws-sdk/types": "3.972.0", + "@aws-sdk/util-endpoints": "3.972.0", + "@aws-sdk/util-user-agent-browser": "3.972.0", + "@aws-sdk/util-user-agent-node": "3.972.0", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.20.6", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.7", + "@smithy/middleware-retry": "^4.4.23", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.10.8", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.22", + "@smithy/util-defaults-mode-node": "^4.2.25", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.972.0.tgz", + "integrity": "sha512-nEeUW2M9F+xdIaD98F5MBcQ4ITtykj3yKbgFZ6J0JtL3bq+Z90szQ6Yy8H/BLPYXTs3V4n9ifnBo8cprRDiE6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.972.0", + "@aws-sdk/xml-builder": "3.972.0", + "@smithy/core": "^3.20.6", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/signature-v4": "^5.3.8", + "@smithy/smithy-client": "^4.10.8", + "@smithy/types": "^4.12.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.0.tgz", + "integrity": "sha512-kKHoNv+maHlPQOAhYamhap0PObd16SAb3jwaY0KYgNTiSbeXlbGUZPLioo9oA3wU10zItJzx83ClU7d7h40luA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.972.0", + "@aws-sdk/types": "3.972.0", + "@smithy/property-provider": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.0.tgz", + "integrity": "sha512-xzEi81L7I5jGUbpmqEHCe7zZr54hCABdj4H+3LzktHYuovV/oqnvoDdvZpGFR0e/KAw1+PL38NbGrpG30j6qlA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.972.0", + "@aws-sdk/types": "3.972.0", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.10.8", + "@smithy/types": "^4.12.0", + "@smithy/util-stream": "^4.5.10", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.0.tgz", + "integrity": "sha512-ruhAMceUIq2aknFd3jhWxmO0P0Efab5efjyIXOkI9i80g+zDY5VekeSxfqRKStEEJSKSCHDLQuOu0BnAn4Rzew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.972.0", + "@aws-sdk/credential-provider-env": "3.972.0", + "@aws-sdk/credential-provider-http": "3.972.0", + "@aws-sdk/credential-provider-login": "3.972.0", + "@aws-sdk/credential-provider-process": "3.972.0", + "@aws-sdk/credential-provider-sso": "3.972.0", + "@aws-sdk/credential-provider-web-identity": "3.972.0", + "@aws-sdk/nested-clients": "3.972.0", + "@aws-sdk/types": "3.972.0", + "@smithy/credential-provider-imds": "^4.2.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.0.tgz", + "integrity": "sha512-SsrsFJsEYAJHO4N/r2P0aK6o8si6f1lprR+Ej8J731XJqTckSGs/HFHcbxOyW/iKt+LNUvZa59/VlJmjhF4bEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.972.0", + "@aws-sdk/nested-clients": "3.972.0", + "@aws-sdk/types": "3.972.0", + "@smithy/property-provider": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.0.tgz", + "integrity": "sha512-wwJDpEGl6+sOygic8QKu0OHVB8SiodqF1fr5jvUlSFfS6tJss/E9vBc2aFjl7zI6KpAIYfIzIgM006lRrZtWCQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.972.0", + "@aws-sdk/credential-provider-http": "3.972.0", + "@aws-sdk/credential-provider-ini": "3.972.0", + "@aws-sdk/credential-provider-process": "3.972.0", + "@aws-sdk/credential-provider-sso": "3.972.0", + "@aws-sdk/credential-provider-web-identity": "3.972.0", + "@aws-sdk/types": "3.972.0", + "@smithy/credential-provider-imds": "^4.2.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.0.tgz", + "integrity": "sha512-nmzYhamLDJ8K+v3zWck79IaKMc350xZnWsf/GeaXO6E3MewSzd3lYkTiMi7lEp3/UwDm9NHfPguoPm+mhlSWQQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.972.0", + "@aws-sdk/types": "3.972.0", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.0.tgz", + "integrity": "sha512-6mYyfk1SrMZ15cH9T53yAF4YSnvq4yU1Xlgm3nqV1gZVQzmF5kr4t/F3BU3ygbvzi4uSwWxG3I3TYYS5eMlAyg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.972.0", + "@aws-sdk/core": "3.972.0", + "@aws-sdk/token-providers": "3.972.0", + "@aws-sdk/types": "3.972.0", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.0.tgz", + "integrity": "sha512-vsJXBGL8H54kz4T6do3p5elATj5d1izVGUXMluRJntm9/I0be/zUYtdd4oDTM2kSUmd4Zhyw3fMQ9lw7CVhd4A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.972.0", + "@aws-sdk/nested-clients": "3.972.0", + "@aws-sdk/types": "3.972.0", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.0.tgz", + "integrity": "sha512-3eztFI6F9/eHtkIaWKN3nT+PM+eQ6p1MALDuNshFk323ixuCZzOOVT8oUqtZa30Z6dycNXJwhlIq7NhUVFfimw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.972.0", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.0.tgz", + "integrity": "sha512-ZvdyVRwzK+ra31v1pQrgbqR/KsLD+wwJjHgko6JfoKUBIcEfAwJzQKO6HspHxdHWTVUz6MgvwskheR/TTYZl2g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.972.0", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.0.tgz", + "integrity": "sha512-F2SmUeO+S6l1h6dydNet3BQIk173uAkcfU1HDkw/bUdRLAnh15D3HP9vCZ7oCPBNcdEICbXYDmx0BR9rRUHGlQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.972.0", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.0.tgz", + "integrity": "sha512-0bcKFXWx+NZ7tIlOo7KjQ+O2rydiHdIQahrq+fN6k9Osky29v17guy68urUKfhTobR6iY6KvxkroFWaFtTgS5w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.972.0", + "@aws-sdk/types": "3.972.0", + "@aws-sdk/util-arn-parser": "3.972.0", + "@smithy/core": "^3.20.6", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/signature-v4": "^5.3.8", + "@smithy/smithy-client": "^4.10.8", + "@smithy/types": "^4.12.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-stream": "^4.5.10", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.0.tgz", + "integrity": "sha512-kFHQm2OCBJCzGWRafgdWHGFjitUXY/OxXngymcX4l8CiyiNDZB27HDDBg2yLj3OUJc4z4fexLMmP8r9vgag19g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.972.0", + "@aws-sdk/types": "3.972.0", + "@aws-sdk/util-endpoints": "3.972.0", + "@smithy/core": "^3.20.6", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.972.0.tgz", + "integrity": "sha512-QGlbnuGzSQJVG6bR9Qw6G0Blh6abFR4VxNa61ttMbzy9jt28xmk2iGtrYLrQPlCCPhY6enHqjTWm3n3LOb0wAw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.972.0", + "@aws-sdk/middleware-host-header": "3.972.0", + "@aws-sdk/middleware-logger": "3.972.0", + "@aws-sdk/middleware-recursion-detection": "3.972.0", + "@aws-sdk/middleware-user-agent": "3.972.0", + "@aws-sdk/region-config-resolver": "3.972.0", + "@aws-sdk/types": "3.972.0", + "@aws-sdk/util-endpoints": "3.972.0", + "@aws-sdk/util-user-agent-browser": "3.972.0", + "@aws-sdk/util-user-agent-node": "3.972.0", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.20.6", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.7", + "@smithy/middleware-retry": "^4.4.23", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.10.8", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.22", + "@smithy/util-defaults-mode-node": "^4.2.25", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.0.tgz", + "integrity": "sha512-JyOf+R/6vJW8OEVFCAyzEOn2reri/Q+L0z9zx4JQSKWvTmJ1qeFO25sOm8VIfB8URKhfGRTQF30pfYaH2zxt/A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.972.0", + "@smithy/config-resolver": "^4.4.6", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.972.0.tgz", + "integrity": "sha512-2udiRijmjpN81Pvajje4TsjbXDZNP6K9bYUanBYH8hXa/tZG5qfGCySD+TyX0sgDxCQmEDMg3LaQdfjNHBDEgQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-sdk-s3": "3.972.0", + "@aws-sdk/types": "3.972.0", + "@smithy/protocol-http": "^5.3.8", + "@smithy/signature-v4": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.972.0.tgz", + "integrity": "sha512-kWlXG+y5nZhgXGEtb72Je+EvqepBPs8E3vZse//1PYLWs2speFqbGE/ywCXmzEJgHgVqSB/u/lqBvs5WlYmSqQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.972.0", + "@aws-sdk/nested-clients": "3.972.0", + "@aws-sdk/types": "3.972.0", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.972.0.tgz", + "integrity": "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-arn-parser": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.972.0.tgz", + "integrity": "sha512-RM5Mmo/KJ593iMSrALlHEOcc9YOIyOsDmS5x2NLOMdEmzv1o00fcpAkCQ02IGu1eFneBFT7uX0Mpag0HI+Cz2g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.972.0.tgz", + "integrity": "sha512-6JHsl1V/a1ZW8D8AFfd4R52fwZPnZ5H4U6DS8m/bWT8qad72NvbOFAC7U2cDtFs2TShqUO3TEiX/EJibtY3ijg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.972.0", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-endpoints": "^3.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.965.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.3.tgz", + "integrity": "sha512-FNUqAjlKAGA7GM05kywE99q8wiPHPZqrzhq3wXRga6PRD6A0kzT85Pb0AzYBVTBRpSrKyyr6M92Y6bnSBVp2BA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.0.tgz", + "integrity": "sha512-eOLdkQyoRbDgioTS3Orr7iVsVEutJyMZxvyZ6WAF95IrF0kfWx5Rd/KXnfbnG/VKa2CvjZiitWfouLzfVEyvJA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.972.0", + "@smithy/types": "^4.12.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.0.tgz", + "integrity": "sha512-GOy+AiSrE9kGiojiwlZvVVSXwylu4+fmP0MJfvras/MwP09RB/YtQuOVR1E0fKQc6OMwaTNBjgAbOEhxuWFbAw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.972.0", + "@aws-sdk/types": "3.972.0", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.0.tgz", + "integrity": "sha512-POaGMcXnozzqBUyJM3HLUZ9GR6OKJWPGJEmhtTnxZXt8B6JcJ/6K3xRJ5H/j8oovVLz8Wg6vFxAHv8lvuASxMg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "fast-xml-parser": "5.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.3.tgz", + "integrity": "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", @@ -591,6 +1352,30 @@ "node": ">=0.1.90" } }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@dabh/diagnostics": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.8.tgz", @@ -602,6 +1387,500 @@ "kuler": "^2.0.0" } }, + "node_modules/@emnapi/core": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", + "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -630,61 +1909,61 @@ } }, "node_modules/@jest/console": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", - "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.2.0.tgz", + "integrity": "sha512-+O1ifRjkvYIkBqASKWgLxrpEhQAAE7hY77ALLUufSk5717KfOShg6IbqLmdsLMPdUiFvA2kTs0R7YZy+l0IzZQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", + "@jest/types": "30.2.0", "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", + "chalk": "^4.1.2", + "jest-message-util": "30.2.0", + "jest-util": "30.2.0", "slash": "^3.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/core": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", - "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.2.0.tgz", + "integrity": "sha512-03W6IhuhjqTlpzh/ojut/pDB2LPRygyWX8ExpgHtQA8H/3K7+1vKmcINx5UzeOX1se6YEsBsOHQ1CRzf3fOwTQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "^29.7.0", - "@jest/reporters": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/console": "30.2.0", + "@jest/pattern": "30.0.1", + "@jest/reporters": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.7.0", - "jest-config": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-resolve-dependencies": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "jest-watcher": "^29.7.0", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-changed-files": "30.2.0", + "jest-config": "30.2.0", + "jest-haste-map": "30.2.0", + "jest-message-util": "30.2.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.2.0", + "jest-resolve-dependencies": "30.2.0", + "jest-runner": "30.2.0", + "jest-runtime": "30.2.0", + "jest-snapshot": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", + "jest-watcher": "30.2.0", + "micromatch": "^4.0.8", + "pretty-format": "30.2.0", + "slash": "^3.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -695,117 +1974,150 @@ } } }, + "node_modules/@jest/diff-sequences": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", + "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@jest/environment": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", - "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.2.0.tgz", + "integrity": "sha512-/QPTL7OBJQ5ac09UDRa3EQes4gt1FTEG/8jZ/4v5IVzx+Cv7dLxlVIvfvSVRiiX2drWyXeBjkMSR8hvOWSog5g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/fake-timers": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", - "jest-mock": "^29.7.0" + "jest-mock": "30.2.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.2.0.tgz", + "integrity": "sha512-V9yxQK5erfzx99Sf+7LbhBwNWEZ9eZay8qQ9+JSC0TrMR1pMDHLMY+BnVPacWU6Jamrh252/IKo4F1Xn/zfiqA==", "dev": true, "license": "MIT", "dependencies": { - "expect": "^29.7.0", - "jest-snapshot": "^29.7.0" + "expect": "30.2.0", + "jest-snapshot": "30.2.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", - "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.2.0.tgz", + "integrity": "sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==", "dev": true, "license": "MIT", "dependencies": { - "jest-get-type": "^29.6.3" + "@jest/get-type": "30.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/fake-timers": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", - "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.2.0.tgz", + "integrity": "sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", + "@jest/types": "30.2.0", + "@sinonjs/fake-timers": "^13.0.0", "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", + "jest-util": "30.2.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/get-type": { + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", + "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/globals": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", - "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.2.0.tgz", + "integrity": "sha512-b63wmnKPaK+6ZZfpYhz9K61oybvbI1aMcIs80++JI1O1rR1vaxHUCNqo3ITu6NU0d4V34yZFoHMn/uoKr/Rwfw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/types": "^29.6.3", - "jest-mock": "^29.7.0" + "@jest/environment": "30.2.0", + "@jest/expect": "30.2.0", + "@jest/types": "30.2.0", + "jest-mock": "30.2.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/reporters": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", - "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.2.0.tgz", + "integrity": "sha512-DRyW6baWPqKMa9CzeiBjHwjd8XeAyco2Vt8XbcLFjiwCOEKOvy82GJ8QQnJE9ofsxCMPjH4MfH8fCWIHHDKpAQ==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", + "@jest/console": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "@jridgewell/trace-mapping": "^0.3.25", "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", + "chalk": "^4.1.2", + "collect-v8-coverage": "^1.0.2", + "exit-x": "^0.2.2", + "glob": "^10.3.10", + "graceful-fs": "^4.2.11", "istanbul-lib-coverage": "^3.0.0", "istanbul-lib-instrument": "^6.0.0", "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", + "istanbul-lib-source-maps": "^5.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", + "jest-message-util": "30.2.0", + "jest-util": "30.2.0", + "jest-worker": "30.2.0", "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", + "string-length": "^4.0.2", "v8-to-istanbul": "^9.0.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -817,108 +2129,125 @@ } }, "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", "dev": true, "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.27.8" + "@sinclair/typebox": "^0.34.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/snapshot-utils": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.2.0.tgz", + "integrity": "sha512-0aVxM3RH6DaiLcjj/b0KrIBZhSX1373Xci4l3cW5xiUWPctZ59zQ7jj4rqcJQ/Z8JuN/4wX3FpJSa3RssVvCug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.2.0", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "natural-compare": "^1.4.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/source-map": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", - "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", + "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.18", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" + "@jridgewell/trace-mapping": "^0.3.25", + "callsites": "^3.1.0", + "graceful-fs": "^4.2.11" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/test-result": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", - "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.2.0.tgz", + "integrity": "sha512-RF+Z+0CCHkARz5HT9mcQCBulb1wgCP3FBvl9VFokMX27acKphwyQsNuWH3c+ojd1LeWBLoTYoxF0zm6S/66mjg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" + "@jest/console": "30.2.0", + "@jest/types": "30.2.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "collect-v8-coverage": "^1.0.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/test-sequencer": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", - "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.2.0.tgz", + "integrity": "sha512-wXKgU/lk8fKXMu/l5Hog1R61bL4q5GCdT6OJvdAFz1P+QrpoFuLU68eoKuVc4RbrTtNnTL5FByhWdLgOPSph+Q==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", + "@jest/test-result": "30.2.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.2.0", "slash": "^3.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.2.0.tgz", + "integrity": "sha512-XsauDV82o5qXbhalKxD7p4TZYYdwcaEXC77PPD2HixEFF+6YGppjrAAQurTl2ECWcEomHBMMNS9AH3kcCFx8jA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", + "@babel/core": "^7.27.4", + "@jest/types": "30.2.0", + "@jridgewell/trace-mapping": "^0.3.25", + "babel-plugin-istanbul": "^7.0.1", + "chalk": "^4.1.2", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.2.0", + "jest-regex-util": "30.0.1", + "jest-util": "30.2.0", + "micromatch": "^4.0.8", + "pirates": "^4.0.7", "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" + "write-file-atomic": "^5.0.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz", + "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jridgewell/gen-mapping": { @@ -971,6 +2300,19 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, "node_modules/@noble/hashes": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", @@ -994,10 +2336,34 @@ "@noble/hashes": "^1.1.5" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgr/core": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "version": "0.34.47", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.47.tgz", + "integrity": "sha512-ZGIBQ+XDvO5JQku9wmwtabcVTHJsgSWAHYtVuM9pBNNR5E88v6Jcj/llpmsjivig5X8A8HHOb4/mbEKPS5EvAw==", "dev": true, "license": "MIT" }, @@ -1012,13 +2378,633 @@ } }, "node_modules/@sinonjs/fake-timers": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "version": "13.0.5", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", + "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "@sinonjs/commons": "^3.0.0" + "@sinonjs/commons": "^3.0.1" + } + }, + "node_modules/@smithy/abort-controller": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.8.tgz", + "integrity": "sha512-peuVfkYHAmS5ybKxWcfraK7WBBP0J+rkfUcbHJJKQ4ir3UAUNQI+Y4Vt/PqSzGqgloJ5O1dk7+WzNL8wcCSXbw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.6.tgz", + "integrity": "sha512-qJpzYC64kaj3S0fueiu3kXm8xPrR3PcXDPEgnaNMRn0EjNSZFoFjvbUp0YUDsRhN1CB90EnHJtbxWKevnH99UQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.21.0.tgz", + "integrity": "sha512-bg2TfzgsERyETAxc/Ims/eJX8eAnIeTi4r4LHpMpfF/2NyO6RsWis0rjKcCPaGksljmOb23BZRiCeT/3NvwkXw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-serde": "^4.2.9", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-stream": "^4.5.10", + "@smithy/util-utf8": "^4.2.0", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.8.tgz", + "integrity": "sha512-FNT0xHS1c/CPN8upqbMFP83+ul5YgdisfCfkZ86Jh2NSmnqw/AJ6x5pEogVCTVvSm7j9MopRU89bmDelxuDMYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.9.tgz", + "integrity": "sha512-I4UhmcTYXBrct03rwzQX1Y/iqQlzVQaPxWjCjula++5EmWq9YGBrx6bbGqluGc1f0XEfhSkiY4jhLgbsJUMKRA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.8", + "@smithy/querystring-builder": "^4.2.8", + "@smithy/types": "^4.12.0", + "@smithy/util-base64": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.8.tgz", + "integrity": "sha512-7ZIlPbmaDGxVoxErDZnuFG18WekhbA/g2/i97wGj+wUBeS6pcUeAym8u4BXh/75RXWhgIJhyC11hBzig6MljwA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.8.tgz", + "integrity": "sha512-N9iozRybwAQ2dn9Fot9kI6/w9vos2oTXLhtK7ovGqwZjlOcxu6XhPlpLpC+INsxktqHinn5gS2DXDjDF2kG5sQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", + "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.8.tgz", + "integrity": "sha512-RO0jeoaYAB1qBRhfVyq0pMgBoUK34YEJxVxyjOWYZiOKOq2yMZ4MnVXMZCUDenpozHue207+9P5ilTV1zeda0A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.10.tgz", + "integrity": "sha512-kwWpNltpxrvPabnjEFvwSmA+66l6s2ReCvgVSzW/z92LU4T28fTdgZ18IdYRYOrisu2NMQ0jUndRScbO65A/zg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.21.0", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-middleware": "^4.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.4.26", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.26.tgz", + "integrity": "sha512-ozZMoTAr+B2aVYfLYfkssFvc8ZV3p/vLpVQ7/k277xxUOA9ykSPe5obL2j6yHfbdrM/SZV7qj0uk/hSqavHrLw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/service-error-classification": "^4.2.8", + "@smithy/smithy-client": "^4.10.11", + "@smithy/types": "^4.12.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.9.tgz", + "integrity": "sha512-eMNiej0u/snzDvlqRGSN3Vl0ESn3838+nKyVfF2FKNXFbi4SERYT6PR392D39iczngbqqGG0Jl1DlCnp7tBbXQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.8.tgz", + "integrity": "sha512-w6LCfOviTYQjBctOKSwy6A8FIkQy7ICvglrZFl6Bw4FmcQ1Z420fUtIhxaUZZshRe0VCq4kvDiPiXrPZAe8oRA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.8.tgz", + "integrity": "sha512-aFP1ai4lrbVlWjfpAfRSL8KFcnJQYfTl5QxLJXY32vghJrDuFyPZ6LtUL+JEGYiFRG1PfPLHLoxj107ulncLIg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.8.tgz", + "integrity": "sha512-q9u+MSbJVIJ1QmJ4+1u+cERXkrhuILCBDsJUBAW1MPE6sFonbCNaegFuwW9ll8kh5UdyY3jOkoOGlc7BesoLpg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/querystring-builder": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.8.tgz", + "integrity": "sha512-EtCTbyIveCKeOXDSWSdze3k612yCPq1YbXsbqX3UHhkOSW8zKsM9NOJG5gTIya0vbY2DIaieG8pKo1rITHYL0w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.8.tgz", + "integrity": "sha512-QNINVDhxpZ5QnP3aviNHQFlRogQZDfYlCkQT+7tJnErPQbDhysondEjhikuANxgMsZrkGeiAxXy4jguEGsDrWQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.8.tgz", + "integrity": "sha512-Xr83r31+DrE8CP3MqPgMJl+pQlLLmOfiEUnoyAlGzzJIrEsbKsPy1hqH0qySaQm4oWrCBlUqRt+idEgunKB+iw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "@smithy/util-uri-escape": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.8.tgz", + "integrity": "sha512-vUurovluVy50CUlazOiXkPq40KGvGWSdmusa3130MwrR1UNnNgKAlj58wlOe61XSHRpUfIIh6cE0zZ8mzKaDPA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.8.tgz", + "integrity": "sha512-mZ5xddodpJhEt3RkCjbmUQuXUOaPNTkbMGR0bcS8FE0bJDLMZlhmpgrvPNCYglVw5rsYTpSnv19womw9WWXKQQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.3.tgz", + "integrity": "sha512-DfQjxXQnzC5UbCUPeC3Ie8u+rIWZTvuDPAGU/BxzrOGhRvgUanaP68kDZA+jaT3ZI+djOf+4dERGlm9mWfFDrg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.8.tgz", + "integrity": "sha512-6A4vdGj7qKNRF16UIcO8HhHjKW27thsxYci+5r/uVRkdcBEkOEiY8OMPuydLX4QHSrJqGHPJzPRwwVTqbLZJhg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-uri-escape": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "4.10.11", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.10.11.tgz", + "integrity": "sha512-6o804SCyHGMXAb5mFJ+iTy9kVKv7F91a9szN0J+9X6p8A0NrdpUxdaC57aye2ipQkP2C4IAqETEpGZ0Zj77Haw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.21.0", + "@smithy/middleware-endpoint": "^4.4.10", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-stream": "^4.5.10", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.8.tgz", + "integrity": "sha512-NQho9U68TGMEU639YkXnVMV3GEFFULmmaWdlu1E9qzyIePOHsoSnagTGSDv1Zi8DCNN6btxOSdgmy5E/hsZwhA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-base64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", + "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", + "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz", + "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", + "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-config-provider": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz", + "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.3.25", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.25.tgz", + "integrity": "sha512-8ugoNMtss2dJHsXnqsibGPqoaafvWJPACmYKxJ4E6QWaDrixsAemmiMMAVbvwYadjR0H9G2+AlzsInSzRi8PSw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.8", + "@smithy/smithy-client": "^4.10.11", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.2.28", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.28.tgz", + "integrity": "sha512-mjUdcP8h3E0K/XvNMi9oBXRV3DMCzeRiYIieZ1LQ7jq5tu6GH/GTWym7a1xIIE0pKSoLcpGsaImuQhGPSIJzAA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/config-resolver": "^4.4.6", + "@smithy/credential-provider-imds": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/smithy-client": "^4.10.11", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.8.tgz", + "integrity": "sha512-8JaVTn3pBDkhZgHQ8R0epwWt+BqPSLCjdjXXusK1onwJlRuN69fbvSK66aIKKO7SwVFM6x2J2ox5X8pOaWcUEw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", + "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-middleware": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.8.tgz", + "integrity": "sha512-PMqfeJxLcNPMDgvPbbLl/2Vpin+luxqTGPpW3NAQVLbRrFRzTa4rNAASYeIGjRV9Ytuhzny39SpyU04EQreF+A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-retry": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.8.tgz", + "integrity": "sha512-CfJqwvoRY0kTGe5AkQokpURNCT1u/MkRzMTASWMPPo2hNSnKtF1D45dQl3DE2LKLr4m+PW9mCeBMJr5mCAVThg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-stream": { + "version": "4.5.10", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.10.tgz", + "integrity": "sha512-jbqemy51UFSZSp2y0ZmRfckmrzuKww95zT9BYMmuJ8v3altGcqjwoV1tzpOwuHaKrwQrCjIzOib499ymr2f98g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/types": "^4.12.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-uri-escape": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", + "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", + "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/uuid": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.0.tgz", + "integrity": "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@so-ric/colorspace": { @@ -1031,6 +3017,45 @@ "text-hex": "1.0.x" } }, + "node_modules/@tsconfig/node10": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1076,6 +3101,62 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/bcryptjs": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz", + "integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-kCFuWS0ebDbmxs0AXYn6e2r2nrGAb5KwQhknjSPSPgJcGd8+HVSILlUyFhGqML2gk39HcG7D1ydW9/qpYkN00Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cookiejar": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz", + "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", @@ -1085,16 +3166,38 @@ "@types/ms": "*" } }, - "node_modules/@types/graceful-fs": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", - "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "node_modules/@types/express": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", "dev": true, "license": "MIT", "dependencies": { - "@types/node": "*" + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^2" } }, + "node_modules/@types/express-serve-static-core": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz", + "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -1122,12 +3225,51 @@ "@types/istanbul-lib-report": "*" } }, + "node_modules/@types/jest": { + "version": "30.0.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", + "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^30.0.0", + "pretty-format": "^30.0.0" + } + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, + "node_modules/@types/methods": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", + "integrity": "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/ms": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", "license": "MIT" }, + "node_modules/@types/multer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-2.0.0.tgz", + "integrity": "sha512-C3Z9v9Evij2yST3RSBktxP9STm6OdMc5uR1xF1SGr98uv8dUlAL2hqwrZ3GVB3uyMyiegnscEK6PGtYvNrjTjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/node": { "version": "25.0.9", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.9.tgz", @@ -1137,6 +3279,64 @@ "undici-types": "~7.16.0" } }, + "node_modules/@types/nodemailer": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-7.0.5.tgz", + "integrity": "sha512-7WtR4MFJUNN2UFy0NIowBRJswj5KXjXDhlZY43Hmots5eGu5q/dTeFd/I6GgJA/qj3RqO6dDy4SvfcV3fOVeIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@aws-sdk/client-sesv2": "^3.839.0", + "@types/node": "*" + } + }, + "node_modules/@types/pg": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.16.0.tgz", + "integrity": "sha512-RmhMd/wD+CF8Dfo+cVIy3RR5cl8CyfXQ0tGgW6XBL8L4LM/UTEbNXYRbLwU6w+CgrKBNbrQWt4FUtTfaU5jSYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*" + } + }, "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", @@ -1144,12 +3344,43 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/superagent": { + "version": "8.1.9", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.9.tgz", + "integrity": "sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/cookiejar": "^2.1.5", + "@types/methods": "^1.1.4", + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/supertest": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-6.0.3.tgz", + "integrity": "sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/methods": "^1.1.4", + "@types/superagent": "^8.1.0" + } + }, "node_modules/@types/triple-beam": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", "license": "MIT" }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/validator": { "version": "13.15.10", "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.10.tgz", @@ -1173,28 +3404,346 @@ "dev": true, "license": "MIT" }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.11" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "license": "MIT", "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" }, "engines": { "node": ">= 0.6" } }, + "node_modules/accepts/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/accepts/node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "license": "MIT", "engines": { "node": ">= 0.6" } }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -1212,13 +3761,16 @@ } }, "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, "node_modules/ansi-styles": { @@ -1257,6 +3809,13 @@ "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", "license": "MIT" }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -1267,12 +3826,6 @@ "sprintf-js": "~1.0.2" } }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "license": "MIT" - }, "node_modules/asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", @@ -1294,75 +3847,58 @@ "license": "MIT" }, "node_modules/babel-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", - "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.2.0.tgz", + "integrity": "sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/transform": "^29.7.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.6.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", + "@jest/transform": "30.2.0", + "@types/babel__core": "^7.20.5", + "babel-plugin-istanbul": "^7.0.1", + "babel-preset-jest": "30.2.0", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", "slash": "^3.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { - "@babel/core": "^7.8.0" + "@babel/core": "^7.11.0 || ^8.0.0-0" } }, "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", + "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", "dev": true, "license": "BSD-3-Clause", + "workspaces": [ + "test/babel-8" + ], "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-instrument": "^6.0.2", "test-exclude": "^6.0.0" }, "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" + "node": ">=12" } }, "node_modules/babel-plugin-jest-hoist": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", - "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.2.0.tgz", + "integrity": "sha512-ftzhzSGMUnOzcCXd6WHdBGMyuwy15Wnn0iyyWGKgBDLxf9/s5ABuraCSpBX2uG0jUg4rqJnxsLc5+oYBqoxVaA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" + "@types/babel__core": "^7.20.5" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/babel-preset-current-node-syntax": { @@ -1393,20 +3929,20 @@ } }, "node_modules/babel-preset-jest": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", - "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.2.0.tgz", + "integrity": "sha512-US4Z3NOieAQumwFnYdUWKvUKh8+YSnS/gB3t6YBiz0bskpu7Pine8pPCheNxlPEW4wnUkma2a94YuW2q3guvCQ==", "dev": true, "license": "MIT", "dependencies": { - "babel-plugin-jest-hoist": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0" + "babel-plugin-jest-hoist": "30.2.0", + "babel-preset-current-node-syntax": "^1.2.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.11.0 || ^8.0.0-beta.1" } }, "node_modules/balanced-match": { @@ -1417,9 +3953,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.9.15", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.15.tgz", - "integrity": "sha512-kX8h7K2srmDyYnXRIppo4AH/wYgzWVCs+eKr3RusRSQ5PvRYoEFmR/I0PbdTjKFAoKqp5+kbxnNTFO9jOfSVJg==", + "version": "2.9.16", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.16.tgz", + "integrity": "sha512-KeUZdBuxngy825i8xvzaK1Ncnkx0tBmb3k8DkEuqjKRkmtvNTjey2ZsNeh8Dw4lfKvbCOu9oeNx2TKm2vHqcRw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -1427,10 +3963,13 @@ } }, "node_modules/bcryptjs": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", - "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==", - "license": "MIT" + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz", + "integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==", + "license": "BSD-3-Clause", + "bin": { + "bcrypt": "bin/bcrypt" + } }, "node_modules/binary-extensions": { "version": "2.3.0", @@ -1446,29 +3985,98 @@ } }, "node_modules/body-parser": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", - "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", "license": "MIT", "dependencies": { - "bytes": "~3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "~1.2.0", - "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", - "on-finished": "~2.4.1", - "qs": "~6.14.0", - "raw-body": "~2.5.3", - "type-is": "~1.6.18", - "unpipe": "~1.0.0" + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" }, "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, + "node_modules/body-parser/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/body-parser/node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/body-parser/node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/bowser": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.13.1.tgz", + "integrity": "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw==", + "dev": true, + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -1692,9 +4300,9 @@ } }, "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", + "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", "dev": true, "funding": [ { @@ -1708,9 +4316,9 @@ } }, "node_modules/cjs-module-lexer": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", - "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", + "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", "dev": true, "license": "MIT" }, @@ -1729,6 +4337,69 @@ "node": ">=12" } }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -1883,30 +4554,31 @@ "license": "MIT" }, "node_modules/concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", "engines": [ - "node >= 0.8" + "node >= 6.0" ], "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", "inherits": "^2.0.3", - "readable-stream": "^2.2.2", + "readable-stream": "^3.0.2", "typedarray": "^0.0.6" } }, "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/content-type": { @@ -1935,10 +4607,13 @@ } }, "node_modules/cookie-signature": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", - "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", - "license": "MIT" + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } }, "node_modules/cookiejar": { "version": "2.1.4", @@ -1947,12 +4622,6 @@ "dev": true, "license": "MIT" }, - "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/cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", @@ -1966,27 +4635,12 @@ "node": ">= 0.10" } }, - "node_modules/create-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", - "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "prompts": "^2.0.1" - }, - "bin": { - "create-jest": "bin/create-jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } + "license": "MIT" }, "node_modules/cross-spawn": { "version": "7.0.6", @@ -2056,16 +4710,6 @@ "node": ">= 0.8" } }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -2087,20 +4731,20 @@ "wrappy": "1" } }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "node_modules/diff": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=0.3.1" } }, "node_modules/dotenv": { - "version": "16.6.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", - "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "version": "17.2.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", + "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -2129,6 +4773,13 @@ "node": ">= 0.4" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -2165,9 +4816,9 @@ } }, "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true, "license": "MIT" }, @@ -2242,6 +4893,48 @@ "node": ">= 0.4" } }, + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -2315,72 +5008,78 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "node_modules/execa/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true, + "license": "ISC" + }, + "node_modules/exit-x": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", + "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", + "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8.0" } }, "node_modules/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.2.0.tgz", + "integrity": "sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/expect-utils": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0" + "@jest/expect-utils": "30.2.0", + "@jest/get-type": "30.1.0", + "jest-matcher-utils": "30.2.0", + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", + "jest-util": "30.2.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/express": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", - "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "~1.20.3", - "content-disposition": "~0.5.4", - "content-type": "~1.0.4", - "cookie": "~0.7.1", - "cookie-signature": "~1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.3.1", - "fresh": "~0.5.2", - "http-errors": "~2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "~2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "~0.1.12", - "proxy-addr": "~2.0.7", - "qs": "~6.14.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "~0.19.0", - "serve-static": "~1.16.2", - "setprototypeof": "1.2.0", - "statuses": "~2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" }, "engines": { - "node": ">= 0.10.0" + "node": ">= 18" }, "funding": { "type": "opencollective", @@ -2388,10 +5087,13 @@ } }, "node_modules/express-rate-limit": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", - "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz", + "integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==", "license": "MIT", + "dependencies": { + "ip-address": "10.0.1" + }, "engines": { "node": ">= 16" }, @@ -2415,6 +5117,68 @@ "node": ">= 8.0.0" } }, + "node_modules/express/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/express/node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/express/node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -2429,6 +5193,25 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-xml-parser": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", + "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^2.1.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", @@ -2459,23 +5242,49 @@ } }, "node_modules/finalhandler": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", - "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", "license": "MIT", "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "~2.4.1", - "parseurl": "~1.3.3", - "statuses": "~2.0.2", - "unpipe": "~1.0.0" + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" }, "engines": { - "node": ">= 0.8" + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, + "node_modules/finalhandler/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -2496,6 +5305,23 @@ "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", "license": "MIT" }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", @@ -2514,16 +5340,18 @@ } }, "node_modules/formidable": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.5.tgz", - "integrity": "sha512-Oz5Hwvwak/DCaXVVUtPn4oLMLLy1CdclLKO1LFgU7XzDpVMUU5UjlSLpGMocyQNNk8F6IJW9M/YdooSn2MRI+Q==", + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", + "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", "dev": true, "license": "MIT", "dependencies": { "@paralleldrive/cuid2": "^2.2.2", "dezalgo": "^1.0.4", - "once": "^1.4.0", - "qs": "^6.11.0" + "once": "^1.4.0" + }, + "engines": { + "node": ">=14.0.0" }, "funding": { "url": "https://ko-fi.com/tunnckoCore/commissions" @@ -2539,12 +5367,12 @@ } }, "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/fs.realpath": { @@ -2658,23 +5486,35 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-tsconfig": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, "license": "ISC", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, - "engines": { - "node": "*" + "bin": { + "glob": "dist/esm/bin.mjs" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -2693,6 +5533,32 @@ "node": ">= 6" } }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -2763,12 +5629,12 @@ } }, "node_modules/helmet": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/helmet/-/helmet-7.2.0.tgz", - "integrity": "sha512-ZRiwvN089JfMXokizgqEPXsl2Guk094yExfoDXR0cBYWxtBbaSww/w+vT4WEJsBW2iTUi1GgZ6swmoug3Oy4Xw==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz", + "integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==", "license": "MIT", "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/html-escaper": { @@ -2809,15 +5675,19 @@ } }, "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/ignore-by-default": { @@ -2884,6 +5754,15 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, + "node_modules/ip-address": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -2913,22 +5792,6 @@ "node": ">=8" } }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2982,6 +5845,12 @@ "node": ">=0.12.0" } }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -2994,12 +5863,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "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/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -3063,15 +5926,15 @@ } }, "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", "dev": true, "license": "BSD-3-Clause", "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" + "istanbul-lib-coverage": "^3.0.0" }, "engines": { "node": ">=10" @@ -3116,23 +5979,39 @@ "node": ">=8" } }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", - "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.2.0.tgz", + "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "^29.7.0", - "@jest/types": "^29.6.3", - "import-local": "^3.0.2", - "jest-cli": "^29.7.0" + "@jest/core": "30.2.0", + "@jest/types": "30.2.0", + "import-local": "^3.2.0", + "jest-cli": "30.2.0" }, "bin": { "jest": "bin/jest.js" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -3144,76 +6023,75 @@ } }, "node_modules/jest-changed-files": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", - "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.2.0.tgz", + "integrity": "sha512-L8lR1ChrRnSdfeOvTrwZMlnWV8G/LLjQ0nG9MBclwWZidA2N5FviRki0Bvh20WRMOX31/JYvzdqTJrk5oBdydQ==", "dev": true, "license": "MIT", "dependencies": { - "execa": "^5.0.0", - "jest-util": "^29.7.0", + "execa": "^5.1.1", + "jest-util": "30.2.0", "p-limit": "^3.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-circus": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", - "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.2.0.tgz", + "integrity": "sha512-Fh0096NC3ZkFx05EP2OXCxJAREVxj1BcW/i6EWqqymcgYKWjyyDpral3fMxVcHXg6oZM7iULer9wGRFvfpl+Tg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/environment": "30.2.0", + "@jest/expect": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", - "chalk": "^4.0.0", + "chalk": "^4.1.2", "co": "^4.6.0", - "dedent": "^1.0.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.7.0", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", + "dedent": "^1.6.0", + "is-generator-fn": "^2.1.0", + "jest-each": "30.2.0", + "jest-matcher-utils": "30.2.0", + "jest-message-util": "30.2.0", + "jest-runtime": "30.2.0", + "jest-snapshot": "30.2.0", + "jest-util": "30.2.0", "p-limit": "^3.1.0", - "pretty-format": "^29.7.0", - "pure-rand": "^6.0.0", + "pretty-format": "30.2.0", + "pure-rand": "^7.0.0", "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "stack-utils": "^2.0.6" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-cli": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", - "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.2.0.tgz", + "integrity": "sha512-Os9ukIvADX/A9sLt6Zse3+nmHtHaE6hqOsjQtNiugFTbKRHYIYtZXNGNK9NChseXy7djFPjndX1tL0sCTlfpAA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "create-jest": "^29.7.0", - "exit": "^0.1.2", - "import-local": "^3.0.2", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "yargs": "^17.3.1" + "@jest/core": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/types": "30.2.0", + "chalk": "^4.1.2", + "exit-x": "^0.2.2", + "import-local": "^3.2.0", + "jest-config": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", + "yargs": "^17.7.2" }, "bin": { "jest": "bin/jest.js" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -3225,215 +6103,211 @@ } }, "node_modules/jest-config": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", - "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.2.0.tgz", + "integrity": "sha512-g4WkyzFQVWHtu6uqGmQR4CQxz/CH3yDSlhzXMWzNjDx843gYjReZnMRanjRCq5XZFuQrGDxgUaiYWE8BRfVckA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-jest": "^29.7.0", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "micromatch": "^4.0.4", + "@babel/core": "^7.27.4", + "@jest/get-type": "30.1.0", + "@jest/pattern": "30.0.1", + "@jest/test-sequencer": "30.2.0", + "@jest/types": "30.2.0", + "babel-jest": "30.2.0", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "deepmerge": "^4.3.1", + "glob": "^10.3.10", + "graceful-fs": "^4.2.11", + "jest-circus": "30.2.0", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.2.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.2.0", + "jest-runner": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", + "micromatch": "^4.0.8", "parse-json": "^5.2.0", - "pretty-format": "^29.7.0", + "pretty-format": "30.2.0", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { "@types/node": "*", + "esbuild-register": ">=3.4.0", "ts-node": ">=9.0.0" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, + "esbuild-register": { + "optional": true + }, "ts-node": { "optional": true } } }, "node_modules/jest-diff": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", - "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz", + "integrity": "sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" + "@jest/diff-sequences": "30.0.1", + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "pretty-format": "30.2.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-docblock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", - "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz", + "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==", "dev": true, "license": "MIT", "dependencies": { - "detect-newline": "^3.0.0" + "detect-newline": "^3.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-each": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", - "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.2.0.tgz", + "integrity": "sha512-lpWlJlM7bCUf1mfmuqTA8+j2lNURW9eNafOy99knBM01i5CQeY5UH1vZjgT9071nDJac1M4XsbyI44oNOdhlDQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "jest-util": "^29.7.0", - "pretty-format": "^29.7.0" + "@jest/get-type": "30.1.0", + "@jest/types": "30.2.0", + "chalk": "^4.1.2", + "jest-util": "30.2.0", + "pretty-format": "30.2.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-environment-node": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", - "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.2.0.tgz", + "integrity": "sha512-ElU8v92QJ9UrYsKrxDIKCxu6PfNj4Hdcktcn0JX12zqNdqWHB0N+hwOnnBBXvjLd2vApZtuLUGs1QSY+MsXoNA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/environment": "30.2.0", + "@jest/fake-timers": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" + "jest-mock": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-get-type": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", - "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.2.0.tgz", + "integrity": "sha512-sQA/jCb9kNt+neM0anSj6eZhLZUIhQgwDt7cPGjumgLM4rXsfb9kpnlacmvZz3Q5tb80nS+oG/if+NBKrHC+Xw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", + "@jest/types": "30.2.0", "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", + "anymatch": "^3.1.3", + "fb-watchman": "^2.0.2", + "graceful-fs": "^4.2.11", + "jest-regex-util": "30.0.1", + "jest-util": "30.2.0", + "jest-worker": "30.2.0", + "micromatch": "^4.0.8", "walker": "^1.0.8" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "optionalDependencies": { - "fsevents": "^2.3.2" + "fsevents": "^2.3.3" } }, "node_modules/jest-leak-detector": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", - "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.2.0.tgz", + "integrity": "sha512-M6jKAjyzjHG0SrQgwhgZGy9hFazcudwCNovY/9HPIicmNSBuockPSedAP9vlPK6ONFJ1zfyH/M2/YYJxOz5cdQ==", "dev": true, "license": "MIT", "dependencies": { - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" + "@jest/get-type": "30.1.0", + "pretty-format": "30.2.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-matcher-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", - "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz", + "integrity": "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "jest-diff": "30.2.0", + "pretty-format": "30.2.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-message-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", - "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.2.0.tgz", + "integrity": "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.2.0", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "micromatch": "^4.0.8", + "pretty-format": "30.2.0", "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "stack-utils": "^2.0.6" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-mock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", - "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.2.0.tgz", + "integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", + "@jest/types": "30.2.0", "@types/node": "*", - "jest-util": "^29.7.0" + "jest-util": "30.2.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-pnp-resolver": { @@ -3455,147 +6329,148 @@ } }, "node_modules/jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", "dev": true, "license": "MIT", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-resolve": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", - "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.2.0.tgz", + "integrity": "sha512-TCrHSxPlx3tBY3hWNtRQKbtgLhsXa1WmbJEqBlTBrGafd5fiQFByy2GNCEoGR+Tns8d15GaL9cxEzKOO3GEb2A==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "resolve": "^1.20.0", - "resolve.exports": "^2.0.0", - "slash": "^3.0.0" + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.2.0", + "jest-pnp-resolver": "^1.2.3", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", + "slash": "^3.0.0", + "unrs-resolver": "^1.7.11" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-resolve-dependencies": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", - "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.2.0.tgz", + "integrity": "sha512-xTOIGug/0RmIe3mmCqCT95yO0vj6JURrn1TKWlNbhiAefJRWINNPgwVkrVgt/YaerPzY3iItufd80v3lOrFJ2w==", "dev": true, "license": "MIT", "dependencies": { - "jest-regex-util": "^29.6.3", - "jest-snapshot": "^29.7.0" + "jest-regex-util": "30.0.1", + "jest-snapshot": "30.2.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-runner": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", - "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.2.0.tgz", + "integrity": "sha512-PqvZ2B2XEyPEbclp+gV6KO/F1FIFSbIwewRgmROCMBo/aZ6J1w8Qypoj2pEOcg3G2HzLlaP6VUtvwCI8dM3oqQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "^29.7.0", - "@jest/environment": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/console": "30.2.0", + "@jest/environment": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", - "chalk": "^4.0.0", + "chalk": "^4.1.2", "emittery": "^0.13.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-leak-detector": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-resolve": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-util": "^29.7.0", - "jest-watcher": "^29.7.0", - "jest-worker": "^29.7.0", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.2.0", + "jest-haste-map": "30.2.0", + "jest-leak-detector": "30.2.0", + "jest-message-util": "30.2.0", + "jest-resolve": "30.2.0", + "jest-runtime": "30.2.0", + "jest-util": "30.2.0", + "jest-watcher": "30.2.0", + "jest-worker": "30.2.0", "p-limit": "^3.1.0", "source-map-support": "0.5.13" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-runtime": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", - "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.2.0.tgz", + "integrity": "sha512-p1+GVX/PJqTucvsmERPMgCPvQJpFt4hFbM+VN3n8TMo47decMUcJbt+rgzwrEme0MQUA/R+1de2axftTHkKckg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/globals": "^29.7.0", - "@jest/source-map": "^29.6.3", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/environment": "30.2.0", + "@jest/fake-timers": "30.2.0", + "@jest/globals": "30.2.0", + "@jest/source-map": "30.0.1", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", + "chalk": "^4.1.2", + "cjs-module-lexer": "^2.1.0", + "collect-v8-coverage": "^1.0.2", + "glob": "^10.3.10", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.2.0", + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.2.0", + "jest-snapshot": "30.2.0", + "jest-util": "30.2.0", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-snapshot": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", - "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.2.0.tgz", + "integrity": "sha512-5WEtTy2jXPFypadKNpbNkZ72puZCa6UjSr/7djeecHWOu7iYhSXSnHScT8wBz3Rn8Ena5d5RYRcsyKIeqG1IyA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "natural-compare": "^1.4.0", - "pretty-format": "^29.7.0", - "semver": "^7.5.3" + "@babel/core": "^7.27.4", + "@babel/generator": "^7.27.5", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1", + "@babel/types": "^7.27.3", + "@jest/expect-utils": "30.2.0", + "@jest/get-type": "30.1.0", + "@jest/snapshot-utils": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "babel-preset-current-node-syntax": "^1.2.0", + "chalk": "^4.1.2", + "expect": "30.2.0", + "graceful-fs": "^4.2.11", + "jest-diff": "30.2.0", + "jest-matcher-utils": "30.2.0", + "jest-message-util": "30.2.0", + "jest-util": "30.2.0", + "pretty-format": "30.2.0", + "semver": "^7.7.2", + "synckit": "^0.11.8" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-snapshot/node_modules/semver": { @@ -3612,39 +6487,52 @@ } }, "node_modules/jest-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz", + "integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", + "@jest/types": "30.2.0", "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-util/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/jest-validate": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", - "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.2.0.tgz", + "integrity": "sha512-FBGWi7dP2hpdi8nBoWxSsLvBFewKAg0+uSQwBaof4Y4DPgBabXgpSYC5/lR7VmnIlSpASmCi/ntRWPbv7089Pw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", + "@jest/get-type": "30.1.0", + "@jest/types": "30.2.0", + "camelcase": "^6.3.0", + "chalk": "^4.1.2", "leven": "^3.1.0", - "pretty-format": "^29.7.0" + "pretty-format": "30.2.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-validate/node_modules/camelcase": { @@ -3661,39 +6549,40 @@ } }, "node_modules/jest-watcher": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", - "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.2.0.tgz", + "integrity": "sha512-PYxa28dxJ9g777pGm/7PrbnMeA0Jr7osHP9bS7eJy9DuAjMgdGtxgf0uKMyoIsTWAkIbUW5hSDdJ3urmgXBqxg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/test-result": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", "emittery": "^0.13.1", - "jest-util": "^29.7.0", - "string-length": "^4.0.1" + "jest-util": "30.2.0", + "string-length": "^4.0.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.2.0.tgz", + "integrity": "sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", - "jest-util": "^29.7.0", + "@ungap/structured-clone": "^1.3.0", + "jest-util": "30.2.0", "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" + "supports-color": "^8.1.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-worker/node_modules/supports-color": { @@ -3827,16 +6716,6 @@ "safe-buffer": "^5.0.1" } }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/kuler": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", @@ -3983,6 +6862,13 @@ "node": ">=10" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -4012,10 +6898,13 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", "license": "MIT", + "engines": { + "node": ">=18" + }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } @@ -4031,6 +6920,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -4051,15 +6941,16 @@ } }, "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, "license": "MIT", "bin": { "mime": "cli.js" }, "engines": { - "node": ">=4" + "node": ">=4.0.0" } }, "node_modules/mime-db": { @@ -4124,6 +7015,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -4164,22 +7065,37 @@ "license": "MIT" }, "node_modules/multer": { - "version": "1.4.5-lts.2", - "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz", - "integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==", - "deprecated": "Multer 1.x is impacted by a number of vulnerabilities, which have been patched in 2.x. You should upgrade to the latest 2.x version.", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-2.0.2.tgz", + "integrity": "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==", "license": "MIT", "dependencies": { "append-field": "^1.0.0", - "busboy": "^1.0.0", - "concat-stream": "^1.5.2", - "mkdirp": "^0.5.4", + "busboy": "^1.6.0", + "concat-stream": "^2.0.0", + "mkdirp": "^0.5.6", "object-assign": "^4.1.1", - "type-is": "^1.6.4", - "xtend": "^4.0.0" + "type-is": "^1.6.18", + "xtend": "^4.0.2" }, "engines": { - "node": ">= 6.0.0" + "node": ">= 10.16.0" + } + }, + "node_modules/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" } }, "node_modules/natural-compare": { @@ -4213,9 +7129,9 @@ "license": "MIT" }, "node_modules/nodemailer": { - "version": "6.10.1", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.1.tgz", - "integrity": "sha512-Z+iLaBGVaSjbIzQ4pX6XV41HrooLsQ10ZWPUehGmuantvzWoDVBnmsdUcOIDM1t+yPor5pDhVlDESgOMEGxhHA==", + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.12.tgz", + "integrity": "sha512-H+rnK5bX2Pi/6ms3sN4/jRQvYSMltV6vqup/0SFOrxYYY/qoNvhXPlYq3e+Pm9RFJRwrMGbMIwi81M4dxpomhA==", "license": "MIT-0", "engines": { "node": ">=6.0.0" @@ -4380,7 +7296,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -4466,6 +7381,13 @@ "node": ">=6" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -4524,26 +7446,47 @@ "node": ">=8" } }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, - "license": "MIT" + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" }, "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "license": "MIT" + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } }, "node_modules/pg": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.17.1.tgz", - "integrity": "sha512-EIR+jXdYNSMOrpRp7g6WgQr7SaZNZfS7IzZIO0oTNEeibq956JxeD15t3Jk3zZH0KH8DmOIx38qJfQenoE8bXQ==", + "version": "8.17.2", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.17.2.tgz", + "integrity": "sha512-vjbKdiBJRqzcYw1fNU5KuHyYvdJ1qpcQg1CeBrHFqV1pWgHeVR6j/+kX0E1AAXfyuLUGY1ICrN2ELKA/z2HWzw==", "license": "MIT", "dependencies": { - "pg-connection-string": "^2.10.0", + "pg-connection-string": "^2.10.1", "pg-pool": "^3.11.0", "pg-protocol": "^1.11.0", "pg-types": "2.2.0", @@ -4572,9 +7515,9 @@ "optional": true }, "node_modules/pg-connection-string": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.10.0.tgz", - "integrity": "sha512-ur/eoPKzDx2IjPaYyXS6Y8NSblxM7X64deV2ObV57vhjsWiwLvUD6meukAzogiOsu60GO8m/3Cb6FdJsWNjwXg==", + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.10.1.tgz", + "integrity": "sha512-iNzslsoeSH2/gmDDKiyMqF64DATUCWj3YJ0wP14kqcsf2TUklwimd+66yYojKwZCA7h2yRNLGug71hCBA2a4sw==", "license": "MIT" }, "node_modules/pg-hstore": { @@ -4721,18 +7664,18 @@ } }, "node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", + "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/pretty-format/node_modules/ansi-styles": { @@ -4748,26 +7691,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "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/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -4789,9 +7712,9 @@ "license": "MIT" }, "node_modules/pure-rand": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", - "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", + "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", "dev": true, "funding": [ { @@ -4830,18 +7753,18 @@ } }, "node_modules/raw-body": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", - "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "license": "MIT", "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", + "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.10" } }, "node_modules/react-is": { @@ -4852,26 +7775,19 @@ "license": "MIT" }, "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==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "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" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" } }, - "node_modules/readable-stream/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/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -4895,27 +7811,6 @@ "node": ">=0.10.0" } }, - "node_modules/resolve": { - "version": "1.22.11", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", - "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/resolve-cwd": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", @@ -4939,14 +7834,14 @@ "node": ">=8" } }, - "node_modules/resolve.exports": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", - "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", "dev": true, "license": "MIT", - "engines": { - "node": ">=10" + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, "node_modules/retry-as-promised": { @@ -4955,6 +7850,45 @@ "integrity": "sha512-hMD7odLOt3LkTjcif8aRZqi/hybjpLNgSk5oF5FCowfCjok6LukpN2bDX7R5wDmbgBQFn7YoBxSagmtXHaJYJw==", "license": "MIT" }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/router/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/router/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -5001,27 +7935,62 @@ } }, "node_modules/send": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", - "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", "license": "MIT", "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "~0.5.2", - "http-errors": "~2.0.1", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "~2.4.1", - "range-parser": "~1.2.1", - "statuses": "~2.0.2" + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/send/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/send/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/send/node_modules/ms": { @@ -5146,18 +8115,22 @@ } }, "node_modules/serve-static": { - "version": "1.16.3", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", - "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", "license": "MIT", "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "~0.19.1" + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/setprototypeof": { @@ -5262,11 +8235,17 @@ } }, "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, - "license": "ISC" + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, "node_modules/simple-update-notifier": { "version": "2.0.0", @@ -5294,13 +8273,6 @@ "node": ">=10" } }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true, - "license": "MIT" - }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -5416,7 +8388,49 @@ "node": ">=10" } }, + "node_modules/string-length/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-length/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", @@ -5431,7 +8445,24 @@ "node": ">=8" } }, - "node_modules/strip-ansi": { + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", @@ -5444,6 +8475,46 @@ "node": ">=8" } }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -5477,27 +8548,38 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strnum": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.2.tgz", + "integrity": "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, "node_modules/superagent": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-8.1.2.tgz", - "integrity": "sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA==", - "deprecated": "Please upgrade to superagent v10.2.2+, see release notes at https://github.com/forwardemail/superagent/releases/tag/v10.2.2 - maintenance is supported by Forward Email @ https://forwardemail.net", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.3.0.tgz", + "integrity": "sha512-B+4Ik7ROgVKrQsXTV0Jwp2u+PXYLSlqtDAhYnkkD+zn3yg8s/zjA2MeGayPoY/KICrbitwneDHrjSotxKL+0XQ==", "dev": true, "license": "MIT", "dependencies": { - "component-emitter": "^1.3.0", + "component-emitter": "^1.3.1", "cookiejar": "^2.1.4", - "debug": "^4.3.4", + "debug": "^4.3.7", "fast-safe-stringify": "^2.1.1", - "form-data": "^4.0.0", - "formidable": "^2.1.2", + "form-data": "^4.0.5", + "formidable": "^3.5.4", "methods": "^1.1.2", "mime": "2.6.0", - "qs": "^6.11.0", - "semver": "^7.3.8" + "qs": "^6.14.1" }, "engines": { - "node": ">=6.4.0 <13 || >=14" + "node": ">=14.18.0" } }, "node_modules/superagent/node_modules/debug": { @@ -5518,19 +8600,6 @@ } } }, - "node_modules/superagent/node_modules/mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "dev": true, - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/superagent/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -5538,32 +8607,19 @@ "dev": true, "license": "MIT" }, - "node_modules/superagent/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/supertest": { - "version": "6.3.4", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-6.3.4.tgz", - "integrity": "sha512-erY3HFDG0dPnhw4U+udPfrzXa4xhSG+n4rxfRuZWCUvjFWwKl+OxWf/7zk50s84/fAAs7vf5QAb9uRa0cCykxw==", - "deprecated": "Please upgrade to supertest v7.1.3+, see release notes at https://github.com/forwardemail/supertest/releases/tag/v7.1.3 - maintenance is supported by Forward Email @ https://forwardemail.net", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.2.2.tgz", + "integrity": "sha512-oK8WG9diS3DlhdUkcFn4tkNIiIbBx9lI2ClF8K+b2/m8Eyv47LSawxUzZQSNKUrVb2KsqeTDCcjAAVPYaSLVTA==", "dev": true, "license": "MIT", "dependencies": { + "cookie-signature": "^1.2.2", "methods": "^1.1.2", - "superagent": "^8.1.2" + "superagent": "^10.3.0" }, "engines": { - "node": ">=6.4.0" + "node": ">=14.18.0" } }, "node_modules/supports-color": { @@ -5579,17 +8635,20 @@ "node": ">=8" } }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "node_modules/synckit": { + "version": "0.11.12", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", + "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", "dev": true, "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.2.9" + }, "engines": { - "node": ">= 0.4" + "node": "^14.18.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://opencollective.com/synckit" } }, "node_modules/test-exclude": { @@ -5607,6 +8666,28 @@ "node": ">=8" } }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/text-hex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", @@ -5667,6 +8748,77 @@ "node": ">= 14.0.0" } }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -5709,6 +8861,20 @@ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", "license": "MIT" }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/undefsafe": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", @@ -5737,6 +8903,41 @@ "node": ">= 0.8" } }, + "node_modules/unrs-resolver": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", + "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.0" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.11.1", + "@unrs/resolver-binding-android-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-x64": "1.11.1", + "@unrs/resolver-binding-freebsd-x64": "1.11.1", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", + "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-musl": "1.11.1", + "@unrs/resolver-binding-wasm32-wasi": "1.11.1", + "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", + "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", + "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" + } + }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", @@ -5774,28 +8975,26 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz", + "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" ], "license": "MIT", "bin": { - "uuid": "dist/bin/uuid" + "uuid": "dist-node/bin/uuid" } }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, "node_modules/v8-to-istanbul": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", @@ -5891,34 +9090,6 @@ "node": ">= 12.0.0" } }, - "node_modules/winston-transport/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/winston/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/wkx": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.5.0.tgz", @@ -5929,6 +9100,25 @@ } }, "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", @@ -5946,25 +9136,82 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, "license": "ISC" }, "node_modules/write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", "dev": true, "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" + "signal-exit": "^4.0.1" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/xtend": { @@ -6022,6 +9269,61 @@ "node": ">=12" } }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 2be7f9e..0261d48 100644 --- a/package.json +++ b/package.json @@ -2,11 +2,14 @@ "name": "royal-enfield-onboarding-backend", "version": "1.0.0", "description": "Backend API for Royal Enfield Dealership Onboarding System", + "type": "module", "main": "server.js", "scripts": { - "start": "node server.js", - "dev": "nodemon server.js", - "migrate": "node scripts/migrate.js", + "start": "node dist/src/server.js", + "dev": "tsx watch src/server.ts", + "build": "tsc", + "type-check": "tsc --noEmit", + "migrate": "node dist/scripts/migrate.js", "test": "jest", "test:coverage": "jest --coverage", "clear-logs": "rm -rf logs/*.log" @@ -20,27 +23,43 @@ "author": "Royal Enfield", "license": "PROPRIETARY", "dependencies": { - "express": "^4.18.2", - "sequelize": "^6.35.2", - "pg": "^8.11.3", - "pg-hstore": "^2.3.4", - "jsonwebtoken": "^9.0.2", - "bcryptjs": "^2.4.3", + "bcryptjs": "^3.0.3", + "compression": "^1.8.1", "cors": "^2.8.5", - "helmet": "^7.1.0", - "express-validator": "^7.0.1", - "multer": "^1.4.5-lts.1", - "nodemailer": "^6.9.7", - "winston": "^3.11.0", - "dotenv": "^16.3.1", - "uuid": "^9.0.1", - "express-rate-limit": "^7.1.5", - "compression": "^1.7.4" + "dotenv": "^17.2.3", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "express-validator": "^7.3.1", + "helmet": "^8.1.0", + "jsonwebtoken": "^9.0.3", + "multer": "^2.0.2", + "nodemailer": "^7.0.12", + "pg": "^8.17.2", + "pg-hstore": "^2.3.4", + "sequelize": "^6.37.7", + "uuid": "^13.0.0", + "winston": "^3.19.0" }, "devDependencies": { + "@types/bcryptjs": "^2.4.6", + "@types/compression": "^1.8.1", + "@types/cors": "^2.8.19", + "@types/express": "^5.0.6", + "@types/jest": "^30.0.0", + "@types/jsonwebtoken": "^9.0.10", + "@types/multer": "^2.0.0", + "@types/node": "^25.0.9", + "@types/nodemailer": "^7.0.5", + "@types/pg": "^8.16.0", + "@types/supertest": "^6.0.3", + "@types/uuid": "^10.0.0", + "@types/validator": "^13.15.10", + "jest": "^30.2.0", "nodemon": "^3.0.2", - "jest": "^29.7.0", - "supertest": "^6.3.3" + "supertest": "^7.2.2", + "ts-node": "^10.9.2", + "tsx": "^4.21.0", + "typescript": "^5.9.3" }, "engines": { "node": ">=18.0.0", diff --git a/scripts/migrate.ts b/scripts/migrate.ts new file mode 100644 index 0000000..c0d87c7 --- /dev/null +++ b/scripts/migrate.ts @@ -0,0 +1,41 @@ +/** + * Database Migration Script + * Synchronizes all Sequelize models with the database + * This script will DROP all existing tables and recreate them. + * + * Run: npx tsx scripts/migrate.ts + */ + +import 'dotenv/config'; +import db from '../src/database/models/index.js'; + +async function runMigrations() { + console.log('🔄 Starting database synchronization (Fresh Startup)...\n'); + console.log('⚠️ WARNING: This will drop all existing tables in the database.\n'); + + try { + // Authenticate with the database + await db.sequelize.authenticate(); + console.log('📡 Connected to the database successfully.'); + + // Synchronize models (force: true drops existing tables) + // This ensures that the schema exactly matches the Sequelize models + await db.sequelize.sync({ force: true }); + + console.log('\n✅ All tables created and synchronized successfully!'); + console.log('----------------------------------------------------'); + const modelNames = Object.keys(db).filter(k => k !== 'sequelize' && k !== 'Sequelize'); + console.log(`Available Models (${modelNames.length}): ${modelNames.join(', ')}`); + console.log('----------------------------------------------------'); + + process.exit(0); + } catch (error: any) { + console.error('\n❌ Migration failed:', error.message); + if (error.stack) { + console.error('\nStack Trace:\n', error.stack); + } + process.exit(1); + } +} + +runMigrations(); diff --git a/server.js b/server.js deleted file mode 100644 index b644310..0000000 --- a/server.js +++ /dev/null @@ -1,156 +0,0 @@ -require('dotenv').config(); -const express = require('express'); -const cors = require('cors'); -const helmet = require('helmet'); -const compression = require('compression'); -const rateLimit = require('express-rate-limit'); -const path = require('path'); - -// Import database -const db = require('./src/database/models'); - -// Import routes (Modular Monolith Structure) -const authRoutes = require('./src/modules/auth/auth.routes'); -const onboardingRoutes = require('./src/modules/onboarding/onboarding.routes'); -const selfServiceRoutes = require('./src/modules/self-service/self-service.routes'); -const masterRoutes = require('./src/modules/master/master.routes'); -const settlementRoutes = require('./src/modules/settlement/settlement.routes'); -const collaborationRoutes = require('./src/modules/collaboration/collaboration.routes'); - -// Import common middleware & utils -const errorHandler = require('./src/common/middleware/errorHandler'); -const logger = require('./src/common/utils/logger'); - -// Initialize Express app -const app = express(); - -// Security middleware -app.use(helmet()); -app.use(cors({ - origin: process.env.FRONTEND_URL || 'http://localhost:5173', - credentials: true -})); - -// Rate limiting -const limiter = rateLimit({ - windowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS) || 15 * 60 * 1000, // 15 minutes - max: parseInt(process.env.RATE_LIMIT_MAX_REQUESTS) || 100, - message: 'Too many requests from this IP, please try again later.' -}); -app.use('/api/', limiter); - -// Body parsing middleware -app.use(express.json({ limit: '10mb' })); -app.use(express.urlencoded({ extended: true, limit: '10mb' })); - -// Compression -app.use(compression()); - -// Static files (uploaded documents) -app.use('/uploads', express.static(path.join(__dirname, 'uploads'))); - -// Request logging -app.use((req, res, next) => { - logger.info(`${req.method} ${req.path}`, { - ip: req.ip, - userAgent: req.get('user-agent') - }); - next(); -}); - -// Health check endpoint -app.get('/health', (req, res) => { - res.json({ - status: 'OK', - timestamp: new Date().toISOString(), - uptime: process.uptime(), - environment: process.env.NODE_ENV - }); -}); - -// API Routes (Modular) -app.use('/api/auth', authRoutes); -app.use('/api/onboarding', onboardingRoutes); -app.use('/api/self-service', selfServiceRoutes); -app.use('/api/master', masterRoutes); -app.use('/api/settlement', settlementRoutes); -app.use('/api/collaboration', collaborationRoutes); - -// Backward Compatibility Aliases -app.use('/api/applications', onboardingRoutes); -app.use('/api/resignations', require('./src/modules/self-service/resignation.routes')); -app.use('/api/constitutional', (req, res, next) => { - // Map /api/constitutional to /api/self-service/constitutional - req.url = '/constitutional' + req.url; - next(); -}, selfServiceRoutes); -app.use('/api/relocations', (req, res, next) => { - // Map /api/relocations to /api/self-service/relocation - req.url = '/relocation' + req.url; - next(); -}, selfServiceRoutes); -app.use('/api/outlets', require('./src/modules/master/outlet.routes')); -app.use('/api/finance', settlementRoutes); -app.use('/api/worknotes', collaborationRoutes); - -// 404 handler -app.use((req, res) => { - res.status(404).json({ - success: false, - message: 'Route not found' - }); -}); - -// Global error handler -app.use(errorHandler); - -// Database connection and server start -const PORT = process.env.PORT || 5000; - -const startServer = async () => { - try { - // Test database connection - await db.sequelize.authenticate(); - logger.info('Database connection established successfully'); - - // Sync database (in development only) - if (process.env.NODE_ENV === 'development') { - await db.sequelize.sync({ alter: false }); - logger.info('Database models synchronized'); - } - - // Start server - app.listen(PORT, () => { - logger.info(`🚀 Server running on port ${PORT}`); - logger.info(`📍 Environment: ${process.env.NODE_ENV}`); - logger.info(`🔗 API Base URL: http://localhost:${PORT}/api`); - }); - } catch (error) { - logger.error('Unable to start server:', error); - process.exit(1); - } -}; - -// Handle unhandled promise rejections -process.on('unhandledRejection', (err) => { - logger.error('Unhandled Promise Rejection:', err); - process.exit(1); -}); - -// Handle uncaught exceptions -process.on('uncaughtException', (err) => { - logger.error('Uncaught Exception:', err); - process.exit(1); -}); - -// Graceful shutdown -process.on('SIGTERM', async () => { - logger.info('SIGTERM signal received: closing HTTP server'); - await db.sequelize.close(); - process.exit(0); -}); - -startServer(); - -module.exports = app; - diff --git a/src/common/config/auth.js b/src/common/config/auth.js deleted file mode 100644 index c402910..0000000 --- a/src/common/config/auth.js +++ /dev/null @@ -1,34 +0,0 @@ -const jwt = require('jsonwebtoken'); - -const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key-change-in-production'; -const JWT_EXPIRE = process.env.JWT_EXPIRE || '7d'; - -// Generate JWT token -const generateToken = (user) => { - const payload = { - userId: user.id, - email: user.email, - role: user.role, - region: user.region, - zone: user.zone - }; - - return jwt.sign(payload, JWT_SECRET, { - expiresIn: JWT_EXPIRE - }); -}; - -// Verify JWT token -const verifyToken = (token) => { - try { - return jwt.verify(token, JWT_SECRET); - } catch (error) { - throw new Error('Invalid or expired token'); - } -}; - -module.exports = { - generateToken, - verifyToken, - JWT_SECRET -}; diff --git a/src/common/config/auth.ts b/src/common/config/auth.ts new file mode 100644 index 0000000..bcbc1ac --- /dev/null +++ b/src/common/config/auth.ts @@ -0,0 +1,31 @@ +import jwt from 'jsonwebtoken'; +import { TokenPayload } from '../../types/auth.types.js'; + +const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key-change-in-production'; +const JWT_EXPIRE = process.env.JWT_EXPIRE || '7d'; + +// Generate JWT token +export const generateToken = (user: any): string => { + const payload: TokenPayload = { + userId: user.id, + email: user.email, + role: user.role, + region: user.region, + zone: user.zone + }; + + return jwt.sign(payload, JWT_SECRET, { + expiresIn: JWT_EXPIRE as any + }); +}; + +// Verify JWT token +export const verifyToken = (token: string): TokenPayload => { + try { + return jwt.verify(token, JWT_SECRET) as TokenPayload; + } catch (error) { + throw new Error('Invalid or expired token'); + } +}; + +export { JWT_SECRET }; diff --git a/src/common/config/constants.js b/src/common/config/constants.ts similarity index 78% rename from src/common/config/constants.js rename to src/common/config/constants.ts index a8227b1..9de482c 100644 --- a/src/common/config/constants.js +++ b/src/common/config/constants.ts @@ -1,5 +1,5 @@ // User Roles -const ROLES = { +export const ROLES = { DD: 'DD', DD_ZM: 'DD-ZM', RBM: 'RBM', @@ -13,19 +13,19 @@ const ROLES = { DD_AM: 'DD AM', FINANCE: 'Finance', DEALER: 'Dealer' -}; +} as const; // Regions -const REGIONS = { +export const REGIONS = { EAST: 'East', WEST: 'West', NORTH: 'North', SOUTH: 'South', CENTRAL: 'Central' -}; +} as const; // Application Stages -const APPLICATION_STAGES = { +export const APPLICATION_STAGES = { DD: 'DD', DD_ZM: 'DD-ZM', RBM: 'RBM', @@ -37,18 +37,18 @@ const APPLICATION_STAGES = { FINANCE: 'Finance', APPROVED: 'Approved', REJECTED: 'Rejected' -}; +} as const; // Application Status -const APPLICATION_STATUS = { +export const APPLICATION_STATUS = { PENDING: 'Pending', IN_REVIEW: 'In Review', APPROVED: 'Approved', REJECTED: 'Rejected' -}; +} as const; // Resignation Stages -const RESIGNATION_STAGES = { +export const RESIGNATION_STAGES = { ASM: 'ASM', RBM: 'RBM', ZBH: 'ZBH', @@ -59,99 +59,99 @@ const RESIGNATION_STAGES = { FNF_INITIATED: 'F&F Initiated', COMPLETED: 'Completed', REJECTED: 'Rejected' -}; +} as const; // Resignation Types -const RESIGNATION_TYPES = { +export const RESIGNATION_TYPES = { VOLUNTARY: 'Voluntary', RETIREMENT: 'Retirement', HEALTH_ISSUES: 'Health Issues', BUSINESS_CLOSURE: 'Business Closure', OTHER: 'Other' -}; +} as const; // Constitutional Change Types -const CONSTITUTIONAL_CHANGE_TYPES = { +export const CONSTITUTIONAL_CHANGE_TYPES = { OWNERSHIP_TRANSFER: 'Ownership Transfer', PARTNERSHIP_CHANGE: 'Partnership Change', LLP_CONVERSION: 'LLP Conversion', COMPANY_FORMATION: 'Company Formation', DIRECTOR_CHANGE: 'Director Change' -}; +} as const; // Constitutional Change Stages -const CONSTITUTIONAL_STAGES = { +export const CONSTITUTIONAL_STAGES = { DD_ADMIN_REVIEW: 'DD Admin Review', LEGAL_REVIEW: 'Legal Review', NBH_APPROVAL: 'NBH Approval', FINANCE_CLEARANCE: 'Finance Clearance', COMPLETED: 'Completed', REJECTED: 'Rejected' -}; +} as const; // Relocation Types -const RELOCATION_TYPES = { +export const RELOCATION_TYPES = { WITHIN_CITY: 'Within City', INTERCITY: 'Intercity', INTERSTATE: 'Interstate' -}; +} as const; // Relocation Stages -const RELOCATION_STAGES = { +export const RELOCATION_STAGES = { DD_ADMIN_REVIEW: 'DD Admin Review', RBM_REVIEW: 'RBM Review', NBH_APPROVAL: 'NBH Approval', LEGAL_CLEARANCE: 'Legal Clearance', COMPLETED: 'Completed', REJECTED: 'Rejected' -}; +} as const; // Outlet Types -const OUTLET_TYPES = { +export const OUTLET_TYPES = { DEALERSHIP: 'Dealership', STUDIO: 'Studio' -}; +} as const; // Outlet Status -const OUTLET_STATUS = { +export const OUTLET_STATUS = { ACTIVE: 'Active', PENDING_RESIGNATION: 'Pending Resignation', CLOSED: 'Closed' -}; +} as const; // Business Types -const BUSINESS_TYPES = { +export const BUSINESS_TYPES = { DEALERSHIP: 'Dealership', STUDIO: 'Studio' -}; +} as const; // Payment Types -const PAYMENT_TYPES = { +export const PAYMENT_TYPES = { SECURITY_DEPOSIT: 'Security Deposit', LICENSE_FEE: 'License Fee', SETUP_FEE: 'Setup Fee', OTHER: 'Other' -}; +} as const; // Payment Status -const PAYMENT_STATUS = { +export const PAYMENT_STATUS = { PENDING: 'Pending', PAID: 'Paid', OVERDUE: 'Overdue', WAIVED: 'Waived' -}; +} as const; // F&F Status -const FNF_STATUS = { +export const FNF_STATUS = { INITIATED: 'Initiated', DD_CLEARANCE: 'DD Clearance', LEGAL_CLEARANCE: 'Legal Clearance', FINANCE_APPROVAL: 'Finance Approval', COMPLETED: 'Completed' -}; +} as const; // Audit Actions -const AUDIT_ACTIONS = { +export const AUDIT_ACTIONS = { CREATED: 'CREATED', UPDATED: 'UPDATED', APPROVED: 'APPROVED', @@ -160,10 +160,10 @@ const AUDIT_ACTIONS = { STAGE_CHANGED: 'STAGE_CHANGED', DOCUMENT_UPLOADED: 'DOCUMENT_UPLOADED', WORKNOTE_ADDED: 'WORKNOTE_ADDED' -}; +} as const; // Document Types -const DOCUMENT_TYPES = { +export const DOCUMENT_TYPES = { GST_CERTIFICATE: 'GST Certificate', PAN_CARD: 'PAN Card', AADHAAR: 'Aadhaar', @@ -176,34 +176,12 @@ const DOCUMENT_TYPES = { PROPERTY_DOCUMENTS: 'Property Documents', BANK_STATEMENT: 'Bank Statement', OTHER: 'Other' -}; +} as const; // Request Types -const REQUEST_TYPES = { +export const REQUEST_TYPES = { APPLICATION: 'application', RESIGNATION: 'resignation', CONSTITUTIONAL: 'constitutional', RELOCATION: 'relocation' -}; - -module.exports = { - ROLES, - REGIONS, - APPLICATION_STAGES, - APPLICATION_STATUS, - RESIGNATION_STAGES, - RESIGNATION_TYPES, - CONSTITUTIONAL_CHANGE_TYPES, - CONSTITUTIONAL_STAGES, - RELOCATION_TYPES, - RELOCATION_STAGES, - OUTLET_TYPES, - OUTLET_STATUS, - BUSINESS_TYPES, - PAYMENT_TYPES, - PAYMENT_STATUS, - FNF_STATUS, - AUDIT_ACTIONS, - DOCUMENT_TYPES, - REQUEST_TYPES -}; +} as const; diff --git a/src/common/config/database.js b/src/common/config/database.ts similarity index 73% rename from src/common/config/database.js rename to src/common/config/database.ts index 8b9fd3b..4a6b35c 100644 --- a/src/common/config/database.js +++ b/src/common/config/database.ts @@ -1,12 +1,14 @@ -require('dotenv').config(); +import 'dotenv/config'; +import { Options } from 'sequelize'; +import { DbConfig } from '../../types/common.types.js'; -module.exports = { +const config: DbConfig = { development: { username: process.env.DB_USER || 'laxman', password: process.env.DB_PASSWORD || 'Admin@123', database: process.env.DB_NAME || 'royal_enfield_onboarding', host: process.env.DB_HOST || 'localhost', - port: process.env.DB_PORT || 5432, + port: parseInt(process.env.DB_PORT || '5432'), dialect: 'postgres', logging: console.log, pool: { @@ -21,7 +23,7 @@ module.exports = { password: process.env.DB_PASSWORD || 'Admin@123', database: process.env.DB_NAME || 'royal_enfield_onboarding', host: process.env.DB_HOST || 'localhost', - port: process.env.DB_PORT || 5432, + port: parseInt(process.env.DB_PORT || '5432'), dialect: 'postgres', logging: false, pool: { @@ -40,10 +42,12 @@ module.exports = { test: { username: process.env.DB_USER || 'laxman', password: process.env.DB_PASSWORD || 'Admin@123', - database: process.env.DB_NAME + '_test' || 'royal_enfield_onboarding_test', + database: (process.env.DB_NAME || 'royal_enfield_onboarding') + '_test', host: process.env.DB_HOST || 'localhost', - port: process.env.DB_PORT || 5432, + port: parseInt(process.env.DB_PORT || '5432'), dialect: 'postgres', logging: false } }; + +export default config; diff --git a/src/common/middleware/auth.js b/src/common/middleware/auth.js deleted file mode 100644 index 9ebbbf3..0000000 --- a/src/common/middleware/auth.js +++ /dev/null @@ -1,100 +0,0 @@ -const jwt = require('jsonwebtoken'); -const db = require('../../database/models'); -const logger = require('../utils/logger'); - -const authenticate = async (req, res, next) => { - try { - // Get token from header - const authHeader = req.header('Authorization'); - - if (!authHeader || !authHeader.startsWith('Bearer ')) { - return res.status(401).json({ - success: false, - message: 'Access denied. No token provided.' - }); - } - - const token = authHeader.replace('Bearer ', ''); - - // Verify token - const decoded = jwt.verify(token, process.env.JWT_SECRET); - - // Find user - const user = await db.User.findByPk(decoded.id, { - attributes: { exclude: ['password'] } - }); - - if (!user) { - return res.status(401).json({ - success: false, - message: 'Invalid token. User not found.' - }); - } - - if (user.status !== 'active') { - return res.status(401).json({ - success: false, - message: 'User account is inactive.' - }); - } - - // Attach user to request - req.user = user; - req.token = token; - - next(); - } catch (error) { - logger.error('Authentication error:', error); - - if (error.name === 'TokenExpiredError') { - return res.status(401).json({ - success: false, - message: 'Token expired' - }); - } - - if (error.name === 'JsonWebTokenError') { - return res.status(401).json({ - success: false, - message: 'Invalid token' - }); - } - - res.status(500).json({ - success: false, - message: 'Authentication failed' - }); - } -}; - -const optionalAuth = async (req, res, next) => { - try { - const authHeader = req.header('Authorization'); - - if (!authHeader || !authHeader.startsWith('Bearer ')) { - return next(); - } - - const token = authHeader.replace('Bearer ', ''); - const decoded = jwt.verify(token, process.env.JWT_SECRET); - - const user = await db.User.findByPk(decoded.id, { - attributes: { exclude: ['password'] } - }); - - if (user && user.status === 'active') { - req.user = user; - req.token = token; - } - - next(); - } catch (error) { - // If token is invalid/expired, just proceed without user - next(); - } -}; - -module.exports = { - authenticate, - optionalAuth -}; diff --git a/src/common/middleware/auth.ts b/src/common/middleware/auth.ts new file mode 100644 index 0000000..f375560 --- /dev/null +++ b/src/common/middleware/auth.ts @@ -0,0 +1,99 @@ +import { Response, NextFunction } from 'express'; +import jwt from 'jsonwebtoken'; +import db from '../../database/models/index.js'; +import logger from '../utils/logger.js'; +import { AuthenticatedRequest, AuthRequest } from '../../types/express.types.js'; + +export const authenticate = async (req: AuthRequest, res: Response, next: NextFunction) => { + try { + // Get token from header + const authHeader = req.header('Authorization'); + + if (!authHeader || !authHeader.startsWith('Bearer ')) { + return res.status(401).json({ + success: false, + message: 'Access denied. No token provided.' + }); + } + + const token = authHeader.replace('Bearer ', ''); + + // Verify token + const jwtSecret = process.env.JWT_SECRET || 'your-default-secret'; + const decoded = jwt.verify(token, jwtSecret) as { id: string }; + + // Find user + const user = await db.User.findByPk(decoded.id, { + attributes: { exclude: ['password'] } + }); + + if (!user) { + return res.status(401).json({ + success: false, + message: 'Invalid token. User not found.' + }); + } + + if (user.status !== 'active') { + return res.status(401).json({ + success: false, + message: 'User account is inactive.' + }); + } + + // Attach user to request + req.user = user; + req.token = token; + + next(); + } catch (error: any) { + logger.error('Authentication error:', error); + + if (error.name === 'TokenExpiredError') { + return res.status(401).json({ + success: false, + message: 'Token expired' + }); + } + + if (error.name === 'JsonWebTokenError') { + return res.status(401).json({ + success: false, + message: 'Invalid token' + }); + } + + res.status(500).json({ + success: false, + message: 'Authentication failed' + }); + } +}; + +export const optionalAuth = async (req: AuthRequest, res: Response, next: NextFunction) => { + try { + const authHeader = req.header('Authorization'); + + if (!authHeader || !authHeader.startsWith('Bearer ')) { + return next(); + } + + const token = authHeader.replace('Bearer ', ''); + const jwtSecret = process.env.JWT_SECRET || 'your-default-secret'; + const decoded = jwt.verify(token, jwtSecret) as { id: string }; + + const user = await db.User.findByPk(decoded.id, { + attributes: { exclude: ['password'] } + }); + + if (user && user.status === 'active') { + req.user = user; + req.token = token; + } + + next(); + } catch (error) { + // If token is invalid/expired, just proceed without user + next(); + } +}; diff --git a/src/common/middleware/errorHandler.js b/src/common/middleware/errorHandler.js deleted file mode 100644 index 6f54581..0000000 --- a/src/common/middleware/errorHandler.js +++ /dev/null @@ -1,76 +0,0 @@ -const logger = require('../utils/logger'); - -const errorHandler = (err, req, res, next) => { - // Log error - logger.error('Error occurred:', { - message: err.message, - stack: err.stack, - path: req.path, - method: req.method, - ip: req.ip - }); - - // Sequelize validation errors - if (err.name === 'SequelizeValidationError') { - const errors = err.errors.map(e => ({ - field: e.path, - message: e.message - })); - - return res.status(400).json({ - success: false, - message: 'Validation error', - errors - }); - } - - // Sequelize unique constraint errors - if (err.name === 'SequelizeUniqueConstraintError') { - return res.status(409).json({ - success: false, - message: 'Resource already exists', - field: err.errors[0]?.path - }); - } - - // JWT errors - if (err.name === 'JsonWebTokenError') { - return res.status(401).json({ - success: false, - message: 'Invalid token' - }); - } - - if (err.name === 'TokenExpiredError') { - return res.status(401).json({ - success: false, - message: 'Token expired' - }); - } - - // Multer file upload errors - if (err.name === 'MulterError') { - if (err.code === 'LIMIT_FILE_SIZE') { - return res.status(413).json({ - success: false, - message: 'File too large' - }); - } - return res.status(400).json({ - success: false, - message: err.message - }); - } - - // Default error - const statusCode = err.statusCode || 500; - const message = err.message || 'Internal server error'; - - res.status(statusCode).json({ - success: false, - message, - ...(process.env.NODE_ENV === 'development' && { stack: err.stack }) - }); -}; - -module.exports = errorHandler; diff --git a/src/common/middleware/errorHandler.ts b/src/common/middleware/errorHandler.ts new file mode 100644 index 0000000..b511b26 --- /dev/null +++ b/src/common/middleware/errorHandler.ts @@ -0,0 +1,78 @@ +import { Request, Response, NextFunction } from 'express'; +import logger from '../utils/logger.js'; +import { AppError } from '../../types/common.types.js'; + +const errorHandler = (err: AppError, req: Request, res: Response, next: NextFunction) => { + // Log error + logger.error('Error occurred:', { + message: err.message, + stack: err.stack, + path: req.path, + method: req.method, + ip: req.ip + }); + + // Sequelize validation errors + if (err.name === 'SequelizeValidationError') { + const errors = err.errors?.map(e => ({ + field: e.path, + message: e.message + })); + + return res.status(400).json({ + success: false, + message: 'Validation error', + errors + }); + } + + // Sequelize unique constraint errors + if (err.name === 'SequelizeUniqueConstraintError') { + return res.status(409).json({ + success: false, + message: 'Resource already exists', + field: err.errors?.[0]?.path + }); + } + + // JWT errors + if (err.name === 'JsonWebTokenError') { + return res.status(401).json({ + success: false, + message: 'Invalid token' + }); + } + + if (err.name === 'TokenExpiredError') { + return res.status(401).json({ + success: false, + message: 'Token expired' + }); + } + + // Multer file upload errors + if (err.name === 'MulterError') { + if (err.code === 'LIMIT_FILE_SIZE') { + return res.status(413).json({ + success: false, + message: 'File too large' + }); + } + return res.status(400).json({ + success: false, + message: err.message + }); + } + + // Default error + const statusCode = err.statusCode || 500; + const message = err.message || 'Internal server error'; + + res.status(statusCode).json({ + success: false, + message, + ...(process.env.NODE_ENV === 'development' && { stack: err.stack }) + }); +}; + +export default errorHandler; diff --git a/src/common/middleware/roleCheck.js b/src/common/middleware/roleCheck.js deleted file mode 100644 index af26545..0000000 --- a/src/common/middleware/roleCheck.js +++ /dev/null @@ -1,47 +0,0 @@ -const { ROLES } = require('../config/constants'); -const logger = require('../utils/logger'); - -/** - * Role-based access control middleware - * @param {Array} allowedRoles - Array of roles that can access the route - * @returns {Function} Express middleware function - */ -const checkRole = (allowedRoles) => { - return (req, res, next) => { - try { - // Check if user is authenticated - if (!req.user) { - return res.status(401).json({ - success: false, - message: 'Authentication required' - }); - } - - // Check if user role is in allowed roles - if (!allowedRoles.includes(req.user.role)) { - logger.warn(`Access denied for user ${req.user.email} (${req.user.role}) to route ${req.path}`); - - return res.status(403).json({ - success: false, - message: 'Access denied. Insufficient permissions.', - requiredRoles: allowedRoles, - yourRole: req.user.role - }); - } - - // User has required role, proceed - next(); - } catch (error) { - logger.error('Role check error:', error); - res.status(500).json({ - success: false, - message: 'Authorization check failed' - }); - } - }; -}; - -module.exports = { - checkRole, - ROLES -}; diff --git a/src/common/middleware/roleCheck.ts b/src/common/middleware/roleCheck.ts new file mode 100644 index 0000000..1b68ade --- /dev/null +++ b/src/common/middleware/roleCheck.ts @@ -0,0 +1,46 @@ +import { Request, Response, NextFunction } from 'express'; +import { ROLES } from '../config/constants.js'; +import logger from '../utils/logger.js'; +import { AuthenticatedRequest } from '../../types/express.types.js'; + +/** + * Role-based access control middleware + * @param allowedRoles - Array of roles that can access the route + * @returns Express middleware function + */ +export const checkRole = (allowedRoles: string[]) => { + return (req: AuthenticatedRequest, res: Response, next: NextFunction) => { + try { + // Check if user is authenticated + if (!req.user) { + return res.status(401).json({ + success: false, + message: 'Authentication required' + }); + } + + // Check if user role is in allowed roles + if (!allowedRoles.includes(req.user.role)) { + logger.warn(`Access denied for user ${req.user.email} (${req.user.role}) to route ${req.path}`); + + return res.status(403).json({ + success: false, + message: 'Access denied. Insufficient permissions.', + requiredRoles: allowedRoles, + yourRole: req.user.role + }); + } + + // User has required role, proceed + next(); + } catch (error) { + logger.error('Role check error:', error); + res.status(500).json({ + success: false, + message: 'Authorization check failed' + }); + } + }; +}; + +export { ROLES }; diff --git a/src/common/middleware/upload.ts b/src/common/middleware/upload.ts new file mode 100644 index 0000000..f6df669 --- /dev/null +++ b/src/common/middleware/upload.ts @@ -0,0 +1,96 @@ +import multer, { FileFilterCallback } from 'multer'; +import path from 'path'; +import fs from 'fs'; +import { Request, Response, NextFunction } from 'express'; +import { v4 as uuidv4 } from 'uuid'; + +// Create uploads directory if it doesn't exist +const uploadDir = process.env.UPLOAD_DIR || './uploads'; +const documentsDir = path.join(uploadDir, 'documents'); +const profilesDir = path.join(uploadDir, 'profiles'); +const tempDir = path.join(uploadDir, 'temp'); + +[uploadDir, documentsDir, profilesDir, tempDir].forEach(dir => { + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } +}); + +// Storage configuration +const storage = multer.diskStorage({ + destination: (req, file, cb) => { + let folder = documentsDir; + + if (req.body.uploadType === 'profile') { + folder = profilesDir; + } else if (req.body.uploadType === 'temp') { + folder = tempDir; + } + + cb(null, folder); + }, + filename: (req, file, cb) => { + const uniqueId = uuidv4(); + const ext = path.extname(file.originalname); + const filename = `${uniqueId}${ext}`; + cb(null, filename); + } +}); + +// File filter +const fileFilter = (req: Request, file: Express.Multer.File, cb: FileFilterCallback) => { + // Allowed file types + const allowedTypes = [ + 'application/pdf', + 'image/jpeg', + 'image/jpg', + 'image/png', + 'application/msword', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'application/vnd.ms-excel', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + ]; + + if (allowedTypes.includes(file.mimetype)) { + cb(null, true); + } else { + cb(new Error('Invalid file type. Only PDF, JPG, PNG, DOC, DOCX, XLS, XLSX allowed')); + } +}; + +// Multer upload configuration +const upload = multer({ + storage: storage, + limits: { + fileSize: parseInt(process.env.MAX_FILE_SIZE || '') || 10 * 1024 * 1024 // 10MB default + }, + fileFilter: fileFilter +}); + +// Single file upload +export const uploadSingle = upload.single('file'); + +// Multiple files upload +export const uploadMultiple = upload.array('files', 10); // Max 10 files + +// Error handler for multer +export const handleUploadError = (err: any, req: Request, res: Response, next: NextFunction) => { + if (err instanceof multer.MulterError) { + if (err.code === 'LIMIT_FILE_SIZE') { + return res.status(400).json({ + success: false, + message: 'File too large. Maximum size is 10MB' + }); + } + return res.status(400).json({ + success: false, + message: `Upload error: ${err.message}` + }); + } else if (err) { + return res.status(400).json({ + success: false, + message: err.message + }); + } + next(); +}; diff --git a/src/common/utils/logger.js b/src/common/utils/logger.js deleted file mode 100644 index 3506b72..0000000 --- a/src/common/utils/logger.js +++ /dev/null @@ -1,67 +0,0 @@ -const winston = require('winston'); -const path = require('path'); -const fs = require('fs'); - -// Create logs directory if it doesn't exist -const logsDir = path.join(__dirname, '../../../logs'); -if (!fs.existsSync(logsDir)) { - fs.mkdirSync(logsDir); -} - -// Define log format -const logFormat = winston.format.combine( - winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), - winston.format.errors({ stack: true }), - winston.format.splat(), - winston.format.json() -); - -// Console format for development -const consoleFormat = winston.format.combine( - winston.format.colorize(), - winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), - winston.format.printf(({ level, message, timestamp, ...meta }) => { - return `${timestamp} [${level}]: ${message} ${Object.keys(meta).length ? JSON.stringify(meta, null, 2) : ''}`; - }) -); - -// Create the logger -const logger = winston.createLogger({ - level: process.env.LOG_LEVEL || 'info', - format: logFormat, - transports: [ - // Write all logs to combined.log - new winston.transports.File({ - filename: path.join(logsDir, 'combined.log'), - maxsize: 5242880, // 5MB - maxFiles: 5 - }), - // Write error logs to error.log - new winston.transports.File({ - filename: path.join(logsDir, 'error.log'), - level: 'error', - maxsize: 5242880, // 5MB - maxFiles: 5 - }) - ], - // Handle exceptions and rejections - exceptionHandlers: [ - new winston.transports.File({ - filename: path.join(logsDir, 'exceptions.log') - }) - ], - rejectionHandlers: [ - new winston.transports.File({ - filename: path.join(logsDir, 'rejections.log') - }) - ] -}); - -// Add console transport in development -if (process.env.NODE_ENV !== 'production') { - logger.add(new winston.transports.Console({ - format: consoleFormat - })); -} - -module.exports = logger; diff --git a/src/common/utils/logger.ts b/src/common/utils/logger.ts new file mode 100644 index 0000000..4e4db3f --- /dev/null +++ b/src/common/utils/logger.ts @@ -0,0 +1,71 @@ +import winston from 'winston'; +import path from 'path'; +import fs from 'fs'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// Create logs directory if it doesn't exist +const logsDir = path.join(process.cwd(), 'logs'); +if (!fs.existsSync(logsDir)) { + fs.mkdirSync(logsDir, { recursive: true }); +} + +// Define log format +const logFormat = winston.format.combine( + winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), + winston.format.errors({ stack: true }), + winston.format.splat(), + winston.format.json() +); + +// Console format for development +const consoleFormat = winston.format.combine( + winston.format.colorize(), + winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), + winston.format.printf(({ level, message, timestamp, ...meta }) => { + return `${timestamp} [${level}]: ${message} ${Object.keys(meta).length ? JSON.stringify(meta, null, 2) : ''}`; + }) +); + +// Create the logger +const logger = winston.createLogger({ + level: process.env.LOG_LEVEL || 'info', + format: logFormat, + transports: [ + // Write all logs to combined.log + new winston.transports.File({ + filename: path.join(logsDir, 'combined.log'), + maxsize: 5242880, // 5MB + maxFiles: 5 + }), + // Write error logs to error.log + new winston.transports.File({ + filename: path.join(logsDir, 'error.log'), + level: 'error', + maxsize: 5242880, // 5MB + maxFiles: 5 + }) + ], + // Handle exceptions and rejections + exceptionHandlers: [ + new winston.transports.File({ + filename: path.join(logsDir, 'exceptions.log') + }) + ], + rejectionHandlers: [ + new winston.transports.File({ + filename: path.join(logsDir, 'rejections.log') + }) + ] +}); + +// Add console transport in development +if (process.env.NODE_ENV !== 'production') { + logger.add(new winston.transports.Console({ + format: consoleFormat + })); +} + +export default logger; diff --git a/src/database/models/AiSummary.ts b/src/database/models/AiSummary.ts new file mode 100644 index 0000000..a5a191e --- /dev/null +++ b/src/database/models/AiSummary.ts @@ -0,0 +1,57 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface AiSummaryAttributes { + id: string; + applicationId: string; + summaryType: string; + summaryText: string; + sentimentScore: number | null; + recommendation: string | null; +} + +export interface AiSummaryInstance extends Model, AiSummaryAttributes { } + +export default (sequelize: Sequelize) => { + const AiSummary = sequelize.define('AiSummary', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + applicationId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'applications', + key: 'id' + } + }, + summaryType: { + type: DataTypes.STRING, + allowNull: false + }, + summaryText: { + type: DataTypes.TEXT, + allowNull: false + }, + sentimentScore: { + type: DataTypes.DECIMAL(5, 2), + allowNull: true + }, + recommendation: { + type: DataTypes.STRING, + allowNull: true + } + }, { + tableName: 'ai_summaries', + timestamps: true, + updatedAt: false, + createdAt: 'generatedAt' + }); + + (AiSummary as any).associate = (models: any) => { + AiSummary.belongsTo(models.Application, { foreignKey: 'applicationId', as: 'application' }); + }; + + return AiSummary; +}; diff --git a/src/database/models/Application.js b/src/database/models/Application.js deleted file mode 100644 index fadf560..0000000 --- a/src/database/models/Application.js +++ /dev/null @@ -1,124 +0,0 @@ -const { APPLICATION_STAGES, APPLICATION_STATUS, BUSINESS_TYPES } = require('../../common/config/constants'); - -module.exports = (sequelize, DataTypes) => { - const Application = sequelize.define('Application', { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true - }, - applicationId: { - type: DataTypes.STRING, - unique: true, - allowNull: false - }, - applicantName: { - type: DataTypes.STRING, - allowNull: false - }, - email: { - type: DataTypes.STRING, - allowNull: false, - validate: { isEmail: true } - }, - phone: { - type: DataTypes.STRING, - allowNull: false - }, - businessType: { - type: DataTypes.ENUM(Object.values(BUSINESS_TYPES)), - allowNull: false - }, - preferredLocation: { - type: DataTypes.STRING, - allowNull: false - }, - city: { - type: DataTypes.STRING, - allowNull: false - }, - state: { - type: DataTypes.STRING, - allowNull: false - }, - experienceYears: { - type: DataTypes.INTEGER, - allowNull: false - }, - investmentCapacity: { - type: DataTypes.STRING, - allowNull: false - }, - currentStage: { - type: DataTypes.ENUM(Object.values(APPLICATION_STAGES)), - defaultValue: APPLICATION_STAGES.DD - }, - overallStatus: { - type: DataTypes.ENUM(Object.values(APPLICATION_STATUS)), - defaultValue: APPLICATION_STATUS.PENDING - }, - progressPercentage: { - type: DataTypes.INTEGER, - defaultValue: 0 - }, - isShortlisted: { - type: DataTypes.BOOLEAN, - defaultValue: false - }, - ddLeadShortlisted: { - type: DataTypes.BOOLEAN, - defaultValue: false - }, - assignedTo: { - type: DataTypes.UUID, - allowNull: true, - references: { - model: 'users', - key: 'id' - } - }, - submittedBy: { - type: DataTypes.UUID, - allowNull: true, - references: { - model: 'users', - key: 'id' - } - }, - documents: { - type: DataTypes.JSON, - defaultValue: [] - }, - timeline: { - type: DataTypes.JSON, - defaultValue: [] - } - }, { - tableName: 'applications', - timestamps: true, - indexes: [ - { fields: ['applicationId'] }, - { fields: ['email'] }, - { fields: ['currentStage'] }, - { fields: ['overallStatus'] } - ] - }); - - Application.associate = (models) => { - Application.belongsTo(models.User, { - foreignKey: 'submittedBy', - as: 'submitter' - }); - Application.belongsTo(models.User, { - foreignKey: 'assignedTo', - as: 'assignee' - }); - Application.hasMany(models.Document, { - foreignKey: 'requestId', - as: 'uploadedDocuments', - scope: { requestType: 'application' } - }); - }; - - return Application; -}; diff --git a/src/database/models/Application.ts b/src/database/models/Application.ts new file mode 100644 index 0000000..0dc4c05 --- /dev/null +++ b/src/database/models/Application.ts @@ -0,0 +1,189 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; +import { APPLICATION_STAGES, APPLICATION_STATUS, BUSINESS_TYPES } from '../../common/config/constants.js'; + +export interface ApplicationAttributes { + id: string; + applicationId: string; + opportunityId: string | null; + applicantName: string; + email: string; + phone: string; + businessType: string; + preferredLocation: string | null; + city: string | null; + state: string | null; + experienceYears: number | null; + investmentCapacity: string | null; + currentStage: string; + overallStatus: string; + progressPercentage: number; + isShortlisted: boolean; + ddLeadShortlisted: boolean; + assignedTo: string | null; + submittedBy: string | null; + zoneId: string | null; + regionId: string | null; + areaId: string | null; + documents: any[]; + timeline: any[]; +} + +export interface ApplicationInstance extends Model, ApplicationAttributes { } + +export default (sequelize: Sequelize) => { + const Application = sequelize.define('Application', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + applicationId: { + type: DataTypes.STRING, + unique: true, + allowNull: false + }, + opportunityId: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'opportunities', + key: 'id' + } + }, + applicantName: { + type: DataTypes.STRING, + allowNull: false + }, + email: { + type: DataTypes.STRING, + allowNull: false, + validate: { isEmail: true } + }, + phone: { + type: DataTypes.STRING, + allowNull: false + }, + businessType: { + type: DataTypes.ENUM(...Object.values(BUSINESS_TYPES)), + allowNull: false + }, + preferredLocation: { + type: DataTypes.STRING, + allowNull: true + }, + city: { + type: DataTypes.STRING, + allowNull: true + }, + state: { + type: DataTypes.STRING, + allowNull: true + }, + experienceYears: { + type: DataTypes.INTEGER, + allowNull: true + }, + investmentCapacity: { + type: DataTypes.STRING, + allowNull: true + }, + currentStage: { + type: DataTypes.ENUM(...Object.values(APPLICATION_STAGES)), + defaultValue: APPLICATION_STAGES.DD + }, + overallStatus: { + type: DataTypes.ENUM(...Object.values(APPLICATION_STATUS)), + defaultValue: APPLICATION_STATUS.PENDING + }, + progressPercentage: { + type: DataTypes.INTEGER, + defaultValue: 0 + }, + isShortlisted: { + type: DataTypes.BOOLEAN, + defaultValue: false + }, + ddLeadShortlisted: { + type: DataTypes.BOOLEAN, + defaultValue: false + }, + assignedTo: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'users', + key: 'id' + } + }, + submittedBy: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'users', + key: 'id' + } + }, + zoneId: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'zones', + key: 'id' + } + }, + regionId: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'regions', + key: 'id' + } + }, + areaId: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'areas', + key: 'id' + } + }, + documents: { + type: DataTypes.JSON, + defaultValue: [] + }, + timeline: { + type: DataTypes.JSON, + defaultValue: [] + } + }, { + tableName: 'applications', + timestamps: true, + indexes: [ + { fields: ['applicationId'] }, + { fields: ['email'] }, + { fields: ['currentStage'] }, + { fields: ['overallStatus'] }, + { fields: ['opportunityId'] } + ] + }); + + (Application as any).associate = (models: any) => { + Application.belongsTo(models.User, { foreignKey: 'submittedBy', as: 'submitter' }); + Application.belongsTo(models.User, { foreignKey: 'assignedTo', as: 'assignee' }); + Application.belongsTo(models.Opportunity, { foreignKey: 'opportunityId', as: 'opportunity' }); + Application.belongsTo(models.Zone, { foreignKey: 'zoneId', as: 'zone' }); + Application.belongsTo(models.Region, { foreignKey: 'regionId', as: 'region' }); + Application.belongsTo(models.Area, { foreignKey: 'areaId', as: 'area' }); + + Application.hasMany(models.ApplicationStatusHistory, { foreignKey: 'applicationId', as: 'statusHistory' }); + Application.hasMany(models.ApplicationProgress, { foreignKey: 'applicationId', as: 'progressTracking' }); + + Application.hasMany(models.Document, { + foreignKey: 'requestId', + as: 'uploadedDocuments', + scope: { requestType: 'application' } + }); + }; + + return Application; +}; diff --git a/src/database/models/ApplicationProgress.ts b/src/database/models/ApplicationProgress.ts new file mode 100644 index 0000000..9eedc66 --- /dev/null +++ b/src/database/models/ApplicationProgress.ts @@ -0,0 +1,65 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface ApplicationProgressAttributes { + id: string; + applicationId: string; + stageName: string; + stageOrder: number; + status: string; + completionPercentage: number; + stageStartedAt: Date | null; + stageCompletedAt: Date | null; +} + +export interface ApplicationProgressInstance extends Model, ApplicationProgressAttributes { } + +export default (sequelize: Sequelize) => { + const ApplicationProgress = sequelize.define('ApplicationProgress', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + applicationId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'applications', + key: 'id' + } + }, + stageName: { + type: DataTypes.STRING, + allowNull: false + }, + stageOrder: { + type: DataTypes.INTEGER, + allowNull: false + }, + status: { + type: DataTypes.STRING, + defaultValue: 'pending' + }, + completionPercentage: { + type: DataTypes.INTEGER, + defaultValue: 0 + }, + stageStartedAt: { + type: DataTypes.DATE, + allowNull: true + }, + stageCompletedAt: { + type: DataTypes.DATE, + allowNull: true + } + }, { + tableName: 'application_progress', + timestamps: true + }); + + (ApplicationProgress as any).associate = (models: any) => { + ApplicationProgress.belongsTo(models.Application, { foreignKey: 'applicationId', as: 'application' }); + }; + + return ApplicationProgress; +}; diff --git a/src/database/models/ApplicationStatusHistory.ts b/src/database/models/ApplicationStatusHistory.ts new file mode 100644 index 0000000..8fa83fd --- /dev/null +++ b/src/database/models/ApplicationStatusHistory.ts @@ -0,0 +1,61 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface ApplicationStatusHistoryAttributes { + id: string; + applicationId: string; + previousStatus: string | null; + newStatus: string; + changedBy: string | null; + changeReason: string | null; +} + +export interface ApplicationStatusHistoryInstance extends Model, ApplicationStatusHistoryAttributes { } + +export default (sequelize: Sequelize) => { + const ApplicationStatusHistory = sequelize.define('ApplicationStatusHistory', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + applicationId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'applications', + key: 'id' + } + }, + previousStatus: { + type: DataTypes.STRING, + allowNull: true + }, + newStatus: { + type: DataTypes.STRING, + allowNull: false + }, + changedBy: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'users', + key: 'id' + } + }, + changeReason: { + type: DataTypes.TEXT, + allowNull: true + } + }, { + tableName: 'application_status_history', + timestamps: true, + updatedAt: false + }); + + (ApplicationStatusHistory as any).associate = (models: any) => { + ApplicationStatusHistory.belongsTo(models.Application, { foreignKey: 'applicationId', as: 'application' }); + ApplicationStatusHistory.belongsTo(models.User, { foreignKey: 'changedBy', as: 'changer' }); + }; + + return ApplicationStatusHistory; +}; diff --git a/src/database/models/Area.ts b/src/database/models/Area.ts new file mode 100644 index 0000000..603f905 --- /dev/null +++ b/src/database/models/Area.ts @@ -0,0 +1,81 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface AreaAttributes { + id: string; + regionId: string; + districtId: string; + areaCode: string; + areaName: string; + city: string | null; + pincode: string | null; + isActive: boolean; +} + +export interface AreaInstance extends Model, AreaAttributes { } + +export default (sequelize: Sequelize) => { + const Area = sequelize.define('Area', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + regionId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'regions', + key: 'id' + } + }, + districtId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'districts', + key: 'id' + } + }, + areaCode: { + type: DataTypes.STRING, + unique: true, + allowNull: false + }, + areaName: { + type: DataTypes.STRING, + allowNull: false + }, + city: { + type: DataTypes.STRING, + allowNull: true + }, + pincode: { + type: DataTypes.STRING, + allowNull: true + }, + isActive: { + type: DataTypes.BOOLEAN, + defaultValue: true + } + }, { + tableName: 'areas', + timestamps: true + }); + + (Area as any).associate = (models: any) => { + Area.belongsTo(models.Region, { + foreignKey: 'regionId', + as: 'region' + }); + Area.belongsTo(models.District, { + foreignKey: 'districtId', + as: 'district' + }); + Area.hasMany(models.Application, { + foreignKey: 'areaId', + as: 'applications' + }); + }; + + return Area; +}; diff --git a/src/database/models/AreaManager.ts b/src/database/models/AreaManager.ts new file mode 100644 index 0000000..07a1139 --- /dev/null +++ b/src/database/models/AreaManager.ts @@ -0,0 +1,67 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface AreaManagerAttributes { + id: string; + areaId: string; + userId: string; + managerType: string; + isActive: boolean; + assignedAt: Date; +} + +export interface AreaManagerInstance extends Model, AreaManagerAttributes { } + +export default (sequelize: Sequelize) => { + const AreaManager = sequelize.define('AreaManager', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + areaId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'areas', + key: 'id' + } + }, + userId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'users', + key: 'id' + } + }, + managerType: { + type: DataTypes.STRING, + allowNull: false + }, + isActive: { + type: DataTypes.BOOLEAN, + defaultValue: true + }, + assignedAt: { + type: DataTypes.DATE, + defaultValue: DataTypes.NOW + } + }, { + tableName: 'area_managers', + timestamps: true, + updatedAt: false + }); + + (AreaManager as any).associate = (models: any) => { + AreaManager.belongsTo(models.Area, { + foreignKey: 'areaId', + as: 'area' + }); + AreaManager.belongsTo(models.User, { + foreignKey: 'userId', + as: 'user' + }); + }; + + return AreaManager; +}; diff --git a/src/database/models/AuditLog.js b/src/database/models/AuditLog.ts similarity index 64% rename from src/database/models/AuditLog.js rename to src/database/models/AuditLog.ts index cf61b78..29c8a58 100644 --- a/src/database/models/AuditLog.js +++ b/src/database/models/AuditLog.ts @@ -1,7 +1,22 @@ -const { AUDIT_ACTIONS } = require('../../common/config/constants'); +import { Model, DataTypes, Sequelize } from 'sequelize'; +import { AUDIT_ACTIONS } from '../../common/config/constants.js'; -module.exports = (sequelize, DataTypes) => { - const AuditLog = sequelize.define('AuditLog', { +export interface AuditLogAttributes { + id: string; + userId: string | null; + action: typeof AUDIT_ACTIONS[keyof typeof AUDIT_ACTIONS]; + entityType: string; + entityId: string; + oldData: any | null; + newData: any | null; + ipAddress: string | null; + userAgent: string | null; +} + +export interface AuditLogInstance extends Model, AuditLogAttributes { } + +export default (sequelize: Sequelize) => { + const AuditLog = sequelize.define('AuditLog', { id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, @@ -16,7 +31,7 @@ module.exports = (sequelize, DataTypes) => { } }, action: { - type: DataTypes.ENUM(Object.values(AUDIT_ACTIONS)), + type: DataTypes.ENUM(...Object.values(AUDIT_ACTIONS)), allowNull: false }, entityType: { @@ -54,7 +69,7 @@ module.exports = (sequelize, DataTypes) => { ] }); - AuditLog.associate = (models) => { + (AuditLog as any).associate = (models: any) => { AuditLog.belongsTo(models.User, { foreignKey: 'userId', as: 'user' diff --git a/src/database/models/ConstitutionalChange.js b/src/database/models/ConstitutionalChange.ts similarity index 65% rename from src/database/models/ConstitutionalChange.js rename to src/database/models/ConstitutionalChange.ts index 5f8db11..50e5191 100644 --- a/src/database/models/ConstitutionalChange.js +++ b/src/database/models/ConstitutionalChange.ts @@ -1,7 +1,24 @@ -const { CONSTITUTIONAL_CHANGE_TYPES, CONSTITUTIONAL_STAGES } = require('../../common/config/constants'); +import { Model, DataTypes, Sequelize } from 'sequelize'; +import { CONSTITUTIONAL_CHANGE_TYPES, CONSTITUTIONAL_STAGES } from '../../common/config/constants.js'; -module.exports = (sequelize, DataTypes) => { - const ConstitutionalChange = sequelize.define('ConstitutionalChange', { +export interface ConstitutionalChangeAttributes { + id: string; + requestId: string; + outletId: string; + dealerId: string; + changeType: typeof CONSTITUTIONAL_CHANGE_TYPES[keyof typeof CONSTITUTIONAL_CHANGE_TYPES]; + description: string; + currentStage: typeof CONSTITUTIONAL_STAGES[keyof typeof CONSTITUTIONAL_STAGES]; + status: string; + progressPercentage: number; + documents: any[]; + timeline: any[]; +} + +export interface ConstitutionalChangeInstance extends Model, ConstitutionalChangeAttributes { } + +export default (sequelize: Sequelize) => { + const ConstitutionalChange = sequelize.define('ConstitutionalChange', { id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, @@ -29,7 +46,7 @@ module.exports = (sequelize, DataTypes) => { } }, changeType: { - type: DataTypes.ENUM(Object.values(CONSTITUTIONAL_CHANGE_TYPES)), + type: DataTypes.ENUM(...Object.values(CONSTITUTIONAL_CHANGE_TYPES)), allowNull: false }, description: { @@ -37,7 +54,7 @@ module.exports = (sequelize, DataTypes) => { allowNull: false }, currentStage: { - type: DataTypes.ENUM(Object.values(CONSTITUTIONAL_STAGES)), + type: DataTypes.ENUM(...Object.values(CONSTITUTIONAL_STAGES)), defaultValue: CONSTITUTIONAL_STAGES.DD_ADMIN_REVIEW }, status: { @@ -67,7 +84,7 @@ module.exports = (sequelize, DataTypes) => { ] }); - ConstitutionalChange.associate = (models) => { + (ConstitutionalChange as any).associate = (models: any) => { ConstitutionalChange.belongsTo(models.Outlet, { foreignKey: 'outletId', as: 'outlet' diff --git a/src/database/models/Dealer.ts b/src/database/models/Dealer.ts new file mode 100644 index 0000000..a1b5219 --- /dev/null +++ b/src/database/models/Dealer.ts @@ -0,0 +1,89 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface DealerAttributes { + id: string; + applicationId: string; + dealerCodeId: string | null; + legalName: string; + businessName: string; + constitutionType: string; + registeredAddress: string | null; + gstNumber: string | null; + panNumber: string | null; + status: string; + onboardedAt: Date | null; +} + +export interface DealerInstance extends Model, DealerAttributes { } + +export default (sequelize: Sequelize) => { + const Dealer = sequelize.define('Dealer', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + applicationId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'applications', + key: 'id' + } + }, + dealerCodeId: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'dealer_codes', + key: 'id' + } + }, + legalName: { + type: DataTypes.STRING, + allowNull: false + }, + businessName: { + type: DataTypes.STRING, + allowNull: false + }, + constitutionType: { + type: DataTypes.STRING, + allowNull: false + }, + registeredAddress: { + type: DataTypes.TEXT, + allowNull: true + }, + gstNumber: { + type: DataTypes.STRING, + allowNull: true + }, + panNumber: { + type: DataTypes.STRING, + allowNull: true + }, + status: { + type: DataTypes.STRING, + defaultValue: 'active' + }, + onboardedAt: { + type: DataTypes.DATE, + allowNull: true + } + }, { + tableName: 'dealers', + timestamps: true + }); + + (Dealer as any).associate = (models: any) => { + Dealer.belongsTo(models.Application, { foreignKey: 'applicationId', as: 'application' }); + Dealer.belongsTo(models.DealerCode, { foreignKey: 'dealerCodeId', as: 'dealerCode' }); + + Dealer.hasMany(models.Document, { foreignKey: 'dealerId', as: 'documents' }); + Dealer.hasMany(models.Resignation, { foreignKey: 'dealerId', as: 'resignations' }); + Dealer.hasMany(models.TerminationRequest, { foreignKey: 'dealerId', as: 'terminationRequests' }); + }; + + return Dealer; +}; diff --git a/src/database/models/DealerCode.ts b/src/database/models/DealerCode.ts new file mode 100644 index 0000000..6deab7c --- /dev/null +++ b/src/database/models/DealerCode.ts @@ -0,0 +1,53 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface DealerCodeAttribute { + id: string; + dealerCode: string; + status: string; + generatedAt: Date; + generatedBy: string | null; +} + +export interface DealerCodeInstance extends Model, DealerCodeAttribute { } + +export default (sequelize: Sequelize) => { + const DealerCode = sequelize.define('DealerCode', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + dealerCode: { + type: DataTypes.STRING, + unique: true, + allowNull: false + }, + status: { + type: DataTypes.STRING, + defaultValue: 'active' + }, + generatedAt: { + type: DataTypes.DATE, + defaultValue: DataTypes.NOW + }, + generatedBy: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'users', + key: 'id' + } + } + }, { + tableName: 'dealer_codes', + timestamps: true, + updatedAt: false + }); + + (DealerCode as any).associate = (models: any) => { + DealerCode.belongsTo(models.User, { foreignKey: 'generatedBy', as: 'generator' }); + DealerCode.hasOne(models.Dealer, { foreignKey: 'dealerCodeId', as: 'dealer' }); + }; + + return DealerCode; +}; diff --git a/src/database/models/District.ts b/src/database/models/District.ts new file mode 100644 index 0000000..56f3169 --- /dev/null +++ b/src/database/models/District.ts @@ -0,0 +1,53 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface DistrictAttributes { + id: string; + stateId: string; + districtName: string; + isActive: boolean; +} + +export interface DistrictInstance extends Model, DistrictAttributes { } + +export default (sequelize: Sequelize) => { + const District = sequelize.define('District', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + stateId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'states', + key: 'id' + } + }, + districtName: { + type: DataTypes.STRING, + allowNull: false + }, + isActive: { + type: DataTypes.BOOLEAN, + defaultValue: true + } + }, { + tableName: 'districts', + timestamps: true, + updatedAt: false + }); + + (District as any).associate = (models: any) => { + District.belongsTo(models.State, { + foreignKey: 'stateId', + as: 'state' + }); + District.hasMany(models.Area, { + foreignKey: 'districtId', + as: 'areas' + }); + }; + + return District; +}; diff --git a/src/database/models/DistrictManager.ts b/src/database/models/DistrictManager.ts new file mode 100644 index 0000000..df6d26d --- /dev/null +++ b/src/database/models/DistrictManager.ts @@ -0,0 +1,67 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface DistrictManagerAttributes { + id: string; + districtId: string; + userId: string; + managerType: string; + isActive: boolean; + assignedAt: Date; +} + +export interface DistrictManagerInstance extends Model, DistrictManagerAttributes { } + +export default (sequelize: Sequelize) => { + const DistrictManager = sequelize.define('DistrictManager', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + districtId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'districts', + key: 'id' + } + }, + userId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'users', + key: 'id' + } + }, + managerType: { + type: DataTypes.STRING, + allowNull: false + }, + isActive: { + type: DataTypes.BOOLEAN, + defaultValue: true + }, + assignedAt: { + type: DataTypes.DATE, + defaultValue: DataTypes.NOW + } + }, { + tableName: 'district_managers', + timestamps: true, + updatedAt: false + }); + + (DistrictManager as any).associate = (models: any) => { + DistrictManager.belongsTo(models.District, { + foreignKey: 'districtId', + as: 'district' + }); + DistrictManager.belongsTo(models.User, { + foreignKey: 'userId', + as: 'user' + }); + }; + + return DistrictManager; +}; diff --git a/src/database/models/Document.js b/src/database/models/Document.js deleted file mode 100644 index e6ff4c2..0000000 --- a/src/database/models/Document.js +++ /dev/null @@ -1,64 +0,0 @@ -const { REQUEST_TYPES, DOCUMENT_TYPES } = require('../../common/config/constants'); - -module.exports = (sequelize, DataTypes) => { - const Document = sequelize.define('Document', { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true - }, - requestId: { - type: DataTypes.UUID, - allowNull: false - }, - requestType: { - type: DataTypes.ENUM(Object.values(REQUEST_TYPES)), - allowNull: false - }, - documentType: { - type: DataTypes.ENUM(Object.values(DOCUMENT_TYPES)), - allowNull: false - }, - fileName: { - type: DataTypes.STRING, - allowNull: false - }, - fileUrl: { - type: DataTypes.STRING, - allowNull: false - }, - fileSize: { - type: DataTypes.INTEGER, - allowNull: true - }, - uploadedBy: { - type: DataTypes.UUID, - allowNull: false, - references: { - model: 'users', - key: 'id' - } - }, - status: { - type: DataTypes.STRING, - defaultValue: 'Active' - } - }, { - tableName: 'documents', - timestamps: true, - indexes: [ - { fields: ['requestId'] }, - { fields: ['requestType'] }, - { fields: ['documentType'] } - ] - }); - - Document.associate = (models) => { - Document.belongsTo(models.User, { - foreignKey: 'uploadedBy', - as: 'uploader' - }); - }; - - return Document; -}; diff --git a/src/database/models/Document.ts b/src/database/models/Document.ts new file mode 100644 index 0000000..d450882 --- /dev/null +++ b/src/database/models/Document.ts @@ -0,0 +1,104 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; +import { REQUEST_TYPES, DOCUMENT_TYPES } from '../../common/config/constants.js'; + +export interface DocumentAttributes { + id: string; + applicationId: string | null; + dealerId: string | null; + requestId: string | null; // Compatibility + requestType: string | null; // Compatibility + documentType: string; + fileName: string; + filePath: string; + fileSize: number | null; + mimeType: string | null; + status: string; + uploadedBy: string | null; +} + +export interface DocumentInstance extends Model, DocumentAttributes { } + +export default (sequelize: Sequelize) => { + const Document = sequelize.define('Document', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + applicationId: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'applications', + key: 'id' + } + }, + dealerId: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'dealers', + key: 'id' + } + }, + requestId: { + type: DataTypes.UUID, + allowNull: true + }, + requestType: { + type: DataTypes.STRING, + allowNull: true + }, + documentType: { + type: DataTypes.STRING, + allowNull: false + }, + fileName: { + type: DataTypes.STRING, + allowNull: false + }, + filePath: { + type: DataTypes.STRING, + allowNull: false + }, + fileSize: { + type: DataTypes.INTEGER, + allowNull: true + }, + mimeType: { + type: DataTypes.STRING, + allowNull: true + }, + status: { + type: DataTypes.STRING, + defaultValue: 'active' + }, + uploadedBy: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'users', + key: 'id' + } + } + }, { + tableName: 'documents', + timestamps: true, + indexes: [ + { fields: ['applicationId'] }, + { fields: ['dealerId'] }, + { fields: ['requestId'] } + ] + }); + + (Document as any).associate = (models: any) => { + Document.belongsTo(models.User, { foreignKey: 'uploadedBy', as: 'uploader' }); + Document.belongsTo(models.Application, { foreignKey: 'applicationId', as: 'application' }); + Document.belongsTo(models.Dealer, { foreignKey: 'dealerId', as: 'dealer' }); + + Document.hasMany(models.DocumentVersion, { foreignKey: 'documentId', as: 'versions' }); + Document.hasMany(models.WorkNoteAttachment, { foreignKey: 'documentId', as: 'workNoteAttachments' }); + }; + + return Document; +}; diff --git a/src/database/models/DocumentVersion.ts b/src/database/models/DocumentVersion.ts new file mode 100644 index 0000000..a580d63 --- /dev/null +++ b/src/database/models/DocumentVersion.ts @@ -0,0 +1,56 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface DocumentVersionAttributes { + id: string; + documentId: string; + versionNumber: number; + filePath: string; + uploadedBy: string | null; +} + +export interface DocumentVersionInstance extends Model, DocumentVersionAttributes { } + +export default (sequelize: Sequelize) => { + const DocumentVersion = sequelize.define('DocumentVersion', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + documentId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'documents', + key: 'id' + } + }, + versionNumber: { + type: DataTypes.INTEGER, + allowNull: false + }, + filePath: { + type: DataTypes.STRING, + allowNull: false + }, + uploadedBy: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'users', + key: 'id' + } + } + }, { + tableName: 'document_versions', + timestamps: true, + updatedAt: false + }); + + (DocumentVersion as any).associate = (models: any) => { + DocumentVersion.belongsTo(models.Document, { foreignKey: 'documentId', as: 'document' }); + DocumentVersion.belongsTo(models.User, { foreignKey: 'uploadedBy', as: 'uploader' }); + }; + + return DocumentVersion; +}; diff --git a/src/database/models/EmailTemplate.ts b/src/database/models/EmailTemplate.ts new file mode 100644 index 0000000..0a56456 --- /dev/null +++ b/src/database/models/EmailTemplate.ts @@ -0,0 +1,53 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface EmailTemplateAttributes { + id: string; + templateCode: string; + description: string; + subject: string; + body: string; + placeholders: any; // JSON array of strings + isActive: boolean; +} + +export interface EmailTemplateInstance extends Model, EmailTemplateAttributes { } + +export default (sequelize: Sequelize) => { + const EmailTemplate = sequelize.define('EmailTemplate', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + templateCode: { + type: DataTypes.STRING, + unique: true, + allowNull: false + }, + description: { + type: DataTypes.STRING, + allowNull: false + }, + subject: { + type: DataTypes.STRING, + allowNull: false + }, + body: { + type: DataTypes.TEXT, + allowNull: false + }, + placeholders: { + type: DataTypes.JSON, + defaultValue: [] + }, + isActive: { + type: DataTypes.BOOLEAN, + defaultValue: true + } + }, { + tableName: 'email_templates', + timestamps: true + }); + + return EmailTemplate; +}; diff --git a/src/database/models/EorChecklist.ts b/src/database/models/EorChecklist.ts new file mode 100644 index 0000000..f1868fe --- /dev/null +++ b/src/database/models/EorChecklist.ts @@ -0,0 +1,62 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface EorChecklistAttributes { + id: string; + applicationId: string; + auditorId: string | null; + auditDate: Date | null; + status: string; + overallComments: string | null; +} + +export interface EorChecklistInstance extends Model, EorChecklistAttributes { } + +export default (sequelize: Sequelize) => { + const EorChecklist = sequelize.define('EorChecklist', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + applicationId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'applications', + key: 'id' + } + }, + auditorId: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'users', + key: 'id' + } + }, + auditDate: { + type: DataTypes.DATE, + allowNull: true + }, + status: { + type: DataTypes.STRING, + defaultValue: 'pending' + }, + overallComments: { + type: DataTypes.TEXT, + allowNull: true + } + }, { + tableName: 'eor_checklists', + timestamps: true + }); + + (EorChecklist as any).associate = (models: any) => { + EorChecklist.belongsTo(models.Application, { foreignKey: 'applicationId', as: 'application' }); + EorChecklist.belongsTo(models.User, { foreignKey: 'auditorId', as: 'auditor' }); + + EorChecklist.hasMany(models.EorChecklistItem, { foreignKey: 'checklistId', as: 'items' }); + }; + + return EorChecklist; +}; diff --git a/src/database/models/EorChecklistItem.ts b/src/database/models/EorChecklistItem.ts new file mode 100644 index 0000000..66c6b88 --- /dev/null +++ b/src/database/models/EorChecklistItem.ts @@ -0,0 +1,65 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface EorChecklistItemAttributes { + id: string; + checklistId: string; + itemType: string; + description: string; + isCompliant: boolean; + remarks: string | null; + proofDocumentId: string | null; +} + +export interface EorChecklistItemInstance extends Model, EorChecklistItemAttributes { } + +export default (sequelize: Sequelize) => { + const EorChecklistItem = sequelize.define('EorChecklistItem', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + checklistId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'eor_checklists', + key: 'id' + } + }, + itemType: { + type: DataTypes.STRING, + allowNull: false + }, + description: { + type: DataTypes.TEXT, + allowNull: false + }, + isCompliant: { + type: DataTypes.BOOLEAN, + defaultValue: false + }, + remarks: { + type: DataTypes.TEXT, + allowNull: true + }, + proofDocumentId: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'documents', + key: 'id' + } + } + }, { + tableName: 'eor_checklist_items', + timestamps: true + }); + + (EorChecklistItem as any).associate = (models: any) => { + EorChecklistItem.belongsTo(models.EorChecklist, { foreignKey: 'checklistId', as: 'checklist' }); + EorChecklistItem.belongsTo(models.Document, { foreignKey: 'proofDocumentId', as: 'proofDocument' }); + }; + + return EorChecklistItem; +}; diff --git a/src/database/models/ExitFeedback.ts b/src/database/models/ExitFeedback.ts new file mode 100644 index 0000000..7e1ec22 --- /dev/null +++ b/src/database/models/ExitFeedback.ts @@ -0,0 +1,76 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface ExitFeedbackAttributes { + id: string; + resignationId: string | null; + terminationRequestId: string | null; + feedbackType: string; + ratings: any; // JSON + comments: string | null; + submittedAt: Date; + submittedBy: string | null; +} + +export interface ExitFeedbackInstance extends Model, ExitFeedbackAttributes { } + +export default (sequelize: Sequelize) => { + const ExitFeedback = sequelize.define('ExitFeedback', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + resignationId: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'resignations', + key: 'id' + } + }, + terminationRequestId: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'termination_requests', + key: 'id' + } + }, + feedbackType: { + type: DataTypes.STRING, + allowNull: false + }, + ratings: { + type: DataTypes.JSON, + defaultValue: {} + }, + comments: { + type: DataTypes.TEXT, + allowNull: true + }, + submittedAt: { + type: DataTypes.DATE, + defaultValue: DataTypes.NOW + }, + submittedBy: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'users', + key: 'id' + } + } + }, { + tableName: 'exit_feedbacks', + timestamps: true, + updatedAt: false + }); + + (ExitFeedback as any).associate = (models: any) => { + ExitFeedback.belongsTo(models.Resignation, { foreignKey: 'resignationId', as: 'resignation' }); + ExitFeedback.belongsTo(models.TerminationRequest, { foreignKey: 'terminationRequestId', as: 'terminationRequest' }); + ExitFeedback.belongsTo(models.User, { foreignKey: 'submittedBy', as: 'submitter' }); + }; + + return ExitFeedback; +}; diff --git a/src/database/models/FddAssignment.ts b/src/database/models/FddAssignment.ts new file mode 100644 index 0000000..8eaf835 --- /dev/null +++ b/src/database/models/FddAssignment.ts @@ -0,0 +1,51 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface FddAssignmentAttributes { + id: string; + applicationId: string; + assignedToAgency: string | null; + status: string; +} + +export interface FddAssignmentInstance extends Model, FddAssignmentAttributes { } + +export default (sequelize: Sequelize) => { + const FddAssignment = sequelize.define('FddAssignment', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + applicationId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'applications', + key: 'id' + } + }, + assignedToAgency: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'users', + key: 'id' + } + }, + status: { + type: DataTypes.STRING, + defaultValue: 'pending' + } + }, { + tableName: 'fdd_assignments', + timestamps: true + }); + + (FddAssignment as any).associate = (models: any) => { + FddAssignment.belongsTo(models.Application, { foreignKey: 'applicationId', as: 'application' }); + FddAssignment.belongsTo(models.User, { foreignKey: 'assignedToAgency', as: 'agency' }); + FddAssignment.hasMany(models.FddReport, { foreignKey: 'assignmentId', as: 'reports' }); + }; + + return FddAssignment; +}; diff --git a/src/database/models/FddReport.ts b/src/database/models/FddReport.ts new file mode 100644 index 0000000..5658726 --- /dev/null +++ b/src/database/models/FddReport.ts @@ -0,0 +1,70 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface FddReportAttributes { + id: string; + assignmentId: string; + reportDocumentId: string | null; + findings: string | null; + recommendation: string | null; + verifiedAt: Date | null; + verifiedBy: string | null; +} + +export interface FddReportInstance extends Model, FddReportAttributes { } + +export default (sequelize: Sequelize) => { + const FddReport = sequelize.define('FddReport', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + assignmentId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'fdd_assignments', + key: 'id' + } + }, + reportDocumentId: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'documents', + key: 'id' + } + }, + findings: { + type: DataTypes.TEXT, + allowNull: true + }, + recommendation: { + type: DataTypes.STRING, + allowNull: true + }, + verifiedAt: { + type: DataTypes.DATE, + allowNull: true + }, + verifiedBy: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'users', + key: 'id' + } + } + }, { + tableName: 'fdd_reports', + timestamps: true + }); + + (FddReport as any).associate = (models: any) => { + FddReport.belongsTo(models.FddAssignment, { foreignKey: 'assignmentId', as: 'assignment' }); + FddReport.belongsTo(models.Document, { foreignKey: 'reportDocumentId', as: 'reportDocument' }); + FddReport.belongsTo(models.User, { foreignKey: 'verifiedBy', as: 'verifier' }); + }; + + return FddReport; +}; diff --git a/src/database/models/FinancePayment.js b/src/database/models/FinancePayment.ts similarity index 63% rename from src/database/models/FinancePayment.js rename to src/database/models/FinancePayment.ts index 42905ca..e55b46a 100644 --- a/src/database/models/FinancePayment.js +++ b/src/database/models/FinancePayment.ts @@ -1,7 +1,23 @@ -const { PAYMENT_TYPES, PAYMENT_STATUS } = require('../../common/config/constants'); +import { Model, DataTypes, Sequelize } from 'sequelize'; +import { PAYMENT_TYPES, PAYMENT_STATUS } from '../../common/config/constants.js'; -module.exports = (sequelize, DataTypes) => { - const FinancePayment = sequelize.define('FinancePayment', { +export interface FinancePaymentAttributes { + id: string; + applicationId: string; + paymentType: typeof PAYMENT_TYPES[keyof typeof PAYMENT_TYPES]; + amount: number; + paymentStatus: typeof PAYMENT_STATUS[keyof typeof PAYMENT_STATUS]; + transactionId: string | null; + paymentDate: Date | null; + verifiedBy: string | null; + verificationDate: Date | null; + remarks: string | null; +} + +export interface FinancePaymentInstance extends Model, FinancePaymentAttributes { } + +export default (sequelize: Sequelize) => { + const FinancePayment = sequelize.define('FinancePayment', { id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, @@ -16,7 +32,7 @@ module.exports = (sequelize, DataTypes) => { } }, paymentType: { - type: DataTypes.ENUM(Object.values(PAYMENT_TYPES)), + type: DataTypes.ENUM(...Object.values(PAYMENT_TYPES)), allowNull: false }, amount: { @@ -24,7 +40,7 @@ module.exports = (sequelize, DataTypes) => { allowNull: false }, paymentStatus: { - type: DataTypes.ENUM(Object.values(PAYMENT_STATUS)), + type: DataTypes.ENUM(...Object.values(PAYMENT_STATUS)), defaultValue: PAYMENT_STATUS.PENDING }, transactionId: { @@ -60,7 +76,7 @@ module.exports = (sequelize, DataTypes) => { ] }); - FinancePayment.associate = (models) => { + (FinancePayment as any).associate = (models: any) => { FinancePayment.belongsTo(models.Application, { foreignKey: 'applicationId', as: 'application' diff --git a/src/database/models/FnF.js b/src/database/models/FnF.js deleted file mode 100644 index 3974072..0000000 --- a/src/database/models/FnF.js +++ /dev/null @@ -1,72 +0,0 @@ -const { FNF_STATUS } = require('../../common/config/constants'); - -module.exports = (sequelize, DataTypes) => { - const FnF = sequelize.define('FnF', { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true - }, - resignationId: { - type: DataTypes.UUID, - allowNull: true, - references: { - model: 'resignations', - key: 'id' - } - }, - outletId: { - type: DataTypes.UUID, - allowNull: false, - references: { - model: 'outlets', - key: 'id' - } - }, - status: { - type: DataTypes.ENUM(Object.values(FNF_STATUS)), - defaultValue: FNF_STATUS.INITIATED - }, - totalReceivables: { - type: DataTypes.DECIMAL(15, 2), - defaultValue: 0 - }, - totalPayables: { - type: DataTypes.DECIMAL(15, 2), - defaultValue: 0 - }, - netAmount: { - type: DataTypes.DECIMAL(15, 2), - defaultValue: 0 - }, - settlementDate: { - type: DataTypes.DATE, - allowNull: true - }, - clearanceDocuments: { - type: DataTypes.JSON, - defaultValue: [] - } - }, { - tableName: 'fnf_settlements', - timestamps: true, - indexes: [ - { fields: ['resignationId'] }, - { fields: ['outletId'] }, - { fields: ['status'] } - ] - }); - - FnF.associate = (models) => { - FnF.belongsTo(models.Resignation, { - foreignKey: 'resignationId', - as: 'resignation' - }); - FnF.belongsTo(models.Outlet, { - foreignKey: 'outletId', - as: 'outlet' - }); - }; - - return FnF; -}; diff --git a/src/database/models/FnF.ts b/src/database/models/FnF.ts new file mode 100644 index 0000000..74d9bfd --- /dev/null +++ b/src/database/models/FnF.ts @@ -0,0 +1,103 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; +import { FNF_STATUS } from '../../common/config/constants.js'; + +export interface FnFAttributes { + id: string; + resignationId: string | null; + terminationRequestId: string | null; + outletId: string | null; + dealerId: string | null; // For direct dealer level F&F + status: string; + totalReceivables: number; + totalPayables: number; + netAmount: number; + settlementDate: Date | null; + clearanceDocuments: any[]; +} + +export interface FnFInstance extends Model, FnFAttributes { } + +export default (sequelize: Sequelize) => { + const FnF = sequelize.define('FnF', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + resignationId: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'resignations', + key: 'id' + } + }, + terminationRequestId: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'termination_requests', + key: 'id' + } + }, + outletId: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'outlets', + key: 'id' + } + }, + dealerId: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'dealers', + key: 'id' + } + }, + status: { + type: DataTypes.STRING, + defaultValue: 'Initiated' + }, + totalReceivables: { + type: DataTypes.DECIMAL(15, 2), + defaultValue: 0 + }, + totalPayables: { + type: DataTypes.DECIMAL(15, 2), + defaultValue: 0 + }, + netAmount: { + type: DataTypes.DECIMAL(15, 2), + defaultValue: 0 + }, + settlementDate: { + type: DataTypes.DATE, + allowNull: true + }, + clearanceDocuments: { + type: DataTypes.JSON, + defaultValue: [] + } + }, { + tableName: 'fnf_settlements', + timestamps: true, + indexes: [ + { fields: ['resignationId'] }, + { fields: ['terminationRequestId'] }, + { fields: ['outletId'] }, + { fields: ['status'] } + ] + }); + + (FnF as any).associate = (models: any) => { + FnF.belongsTo(models.Resignation, { foreignKey: 'resignationId', as: 'resignation' }); + FnF.belongsTo(models.TerminationRequest, { foreignKey: 'terminationRequestId', as: 'terminationRequest' }); + FnF.belongsTo(models.Outlet, { foreignKey: 'outletId', as: 'outlet' }); + FnF.belongsTo(models.Dealer, { foreignKey: 'dealerId', as: 'dealer' }); + FnF.hasMany(models.FnFLineItem, { foreignKey: 'fnfId', as: 'lineItems' }); + }; + + return FnF; +}; diff --git a/src/database/models/FnFLineItem.js b/src/database/models/FnFLineItem.ts similarity index 72% rename from src/database/models/FnFLineItem.js rename to src/database/models/FnFLineItem.ts index 65e15cc..3cf9791 100644 --- a/src/database/models/FnFLineItem.js +++ b/src/database/models/FnFLineItem.ts @@ -1,5 +1,19 @@ -module.exports = (sequelize, DataTypes) => { - const FnFLineItem = sequelize.define('FnFLineItem', { +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface FnFLineItemAttributes { + id: string; + fnfId: string; + itemType: 'Payable' | 'Receivable' | 'Deduction'; + description: string; + department: string; + amount: number; + addedBy: string | null; +} + +export interface FnFLineItemInstance extends Model, FnFLineItemAttributes { } + +export default (sequelize: Sequelize) => { + const FnFLineItem = sequelize.define('FnFLineItem', { id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, @@ -48,7 +62,7 @@ module.exports = (sequelize, DataTypes) => { ] }); - FnFLineItem.associate = (models) => { + (FnFLineItem as any).associate = (models: any) => { FnFLineItem.belongsTo(models.FnF, { foreignKey: 'fnfId', as: 'settlement' diff --git a/src/database/models/Interview.ts b/src/database/models/Interview.ts new file mode 100644 index 0000000..e08b10b --- /dev/null +++ b/src/database/models/Interview.ts @@ -0,0 +1,62 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface InterviewAttributes { + id: string; + applicationId: string; + level: number; + scheduleDate: Date | null; + interviewType: string; + linkOrLocation: string | null; + status: string; +} + +export interface InterviewInstance extends Model, InterviewAttributes { } + +export default (sequelize: Sequelize) => { + const Interview = sequelize.define('Interview', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + applicationId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'applications', + key: 'id' + } + }, + level: { + type: DataTypes.INTEGER, + allowNull: false + }, + scheduleDate: { + type: DataTypes.DATE, + allowNull: true + }, + interviewType: { + type: DataTypes.STRING, + allowNull: false + }, + linkOrLocation: { + type: DataTypes.STRING, + allowNull: true + }, + status: { + type: DataTypes.STRING, + defaultValue: 'scheduled' + } + }, { + tableName: 'interviews', + timestamps: true + }); + + (Interview as any).associate = (models: any) => { + Interview.belongsTo(models.Application, { foreignKey: 'applicationId', as: 'application' }); + Interview.hasMany(models.InterviewParticipant, { foreignKey: 'interviewId', as: 'participants' }); + Interview.hasMany(models.InterviewEvaluation, { foreignKey: 'interviewId', as: 'evaluations' }); + }; + + return Interview; +}; diff --git a/src/database/models/InterviewEvaluation.ts b/src/database/models/InterviewEvaluation.ts new file mode 100644 index 0000000..3de45e9 --- /dev/null +++ b/src/database/models/InterviewEvaluation.ts @@ -0,0 +1,62 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface InterviewEvaluationAttributes { + id: string; + interviewId: string; + evaluatorId: string; + ktMatrixScore: number | null; + qualitativeFeedback: string | null; + recommendation: string | null; +} + +export interface InterviewEvaluationInstance extends Model, InterviewEvaluationAttributes { } + +export default (sequelize: Sequelize) => { + const InterviewEvaluation = sequelize.define('InterviewEvaluation', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + interviewId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'interviews', + key: 'id' + } + }, + evaluatorId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'users', + key: 'id' + } + }, + ktMatrixScore: { + type: DataTypes.DECIMAL(10, 2), + allowNull: true + }, + qualitativeFeedback: { + type: DataTypes.TEXT, + allowNull: true + }, + recommendation: { + type: DataTypes.STRING, + allowNull: true + } + }, { + tableName: 'interview_evaluations', + timestamps: true + }); + + (InterviewEvaluation as any).associate = (models: any) => { + InterviewEvaluation.belongsTo(models.Interview, { foreignKey: 'interviewId', as: 'interview' }); + InterviewEvaluation.belongsTo(models.User, { foreignKey: 'evaluatorId', as: 'evaluator' }); + InterviewEvaluation.hasMany(models.KTMatrixScore, { foreignKey: 'evaluationId', as: 'criterionScores' }); + InterviewEvaluation.hasMany(models.InterviewFeedback, { foreignKey: 'evaluationId', as: 'feedbackDetails' }); + }; + + return InterviewEvaluation; +}; diff --git a/src/database/models/InterviewFeedback.ts b/src/database/models/InterviewFeedback.ts new file mode 100644 index 0000000..f9cdc7e --- /dev/null +++ b/src/database/models/InterviewFeedback.ts @@ -0,0 +1,46 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface InterviewFeedbackAttributes { + id: string; + evaluationId: string; + feedbackType: string; + comments: string; +} + +export interface InterviewFeedbackInstance extends Model, InterviewFeedbackAttributes { } + +export default (sequelize: Sequelize) => { + const InterviewFeedback = sequelize.define('InterviewFeedback', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + evaluationId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'interview_evaluations', + key: 'id' + } + }, + feedbackType: { + type: DataTypes.STRING, + allowNull: false + }, + comments: { + type: DataTypes.TEXT, + allowNull: false + } + }, { + tableName: 'interview_feedback', + timestamps: true, + updatedAt: false + }); + + (InterviewFeedback as any).associate = (models: any) => { + InterviewFeedback.belongsTo(models.InterviewEvaluation, { foreignKey: 'evaluationId', as: 'evaluation' }); + }; + + return InterviewFeedback; +}; diff --git a/src/database/models/InterviewParticipant.ts b/src/database/models/InterviewParticipant.ts new file mode 100644 index 0000000..635bb34 --- /dev/null +++ b/src/database/models/InterviewParticipant.ts @@ -0,0 +1,51 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface InterviewParticipantAttributes { + id: string; + interviewId: string; + userId: string; + roleInPanel: string | null; +} + +export interface InterviewParticipantInstance extends Model, InterviewParticipantAttributes { } + +export default (sequelize: Sequelize) => { + const InterviewParticipant = sequelize.define('InterviewParticipant', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + interviewId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'interviews', + key: 'id' + } + }, + userId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'users', + key: 'id' + } + }, + roleInPanel: { + type: DataTypes.STRING, + allowNull: true + } + }, { + tableName: 'interview_participants', + timestamps: true, + updatedAt: false + }); + + (InterviewParticipant as any).associate = (models: any) => { + InterviewParticipant.belongsTo(models.Interview, { foreignKey: 'interviewId', as: 'interview' }); + InterviewParticipant.belongsTo(models.User, { foreignKey: 'userId', as: 'user' }); + }; + + return InterviewParticipant; +}; diff --git a/src/database/models/KTMatrixScore.ts b/src/database/models/KTMatrixScore.ts new file mode 100644 index 0000000..e847577 --- /dev/null +++ b/src/database/models/KTMatrixScore.ts @@ -0,0 +1,61 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface KTMatrixScoreAttributes { + id: string; + evaluationId: string; + criterionName: string; + score: number; + maxScore: number; + weightage: number; + weightedScore: number; +} + +export interface KTMatrixScoreInstance extends Model, KTMatrixScoreAttributes { } + +export default (sequelize: Sequelize) => { + const KTMatrixScore = sequelize.define('KTMatrixScore', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + evaluationId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'interview_evaluations', + key: 'id' + } + }, + criterionName: { + type: DataTypes.STRING, + allowNull: false + }, + score: { + type: DataTypes.DECIMAL(10, 2), + allowNull: false + }, + maxScore: { + type: DataTypes.DECIMAL(10, 2), + allowNull: false + }, + weightage: { + type: DataTypes.DECIMAL(10, 2), + allowNull: false + }, + weightedScore: { + type: DataTypes.DECIMAL(10, 2), + allowNull: false + } + }, { + tableName: 'kt_matrix_scores', + timestamps: true, + updatedAt: false + }); + + (KTMatrixScore as any).associate = (models: any) => { + KTMatrixScore.belongsTo(models.InterviewEvaluation, { foreignKey: 'evaluationId', as: 'evaluation' }); + }; + + return KTMatrixScore; +}; diff --git a/src/database/models/LoaAcknowledgement.ts b/src/database/models/LoaAcknowledgement.ts new file mode 100644 index 0000000..5df1411 --- /dev/null +++ b/src/database/models/LoaAcknowledgement.ts @@ -0,0 +1,56 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface LoaAcknowledgementAttributes { + id: string; + loaDocId: string; + applicantId: string; + acknowledgedAt: Date; + remarks: string | null; +} + +export interface LoaAcknowledgementInstance extends Model, LoaAcknowledgementAttributes { } + +export default (sequelize: Sequelize) => { + const LoaAcknowledgement = sequelize.define('LoaAcknowledgement', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + loaDocId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'loa_documents_generated', + key: 'id' + } + }, + applicantId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'users', + key: 'id' + } + }, + acknowledgedAt: { + type: DataTypes.DATE, + defaultValue: DataTypes.NOW + }, + remarks: { + type: DataTypes.TEXT, + allowNull: true + } + }, { + tableName: 'loa_acknowledgements', + timestamps: true, + updatedAt: false + }); + + (LoaAcknowledgement as any).associate = (models: any) => { + LoaAcknowledgement.belongsTo(models.LoaDocumentGenerated, { foreignKey: 'loaDocId', as: 'loaDocument' }); + LoaAcknowledgement.belongsTo(models.User, { foreignKey: 'applicantId', as: 'applicant' }); + }; + + return LoaAcknowledgement; +}; diff --git a/src/database/models/LoaApproval.ts b/src/database/models/LoaApproval.ts new file mode 100644 index 0000000..ddbaa98 --- /dev/null +++ b/src/database/models/LoaApproval.ts @@ -0,0 +1,66 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface LoaApprovalAttributes { + id: string; + requestId: string; + level: number; + approverRole: string; + approverId: string | null; + action: string; + remarks: string | null; +} + +export interface LoaApprovalInstance extends Model, LoaApprovalAttributes { } + +export default (sequelize: Sequelize) => { + const LoaApproval = sequelize.define('LoaApproval', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + requestId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'loa_requests', + key: 'id' + } + }, + level: { + type: DataTypes.INTEGER, + allowNull: false + }, + approverRole: { + type: DataTypes.STRING, + allowNull: false + }, + approverId: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'users', + key: 'id' + } + }, + action: { + type: DataTypes.STRING, + allowNull: false + }, + remarks: { + type: DataTypes.TEXT, + allowNull: true + } + }, { + tableName: 'loa_approvals', + timestamps: true, + updatedAt: false + }); + + (LoaApproval as any).associate = (models: any) => { + LoaApproval.belongsTo(models.LoaRequest, { foreignKey: 'requestId', as: 'request' }); + LoaApproval.belongsTo(models.User, { foreignKey: 'approverId', as: 'approver' }); + }; + + return LoaApproval; +}; diff --git a/src/database/models/LoaDocumentGenerated.ts b/src/database/models/LoaDocumentGenerated.ts new file mode 100644 index 0000000..67b3f8f --- /dev/null +++ b/src/database/models/LoaDocumentGenerated.ts @@ -0,0 +1,57 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface LoaDocumentGeneratedAttributes { + id: string; + requestId: string; + documentId: string; + version: string; + generatedAt: Date; +} + +export interface LoaDocumentGeneratedInstance extends Model, LoaDocumentGeneratedAttributes { } + +export default (sequelize: Sequelize) => { + const LoaDocumentGenerated = sequelize.define('LoaDocumentGenerated', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + requestId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'loa_requests', + key: 'id' + } + }, + documentId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'documents', + key: 'id' + } + }, + version: { + type: DataTypes.STRING, + allowNull: false + }, + generatedAt: { + type: DataTypes.DATE, + defaultValue: DataTypes.NOW + } + }, { + tableName: 'loa_documents_generated', + timestamps: true, + updatedAt: false + }); + + (LoaDocumentGenerated as any).associate = (models: any) => { + LoaDocumentGenerated.belongsTo(models.LoaRequest, { foreignKey: 'requestId', as: 'request' }); + LoaDocumentGenerated.belongsTo(models.Document, { foreignKey: 'documentId', as: 'document' }); + LoaDocumentGenerated.hasMany(models.LoaAcknowledgement, { foreignKey: 'loaDocId', as: 'acknowledgements' }); + }; + + return LoaDocumentGenerated; +}; diff --git a/src/database/models/LoaRequest.ts b/src/database/models/LoaRequest.ts new file mode 100644 index 0000000..f3f9c2d --- /dev/null +++ b/src/database/models/LoaRequest.ts @@ -0,0 +1,68 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface LoaRequestAttributes { + id: string; + applicationId: string; + status: string; + requestedBy: string | null; + approvedAt: Date | null; + approvedBy: string | null; +} + +export interface LoaRequestInstance extends Model, LoaRequestAttributes { } + +export default (sequelize: Sequelize) => { + const LoaRequest = sequelize.define('LoaRequest', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + applicationId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'applications', + key: 'id' + } + }, + status: { + type: DataTypes.STRING, + defaultValue: 'pending' + }, + requestedBy: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'users', + key: 'id' + } + }, + approvedAt: { + type: DataTypes.DATE, + allowNull: true + }, + approvedBy: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'users', + key: 'id' + } + } + }, { + tableName: 'loa_requests', + timestamps: true + }); + + (LoaRequest as any).associate = (models: any) => { + LoaRequest.belongsTo(models.Application, { foreignKey: 'applicationId', as: 'application' }); + LoaRequest.belongsTo(models.User, { foreignKey: 'requestedBy', as: 'requester' }); + LoaRequest.belongsTo(models.User, { foreignKey: 'approvedBy', as: 'approver' }); + + LoaRequest.hasMany(models.LoaApproval, { foreignKey: 'requestId', as: 'approvals' }); + LoaRequest.hasMany(models.LoaDocumentGenerated, { foreignKey: 'requestId', as: 'generatedDocuments' }); + }; + + return LoaRequest; +}; diff --git a/src/database/models/LoiAcknowledgement.ts b/src/database/models/LoiAcknowledgement.ts new file mode 100644 index 0000000..687be84 --- /dev/null +++ b/src/database/models/LoiAcknowledgement.ts @@ -0,0 +1,56 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface LoiAcknowledgementAttributes { + id: string; + loiDocId: string; + applicantId: string; + acknowledgedAt: Date; + remarks: string | null; +} + +export interface LoiAcknowledgementInstance extends Model, LoiAcknowledgementAttributes { } + +export default (sequelize: Sequelize) => { + const LoiAcknowledgement = sequelize.define('LoiAcknowledgement', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + loiDocId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'loi_documents_generated', + key: 'id' + } + }, + applicantId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'users', // Applicant is a user + key: 'id' + } + }, + acknowledgedAt: { + type: DataTypes.DATE, + defaultValue: DataTypes.NOW + }, + remarks: { + type: DataTypes.TEXT, + allowNull: true + } + }, { + tableName: 'loi_acknowledgements', + timestamps: true, + updatedAt: false + }); + + (LoiAcknowledgement as any).associate = (models: any) => { + LoiAcknowledgement.belongsTo(models.LoiDocumentGenerated, { foreignKey: 'loiDocId', as: 'loiDocument' }); + LoiAcknowledgement.belongsTo(models.User, { foreignKey: 'applicantId', as: 'applicant' }); + }; + + return LoiAcknowledgement; +}; diff --git a/src/database/models/LoiApproval.ts b/src/database/models/LoiApproval.ts new file mode 100644 index 0000000..9ef5e33 --- /dev/null +++ b/src/database/models/LoiApproval.ts @@ -0,0 +1,66 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface LoiApprovalAttributes { + id: string; + requestId: string; + level: number; + approverRole: string; + approverId: string | null; + action: string; + remarks: string | null; +} + +export interface LoiApprovalInstance extends Model, LoiApprovalAttributes { } + +export default (sequelize: Sequelize) => { + const LoiApproval = sequelize.define('LoiApproval', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + requestId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'loi_requests', + key: 'id' + } + }, + level: { + type: DataTypes.INTEGER, + allowNull: false + }, + approverRole: { + type: DataTypes.STRING, + allowNull: false + }, + approverId: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'users', + key: 'id' + } + }, + action: { + type: DataTypes.STRING, + allowNull: false + }, + remarks: { + type: DataTypes.TEXT, + allowNull: true + } + }, { + tableName: 'loi_approvals', + timestamps: true, + updatedAt: false + }); + + (LoiApproval as any).associate = (models: any) => { + LoiApproval.belongsTo(models.LoiRequest, { foreignKey: 'requestId', as: 'request' }); + LoiApproval.belongsTo(models.User, { foreignKey: 'approverId', as: 'approver' }); + }; + + return LoiApproval; +}; diff --git a/src/database/models/LoiDocumentGenerated.ts b/src/database/models/LoiDocumentGenerated.ts new file mode 100644 index 0000000..11ec767 --- /dev/null +++ b/src/database/models/LoiDocumentGenerated.ts @@ -0,0 +1,57 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface LoiDocumentGeneratedAttributes { + id: string; + requestId: string; + documentId: string; + version: string; + generatedAt: Date; +} + +export interface LoiDocumentGeneratedInstance extends Model, LoiDocumentGeneratedAttributes { } + +export default (sequelize: Sequelize) => { + const LoiDocumentGenerated = sequelize.define('LoiDocumentGenerated', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + requestId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'loi_requests', + key: 'id' + } + }, + documentId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'documents', + key: 'id' + } + }, + version: { + type: DataTypes.STRING, + allowNull: false + }, + generatedAt: { + type: DataTypes.DATE, + defaultValue: DataTypes.NOW + } + }, { + tableName: 'loi_documents_generated', + timestamps: true, + updatedAt: false + }); + + (LoiDocumentGenerated as any).associate = (models: any) => { + LoiDocumentGenerated.belongsTo(models.LoiRequest, { foreignKey: 'requestId', as: 'request' }); + LoiDocumentGenerated.belongsTo(models.Document, { foreignKey: 'documentId', as: 'document' }); + LoiDocumentGenerated.hasMany(models.LoiAcknowledgement, { foreignKey: 'loiDocId', as: 'acknowledgements' }); + }; + + return LoiDocumentGenerated; +}; diff --git a/src/database/models/LoiRequest.ts b/src/database/models/LoiRequest.ts new file mode 100644 index 0000000..d9c3b67 --- /dev/null +++ b/src/database/models/LoiRequest.ts @@ -0,0 +1,68 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface LoiRequestAttributes { + id: string; + applicationId: string; + status: string; + requestedBy: string | null; + approvedAt: Date | null; + approvedBy: string | null; +} + +export interface LoiRequestInstance extends Model, LoiRequestAttributes { } + +export default (sequelize: Sequelize) => { + const LoiRequest = sequelize.define('LoiRequest', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + applicationId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'applications', + key: 'id' + } + }, + status: { + type: DataTypes.STRING, + defaultValue: 'pending' + }, + requestedBy: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'users', + key: 'id' + } + }, + approvedAt: { + type: DataTypes.DATE, + allowNull: true + }, + approvedBy: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'users', + key: 'id' + } + } + }, { + tableName: 'loi_requests', + timestamps: true + }); + + (LoiRequest as any).associate = (models: any) => { + LoiRequest.belongsTo(models.Application, { foreignKey: 'applicationId', as: 'application' }); + LoiRequest.belongsTo(models.User, { foreignKey: 'requestedBy', as: 'requester' }); + LoiRequest.belongsTo(models.User, { foreignKey: 'approvedBy', as: 'approver' }); + + LoiRequest.hasMany(models.LoiApproval, { foreignKey: 'requestId', as: 'approvals' }); + LoiRequest.hasMany(models.LoiDocumentGenerated, { foreignKey: 'requestId', as: 'generatedDocuments' }); + }; + + return LoiRequest; +}; diff --git a/src/database/models/Notification.js b/src/database/models/Notification.ts similarity index 68% rename from src/database/models/Notification.js rename to src/database/models/Notification.ts index a2cd94c..5b6d4f4 100644 --- a/src/database/models/Notification.js +++ b/src/database/models/Notification.ts @@ -1,5 +1,19 @@ -module.exports = (sequelize, DataTypes) => { - const Notification = sequelize.define('Notification', { +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface NotificationAttributes { + id: string; + userId: string; + title: string; + message: string; + type: string; + link: string | null; + isRead: boolean; +} + +export interface NotificationInstance extends Model, NotificationAttributes { } + +export default (sequelize: Sequelize) => { + const Notification = sequelize.define('Notification', { id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, @@ -42,7 +56,7 @@ module.exports = (sequelize, DataTypes) => { ] }); - Notification.associate = (models) => { + (Notification as any).associate = (models: any) => { Notification.belongsTo(models.User, { foreignKey: 'userId', as: 'recipient' diff --git a/src/database/models/Opportunity.ts b/src/database/models/Opportunity.ts new file mode 100644 index 0000000..96e6854 --- /dev/null +++ b/src/database/models/Opportunity.ts @@ -0,0 +1,116 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface OpportunityAttributes { + id: string; + zoneId: string; + regionId: string; + stateId: string | null; + districtId: string | null; + city: string; + opportunityType: string; + capacity: string; + priority: string; + openFrom: Date | null; + openTo: Date | null; + status: string; + notes: string | null; + createdBy: string | null; +} + +export interface OpportunityInstance extends Model, OpportunityAttributes { } + +export default (sequelize: Sequelize) => { + const Opportunity = sequelize.define('Opportunity', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + zoneId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'zones', + key: 'id' + } + }, + regionId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'regions', + key: 'id' + } + }, + stateId: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'states', + key: 'id' + } + }, + districtId: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'districts', + key: 'id' + } + }, + city: { + type: DataTypes.STRING, + allowNull: false + }, + opportunityType: { + type: DataTypes.STRING, + allowNull: false + }, + capacity: { + type: DataTypes.STRING, + allowNull: false + }, + priority: { + type: DataTypes.STRING, + allowNull: false + }, + openFrom: { + type: DataTypes.DATE, + allowNull: true + }, + openTo: { + type: DataTypes.DATE, + allowNull: true + }, + status: { + type: DataTypes.STRING, + defaultValue: 'active' + }, + notes: { + type: DataTypes.TEXT, + allowNull: true + }, + createdBy: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'users', + key: 'id' + } + } + }, { + tableName: 'opportunities', + timestamps: true + }); + + (Opportunity as any).associate = (models: any) => { + Opportunity.belongsTo(models.Zone, { foreignKey: 'zoneId', as: 'zone' }); + Opportunity.belongsTo(models.Region, { foreignKey: 'regionId', as: 'region' }); + Opportunity.belongsTo(models.State, { foreignKey: 'stateId', as: 'state' }); + Opportunity.belongsTo(models.District, { foreignKey: 'districtId', as: 'district' }); + Opportunity.belongsTo(models.User, { foreignKey: 'createdBy', as: 'creator' }); + Opportunity.hasMany(models.Application, { foreignKey: 'opportunityId', as: 'applications' }); + }; + + return Opportunity; +}; diff --git a/src/database/models/Outlet.js b/src/database/models/Outlet.js deleted file mode 100644 index 990ec48..0000000 --- a/src/database/models/Outlet.js +++ /dev/null @@ -1,104 +0,0 @@ -const { OUTLET_TYPES, OUTLET_STATUS, REGIONS } = require('../../common/config/constants'); - -module.exports = (sequelize, DataTypes) => { - const Outlet = sequelize.define('Outlet', { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true - }, - code: { - type: DataTypes.STRING, - unique: true, - allowNull: false - }, - name: { - type: DataTypes.STRING, - allowNull: false - }, - type: { - type: DataTypes.ENUM(Object.values(OUTLET_TYPES)), - allowNull: false - }, - address: { - type: DataTypes.TEXT, - allowNull: false - }, - city: { - type: DataTypes.STRING, - allowNull: false - }, - state: { - type: DataTypes.STRING, - allowNull: false - }, - pincode: { - type: DataTypes.STRING, - allowNull: false - }, - latitude: { - type: DataTypes.DECIMAL(10, 8), - allowNull: true - }, - longitude: { - type: DataTypes.DECIMAL(11, 8), - allowNull: true - }, - status: { - type: DataTypes.ENUM(Object.values(OUTLET_STATUS)), - defaultValue: OUTLET_STATUS.ACTIVE - }, - establishedDate: { - type: DataTypes.DATEONLY, - allowNull: false - }, - dealerId: { - type: DataTypes.UUID, - allowNull: false, - references: { - model: 'users', - key: 'id' - } - }, - region: { - type: DataTypes.ENUM(Object.values(REGIONS)), - allowNull: false - }, - zone: { - type: DataTypes.STRING, - allowNull: false - } - }, { - tableName: 'outlets', - timestamps: true, - indexes: [ - { fields: ['code'] }, - { fields: ['dealerId'] }, - { fields: ['type'] }, - { fields: ['status'] }, - { fields: ['region'] }, - { fields: ['zone'] } - ] - }); - - Outlet.associate = (models) => { - Outlet.belongsTo(models.User, { - foreignKey: 'dealerId', - as: 'dealer' - }); - Outlet.hasMany(models.Resignation, { - foreignKey: 'outletId', - as: 'resignations' - }); - Outlet.hasMany(models.ConstitutionalChange, { - foreignKey: 'outletId', - as: 'constitutionalChanges' - }); - Outlet.hasMany(models.RelocationRequest, { - foreignKey: 'outletId', - as: 'relocationRequests' - }); - }; - - return Outlet; -}; diff --git a/src/database/models/Outlet.ts b/src/database/models/Outlet.ts new file mode 100644 index 0000000..801300b --- /dev/null +++ b/src/database/models/Outlet.ts @@ -0,0 +1,125 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; +import { OUTLET_TYPES, OUTLET_STATUS, REGIONS } from '../../common/config/constants.js'; + +export interface OutletAttributes { + id: string; + code: string; + name: string; + type: typeof OUTLET_TYPES[keyof typeof OUTLET_TYPES]; + address: string; + city: string; + state: string; + pincode: string; + latitude: number | null; + longitude: number | null; + status: typeof OUTLET_STATUS[keyof typeof OUTLET_STATUS]; + establishedDate: string; + dealerId: string; + region: typeof REGIONS[keyof typeof REGIONS]; + zone: string; +} + +export interface OutletInstance extends Model, OutletAttributes { } + +export default (sequelize: Sequelize) => { + const Outlet = sequelize.define('Outlet', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + code: { + type: DataTypes.STRING, + unique: true, + allowNull: false + }, + name: { + type: DataTypes.STRING, + allowNull: false + }, + type: { + type: DataTypes.ENUM(...Object.values(OUTLET_TYPES)), + allowNull: false + }, + address: { + type: DataTypes.TEXT, + allowNull: false + }, + city: { + type: DataTypes.STRING, + allowNull: false + }, + state: { + type: DataTypes.STRING, + allowNull: false + }, + pincode: { + type: DataTypes.STRING, + allowNull: false + }, + latitude: { + type: DataTypes.DECIMAL(10, 8), + allowNull: true + }, + longitude: { + type: DataTypes.DECIMAL(11, 8), + allowNull: true + }, + status: { + type: DataTypes.ENUM(...Object.values(OUTLET_STATUS)), + defaultValue: OUTLET_STATUS.ACTIVE + }, + establishedDate: { + type: DataTypes.DATEONLY, + allowNull: false + }, + dealerId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'users', + key: 'id' + } + }, + region: { + type: DataTypes.ENUM(...Object.values(REGIONS)), + allowNull: false + }, + zone: { + type: DataTypes.STRING, + allowNull: false + } + }, { + tableName: 'outlets', + timestamps: true, + indexes: [ + { fields: ['code'] }, + { fields: ['dealerId'] }, + { fields: ['type'] }, + { fields: ['status'] }, + { fields: ['region'] }, + { fields: ['zone'] } + ] + }); + + (Outlet as any).associate = (models: any) => { + Outlet.belongsTo(models.User, { + foreignKey: 'dealerId', + as: 'dealer' + }); + Outlet.hasMany(models.Resignation, { + foreignKey: 'outletId', + as: 'resignations' + }); + Outlet.hasMany(models.ConstitutionalChange, { + foreignKey: 'outletId', + as: 'constitutionalChanges' + }); + Outlet.hasMany(models.RelocationRequest, { + foreignKey: 'outletId', + as: 'relocationRequests' + }); + }; + + return Outlet; +}; diff --git a/src/database/models/Permission.ts b/src/database/models/Permission.ts new file mode 100644 index 0000000..c98805a --- /dev/null +++ b/src/database/models/Permission.ts @@ -0,0 +1,61 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface PermissionAttributes { + id: string; + permissionCode: string; + permissionName: string; + module: string; + permissionType: string; + action: string; + description: string | null; +} + +export interface PermissionInstance extends Model, PermissionAttributes { } + +export default (sequelize: Sequelize) => { + const Permission = sequelize.define('Permission', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + permissionCode: { + type: DataTypes.STRING, + unique: true, + allowNull: false + }, + permissionName: { + type: DataTypes.STRING, + allowNull: false + }, + module: { + type: DataTypes.STRING, + allowNull: false + }, + permissionType: { + type: DataTypes.STRING, + allowNull: false + }, + action: { + type: DataTypes.STRING, + allowNull: false + }, + description: { + type: DataTypes.TEXT, + allowNull: true + } + }, { + tableName: 'permissions', + timestamps: true, + updatedAt: false + }); + + (Permission as any).associate = (models: any) => { + Permission.hasMany(models.RolePermission, { + foreignKey: 'permissionId', + as: 'rolePermissions' + }); + }; + + return Permission; +}; diff --git a/src/database/models/Questionnaire.ts b/src/database/models/Questionnaire.ts new file mode 100644 index 0000000..cc1bb3c --- /dev/null +++ b/src/database/models/Questionnaire.ts @@ -0,0 +1,37 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface QuestionnaireAttributes { + id: string; + version: string; + isActive: boolean; +} + +export interface QuestionnaireInstance extends Model, QuestionnaireAttributes { } + +export default (sequelize: Sequelize) => { + const Questionnaire = sequelize.define('Questionnaire', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + version: { + type: DataTypes.STRING, + allowNull: false + }, + isActive: { + type: DataTypes.BOOLEAN, + defaultValue: true + } + }, { + tableName: 'questionnaires', + timestamps: true + }); + + (Questionnaire as any).associate = (models: any) => { + Questionnaire.hasMany(models.QuestionnaireQuestion, { foreignKey: 'questionnaireId', as: 'questions' }); + Questionnaire.hasMany(models.QuestionnaireResponse, { foreignKey: 'questionnaireId', as: 'responses' }); + }; + + return Questionnaire; +}; diff --git a/src/database/models/QuestionnaireQuestion.ts b/src/database/models/QuestionnaireQuestion.ts new file mode 100644 index 0000000..91c74db --- /dev/null +++ b/src/database/models/QuestionnaireQuestion.ts @@ -0,0 +1,62 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface QuestionnaireQuestionAttributes { + id: string; + questionnaireId: string; + sectionName: string; + questionText: string; + inputType: string; + options: any; + isMandatory: boolean; +} + +export interface QuestionnaireQuestionInstance extends Model, QuestionnaireQuestionAttributes { } + +export default (sequelize: Sequelize) => { + const QuestionnaireQuestion = sequelize.define('QuestionnaireQuestion', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + questionnaireId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'questionnaires', + key: 'id' + } + }, + sectionName: { + type: DataTypes.STRING, + allowNull: false + }, + questionText: { + type: DataTypes.TEXT, + allowNull: false + }, + inputType: { + type: DataTypes.STRING, + allowNull: false + }, + options: { + type: DataTypes.JSON, + allowNull: true + }, + isMandatory: { + type: DataTypes.BOOLEAN, + defaultValue: true + } + }, { + tableName: 'questionnaire_questions', + timestamps: true, + updatedAt: false + }); + + (QuestionnaireQuestion as any).associate = (models: any) => { + QuestionnaireQuestion.belongsTo(models.Questionnaire, { foreignKey: 'questionnaireId', as: 'questionnaire' }); + QuestionnaireQuestion.hasMany(models.QuestionnaireResponse, { foreignKey: 'questionId', as: 'responses' }); + }; + + return QuestionnaireQuestion; +}; diff --git a/src/database/models/QuestionnaireResponse.ts b/src/database/models/QuestionnaireResponse.ts new file mode 100644 index 0000000..4067219 --- /dev/null +++ b/src/database/models/QuestionnaireResponse.ts @@ -0,0 +1,66 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface QuestionnaireResponseAttributes { + id: string; + applicationId: string; + questionnaireId: string; + questionId: string; + responseValue: string | null; + attachmentUrl: string | null; +} + +export interface QuestionnaireResponseInstance extends Model, QuestionnaireResponseAttributes { } + +export default (sequelize: Sequelize) => { + const QuestionnaireResponse = sequelize.define('QuestionnaireResponse', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + applicationId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'applications', + key: 'id' + } + }, + questionnaireId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'questionnaires', + key: 'id' + } + }, + questionId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'questionnaire_questions', + key: 'id' + } + }, + responseValue: { + type: DataTypes.TEXT, + allowNull: true + }, + attachmentUrl: { + type: DataTypes.STRING, + allowNull: true + } + }, { + tableName: 'questionnaire_responses', + timestamps: true, + updatedAt: false + }); + + (QuestionnaireResponse as any).associate = (models: any) => { + QuestionnaireResponse.belongsTo(models.Application, { foreignKey: 'applicationId', as: 'application' }); + QuestionnaireResponse.belongsTo(models.Questionnaire, { foreignKey: 'questionnaireId', as: 'questionnaire' }); + QuestionnaireResponse.belongsTo(models.QuestionnaireQuestion, { foreignKey: 'questionId', as: 'question' }); + }; + + return QuestionnaireResponse; +}; diff --git a/src/database/models/QuestionnaireScore.ts b/src/database/models/QuestionnaireScore.ts new file mode 100644 index 0000000..8b57a28 --- /dev/null +++ b/src/database/models/QuestionnaireScore.ts @@ -0,0 +1,56 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface QuestionnaireScoreAttributes { + id: string; + applicationId: string; + sectionName: string; + score: number; + weightage: number; + weightedScore: number; +} + +export interface QuestionnaireScoreInstance extends Model, QuestionnaireScoreAttributes { } + +export default (sequelize: Sequelize) => { + const QuestionnaireScore = sequelize.define('QuestionnaireScore', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + applicationId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'applications', + key: 'id' + } + }, + sectionName: { + type: DataTypes.STRING, + allowNull: false + }, + score: { + type: DataTypes.DECIMAL(10, 2), + allowNull: false + }, + weightage: { + type: DataTypes.DECIMAL(10, 2), + allowNull: false + }, + weightedScore: { + type: DataTypes.DECIMAL(10, 2), + allowNull: false + } + }, { + tableName: 'questionnaire_scores', + timestamps: true, + updatedAt: false + }); + + (QuestionnaireScore as any).associate = (models: any) => { + QuestionnaireScore.belongsTo(models.Application, { foreignKey: 'applicationId', as: 'application' }); + }; + + return QuestionnaireScore; +}; diff --git a/src/database/models/Region.js b/src/database/models/Region.js deleted file mode 100644 index 22b91b4..0000000 --- a/src/database/models/Region.js +++ /dev/null @@ -1,44 +0,0 @@ -const { REGIONS } = require('../../common/config/constants'); - -module.exports = (sequelize, DataTypes) => { - const Region = sequelize.define('Region', { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true - }, - name: { - type: DataTypes.ENUM(Object.values(REGIONS)), - unique: true, - allowNull: false - }, - description: { - type: DataTypes.TEXT, - allowNull: true - }, - regionalManagerId: { - type: DataTypes.UUID, - allowNull: true, - references: { - model: 'users', - key: 'id' - } - } - }, { - tableName: 'regions', - timestamps: true - }); - - Region.associate = (models) => { - Region.belongsTo(models.User, { - foreignKey: 'regionalManagerId', - as: 'regionalManager' - }); - Region.hasMany(models.Zone, { - foreignKey: 'regionId', - as: 'zones' - }); - }; - - return Region; -}; diff --git a/src/database/models/Region.ts b/src/database/models/Region.ts new file mode 100644 index 0000000..dfb534a --- /dev/null +++ b/src/database/models/Region.ts @@ -0,0 +1,84 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface RegionAttributes { + id: string; + zoneId: string; + stateId: string | null; + regionCode: string; + regionName: string; + description: string | null; + isActive: boolean; +} + +export interface RegionInstance extends Model, RegionAttributes { } + +export default (sequelize: Sequelize) => { + const Region = sequelize.define('Region', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + zoneId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'zones', + key: 'id' + } + }, + stateId: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'states', + key: 'id' + } + }, + regionCode: { + type: DataTypes.STRING, + unique: true, + allowNull: false + }, + regionName: { + type: DataTypes.STRING, + allowNull: false + }, + description: { + type: DataTypes.TEXT, + allowNull: true + }, + isActive: { + type: DataTypes.BOOLEAN, + defaultValue: true + } + }, { + tableName: 'regions', + timestamps: true + }); + + (Region as any).associate = (models: any) => { + Region.belongsTo(models.Zone, { + foreignKey: 'zoneId', + as: 'zone' + }); + Region.belongsTo(models.State, { + foreignKey: 'stateId', + as: 'state' + }); + Region.hasMany(models.Area, { + foreignKey: 'regionId', + as: 'areas' + }); + Region.hasMany(models.RegionManager, { + foreignKey: 'regionId', + as: 'managers' + }); + Region.hasMany(models.Application, { + foreignKey: 'regionId', + as: 'applications' + }); + }; + + return Region; +}; diff --git a/src/database/models/RegionManager.ts b/src/database/models/RegionManager.ts new file mode 100644 index 0000000..ab61ec2 --- /dev/null +++ b/src/database/models/RegionManager.ts @@ -0,0 +1,67 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface RegionManagerAttributes { + id: string; + regionId: string; + userId: string; + managerType: string; + isActive: boolean; + assignedAt: Date; +} + +export interface RegionManagerInstance extends Model, RegionManagerAttributes { } + +export default (sequelize: Sequelize) => { + const RegionManager = sequelize.define('RegionManager', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + regionId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'regions', + key: 'id' + } + }, + userId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'users', + key: 'id' + } + }, + managerType: { + type: DataTypes.STRING, + allowNull: false + }, + isActive: { + type: DataTypes.BOOLEAN, + defaultValue: true + }, + assignedAt: { + type: DataTypes.DATE, + defaultValue: DataTypes.NOW + } + }, { + tableName: 'region_managers', + timestamps: true, + updatedAt: false + }); + + (RegionManager as any).associate = (models: any) => { + RegionManager.belongsTo(models.Region, { + foreignKey: 'regionId', + as: 'region' + }); + RegionManager.belongsTo(models.User, { + foreignKey: 'userId', + as: 'user' + }); + }; + + return RegionManager; +}; diff --git a/src/database/models/RelocationRequest.js b/src/database/models/RelocationRequest.ts similarity index 68% rename from src/database/models/RelocationRequest.js rename to src/database/models/RelocationRequest.ts index 255d072..9a96c2e 100644 --- a/src/database/models/RelocationRequest.js +++ b/src/database/models/RelocationRequest.ts @@ -1,7 +1,27 @@ -const { RELOCATION_TYPES, RELOCATION_STAGES } = require('../../common/config/constants'); +import { Model, DataTypes, Sequelize } from 'sequelize'; +import { RELOCATION_TYPES, RELOCATION_STAGES } from '../../common/config/constants.js'; -module.exports = (sequelize, DataTypes) => { - const RelocationRequest = sequelize.define('RelocationRequest', { +export interface RelocationRequestAttributes { + id: string; + requestId: string; + outletId: string; + dealerId: string; + relocationType: typeof RELOCATION_TYPES[keyof typeof RELOCATION_TYPES]; + newAddress: string; + newCity: string; + newState: string; + reason: string; + currentStage: typeof RELOCATION_STAGES[keyof typeof RELOCATION_STAGES]; + status: string; + progressPercentage: number; + documents: any[]; + timeline: any[]; +} + +export interface RelocationRequestInstance extends Model, RelocationRequestAttributes { } + +export default (sequelize: Sequelize) => { + const RelocationRequest = sequelize.define('RelocationRequest', { id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, @@ -29,7 +49,7 @@ module.exports = (sequelize, DataTypes) => { } }, relocationType: { - type: DataTypes.ENUM(Object.values(RELOCATION_TYPES)), + type: DataTypes.ENUM(...Object.values(RELOCATION_TYPES)), allowNull: false }, newAddress: { @@ -49,7 +69,7 @@ module.exports = (sequelize, DataTypes) => { allowNull: false }, currentStage: { - type: DataTypes.ENUM(Object.values(RELOCATION_STAGES)), + type: DataTypes.ENUM(...Object.values(RELOCATION_STAGES)), defaultValue: RELOCATION_STAGES.DD_ADMIN_REVIEW }, status: { @@ -79,7 +99,7 @@ module.exports = (sequelize, DataTypes) => { ] }); - RelocationRequest.associate = (models) => { + (RelocationRequest as any).associate = (models: any) => { RelocationRequest.belongsTo(models.Outlet, { foreignKey: 'outletId', as: 'outlet' diff --git a/src/database/models/Resignation.js b/src/database/models/Resignation.js deleted file mode 100644 index 7665c83..0000000 --- a/src/database/models/Resignation.js +++ /dev/null @@ -1,110 +0,0 @@ -const { RESIGNATION_TYPES, RESIGNATION_STAGES } = require('../../common/config/constants'); - -module.exports = (sequelize, DataTypes) => { - const Resignation = sequelize.define('Resignation', { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true - }, - resignationId: { - type: DataTypes.STRING, - unique: true, - allowNull: false - }, - outletId: { - type: DataTypes.UUID, - allowNull: false, - references: { - model: 'outlets', - key: 'id' - } - }, - dealerId: { - type: DataTypes.UUID, - allowNull: false, - references: { - model: 'users', - key: 'id' - } - }, - resignationType: { - type: DataTypes.ENUM(Object.values(RESIGNATION_TYPES)), - allowNull: false - }, - lastOperationalDateSales: { - type: DataTypes.DATEONLY, - allowNull: false - }, - lastOperationalDateServices: { - type: DataTypes.DATEONLY, - allowNull: false - }, - reason: { - type: DataTypes.TEXT, - allowNull: false - }, - additionalInfo: { - type: DataTypes.TEXT, - allowNull: true - }, - currentStage: { - type: DataTypes.ENUM(Object.values(RESIGNATION_STAGES)), - defaultValue: RESIGNATION_STAGES.ASM - }, - status: { - type: DataTypes.STRING, - defaultValue: 'Pending' - }, - progressPercentage: { - type: DataTypes.INTEGER, - defaultValue: 0 - }, - submittedOn: { - type: DataTypes.DATE, - defaultValue: DataTypes.NOW - }, - documents: { - type: DataTypes.JSON, - defaultValue: [] - }, - timeline: { - type: DataTypes.JSON, - defaultValue: [] - }, - rejectionReason: { - type: DataTypes.TEXT, - allowNull: true - } - }, { - tableName: 'resignations', - timestamps: true, - indexes: [ - { fields: ['resignationId'] }, - { fields: ['outletId'] }, - { fields: ['dealerId'] }, - { fields: ['currentStage'] }, - { fields: ['status'] } - ] - }); - - Resignation.associate = (models) => { - Resignation.belongsTo(models.Outlet, { - foreignKey: 'outletId', - as: 'outlet' - }); - Resignation.belongsTo(models.User, { - foreignKey: 'dealerId', - as: 'dealer' - }); - Resignation.hasMany(models.Worknote, { - foreignKey: 'requestId', - as: 'worknotes', - scope: { - requestType: 'resignation' - } - }); - }; - - return Resignation; -}; diff --git a/src/database/models/Resignation.ts b/src/database/models/Resignation.ts new file mode 100644 index 0000000..3952cde --- /dev/null +++ b/src/database/models/Resignation.ts @@ -0,0 +1,132 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; +import { RESIGNATION_TYPES, RESIGNATION_STAGES } from '../../common/config/constants.js'; + +export interface ResignationAttributes { + id: string; + resignationId: string; + outletId: string; + dealerId: string; + resignationType: typeof RESIGNATION_TYPES[keyof typeof RESIGNATION_TYPES]; + lastOperationalDateSales: string; + lastOperationalDateServices: string; + reason: string; + additionalInfo: string | null; + currentStage: typeof RESIGNATION_STAGES[keyof typeof RESIGNATION_STAGES]; + status: string; + progressPercentage: number; + submittedOn: Date; + documents: any[]; + timeline: any[]; + rejectionReason: string | null; +} + +export interface ResignationInstance extends Model, ResignationAttributes { } + +export default (sequelize: Sequelize) => { + const Resignation = sequelize.define('Resignation', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + resignationId: { + type: DataTypes.STRING, + unique: true, + allowNull: false + }, + outletId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'outlets', + key: 'id' + } + }, + dealerId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'users', + key: 'id' + } + }, + resignationType: { + type: DataTypes.ENUM(...Object.values(RESIGNATION_TYPES)), + allowNull: false + }, + lastOperationalDateSales: { + type: DataTypes.DATEONLY, + allowNull: false + }, + lastOperationalDateServices: { + type: DataTypes.DATEONLY, + allowNull: false + }, + reason: { + type: DataTypes.TEXT, + allowNull: false + }, + additionalInfo: { + type: DataTypes.TEXT, + allowNull: true + }, + currentStage: { + type: DataTypes.ENUM(...Object.values(RESIGNATION_STAGES)), + defaultValue: RESIGNATION_STAGES.ASM + }, + status: { + type: DataTypes.STRING, + defaultValue: 'Pending' + }, + progressPercentage: { + type: DataTypes.INTEGER, + defaultValue: 0 + }, + submittedOn: { + type: DataTypes.DATE, + defaultValue: DataTypes.NOW + }, + documents: { + type: DataTypes.JSON, + defaultValue: [] + }, + timeline: { + type: DataTypes.JSON, + defaultValue: [] + }, + rejectionReason: { + type: DataTypes.TEXT, + allowNull: true + } + }, { + tableName: 'resignations', + timestamps: true, + indexes: [ + { fields: ['resignationId'] }, + { fields: ['outletId'] }, + { fields: ['dealerId'] }, + { fields: ['currentStage'] }, + { fields: ['status'] } + ] + }); + + (Resignation as any).associate = (models: any) => { + Resignation.belongsTo(models.Outlet, { + foreignKey: 'outletId', + as: 'outlet' + }); + Resignation.belongsTo(models.User, { + foreignKey: 'dealerId', + as: 'dealer' + }); + Resignation.hasMany(models.Worknote, { + foreignKey: 'requestId', + as: 'worknotes', + scope: { + requestType: 'resignation' + } + }); + }; + + return Resignation; +}; diff --git a/src/database/models/Role.ts b/src/database/models/Role.ts new file mode 100644 index 0000000..89b2299 --- /dev/null +++ b/src/database/models/Role.ts @@ -0,0 +1,59 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface RoleAttributes { + id: string; + roleCode: string; + roleName: string; + description: string | null; + category: string | null; + isActive: boolean; +} + +export interface RoleInstance extends Model, RoleAttributes { } + +export default (sequelize: Sequelize) => { + const Role = sequelize.define('Role', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + roleCode: { + type: DataTypes.STRING, + unique: true, + allowNull: false + }, + roleName: { + type: DataTypes.STRING, + allowNull: false + }, + description: { + type: DataTypes.TEXT, + allowNull: true + }, + category: { + type: DataTypes.STRING, + allowNull: true + }, + isActive: { + type: DataTypes.BOOLEAN, + defaultValue: true + } + }, { + tableName: 'roles', + timestamps: true + }); + + (Role as any).associate = (models: any) => { + Role.hasMany(models.UserRole, { + foreignKey: 'roleId', + as: 'userRoles' + }); + Role.hasMany(models.RolePermission, { + foreignKey: 'roleId', + as: 'permissions' + }); + }; + + return Role; +}; diff --git a/src/database/models/RolePermission.ts b/src/database/models/RolePermission.ts new file mode 100644 index 0000000..9532132 --- /dev/null +++ b/src/database/models/RolePermission.ts @@ -0,0 +1,77 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface RolePermissionAttributes { + id: string; + roleId: string; + permissionId: string; + canView: boolean; + canCreate: boolean; + canEdit: boolean; + canDelete: boolean; + canApprove: boolean; +} + +export interface RolePermissionInstance extends Model, RolePermissionAttributes { } + +export default (sequelize: Sequelize) => { + const RolePermission = sequelize.define('RolePermission', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + roleId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'roles', + key: 'id' + } + }, + permissionId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'permissions', + key: 'id' + } + }, + canView: { + type: DataTypes.BOOLEAN, + defaultValue: true + }, + canCreate: { + type: DataTypes.BOOLEAN, + defaultValue: false + }, + canEdit: { + type: DataTypes.BOOLEAN, + defaultValue: false + }, + canDelete: { + type: DataTypes.BOOLEAN, + defaultValue: false + }, + canApprove: { + type: DataTypes.BOOLEAN, + defaultValue: false + } + }, { + tableName: 'role_permissions', + timestamps: true, + updatedAt: false + }); + + (RolePermission as any).associate = (models: any) => { + RolePermission.belongsTo(models.Role, { + foreignKey: 'roleId', + as: 'role' + }); + RolePermission.belongsTo(models.Permission, { + foreignKey: 'permissionId', + as: 'permission' + }); + }; + + return RolePermission; +}; diff --git a/src/database/models/SLABreach.ts b/src/database/models/SLABreach.ts new file mode 100644 index 0000000..d2e7b0e --- /dev/null +++ b/src/database/models/SLABreach.ts @@ -0,0 +1,56 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface SLABreachAttributes { + id: string; + trackingId: string; + breachedAt: Date; + notifiedTo: string | null; // Email or User ID + status: string; // Open, Acknowledged, Resolved + actionTaken: string | null; +} + +export interface SLABreachInstance extends Model, SLABreachAttributes { } + +export default (sequelize: Sequelize) => { + const SLABreach = sequelize.define('SLABreach', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + trackingId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'sla_tracking', + key: 'id' + } + }, + breachedAt: { + type: DataTypes.DATE, + defaultValue: DataTypes.NOW + }, + notifiedTo: { + type: DataTypes.STRING, + allowNull: true + }, + status: { + type: DataTypes.STRING, + defaultValue: 'Open' + }, + actionTaken: { + type: DataTypes.TEXT, + allowNull: true + } + }, { + tableName: 'sla_breaches', + timestamps: true, + updatedAt: false + }); + + (SLABreach as any).associate = (models: any) => { + SLABreach.belongsTo(models.SLATracking, { foreignKey: 'trackingId', as: 'slaTracking' }); + }; + + return SLABreach; +}; diff --git a/src/database/models/SLAConfiguration.js b/src/database/models/SLAConfiguration.ts similarity index 67% rename from src/database/models/SLAConfiguration.js rename to src/database/models/SLAConfiguration.ts index c90db2f..d18e644 100644 --- a/src/database/models/SLAConfiguration.js +++ b/src/database/models/SLAConfiguration.ts @@ -1,5 +1,18 @@ -module.exports = (sequelize, DataTypes) => { - const SLAConfiguration = sequelize.define('SLAConfiguration', { +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface SLAConfigurationAttributes { + id: string; + activityName: string; + ownerRole: string; + tatHours: number; + tatUnit: 'hours' | 'days'; + isActive: boolean; +} + +export interface SLAConfigurationInstance extends Model, SLAConfigurationAttributes { } + +export default (sequelize: Sequelize) => { + const SLAConfiguration = sequelize.define('SLAConfiguration', { id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, @@ -34,7 +47,7 @@ module.exports = (sequelize, DataTypes) => { ] }); - SLAConfiguration.associate = (models) => { + (SLAConfiguration as any).associate = (models: any) => { SLAConfiguration.hasMany(models.SLAReminder, { foreignKey: 'slaConfigId', as: 'reminders' diff --git a/src/database/models/SLAEscalationConfig.js b/src/database/models/SLAEscalationConfig.ts similarity index 65% rename from src/database/models/SLAEscalationConfig.js rename to src/database/models/SLAEscalationConfig.ts index 4a385ab..749babd 100644 --- a/src/database/models/SLAEscalationConfig.js +++ b/src/database/models/SLAEscalationConfig.ts @@ -1,5 +1,18 @@ -module.exports = (sequelize, DataTypes) => { - const SLAEscalationConfig = sequelize.define('SLAEscalationConfig', { +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface SLAEscalationConfigAttributes { + id: string; + slaConfigId: string; + level: number; + timeValue: number; + timeUnit: 'hours' | 'days'; + notifyEmail: string; +} + +export interface SLAEscalationConfigInstance extends Model, SLAEscalationConfigAttributes { } + +export default (sequelize: Sequelize) => { + const SLAEscalationConfig = sequelize.define('SLAEscalationConfig', { id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, @@ -38,7 +51,7 @@ module.exports = (sequelize, DataTypes) => { ] }); - SLAEscalationConfig.associate = (models) => { + (SLAEscalationConfig as any).associate = (models: any) => { SLAEscalationConfig.belongsTo(models.SLAConfiguration, { foreignKey: 'slaConfigId', as: 'slaConfig' diff --git a/src/database/models/SLAReminder.js b/src/database/models/SLAReminder.ts similarity index 66% rename from src/database/models/SLAReminder.js rename to src/database/models/SLAReminder.ts index 95a16f6..dad4e7a 100644 --- a/src/database/models/SLAReminder.js +++ b/src/database/models/SLAReminder.ts @@ -1,5 +1,17 @@ -module.exports = (sequelize, DataTypes) => { - const SLAReminder = sequelize.define('SLAReminder', { +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface SLAReminderAttributes { + id: string; + slaConfigId: string; + timeValue: number; + timeUnit: 'hours' | 'days'; + isEnabled: boolean; +} + +export interface SLAReminderInstance extends Model, SLAReminderAttributes { } + +export default (sequelize: Sequelize) => { + const SLAReminder = sequelize.define('SLAReminder', { id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, @@ -33,7 +45,7 @@ module.exports = (sequelize, DataTypes) => { ] }); - SLAReminder.associate = (models) => { + (SLAReminder as any).associate = (models: any) => { SLAReminder.belongsTo(models.SLAConfiguration, { foreignKey: 'slaConfigId', as: 'slaConfig' diff --git a/src/database/models/SLATracking.ts b/src/database/models/SLATracking.ts new file mode 100644 index 0000000..eafd8c6 --- /dev/null +++ b/src/database/models/SLATracking.ts @@ -0,0 +1,76 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface SLATrackingAttributes { + id: string; + applicationId: string | null; + entityType: string; + entityId: string; + stageName: string; + startTime: Date; + endTime: Date | null; + duration: number | null; // minutes or hours + isBreached: boolean; + isActive: boolean; +} + +export interface SLATrackingInstance extends Model, SLATrackingAttributes { } + +export default (sequelize: Sequelize) => { + const SLATracking = sequelize.define('SLATracking', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + applicationId: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'applications', + key: 'id' + } + }, + entityType: { + type: DataTypes.STRING, + allowNull: false + }, + entityId: { + type: DataTypes.UUID, + allowNull: false + }, + stageName: { + type: DataTypes.STRING, + allowNull: false + }, + startTime: { + type: DataTypes.DATE, + defaultValue: DataTypes.NOW + }, + endTime: { + type: DataTypes.DATE, + allowNull: true + }, + duration: { + type: DataTypes.INTEGER, + allowNull: true + }, + isBreached: { + type: DataTypes.BOOLEAN, + defaultValue: false + }, + isActive: { + type: DataTypes.BOOLEAN, + defaultValue: true + } + }, { + tableName: 'sla_tracking', + timestamps: true + }); + + (SLATracking as any).associate = (models: any) => { + SLATracking.belongsTo(models.Application, { foreignKey: 'applicationId', as: 'application' }); + SLATracking.hasMany(models.SLABreach, { foreignKey: 'trackingId', as: 'breaches' }); + }; + + return SLATracking; +}; diff --git a/src/database/models/SecurityDeposit.ts b/src/database/models/SecurityDeposit.ts new file mode 100644 index 0000000..d45a373 --- /dev/null +++ b/src/database/models/SecurityDeposit.ts @@ -0,0 +1,75 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface SecurityDepositAttributes { + id: string; + applicationId: string; + amount: number; + paymentReference: string | null; + proofDocumentId: string | null; + status: string; + verifiedAt: Date | null; + verifiedBy: string | null; +} + +export interface SecurityDepositInstance extends Model, SecurityDepositAttributes { } + +export default (sequelize: Sequelize) => { + const SecurityDeposit = sequelize.define('SecurityDeposit', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + applicationId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'applications', + key: 'id' + } + }, + amount: { + type: DataTypes.DECIMAL(15, 2), + allowNull: false + }, + paymentReference: { + type: DataTypes.STRING, + allowNull: true + }, + proofDocumentId: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'documents', + key: 'id' + } + }, + status: { + type: DataTypes.STRING, + defaultValue: 'pending' + }, + verifiedAt: { + type: DataTypes.DATE, + allowNull: true + }, + verifiedBy: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'users', + key: 'id' + } + } + }, { + tableName: 'security_deposits', + timestamps: true + }); + + (SecurityDeposit as any).associate = (models: any) => { + SecurityDeposit.belongsTo(models.Application, { foreignKey: 'applicationId', as: 'application' }); + SecurityDeposit.belongsTo(models.Document, { foreignKey: 'proofDocumentId', as: 'proofDocument' }); + SecurityDeposit.belongsTo(models.User, { foreignKey: 'verifiedBy', as: 'verifier' }); + }; + + return SecurityDeposit; +}; diff --git a/src/database/models/State.ts b/src/database/models/State.ts new file mode 100644 index 0000000..092367e --- /dev/null +++ b/src/database/models/State.ts @@ -0,0 +1,58 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface StateAttributes { + id: string; + stateName: string; + zoneId: string; + isActive: boolean; +} + +export interface StateInstance extends Model, StateAttributes { } + +export default (sequelize: Sequelize) => { + const State = sequelize.define('State', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + stateName: { + type: DataTypes.STRING, + unique: true, + allowNull: false + }, + zoneId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'zones', + key: 'id' + } + }, + isActive: { + type: DataTypes.BOOLEAN, + defaultValue: true + } + }, { + tableName: 'states', + timestamps: true, + updatedAt: false + }); + + (State as any).associate = (models: any) => { + State.belongsTo(models.Zone, { + foreignKey: 'zoneId', + as: 'zone' + }); + State.hasMany(models.District, { + foreignKey: 'stateId', + as: 'districts' + }); + State.hasMany(models.Region, { + foreignKey: 'stateId', + as: 'regions' + }); + }; + + return State; +}; diff --git a/src/database/models/TerminationRequest.ts b/src/database/models/TerminationRequest.ts new file mode 100644 index 0000000..e68c74c --- /dev/null +++ b/src/database/models/TerminationRequest.ts @@ -0,0 +1,71 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface TerminationRequestAttributes { + id: string; + dealerId: string; + category: string; + reason: string; + proposedLwd: Date; + status: string; + initiatedBy: string; + comments: string | null; +} + +export interface TerminationRequestInstance extends Model, TerminationRequestAttributes { } + +export default (sequelize: Sequelize) => { + const TerminationRequest = sequelize.define('TerminationRequest', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + dealerId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'dealers', + key: 'id' + } + }, + category: { + type: DataTypes.STRING, + allowNull: false + }, + reason: { + type: DataTypes.TEXT, + allowNull: false + }, + proposedLwd: { + type: DataTypes.DATEONLY, + allowNull: false + }, + status: { + type: DataTypes.STRING, + defaultValue: 'pending' + }, + initiatedBy: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'users', + key: 'id' + } + }, + comments: { + type: DataTypes.TEXT, + allowNull: true + } + }, { + tableName: 'termination_requests', + timestamps: true + }); + + (TerminationRequest as any).associate = (models: any) => { + TerminationRequest.belongsTo(models.Dealer, { foreignKey: 'dealerId', as: 'dealer' }); + TerminationRequest.belongsTo(models.User, { foreignKey: 'initiatedBy', as: 'initiator' }); + TerminationRequest.hasOne(models.FnF, { foreignKey: 'terminationRequestId', as: 'fnfSettlement' }); + }; + + return TerminationRequest; +}; diff --git a/src/database/models/User.js b/src/database/models/User.js deleted file mode 100644 index f93b832..0000000 --- a/src/database/models/User.js +++ /dev/null @@ -1,89 +0,0 @@ -const { ROLES, REGIONS } = require('../../common/config/constants'); - -module.exports = (sequelize, DataTypes) => { - const User = sequelize.define('User', { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true - }, - email: { - type: DataTypes.STRING, - unique: true, - allowNull: false, - validate: { - isEmail: true - } - }, - password: { - type: DataTypes.STRING, - allowNull: false - }, - name: { - type: DataTypes.STRING, - allowNull: false - }, - role: { - type: DataTypes.ENUM(Object.values(ROLES)), - allowNull: false - }, - region: { - type: DataTypes.ENUM(Object.values(REGIONS)), - allowNull: true - }, - zone: { - type: DataTypes.STRING, - allowNull: true - }, - phone: { - type: DataTypes.STRING, - allowNull: true - }, - status: { - type: DataTypes.ENUM('active', 'inactive'), - defaultValue: 'active' - }, - lastLogin: { - type: DataTypes.DATE, - allowNull: true - } - }, { - tableName: 'users', - timestamps: true, - indexes: [ - { fields: ['email'] }, - { fields: ['role'] }, - { fields: ['region'] }, - { fields: ['zone'] } - ] - }); - - User.associate = (models) => { - User.hasMany(models.Application, { - foreignKey: 'submittedBy', - as: 'applications' - }); - User.hasMany(models.Outlet, { - foreignKey: 'dealerId', - as: 'outlets' - }); - User.hasMany(models.Resignation, { - foreignKey: 'dealerId', - as: 'resignations' - }); - User.hasMany(models.ConstitutionalChange, { - foreignKey: 'dealerId', - as: 'constitutionalChanges' - }); - User.hasMany(models.RelocationRequest, { - foreignKey: 'dealerId', - as: 'relocationRequests' - }); - User.hasMany(models.AuditLog, { - foreignKey: 'userId', - as: 'auditLogs' - }); - }; - - return User; -}; diff --git a/src/database/models/User.ts b/src/database/models/User.ts new file mode 100644 index 0000000..5b6ffcf --- /dev/null +++ b/src/database/models/User.ts @@ -0,0 +1,160 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; +import { ROLES, REGIONS } from '../../common/config/constants.js'; + +export interface UserAttributes { + id: string; + employeeId: string | null; + email: string; + password?: string; + fullName: string; + mobileNumber: string | null; + department: string | null; + designation: string | null; + roleCode: string | null; + zoneId: string | null; + regionId: string | null; + stateId: string | null; + districtId: string | null; + areaId: string | null; + dealerId: string | null; + isActive: boolean; + isExternal: boolean; + ssoProvider: string | null; + status: string; + lastLogin: Date | null; +} + +export interface UserInstance extends Model, UserAttributes { } + +export default (sequelize: Sequelize) => { + const User = sequelize.define('User', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + employeeId: { + type: DataTypes.STRING, + unique: true, + allowNull: true + }, + email: { + type: DataTypes.STRING, + unique: true, + allowNull: false, + validate: { + isEmail: true + } + }, + password: { + type: DataTypes.STRING, + allowNull: true // SSO might not need passwords + }, + fullName: { + type: DataTypes.STRING, + allowNull: false + }, + mobileNumber: { + type: DataTypes.STRING, + allowNull: true + }, + department: { + type: DataTypes.STRING, + allowNull: true + }, + designation: { + type: DataTypes.STRING, + allowNull: true + }, + roleCode: { + type: DataTypes.STRING, + allowNull: true + }, + zoneId: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'zones', + key: 'id' + } + }, + regionId: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'regions', + key: 'id' + } + }, + stateId: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'states', + key: 'id' + } + }, + districtId: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'districts', + key: 'id' + } + }, + areaId: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'areas', + key: 'id' + } + }, + dealerId: { + type: DataTypes.UUID, // Link to Dealer entity if applicable + allowNull: true + }, + isActive: { + type: DataTypes.BOOLEAN, + defaultValue: true + }, + isExternal: { + type: DataTypes.BOOLEAN, + defaultValue: false + }, + ssoProvider: { + type: DataTypes.STRING, + allowNull: true + }, + status: { + type: DataTypes.STRING, + defaultValue: 'active' + }, + lastLogin: { + type: DataTypes.DATE, + allowNull: true + } + }, { + tableName: 'users', + timestamps: true, + indexes: [ + { fields: ['email'] }, + { fields: ['employeeId'] }, + { fields: ['roleCode'] } + ] + }); + + (User as any).associate = (models: any) => { + User.hasMany(models.UserRole, { foreignKey: 'userId', as: 'userRoles' }); + User.hasMany(models.UserRole, { foreignKey: 'assignedBy', as: 'assignedRoles' }); + User.belongsTo(models.Zone, { foreignKey: 'zoneId', as: 'zone' }); + User.belongsTo(models.Region, { foreignKey: 'regionId', as: 'region' }); + User.belongsTo(models.State, { foreignKey: 'stateId', as: 'state' }); + User.belongsTo(models.District, { foreignKey: 'districtId', as: 'district' }); + User.belongsTo(models.Area, { foreignKey: 'areaId', as: 'area' }); + + User.hasMany(models.AuditLog, { foreignKey: 'userId', as: 'auditLogs' }); + }; + + return User; +}; diff --git a/src/database/models/UserRole.ts b/src/database/models/UserRole.ts new file mode 100644 index 0000000..3f08215 --- /dev/null +++ b/src/database/models/UserRole.ts @@ -0,0 +1,109 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface UserRoleAttributes { + id: string; + userId: string; + roleId: string; + zoneId: string | null; + regionId: string | null; + areaId: string | null; + assignedAt: Date; + assignedBy: string | null; +} + +export interface UserRoleInstance extends Model, UserRoleAttributes { } + +export default (sequelize: Sequelize) => { + const UserRole = sequelize.define('UserRole', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + userId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'users', + key: 'id' + } + }, + roleId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'roles', + key: 'id' + } + }, + zoneId: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'zones', + key: 'id' + } + }, + regionId: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'regions', + key: 'id' + } + }, + areaId: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'areas', + key: 'id' + } + }, + assignedAt: { + type: DataTypes.DATE, + defaultValue: DataTypes.NOW + }, + assignedBy: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'users', + key: 'id' + } + } + }, { + tableName: 'user_roles', + timestamps: true, + updatedAt: false + }); + + (UserRole as any).associate = (models: any) => { + UserRole.belongsTo(models.User, { + foreignKey: 'userId', + as: 'user' + }); + UserRole.belongsTo(models.Role, { + foreignKey: 'roleId', + as: 'role' + }); + UserRole.belongsTo(models.Zone, { + foreignKey: 'zoneId', + as: 'zone' + }); + UserRole.belongsTo(models.Region, { + foreignKey: 'regionId', + as: 'region' + }); + UserRole.belongsTo(models.Area, { + foreignKey: 'areaId', + as: 'area' + }); + UserRole.belongsTo(models.User, { + foreignKey: 'assignedBy', + as: 'assigner' + }); + }; + + return UserRole; +}; diff --git a/src/database/models/WorkNoteAttachment.ts b/src/database/models/WorkNoteAttachment.ts new file mode 100644 index 0000000..f0f0013 --- /dev/null +++ b/src/database/models/WorkNoteAttachment.ts @@ -0,0 +1,46 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface WorkNoteAttachmentAttributes { + id: string; + noteId: string; + documentId: string; +} + +export interface WorkNoteAttachmentInstance extends Model, WorkNoteAttachmentAttributes { } + +export default (sequelize: Sequelize) => { + const WorkNoteAttachment = sequelize.define('WorkNoteAttachment', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + noteId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'worknotes', + key: 'id' + } + }, + documentId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'documents', + key: 'id' + } + } + }, { + tableName: 'work_note_attachments', + timestamps: true, + updatedAt: false + }); + + (WorkNoteAttachment as any).associate = (models: any) => { + WorkNoteAttachment.belongsTo(models.Worknote, { foreignKey: 'noteId', as: 'workNote' }); + WorkNoteAttachment.belongsTo(models.Document, { foreignKey: 'documentId', as: 'document' }); + }; + + return WorkNoteAttachment; +}; diff --git a/src/database/models/WorkNoteTag.ts b/src/database/models/WorkNoteTag.ts new file mode 100644 index 0000000..07448b9 --- /dev/null +++ b/src/database/models/WorkNoteTag.ts @@ -0,0 +1,41 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface WorkNoteTagAttributes { + id: string; + noteId: string; + tagName: string; +} + +export interface WorkNoteTagInstance extends Model, WorkNoteTagAttributes { } + +export default (sequelize: Sequelize) => { + const WorkNoteTag = sequelize.define('WorkNoteTag', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + noteId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'worknotes', + key: 'id' + } + }, + tagName: { + type: DataTypes.STRING, + allowNull: false + } + }, { + tableName: 'work_note_tags', + timestamps: true, + updatedAt: false + }); + + (WorkNoteTag as any).associate = (models: any) => { + WorkNoteTag.belongsTo(models.Worknote, { foreignKey: 'noteId', as: 'workNote' }); + }; + + return WorkNoteTag; +}; diff --git a/src/database/models/WorkflowStageConfig.js b/src/database/models/WorkflowStageConfig.ts similarity index 62% rename from src/database/models/WorkflowStageConfig.js rename to src/database/models/WorkflowStageConfig.ts index 0e3b3e0..6b27db9 100644 --- a/src/database/models/WorkflowStageConfig.js +++ b/src/database/models/WorkflowStageConfig.ts @@ -1,5 +1,19 @@ -module.exports = (sequelize, DataTypes) => { - const WorkflowStageConfig = sequelize.define('WorkflowStageConfig', { +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface WorkflowStageConfigAttributes { + id: string; + stageName: string; + stageOrder: number; + colorCode: string | null; + isParallel: boolean; + defaultEvaluators: any[]; + isActive: boolean; +} + +export interface WorkflowStageConfigInstance extends Model, WorkflowStageConfigAttributes { } + +export default (sequelize: Sequelize) => { + const WorkflowStageConfig = sequelize.define('WorkflowStageConfig', { id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, @@ -39,7 +53,7 @@ module.exports = (sequelize, DataTypes) => { ] }); - WorkflowStageConfig.associate = (models) => { + (WorkflowStageConfig as any).associate = (models: any) => { // Future associations with applications or tasks }; diff --git a/src/database/models/Worknote.js b/src/database/models/Worknote.js deleted file mode 100644 index 47e47bb..0000000 --- a/src/database/models/Worknote.js +++ /dev/null @@ -1,52 +0,0 @@ -const { REQUEST_TYPES } = require('../../common/config/constants'); - -module.exports = (sequelize, DataTypes) => { - const Worknote = sequelize.define('Worknote', { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true - }, - requestId: { - type: DataTypes.UUID, - allowNull: false - }, - requestType: { - type: DataTypes.ENUM(Object.values(REQUEST_TYPES)), - allowNull: false - }, - userId: { - type: DataTypes.UUID, - allowNull: false, - references: { - model: 'users', - key: 'id' - } - }, - content: { - type: DataTypes.TEXT, - allowNull: false - }, - isInternal: { - type: DataTypes.BOOLEAN, - defaultValue: true - } - }, { - tableName: 'worknotes', - timestamps: true, - indexes: [ - { fields: ['requestId'] }, - { fields: ['requestType'] }, - { fields: ['userId'] } - ] - }); - - Worknote.associate = (models) => { - Worknote.belongsTo(models.User, { - foreignKey: 'userId', - as: 'author' - }); - }; - - return Worknote; -}; diff --git a/src/database/models/Worknote.ts b/src/database/models/Worknote.ts new file mode 100644 index 0000000..71b6a48 --- /dev/null +++ b/src/database/models/Worknote.ts @@ -0,0 +1,78 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface WorknoteAttributes { + id: string; + applicationId: string | null; + requestId: string | null; // Compatibility + requestType: string | null; // Compatibility + userId: string; + noteText: string; + noteType: string; + status: string; +} + +export interface WorknoteInstance extends Model, WorknoteAttributes { } + +export default (sequelize: Sequelize) => { + const Worknote = sequelize.define('Worknote', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + applicationId: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'applications', + key: 'id' + } + }, + requestId: { + type: DataTypes.UUID, + allowNull: true + }, + requestType: { + type: DataTypes.STRING, + allowNull: true + }, + userId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'users', + key: 'id' + } + }, + noteText: { + type: DataTypes.TEXT, + allowNull: false + }, + noteType: { + type: DataTypes.STRING, + defaultValue: 'general' + }, + status: { + type: DataTypes.STRING, + defaultValue: 'active' + } + }, { + tableName: 'worknotes', + timestamps: true, + indexes: [ + { fields: ['applicationId'] }, + { fields: ['requestId'] }, + { fields: ['userId'] } + ] + }); + + (Worknote as any).associate = (models: any) => { + Worknote.belongsTo(models.User, { foreignKey: 'userId', as: 'author' }); + Worknote.belongsTo(models.Application, { foreignKey: 'applicationId', as: 'application' }); + + Worknote.hasMany(models.WorkNoteTag, { foreignKey: 'noteId', as: 'tags' }); + Worknote.hasMany(models.WorkNoteAttachment, { foreignKey: 'noteId', as: 'attachments' }); + }; + + return Worknote; +}; diff --git a/src/database/models/Zone.js b/src/database/models/Zone.js deleted file mode 100644 index a780694..0000000 --- a/src/database/models/Zone.js +++ /dev/null @@ -1,49 +0,0 @@ -module.exports = (sequelize, DataTypes) => { - const Zone = sequelize.define('Zone', { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true - }, - name: { - type: DataTypes.STRING, - allowNull: false - }, - regionId: { - type: DataTypes.UUID, - allowNull: false, - references: { - model: 'regions', - key: 'id' - } - }, - zonalManagerId: { - type: DataTypes.UUID, - allowNull: true, - references: { - model: 'users', - key: 'id' - } - } - }, { - tableName: 'zones', - timestamps: true, - indexes: [ - { fields: ['regionId'] }, - { unique: true, fields: ['name', 'regionId'] } - ] - }); - - Zone.associate = (models) => { - Zone.belongsTo(models.Region, { - foreignKey: 'regionId', - as: 'region' - }); - Zone.belongsTo(models.User, { - foreignKey: 'zonalManagerId', - as: 'zonalManager' - }); - }; - - return Zone; -}; diff --git a/src/database/models/Zone.ts b/src/database/models/Zone.ts new file mode 100644 index 0000000..2b4b16b --- /dev/null +++ b/src/database/models/Zone.ts @@ -0,0 +1,75 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface ZoneAttributes { + id: string; + zoneCode: string; + zoneName: string; + description: string | null; + isActive: boolean; + zonalBusinessHeadId: string | null; +} + +export interface ZoneInstance extends Model, ZoneAttributes { } + +export default (sequelize: Sequelize) => { + const Zone = sequelize.define('Zone', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + zoneCode: { + type: DataTypes.STRING, + unique: true, + allowNull: false + }, + zoneName: { + type: DataTypes.STRING, + allowNull: false + }, + description: { + type: DataTypes.TEXT, + allowNull: true + }, + isActive: { + type: DataTypes.BOOLEAN, + defaultValue: true + }, + zonalBusinessHeadId: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'users', + key: 'id' + } + } + }, { + tableName: 'zones', + timestamps: true + }); + + (Zone as any).associate = (models: any) => { + Zone.belongsTo(models.User, { + foreignKey: 'zonalBusinessHeadId', + as: 'zonalBusinessHead' + }); + Zone.hasMany(models.Region, { + foreignKey: 'zoneId', + as: 'regions' + }); + Zone.hasMany(models.State, { + foreignKey: 'zoneId', + as: 'states' + }); + Zone.hasMany(models.ZoneManager, { + foreignKey: 'zoneId', + as: 'managers' + }); + Zone.hasMany(models.Application, { + foreignKey: 'zoneId', + as: 'applications' + }); + }; + + return Zone; +}; diff --git a/src/database/models/ZoneManager.ts b/src/database/models/ZoneManager.ts new file mode 100644 index 0000000..ea8887d --- /dev/null +++ b/src/database/models/ZoneManager.ts @@ -0,0 +1,67 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface ZoneManagerAttributes { + id: string; + zoneId: string; + userId: string; + managerType: string; + isActive: boolean; + assignedAt: Date; +} + +export interface ZoneManagerInstance extends Model, ZoneManagerAttributes { } + +export default (sequelize: Sequelize) => { + const ZoneManager = sequelize.define('ZoneManager', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + zoneId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'zones', + key: 'id' + } + }, + userId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'users', + key: 'id' + } + }, + managerType: { + type: DataTypes.STRING, + allowNull: false + }, + isActive: { + type: DataTypes.BOOLEAN, + defaultValue: true + }, + assignedAt: { + type: DataTypes.DATE, + defaultValue: DataTypes.NOW + } + }, { + tableName: 'zone_managers', + timestamps: true, + updatedAt: false + }); + + (ZoneManager as any).associate = (models: any) => { + ZoneManager.belongsTo(models.Zone, { + foreignKey: 'zoneId', + as: 'zone' + }); + ZoneManager.belongsTo(models.User, { + foreignKey: 'userId', + as: 'user' + }); + }; + + return ZoneManager; +}; diff --git a/src/database/models/index.js b/src/database/models/index.js deleted file mode 100644 index 80bbf57..0000000 --- a/src/database/models/index.js +++ /dev/null @@ -1,55 +0,0 @@ -const { Sequelize } = require('sequelize'); -const config = require('../../common/config/database'); - -const env = process.env.NODE_ENV || 'development'; -const dbConfig = config[env]; - -// Initialize Sequelize -const sequelize = new Sequelize( - dbConfig.database, - dbConfig.username, - dbConfig.password, - { - host: dbConfig.host, - port: dbConfig.port, - dialect: dbConfig.dialect, - logging: dbConfig.logging, - pool: dbConfig.pool, - dialectOptions: dbConfig.dialectOptions - } -); - -const db = {}; - -// Import models -db.User = require('./User')(sequelize, Sequelize.DataTypes); -db.Application = require('./Application')(sequelize, Sequelize.DataTypes); -db.Resignation = require('./Resignation')(sequelize, Sequelize.DataTypes); -db.ConstitutionalChange = require('./ConstitutionalChange')(sequelize, Sequelize.DataTypes); -db.RelocationRequest = require('./RelocationRequest')(sequelize, Sequelize.DataTypes); -db.Outlet = require('./Outlet')(sequelize, Sequelize.DataTypes); -db.Worknote = require('./Worknote')(sequelize, Sequelize.DataTypes); -db.Document = require('./Document')(sequelize, Sequelize.DataTypes); -db.AuditLog = require('./AuditLog')(sequelize, Sequelize.DataTypes); -db.FinancePayment = require('./FinancePayment')(sequelize, Sequelize.DataTypes); -db.FnF = require('./FnF')(sequelize, Sequelize.DataTypes); -db.FnFLineItem = require('./FnFLineItem')(sequelize, Sequelize.DataTypes); -db.Region = require('./Region')(sequelize, Sequelize.DataTypes); -db.Zone = require('./Zone')(sequelize, Sequelize.DataTypes); -db.SLAConfiguration = require('./SLAConfiguration')(sequelize, Sequelize.DataTypes); -db.SLAReminder = require('./SLAReminder')(sequelize, Sequelize.DataTypes); -db.SLAEscalationConfig = require('./SLAEscalationConfig')(sequelize, Sequelize.DataTypes); -db.WorkflowStageConfig = require('./WorkflowStageConfig')(sequelize, Sequelize.DataTypes); -db.Notification = require('./Notification')(sequelize, Sequelize.DataTypes); - -// Define associations -Object.keys(db).forEach(modelName => { - if (db[modelName].associate) { - db[modelName].associate(db); - } -}); - -db.sequelize = sequelize; -db.Sequelize = Sequelize; - -module.exports = db; diff --git a/src/database/models/index.ts b/src/database/models/index.ts new file mode 100644 index 0000000..456c27e --- /dev/null +++ b/src/database/models/index.ts @@ -0,0 +1,203 @@ +import { Sequelize } from 'sequelize'; +import config from '../../common/config/database.js'; + +// Import individual model factories +import createUser from './User.js'; +import createApplication from './Application.js'; +import createResignation from './Resignation.js'; +import createConstitutionalChange from './ConstitutionalChange.js'; +import createRelocationRequest from './RelocationRequest.js'; +import createOutlet from './Outlet.js'; +import createWorknote from './Worknote.js'; +import createDocument from './Document.js'; +import createAuditLog from './AuditLog.js'; +import createFinancePayment from './FinancePayment.js'; +import createFnF from './FnF.js'; +import createFnFLineItem from './FnFLineItem.js'; +import createRegion from './Region.js'; +import createZone from './Zone.js'; +import createSLAConfiguration from './SLAConfiguration.js'; +import createSLAReminder from './SLAReminder.js'; +import createSLAEscalationConfig from './SLAEscalationConfig.js'; +import createWorkflowStageConfig from './WorkflowStageConfig.js'; +import createNotification from './Notification.js'; + +// Batch 1: Organizational Hierarchy & User Management +import createRole from './Role.js'; +import createPermission from './Permission.js'; +import createRolePermission from './RolePermission.js'; +import createState from './State.js'; +import createDistrict from './District.js'; +import createArea from './Area.js'; +import createUserRole from './UserRole.js'; +import createZoneManager from './ZoneManager.js'; +import createRegionManager from './RegionManager.js'; +import createAreaManager from './AreaManager.js'; +import createDistrictManager from './DistrictManager.js'; + +// Batch 2: Opportunity & Application Framework +import createOpportunity from './Opportunity.js'; +import createApplicationStatusHistory from './ApplicationStatusHistory.js'; +import createApplicationProgress from './ApplicationProgress.js'; + +// Batch 3: Questionnaire & Interview Systems +import createQuestionnaire from './Questionnaire.js'; +import createQuestionnaireQuestion from './QuestionnaireQuestion.js'; +import createQuestionnaireResponse from './QuestionnaireResponse.js'; +import createQuestionnaireScore from './QuestionnaireScore.js'; +import createInterview from './Interview.js'; +import createInterviewParticipant from './InterviewParticipant.js'; +import createInterviewEvaluation from './InterviewEvaluation.js'; +import createKTMatrixScore from './KTMatrixScore.js'; +import createInterviewFeedback from './InterviewFeedback.js'; +import createAiSummary from './AiSummary.js'; + +// Batch 4: Dealer Entity, Documents & Work Notes +import createDealer from './Dealer.js'; +import createDealerCode from './DealerCode.js'; +import createDocumentVersion from './DocumentVersion.js'; +import createWorkNoteTag from './WorkNoteTag.js'; +import createWorkNoteAttachment from './WorkNoteAttachment.js'; + +// Batch 5: FDD, LOI, LOA, EOR & Security Deposit +import createFddAssignment from './FddAssignment.js'; +import createFddReport from './FddReport.js'; +import createLoiRequest from './LoiRequest.js'; +import createLoiApproval from './LoiApproval.js'; +import createLoiDocumentGenerated from './LoiDocumentGenerated.js'; +import createLoiAcknowledgement from './LoiAcknowledgement.js'; +import createSecurityDeposit from './SecurityDeposit.js'; +import createLoaRequest from './LoaRequest.js'; +import createLoaApproval from './LoaApproval.js'; +import createLoaDocumentGenerated from './LoaDocumentGenerated.js'; +import createLoaAcknowledgement from './LoaAcknowledgement.js'; +import createEorChecklist from './EorChecklist.js'; +import createEorChecklistItem from './EorChecklistItem.js'; + +// Batch 6: Offboarding & F&F Settlement +import createTerminationRequest from './TerminationRequest.js'; +import createExitFeedback from './ExitFeedback.js'; + +// Batch 7: Notifications, Logs & Templates +import createEmailTemplate from './EmailTemplate.js'; + +// Batch 8: SLA & TAT Tracking +import createSLATracking from './SLATracking.js'; +import createSLABreach from './SLABreach.js'; + +const env = process.env.NODE_ENV || 'development'; +const dbConfig = config[env]; + +// Initialize Sequelize +const sequelize = new Sequelize( + dbConfig.database!, + dbConfig.username!, + dbConfig.password!, + { + host: dbConfig.host, + port: dbConfig.port as number, + dialect: dbConfig.dialect as any, + logging: dbConfig.logging, + pool: dbConfig.pool, + dialectOptions: dbConfig.dialectOptions + } +); + +const db: any = {}; + +// Initialize models +db.User = createUser(sequelize); +db.Application = createApplication(sequelize); +db.Resignation = createResignation(sequelize); +db.ConstitutionalChange = createConstitutionalChange(sequelize); +db.RelocationRequest = createRelocationRequest(sequelize); +db.Outlet = createOutlet(sequelize); +db.Worknote = createWorknote(sequelize); +db.Document = createDocument(sequelize); +db.AuditLog = createAuditLog(sequelize); +db.FinancePayment = createFinancePayment(sequelize); +db.FnF = createFnF(sequelize); +db.FnFLineItem = createFnFLineItem(sequelize); +db.Region = createRegion(sequelize); +db.Zone = createZone(sequelize); +db.SLAConfiguration = createSLAConfiguration(sequelize); +db.SLAReminder = createSLAReminder(sequelize); +db.SLAEscalationConfig = createSLAEscalationConfig(sequelize); +db.WorkflowStageConfig = createWorkflowStageConfig(sequelize); +db.Notification = createNotification(sequelize); + +// Batch 1: Organizational Hierarchy & User Management +db.Role = createRole(sequelize); +db.Permission = createPermission(sequelize); +db.RolePermission = createRolePermission(sequelize); +db.State = createState(sequelize); +db.District = createDistrict(sequelize); +db.Area = createArea(sequelize); +db.UserRole = createUserRole(sequelize); +db.ZoneManager = createZoneManager(sequelize); +db.RegionManager = createRegionManager(sequelize); +db.AreaManager = createAreaManager(sequelize); +db.DistrictManager = createDistrictManager(sequelize); + +// Batch 2: Opportunity & Application Framework +db.Opportunity = createOpportunity(sequelize); +db.ApplicationStatusHistory = createApplicationStatusHistory(sequelize); +db.ApplicationProgress = createApplicationProgress(sequelize); + +// Batch 3: Questionnaire & Interview Systems +db.Questionnaire = createQuestionnaire(sequelize); +db.QuestionnaireQuestion = createQuestionnaireQuestion(sequelize); +db.QuestionnaireResponse = createQuestionnaireResponse(sequelize); +db.QuestionnaireScore = createQuestionnaireScore(sequelize); +db.Interview = createInterview(sequelize); +db.InterviewParticipant = createInterviewParticipant(sequelize); +db.InterviewEvaluation = createInterviewEvaluation(sequelize); +db.KTMatrixScore = createKTMatrixScore(sequelize); +db.InterviewFeedback = createInterviewFeedback(sequelize); +db.AiSummary = createAiSummary(sequelize); + +// Batch 4: Dealer Entity, Documents & Work Notes +db.Dealer = createDealer(sequelize); +db.DealerCode = createDealerCode(sequelize); +db.DocumentVersion = createDocumentVersion(sequelize); +db.WorkNoteTag = createWorkNoteTag(sequelize); +db.WorkNoteAttachment = createWorkNoteAttachment(sequelize); + +// Batch 5: FDD, LOI, LOA, EOR & Security Deposit +db.FddAssignment = createFddAssignment(sequelize); +db.FddReport = createFddReport(sequelize); +db.LoiRequest = createLoiRequest(sequelize); +db.LoiApproval = createLoiApproval(sequelize); +db.LoiDocumentGenerated = createLoiDocumentGenerated(sequelize); +db.LoiAcknowledgement = createLoiAcknowledgement(sequelize); +db.SecurityDeposit = createSecurityDeposit(sequelize); +db.LoaRequest = createLoaRequest(sequelize); +db.LoaApproval = createLoaApproval(sequelize); +db.LoaDocumentGenerated = createLoaDocumentGenerated(sequelize); +db.LoaAcknowledgement = createLoaAcknowledgement(sequelize); +db.EorChecklist = createEorChecklist(sequelize); +db.EorChecklistItem = createEorChecklistItem(sequelize); + +// Batch 6: Offboarding & F&F Settlement +db.TerminationRequest = createTerminationRequest(sequelize); +db.ExitFeedback = createExitFeedback(sequelize); + +// Batch 7: Notifications, Logs & Templates +db.EmailTemplate = createEmailTemplate(sequelize); + +// Batch 8: SLA & TAT Tracking +db.SLATracking = createSLATracking(sequelize); +db.SLABreach = createSLABreach(sequelize); + +// Define associations +Object.keys(db).forEach((modelName) => { + if (db[modelName].associate) { + db[modelName].associate(db); + } +}); + +db.sequelize = sequelize; +db.Sequelize = Sequelize; + +export default db; +export { sequelize, Sequelize }; diff --git a/src/modules/admin/admin.controller.ts b/src/modules/admin/admin.controller.ts new file mode 100644 index 0000000..efdc27f --- /dev/null +++ b/src/modules/admin/admin.controller.ts @@ -0,0 +1,169 @@ +import { Request, Response } from 'express'; +import db from '../../database/models/index.js'; +const { Role, Permission, RolePermission, User, DealerCode, AuditLog } = db; +import { AUDIT_ACTIONS } from '../../common/config/constants.js'; +import { AuthRequest } from '../../types/express.types.js'; + +// --- Roles Management --- + +export const getRoles = async (req: Request, res: Response) => { + try { + const roles = await Role.findAll({ + include: [{ + model: Permission, + as: 'permissions', + through: { attributes: ['canCreate', 'canRead', 'canUpdate', 'canDelete', 'canApprove'] } + }], + order: [['roleName', 'ASC']] + }); + res.json({ success: true, data: roles }); + } catch (error) { + console.error('Get roles error:', error); + res.status(500).json({ success: false, message: 'Error fetching roles' }); + } +}; + +export const createRole = async (req: AuthRequest, res: Response) => { + try { + const { roleCode, roleName, description, permissions } = req.body; // permissions: [{ permissionId, actions: { canCreate... } }] + + const role = await Role.create({ roleCode, roleName, description }); + + if (permissions && permissions.length > 0) { + for (const p of permissions) { + await RolePermission.create({ + roleId: role.id, + permissionId: p.permissionId, + ...p.actions + }); + } + } + + await AuditLog.create({ + userId: req.user?.id, // Optional chaining as user might be undefined if auth middleware fails or not strict + action: AUDIT_ACTIONS.CREATED, + entityType: 'role', + entityId: role.id, + newData: req.body + }); + + res.status(201).json({ success: true, data: role, message: 'Role created successfully' }); + } catch (error) { + console.error('Create role error:', error); + res.status(500).json({ success: false, message: 'Error creating role' }); + } +}; + +export const updateRole = async (req: AuthRequest, res: Response) => { + try { + const { id } = req.params; + const { roleName, description, permissions, isActive } = req.body; + + const role = await Role.findByPk(id); + if (!role) return res.status(404).json({ success: false, message: 'Role not found' }); + + await role.update({ roleName, description, isActive }); + + if (permissions) { + // Simplistic: Remove all and re-add (or smart update). for MVP redo all is fine or use bulkCreate with updateOnDuplicate + await RolePermission.destroy({ where: { roleId: id } }); + for (const p of permissions) { + await RolePermission.create({ + roleId: id, + permissionId: p.permissionId, + ...p.actions + }); + } + } + + await AuditLog.create({ + userId: req.user?.id, + action: AUDIT_ACTIONS.UPDATED, + entityType: 'role', + entityId: id, + newData: req.body + }); + + res.json({ success: true, message: 'Role updated successfully' }); + } catch (error) { + console.error('Update role error:', error); + res.status(500).json({ success: false, message: 'Error updating role' }); + } +}; + +// --- Permissions Management --- + +export const getPermissions = async (req: Request, res: Response) => { + try { + const permissions = await Permission.findAll({ order: [['module', 'ASC']] }); + res.json({ success: true, data: permissions }); + } catch (error) { + console.error('Get permissions error:', error); + res.status(500).json({ success: false, message: 'Error fetching permissions' }); + } +}; + +// --- User Management (Admin) --- + +export const getAllUsers = async (req: Request, res: Response) => { + try { + const users = await User.findAll({ + attributes: { exclude: ['password'] }, + include: ['roleDetails', 'zoneDetails', 'regionDetails', 'areaDetails'], // Assuming associations are named like this or similar + order: [['createdAt', 'DESC']] + }); + res.json({ success: true, data: users }); + } catch (error) { + console.error('Get users error:', error); + res.status(500).json({ success: false, message: 'Error fetching users' }); + } +}; + +export const updateUserStatus = async (req: AuthRequest, res: Response) => { + try { + const { id } = req.params; + const { status, isActive } = req.body; + + const user = await User.findByPk(id); + if (!user) return res.status(404).json({ success: false, message: 'User not found' }); + + await user.update({ status, isActive }); + + await AuditLog.create({ + userId: req.user?.id, + action: AUDIT_ACTIONS.UPDATED, + entityType: 'user', + entityId: id, + newData: { status, isActive } + }); + + res.json({ success: true, message: 'User status updated' }); + } catch (error) { + console.error('Update user status error:', error); + res.status(500).json({ success: false, message: 'Error updating user status' }); + } +}; + +// --- Dealer Codes --- + +export const generateDealerCode = async (req: AuthRequest, res: Response) => { + try { + const { regionId, stateId, channel } = req.body; + // Logic to generate unique code based on format (e.g., RE-[Region]-[State]-[Seq]) + // This is a placeholder for the actual business logic + + const timestamp = Date.now().toString().slice(-6); + const code = `DLR-${timestamp}`; + + const dealerCode = await DealerCode.create({ + code, + isUsed: false, + generatedBy: req.user?.id + }); + + res.status(201).json({ success: true, data: dealerCode }); + } catch (error) { + console.error('Generate dealer code error:', error); + res.status(500).json({ success: false, message: 'Error generating dealer code' }); + } +}; diff --git a/src/modules/admin/admin.routes.ts b/src/modules/admin/admin.routes.ts new file mode 100644 index 0000000..92095a5 --- /dev/null +++ b/src/modules/admin/admin.routes.ts @@ -0,0 +1,27 @@ +import express from 'express'; +const router = express.Router(); +import * as adminController from './admin.controller.js'; +import { authenticate } from '../../common/middleware/auth.js'; +import { checkRole } from '../../common/middleware/roleCheck.js'; +import { ROLES } from '../../common/config/constants.js'; + +// All admin routes require authentication and typically Admin roles +// For now, allowing all for dev, but should be: checkRole([ROLES.SUPER_ADMIN]) +router.use(authenticate as any); + +// Roles +router.get('/roles', adminController.getRoles); +router.post('/roles', checkRole([ROLES.SUPER_ADMIN]) as any, adminController.createRole); +router.put('/roles/:id', checkRole([ROLES.SUPER_ADMIN]) as any, adminController.updateRole); + +// Permissions +router.get('/permissions', adminController.getPermissions); + +// Users (Admin View) +router.get('/users', checkRole([ROLES.SUPER_ADMIN, ROLES.DD_ADMIN]) as any, adminController.getAllUsers); +router.patch('/users/:id/status', checkRole([ROLES.SUPER_ADMIN]) as any, adminController.updateUserStatus); + +// Dealer Codes +router.post('/dealer-codes/generate', checkRole([ROLES.SUPER_ADMIN, ROLES.DD_ADMIN]) as any, adminController.generateDealerCode); + +export default router; diff --git a/src/modules/assessment/assessment.controller.ts b/src/modules/assessment/assessment.controller.ts new file mode 100644 index 0000000..5b8121a --- /dev/null +++ b/src/modules/assessment/assessment.controller.ts @@ -0,0 +1,160 @@ +import { Request, Response } from 'express'; +import db from '../../database/models/index.js'; +const { + Questionnaire, QuestionnaireQuestion, QuestionnaireResponse, QuestionnaireScore, + Interview, InterviewEvaluation, InterviewParticipant, AiSummary +} = db; +import { AuthRequest } from '../../types/express.types.js'; +import { Op } from 'sequelize'; + +// --- Questionnaires --- + +export const getQuestionnaire = async (req: Request, res: Response) => { + try { + const { version } = req.query; + const where: any = { isActive: true }; + if (version) where.version = version; + + const questionnaire = await Questionnaire.findOne({ + where, + include: [{ model: QuestionnaireQuestion, as: 'questions' }], + order: [['createdAt', 'DESC']] // GET latest if no version + }); + + res.json({ success: true, data: questionnaire }); + } catch (error) { + console.error('Get questionnaire error:', error); + res.status(500).json({ success: false, message: 'Error fetching questionnaire' }); + } +}; + +export const submitQuestionnaireResponse = async (req: AuthRequest, res: Response) => { + try { + const { applicationId, questionnaireId, responses } = req.body; // responses: [{ questionId, responseValue, attachmentUrl }] + + // Calculate score logic (Placeholder) + let calculatedScore = 0; + let totalWeight = 0; + + for (const resp of responses) { + await QuestionnaireResponse.create({ + applicationId, + questionnaireId, + questionId: resp.questionId, + responseValue: resp.responseValue, + attachmentUrl: resp.attachmentUrl + }); + // Add scoring logic here based on question type/weight + } + + // Create Score Record + await QuestionnaireScore.create({ + applicationId, + questionnaireId, + score: calculatedScore, + maxScore: 100, // Placeholder + status: 'Completed' + }); + + res.status(201).json({ success: true, message: 'Responses submitted successfully' }); + } catch (error) { + console.error('Submit response error:', error); + res.status(500).json({ success: false, message: 'Error submitting responses' }); + } +}; + +// --- Interviews --- + +export const scheduleInterview = async (req: AuthRequest, res: Response) => { + try { + const { applicationId, level, scheduledAt, type, location, participants } = req.body; // participants: [userId] + + const interview = await Interview.create({ + applicationId, + level, + scheduledAt, + type, + location, + status: 'Scheduled', + scheduledBy: req.user?.id + }); + + if (participants && participants.length > 0) { + for (const userId of participants) { + await InterviewParticipant.create({ + interviewId: interview.id, + userId, + role: 'Panelist' + }); + } + } + + res.status(201).json({ success: true, message: 'Interview scheduled successfully', data: interview }); + } catch (error) { + console.error('Schedule interview error:', error); + res.status(500).json({ success: false, message: 'Error scheduling interview' }); + } +}; + +export const updateInterview = async (req: AuthRequest, res: Response) => { + try { + const { id } = req.params; + const { status, scheduledAt, outcome } = req.body; + + const interview = await Interview.findByPk(id); + if (!interview) return res.status(404).json({ success: false, message: 'Interview not found' }); + + await interview.update({ status, scheduledAt, outcome }); + + res.json({ success: true, message: 'Interview updated successfully' }); + } catch (error) { + console.error('Update interview error:', error); + res.status(500).json({ success: false, message: 'Error updating interview' }); + } +}; + +export const submitEvaluation = async (req: AuthRequest, res: Response) => { + try { + const { id } = req.params; // Interview ID + const { ktScore, feedback, recommendation, status } = req.body; + + const interview = await Interview.findByPk(id); + if (!interview) return res.status(404).json({ success: false, message: 'Interview not found' }); + + const evaluation = await InterviewEvaluation.create({ + interviewId: id, + evaluatorId: req.user?.id, + ktScore, + feedback, + recommendation + }); + + // Auto update interview status if completed + if (status === 'Completed') { + await interview.update({ status: 'Completed', outcome: recommendation }); + } + + res.status(201).json({ success: true, message: 'Evaluation submitted successfully', data: evaluation }); + } catch (error) { + console.error('Submit evaluation error:', error); + res.status(500).json({ success: false, message: 'Error submitting evaluation' }); + } +}; + +// --- AI Summary --- + +export const getAiSummary = async (req: Request, res: Response) => { + try { + const { applicationId } = req.params; + + const summary = await AiSummary.findOne({ + where: { applicationId }, + order: [['createdAt', 'DESC']] + }); + + res.json({ success: true, data: summary }); + } catch (error) { + console.error('Get AI summary error:', error); + res.status(500).json({ success: false, message: 'Error fetching AI summary' }); + } +}; diff --git a/src/modules/assessment/assessment.routes.ts b/src/modules/assessment/assessment.routes.ts new file mode 100644 index 0000000..085f6df --- /dev/null +++ b/src/modules/assessment/assessment.routes.ts @@ -0,0 +1,20 @@ +import express from 'express'; +const router = express.Router(); +import * as assessmentController from './assessment.controller.js'; +import { authenticate } from '../../common/middleware/auth.js'; + +router.use(authenticate as any); + +// Questionnaires +router.get('/questionnaire', assessmentController.getQuestionnaire); +router.post('/questionnaire/response', assessmentController.submitQuestionnaireResponse); + +// Interviews +router.post('/interviews', assessmentController.scheduleInterview); +router.put('/interviews/:id', assessmentController.updateInterview); +router.post('/interviews/:id/evaluation', assessmentController.submitEvaluation); + +// AI Summary +router.get('/ai-summary/:applicationId', assessmentController.getAiSummary); + +export default router; diff --git a/src/modules/auth/auth.controller.js b/src/modules/auth/auth.controller.ts similarity index 84% rename from src/modules/auth/auth.controller.js rename to src/modules/auth/auth.controller.ts index 763931f..ae10e9b 100644 --- a/src/modules/auth/auth.controller.js +++ b/src/modules/auth/auth.controller.ts @@ -1,10 +1,13 @@ -const bcrypt = require('bcryptjs'); -const { User, AuditLog } = require('../../database/models'); -const { generateToken } = require('../../common/config/auth'); -const { AUDIT_ACTIONS } = require('../../common/config/constants'); +import { Request, Response } from 'express'; +import bcrypt from 'bcryptjs'; +import db from '../../database/models/index.js'; +const { User, AuditLog } = db; +import { generateToken } from '../../common/config/auth.js'; +import { AUDIT_ACTIONS } from '../../common/config/constants.js'; +import { AuthRequest } from '../../types/express.types.js'; // Register new user -exports.register = async (req, res) => { +export const register = async (req: Request, res: Response) => { try { const { email, password, fullName, role, phone, region, zone } = req.body; @@ -36,7 +39,8 @@ exports.register = async (req, res) => { role, phone, region, - zone + zone, + status: 'active' }); // Log audit @@ -62,7 +66,7 @@ exports.register = async (req, res) => { }; // Login -exports.login = async (req, res) => { +export const login = async (req: Request, res: Response) => { try { const { email, password } = req.body; @@ -93,7 +97,7 @@ exports.login = async (req, res) => { } // Verify password - const isValidPassword = await bcrypt.compare(password, user.password); + const isValidPassword = await bcrypt.compare(password, user.password!); if (!isValidPassword) { return res.status(401).json({ success: false, @@ -110,7 +114,7 @@ exports.login = async (req, res) => { // Log audit await AuditLog.create({ userId: user.id, - action: 'user_login', + action: 'user_login' as any, entityType: 'user', entityId: user.id }); @@ -137,8 +141,12 @@ exports.login = async (req, res) => { }; // Get profile -exports.getProfile = async (req, res) => { +export const getProfile = async (req: AuthRequest, res: Response) => { try { + if (!req.user) { + return res.status(401).json({ success: false, message: 'Unauthorized' }); + } + const user = await User.findByPk(req.user.id, { attributes: ['id', 'email', 'name', 'role', 'region', 'zone', 'phone', 'createdAt'] }); @@ -173,8 +181,12 @@ exports.getProfile = async (req, res) => { }; // Update profile -exports.updateProfile = async (req, res) => { +export const updateProfile = async (req: AuthRequest, res: Response) => { try { + if (!req.user) { + return res.status(401).json({ success: false, message: 'Unauthorized' }); + } + const { fullName, phone } = req.body; const user = await User.findByPk(req.user.id); @@ -212,8 +224,12 @@ exports.updateProfile = async (req, res) => { }; // Change password -exports.changePassword = async (req, res) => { +export const changePassword = async (req: AuthRequest, res: Response) => { try { + if (!req.user) { + return res.status(401).json({ success: false, message: 'Unauthorized' }); + } + const { currentPassword, newPassword } = req.body; if (!currentPassword || !newPassword) { @@ -233,7 +249,7 @@ exports.changePassword = async (req, res) => { } // Verify current password - const isValid = await bcrypt.compare(currentPassword, user.password); + const isValid = await bcrypt.compare(currentPassword, user.password!); if (!isValid) { return res.status(401).json({ success: false, @@ -250,7 +266,7 @@ exports.changePassword = async (req, res) => { // Log audit await AuditLog.create({ userId: req.user.id, - action: 'password_changed', + action: 'password_changed' as any, entityType: 'user', entityId: req.user.id }); diff --git a/src/modules/auth/auth.routes.js b/src/modules/auth/auth.routes.js deleted file mode 100644 index 1189a6d..0000000 --- a/src/modules/auth/auth.routes.js +++ /dev/null @@ -1,15 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const authController = require('./auth.controller'); -const { authenticate } = require('../../common/middleware/auth'); - -// Public routes -router.post('/register', authController.register); -router.post('/login', authController.login); - -// Protected routes -router.get('/profile', authenticate, authController.getProfile); -router.put('/profile', authenticate, authController.updateProfile); -router.post('/change-password', authenticate, authController.changePassword); - -module.exports = router; diff --git a/src/modules/auth/auth.routes.ts b/src/modules/auth/auth.routes.ts new file mode 100644 index 0000000..ac3235c --- /dev/null +++ b/src/modules/auth/auth.routes.ts @@ -0,0 +1,15 @@ +import express from 'express'; +const router = express.Router(); +import * as authController from './auth.controller.js'; +import { authenticate } from '../../common/middleware/auth.js'; + +// Public routes +router.post('/register', authController.register); +router.post('/login', authController.login); + +// Protected routes +router.get('/profile', authenticate as any, authController.getProfile); +router.put('/profile', authenticate as any, authController.updateProfile); +router.post('/change-password', authenticate as any, authController.changePassword); + +export default router; diff --git a/src/modules/collaboration/collaboration.controller.js b/src/modules/collaboration/collaboration.controller.js deleted file mode 100644 index d91cbc1..0000000 --- a/src/modules/collaboration/collaboration.controller.js +++ /dev/null @@ -1,84 +0,0 @@ -const { Worknote, User } = require('../../database/models'); - -exports.addWorknote = async (req, res) => { - try { - const { requestId, requestType, message, isInternal } = req.body; - - if (!requestId || !requestType || !message) { - return res.status(400).json({ - success: false, - message: 'Request ID, type, and message are required' - }); - } - - await Worknote.create({ - requestId, - requestType, - userId: req.user.id, - content: message, - isInternal: isInternal || false - }); - - res.status(201).json({ - success: true, - message: 'Worknote added successfully' - }); - } catch (error) { - console.error('Add worknote error:', error); - res.status(500).json({ success: false, message: 'Error adding worknote' }); - } -}; - -exports.getWorknotes = async (req, res) => { - try { - const { requestId } = req.params; - const { requestType } = req.query; - - const worknotes = await Worknote.findAll({ - where: { - requestId, - requestType - }, - include: [{ - model: User, - as: 'author', - attributes: ['name', 'role'] - }], - order: [['createdAt', 'DESC']] - }); - - res.json({ - success: true, - worknotes - }); - } catch (error) { - console.error('Get worknotes error:', error); - res.status(500).json({ success: false, message: 'Error fetching worknotes' }); - } -}; - -exports.deleteWorknote = async (req, res) => { - try { - const { id } = req.params; - - const worknote = await Worknote.findByPk(id); - if (!worknote) { - return res.status(404).json({ success: false, message: 'Worknote not found' }); - } - - // Only allow user who created it or admin to delete - if (worknote.userId !== req.user.id && req.user.role !== 'Super Admin') { - return res.status(403).json({ success: false, message: 'Access denied' }); - } - - await worknote.destroy(); - - res.json({ - success: true, - message: 'Worknote deleted successfully' - }); - } catch (error) { - console.error('Delete worknote error:', error); - res.status(500).json({ success: false, message: 'Error deleting worknote' }); - } -}; diff --git a/src/modules/collaboration/collaboration.controller.ts b/src/modules/collaboration/collaboration.controller.ts new file mode 100644 index 0000000..47208a7 --- /dev/null +++ b/src/modules/collaboration/collaboration.controller.ts @@ -0,0 +1,117 @@ +import { Response } from 'express'; +import db from '../../database/models/index.js'; +const { Worknote, User, WorkNoteTag, WorkNoteAttachment, Document, DocumentVersion } = db; +import { AuthRequest } from '../../types/express.types.js'; + +// --- Worknotes --- + +export const addWorknote = async (req: AuthRequest, res: Response) => { + try { + const { requestId, requestType, noteText, noteType, tags, attachmentDocIds } = req.body; // tags: ['Urgent'], attachmentDocIds: [uuid] + + const worknote = await Worknote.create({ + requestId, + requestType, // application, opportunity, etc. + userId: req.user?.id, + noteText, + noteType: noteType || 'General', + status: 'Active' + }); + + if (tags && tags.length > 0) { + for (const tag of tags) { + await WorkNoteTag.create({ workNoteId: worknote.id, tag }); + } + } + + if (attachmentDocIds && attachmentDocIds.length > 0) { + for (const docId of attachmentDocIds) { + await WorkNoteAttachment.create({ workNoteId: worknote.id, documentId: docId }); + } + } + + res.status(201).json({ success: true, message: 'Worknote added', data: worknote }); + } catch (error) { + console.error('Add worknote error:', error); + res.status(500).json({ success: false, message: 'Error adding worknote' }); + } +}; + +export const getWorknotes = async (req: AuthRequest, res: Response) => { + try { + const { requestId, requestType } = req.query as any; + + const worknotes = await Worknote.findAll({ + where: { requestId, requestType }, + include: [ + { model: User, as: 'author', attributes: ['name', 'role'] }, + { model: WorkNoteTag, as: 'tags' }, + { model: WorkNoteAttachment, as: 'attachments', include: ['document'] } + ], + order: [['createdAt', 'DESC']] + }); + + res.json({ success: true, data: worknotes }); + } catch (error) { + res.status(500).json({ success: false, message: 'Error fetching worknotes' }); + } +}; + +// --- Documents --- + +export const uploadDocument = async (req: AuthRequest, res: Response) => { + try { + const { applicationId, dealerId, docType, fileName, fileUrl, mimeType } = req.body; + + const document = await Document.create({ + applicationId, + dealerId, + docType, + fileName, + filePath: fileUrl, // Assuming URL from cloud storage + mimeType, + uploadedBy: req.user?.id + }); + + // Create Initial Version + await DocumentVersion.create({ + documentId: document.id, + versionNumber: 1, + filePath: fileUrl, + uploadedBy: req.user?.id, + changeReason: 'Initial Upload' + }); + + res.status(201).json({ success: true, message: 'Document uploaded', data: document }); + } catch (error) { + console.error('Upload document error:', error); + res.status(500).json({ success: false, message: 'Error uploading document' }); + } +}; + +export const uploadNewVersion = async (req: AuthRequest, res: Response) => { + try { + const { documentId, fileUrl, changeReason } = req.body; + + const lastVersion = await DocumentVersion.findOne({ + where: { documentId }, + order: [['versionNumber', 'DESC']] + }); + const nextVersion = (lastVersion?.versionNumber || 0) + 1; + + await DocumentVersion.create({ + documentId, + versionNumber: nextVersion, + filePath: fileUrl, + uploadedBy: req.user?.id, + changeReason + }); + + // Update main document pointer if needed (usually main doc points to latest or metadata) + await Document.update({ filePath: fileUrl }, { where: { id: documentId } }); + + res.status(201).json({ success: true, message: 'New version uploaded' }); + } catch (error) { + res.status(500).json({ success: false, message: 'Error uploading version' }); + } +}; diff --git a/src/modules/collaboration/collaboration.routes.js b/src/modules/collaboration/collaboration.routes.js deleted file mode 100644 index 30d00e4..0000000 --- a/src/modules/collaboration/collaboration.routes.js +++ /dev/null @@ -1,14 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const collaborationController = require('./collaboration.controller'); -const { authenticate } = require('../../common/middleware/auth'); - -// All routes require authentication -router.use(authenticate); - -// Worknotes routes (mounted at /api/collaboration/worknotes or /api/worknotes) -router.post('/', collaborationController.addWorknote); -router.get('/:requestId', collaborationController.getWorknotes); -router.delete('/:id', collaborationController.deleteWorknote); - -module.exports = router; diff --git a/src/modules/collaboration/collaboration.routes.ts b/src/modules/collaboration/collaboration.routes.ts new file mode 100644 index 0000000..dce623d --- /dev/null +++ b/src/modules/collaboration/collaboration.routes.ts @@ -0,0 +1,16 @@ +import express from 'express'; +const router = express.Router(); +import * as collaborationController from './collaboration.controller.js'; +import { authenticate } from '../../common/middleware/auth.js'; + +router.use(authenticate as any); + +// Worknotes +router.get('/worknotes', collaborationController.getWorknotes); +router.post('/worknotes', collaborationController.addWorknote); + +// Documents +router.post('/documents', collaborationController.uploadDocument); +router.post('/documents/version', collaborationController.uploadNewVersion); + +export default router; diff --git a/src/modules/communication/communication.controller.ts b/src/modules/communication/communication.controller.ts new file mode 100644 index 0000000..6713be0 --- /dev/null +++ b/src/modules/communication/communication.controller.ts @@ -0,0 +1,37 @@ +import { Request, Response } from 'express'; +import db from '../../database/models/index.js'; +const { EmailTemplate, Notification } = db; +import { AuthRequest } from '../../types/express.types.js'; + +export const getTemplates = async (req: Request, res: Response) => { + try { + const templates = await EmailTemplate.findAll({ where: { isActive: true } }); + res.json({ success: true, data: templates }); + } catch (error) { + console.error('Get email templates error:', error); + res.status(500).json({ success: false, message: 'Error fetching templates' }); + } +}; + +export const createTemplate = async (req: AuthRequest, res: Response) => { + try { + const { code, description, subject, body, placeholders } = req.body; + const template = await EmailTemplate.create({ code, description, subject, body, placeholders }); + res.status(201).json({ success: true, data: template }); + } catch (error) { + res.status(500).json({ success: false, message: 'Error creating template' }); + } +}; + +export const getNotifications = async (req: AuthRequest, res: Response) => { + try { + const notifications = await Notification.findAll({ + where: { userId: req.user?.id }, + order: [['createdAt', 'DESC']], + limit: 50 + }); + res.json({ success: true, data: notifications }); + } catch (error) { + res.status(500).json({ success: false, message: 'Error fetching notifications' }); + } +}; diff --git a/src/modules/communication/communication.routes.ts b/src/modules/communication/communication.routes.ts new file mode 100644 index 0000000..1c5c8dc --- /dev/null +++ b/src/modules/communication/communication.routes.ts @@ -0,0 +1,17 @@ +import express from 'express'; +const router = express.Router(); +import * as commController from './communication.controller.js'; +import { authenticate } from '../../common/middleware/auth.js'; +import { checkRole } from '../../common/middleware/roleCheck.js'; +import { ROLES } from '../../common/config/constants.js'; + +router.use(authenticate as any); + +// Templates +router.get('/templates', checkRole([ROLES.SUPER_ADMIN, ROLES.DD_ADMIN]) as any, commController.getTemplates); +router.post('/templates', checkRole([ROLES.SUPER_ADMIN]) as any, commController.createTemplate); + +// Notifications +router.get('/notifications', commController.getNotifications); + +export default router; diff --git a/src/modules/dealer/dealer.controller.ts b/src/modules/dealer/dealer.controller.ts new file mode 100644 index 0000000..62f433b --- /dev/null +++ b/src/modules/dealer/dealer.controller.ts @@ -0,0 +1,71 @@ +import { Request, Response } from 'express'; +import db from '../../database/models/index.js'; +const { Dealer, DealerCode, Application, User, AuditLog } = db; +import { AuthRequest } from '../../types/express.types.js'; +import { AUDIT_ACTIONS } from '../../common/config/constants.js'; + +export const getDealers = async (req: Request, res: Response) => { + try { + const dealers = await Dealer.findAll({ + include: [ + { model: DealerCode, as: 'dealerCode' }, + { model: Application, as: 'application', attributes: ['city', 'state', 'preferredLocation'] } + ], + order: [['createdAt', 'DESC']] + }); + res.json({ success: true, data: dealers }); + } catch (error) { + console.error('Get dealers error:', error); + res.status(500).json({ success: false, message: 'Error fetching dealers' }); + } +}; + +export const createDealer = async (req: AuthRequest, res: Response) => { + try { + const { applicationId, dealerCodeId } = req.body; + + const application = await Application.findByPk(applicationId); + if (!application) return res.status(404).json({ success: false, message: 'Application not found' }); + + // Mark Code as Used + if (dealerCodeId) { + await DealerCode.update({ isUsed: true, usedAt: new Date(), usedByApplicationId: applicationId }, { where: { id: dealerCodeId } }); + } + + const dealer = await Dealer.create({ + applicationId, + dealerCodeId, + dealerName: application.applicantName, // Or Trade Name + status: 'Active', + onboardedAt: new Date() + }); + + await AuditLog.create({ + userId: req.user?.id, + action: AUDIT_ACTIONS.CREATED, + entityType: 'dealer', + entityId: dealer.id + }); + + res.status(201).json({ success: true, message: 'Dealer profile created', data: dealer }); + } catch (error) { + console.error('Create dealer error:', error); + res.status(500).json({ success: false, message: 'Error creating dealer' }); + } +}; + +export const updateDealer = async (req: AuthRequest, res: Response) => { + try { + const { id } = req.params; + const updateData = req.body; + + const dealer = await Dealer.findByPk(id); + if (!dealer) return res.status(404).json({ success: false, message: 'Dealer not found' }); + + await dealer.update(updateData); + + res.json({ success: true, message: 'Dealer updated successfully' }); + } catch (error) { + res.status(500).json({ success: false, message: 'Error updating dealer' }); + } +}; diff --git a/src/modules/dealer/dealer.routes.ts b/src/modules/dealer/dealer.routes.ts new file mode 100644 index 0000000..0194f0b --- /dev/null +++ b/src/modules/dealer/dealer.routes.ts @@ -0,0 +1,12 @@ +import express from 'express'; +const router = express.Router(); +import * as dealerController from './dealer.controller.js'; +import { authenticate } from '../../common/middleware/auth.js'; + +router.use(authenticate as any); + +router.get('/', dealerController.getDealers); +router.post('/', dealerController.createDealer); +router.put('/:id', dealerController.updateDealer); + +export default router; diff --git a/src/modules/eor/eor.controller.ts b/src/modules/eor/eor.controller.ts new file mode 100644 index 0000000..96b4f07 --- /dev/null +++ b/src/modules/eor/eor.controller.ts @@ -0,0 +1,76 @@ +import { Request, Response } from 'express'; +import db from '../../database/models/index.js'; +const { EorChecklist, EorChecklistItem, Document } = db; +import { AuthRequest } from '../../types/express.types.js'; + +export const getChecklist = async (req: Request, res: Response) => { + try { + const { applicationId } = req.params; + // Could auto-create if not exists? + let checklist = await EorChecklist.findOne({ + where: { applicationId }, + include: [{ model: EorChecklistItem, as: 'items', include: ['proofDocument'] }] + }); + + if (!checklist) { + // Optional: Return empty or create new + res.status(404).json({ success: false, message: 'Checklist not found' }); + return; + } + + res.json({ success: true, data: checklist }); + } catch (error) { + console.error('Get EOR checklist error:', error); + res.status(500).json({ success: false, message: 'Error fetching EOR checklist' }); + } +}; + +export const createChecklist = async (req: AuthRequest, res: Response) => { + try { + const { applicationId } = req.body; + const checklist = await EorChecklist.create({ applicationId }); + // Create Default Items? + // const defaultItems = [...]; + // for... + res.status(201).json({ success: true, message: 'EOR Checklist initiated', data: checklist }); + } catch (error) { + res.status(500).json({ success: false, message: 'Error creating checklist' }); + } +} + +export const updateItem = async (req: AuthRequest, res: Response) => { + try { + const { checklistId } = req.params; + const { itemType, description, isCompliant, remarks, proofDocumentId } = req.body; + + const item = await EorChecklistItem.create({ + checklistId, + itemType, + description, + isCompliant, + remarks, + proofDocumentId + }); + + res.status(201).json({ success: true, message: 'Item added/updated', data: item }); + } catch (error) { + console.error('Update EOR item error:', error); + res.status(500).json({ success: false, message: 'Error updating item' }); + } +}; + +export const submitAudit = async (req: AuthRequest, res: Response) => { + try { + const { checklistId } = req.params; // or ID + const { status, overallComments } = req.body; + + await EorChecklist.update( + { status, overallComments, auditDate: new Date(), auditorId: req.user?.id }, + { where: { id: checklistId } } + ); + + res.json({ success: true, message: 'EOR Audit submitted' }); + } catch (error) { + res.status(500).json({ success: false, message: 'Error submitting audit' }); + } +}; diff --git a/src/modules/eor/eor.routes.ts b/src/modules/eor/eor.routes.ts new file mode 100644 index 0000000..ae2f0a8 --- /dev/null +++ b/src/modules/eor/eor.routes.ts @@ -0,0 +1,13 @@ +import express from 'express'; +const router = express.Router(); +import * as eorController from './eor.controller.js'; +import { authenticate } from '../../common/middleware/auth.js'; + +router.use(authenticate as any); + +router.get('/:applicationId', eorController.getChecklist); +router.post('/', eorController.createChecklist); +router.post('/item/:checklistId', eorController.updateItem); +router.post('/audit/:checklistId', eorController.submitAudit); + +export default router; diff --git a/src/modules/fdd/fdd.controller.ts b/src/modules/fdd/fdd.controller.ts new file mode 100644 index 0000000..d0e3aa9 --- /dev/null +++ b/src/modules/fdd/fdd.controller.ts @@ -0,0 +1,69 @@ +import { Request, Response } from 'express'; +import db from '../../database/models/index.js'; +const { FddAssignment, FddReport, AuditLog } = db; +import { AuthRequest } from '../../types/express.types.js'; +import { AUDIT_ACTIONS } from '../../common/config/constants.js'; + +export const getAssignment = async (req: Request, res: Response) => { + try { + const { applicationId } = req.params; + const assignment = await FddAssignment.findOne({ + where: { applicationId }, + include: [{ model: FddReport, as: 'reports' }] + }); + res.json({ success: true, data: assignment }); + } catch (error) { + console.error('Get FDD assignment error:', error); + res.status(500).json({ success: false, message: 'Error fetching FDD assignment' }); + } +}; + +export const assignAgency = async (req: AuthRequest, res: Response) => { + try { + const { applicationId, assignedToAgency } = req.body; + + const assignment = await FddAssignment.create({ + applicationId, + assignedToAgency, // Agency User ID + status: 'Assigned' + }); + + await AuditLog.create({ + userId: req.user?.id, + action: AUDIT_ACTIONS.CREATED, + entityType: 'fdd_assignment', + entityId: assignment.id + }); + + res.status(201).json({ success: true, message: 'FDD Agency assigned', data: assignment }); + } catch (error) { + console.error('Assign FDD agency error:', error); + res.status(500).json({ success: false, message: 'Error assigning agency' }); + } +}; + +export const uploadReport = async (req: AuthRequest, res: Response) => { + try { + const { assignmentId, reportDocumentId, findings, recommendation } = req.body; + + const report = await FddReport.create({ + assignmentId, + reportDocumentId, + findings, + recommendation, + verifiedAt: new Date(), + verifiedBy: req.user?.id // Auto-verified by uploader for now? Or separate verify step? + }); + + // Update Assignment Status + await FddAssignment.update( + { status: 'Report Submitted' }, + { where: { id: assignmentId } } + ); + + res.status(201).json({ success: true, message: 'FDD Report uploaded', data: report }); + } catch (error) { + console.error('Upload FDD report error:', error); + res.status(500).json({ success: false, message: 'Error uploading report' }); + } +}; diff --git a/src/modules/fdd/fdd.routes.ts b/src/modules/fdd/fdd.routes.ts new file mode 100644 index 0000000..4b5e47d --- /dev/null +++ b/src/modules/fdd/fdd.routes.ts @@ -0,0 +1,12 @@ +import express from 'express'; +const router = express.Router(); +import * as fddController from './fdd.controller.js'; +import { authenticate } from '../../common/middleware/auth.js'; + +router.use(authenticate as any); + +router.get('/:applicationId', fddController.getAssignment); +router.post('/assign', fddController.assignAgency); +router.post('/report', fddController.uploadReport); + +export default router; diff --git a/src/modules/loa/loa.controller.ts b/src/modules/loa/loa.controller.ts new file mode 100644 index 0000000..4535832 --- /dev/null +++ b/src/modules/loa/loa.controller.ts @@ -0,0 +1,91 @@ +import { Request, Response } from 'express'; +import db from '../../database/models/index.js'; +const { LoaRequest, LoaApproval, LoaDocumentGenerated, SecurityDeposit, AuditLog } = db; +import { AuthRequest } from '../../types/express.types.js'; +import { AUDIT_ACTIONS } from '../../common/config/constants.js'; + +// --- LOA --- + +export const getRequest = async (req: Request, res: Response) => { + try { + const { applicationId } = req.params; + const request = await LoaRequest.findOne({ + where: { applicationId }, + include: [ + { model: LoaApproval, as: 'approvals' }, + { model: LoaDocumentGenerated, as: 'generatedDocuments' } + ] + }); + res.json({ success: true, data: request }); + } catch (error) { + console.error('Get LOA request error:', error); + res.status(500).json({ success: false, message: 'Error fetching LOA request' }); + } +}; + +export const createRequest = async (req: AuthRequest, res: Response) => { + try { + const { applicationId } = req.body; + + const request = await LoaRequest.create({ + applicationId, + requestedBy: req.user?.id, + status: 'Pending' + }); + + await LoaApproval.create({ + requestId: request.id, + level: 1, + approverRole: 'Zone Manager', // Example + action: 'Pending' + }); + + res.status(201).json({ success: true, message: 'LOA Request created', data: request }); + } catch (error) { + console.error('Create LOA request error:', error); + res.status(500).json({ success: false, message: 'Error creating LOA request' }); + } +}; + +// ... Similar approve/generate logic as LOI ... + +// --- Security Deposit --- + +export const updateSecurityDeposit = async (req: AuthRequest, res: Response) => { + try { + const { applicationId, amount, paymentReference, proofDocumentId, status } = req.body; + + let deposit = await SecurityDeposit.findOne({ where: { applicationId } }); + + if (deposit) { + await deposit.update({ + amount, paymentReference, proofDocumentId, status, + verifiedBy: status === 'Verified' ? req.user?.id : null, + verifiedAt: status === 'Verified' ? new Date() : null + }); + } else { + deposit = await SecurityDeposit.create({ + applicationId, + amount, + paymentReference, + proofDocumentId, + status: status || 'Pending' + }); + } + + res.json({ success: true, message: 'Security Deposit updated', data: deposit }); + } catch (error) { + console.error('Update Security Deposit error:', error); + res.status(500).json({ success: false, message: 'Error updating security deposit' }); + } +}; + +export const getSecurityDeposit = async (req: Request, res: Response) => { + try { + const { applicationId } = req.params; + const deposit = await SecurityDeposit.findOne({ where: { applicationId } }); + res.json({ success: true, data: deposit }); + } catch (error) { + res.status(500).json({ success: false, message: 'Error fetching security deposit' }); + } +} diff --git a/src/modules/loa/loa.routes.ts b/src/modules/loa/loa.routes.ts new file mode 100644 index 0000000..6c2e60d --- /dev/null +++ b/src/modules/loa/loa.routes.ts @@ -0,0 +1,13 @@ +import express from 'express'; +const router = express.Router(); +import * as loaController from './loa.controller.js'; +import { authenticate } from '../../common/middleware/auth.js'; + +router.use(authenticate as any); + +router.get('/request/:applicationId', loaController.getRequest); +router.post('/request', loaController.createRequest); +router.post('/security-deposit', loaController.updateSecurityDeposit); +router.get('/security-deposit/:applicationId', loaController.getSecurityDeposit); + +export default router; diff --git a/src/modules/loi/loi.controller.ts b/src/modules/loi/loi.controller.ts new file mode 100644 index 0000000..aedb7aa --- /dev/null +++ b/src/modules/loi/loi.controller.ts @@ -0,0 +1,91 @@ +import { Request, Response } from 'express'; +import db from '../../database/models/index.js'; +const { LoiRequest, LoiApproval, LoiDocumentGenerated, LoiAcknowledgement, AuditLog } = db; +import { AuthRequest } from '../../types/express.types.js'; +import { AUDIT_ACTIONS } from '../../common/config/constants.js'; + +export const getRequest = async (req: Request, res: Response) => { + try { + const { applicationId } = req.params; + const request = await LoiRequest.findOne({ + where: { applicationId }, + include: [ + { model: LoiApproval, as: 'approvals' }, + { model: LoiDocumentGenerated, as: 'generatedDocuments' } + ] + }); + res.json({ success: true, data: request }); + } catch (error) { + console.error('Get LOI request error:', error); + res.status(500).json({ success: false, message: 'Error fetching LOI request' }); + } +}; + +export const createRequest = async (req: AuthRequest, res: Response) => { + try { + const { applicationId } = req.body; + + const request = await LoiRequest.create({ + applicationId, + requestedBy: req.user?.id, + status: 'Pending' + }); + + // Create Initial Approvals? (Or triggered by Workflow engine) + // Manual for now: + await LoiApproval.create({ + requestId: request.id, + level: 1, + approverRole: 'Zone Manager', // Example + action: 'Pending' + }); + + res.status(201).json({ success: true, message: 'LOI Request created', data: request }); + } catch (error) { + console.error('Create LOI request error:', error); + res.status(500).json({ success: false, message: 'Error creating LOI request' }); + } +}; + +export const approveRequest = async (req: AuthRequest, res: Response) => { + try { + const { requestId } = req.params; + const { action, remarks } = req.body; // action: Approved/Rejected + + // Find pending approval for this user/role (Simplified logic) + const approval = await LoiApproval.findOne({ + where: { requestId, action: 'Pending' } // , level: 1 etc. + }); + + if (!approval) return res.status(404).json({ success: false, message: 'No pending approval found' }); + + await approval.update({ + action, + remarks, + approverId: req.user?.id + }); + + // If Approved, check if next level needed or finalize + if (action === 'Approved') { + await LoiRequest.update({ status: 'Approved', approvedBy: req.user?.id, approvedAt: new Date() }, { where: { id: requestId } }); + } else { + await LoiRequest.update({ status: 'Rejected' }, { where: { id: requestId } }); + } + + res.json({ success: true, message: 'LOI Request updated' }); + } catch (error) { + console.error('Approve LOI request error:', error); + res.status(500).json({ success: false, message: 'Error processing approval' }); + } +}; + +export const generateDocument = async (req: AuthRequest, res: Response) => { + try { + // Logic to generate PDF from Template + // Create Document record + // Create LoiDocumentGenerated record + res.json({ success: true, message: 'LOI Document generated (Placeholder)' }); + } catch (error) { + res.status(500).json({ success: false, message: 'Error generating document' }); + } +}; diff --git a/src/modules/loi/loi.routes.ts b/src/modules/loi/loi.routes.ts new file mode 100644 index 0000000..9d9340f --- /dev/null +++ b/src/modules/loi/loi.routes.ts @@ -0,0 +1,13 @@ +import express from 'express'; +const router = express.Router(); +import * as loiController from './loi.controller.js'; +import { authenticate } from '../../common/middleware/auth.js'; + +router.use(authenticate as any); + +router.get('/request/:applicationId', loiController.getRequest); +router.post('/request', loiController.createRequest); +router.post('/request/:requestId/approve', loiController.approveRequest); +router.post('/request/:requestId/generate', loiController.generateDocument); + +export default router; diff --git a/src/modules/master/master.controller.js b/src/modules/master/master.controller.js deleted file mode 100644 index fce3049..0000000 --- a/src/modules/master/master.controller.js +++ /dev/null @@ -1,121 +0,0 @@ -const { Region, Zone } = require('../../database/models'); - -exports.getRegions = async (req, res) => { - try { - const regions = await Region.findAll({ - order: [['name', 'ASC']] - }); - - res.json({ success: true, regions }); - } catch (error) { - console.error('Get regions error:', error); - res.status(500).json({ success: false, message: 'Error fetching regions' }); - } -}; - -exports.createRegion = async (req, res) => { - try { - const { regionName } = req.body; - - if (!regionName) { - return res.status(400).json({ success: false, message: 'Region name is required' }); - } - - await Region.create({ name: regionName }); - - res.status(201).json({ success: true, message: 'Region created successfully' }); - } catch (error) { - console.error('Create region error:', error); - res.status(500).json({ success: false, message: 'Error creating region' }); - } -}; - -exports.updateRegion = async (req, res) => { - try { - const { id } = req.params; - const { regionName, isActive } = req.body; - - const region = await Region.findByPk(id); - if (!region) { - return res.status(404).json({ success: false, message: 'Region not found' }); - } - - await region.update({ - name: regionName || region.name, - updatedAt: new Date() - }); - - res.json({ success: true, message: 'Region updated successfully' }); - } catch (error) { - console.error('Update region error:', error); - res.status(500).json({ success: false, message: 'Error updating region' }); - } -}; - -exports.getZones = async (req, res) => { - try { - const { regionId } = req.query; - - const where = {}; - if (regionId) { - where.regionId = regionId; - } - - const zones = await Zone.findAll({ - where, - include: [{ - model: Region, - as: 'region', - attributes: ['name'] - }], - order: [['name', 'ASC']] - }); - - res.json({ success: true, zones }); - } catch (error) { - console.error('Get zones error:', error); - res.status(500).json({ success: false, message: 'Error fetching zones' }); - } -}; - -exports.createZone = async (req, res) => { - try { - const { regionId, zoneName, zoneCode } = req.body; - - if (!regionId || !zoneName) { - return res.status(400).json({ success: false, message: 'Region ID and zone name are required' }); - } - - await Zone.create({ - regionId, - name: zoneName - }); - - res.status(201).json({ success: true, message: 'Zone created successfully' }); - } catch (error) { - console.error('Create zone error:', error); - res.status(500).json({ success: false, message: 'Error creating zone' }); - } -}; - -exports.updateZone = async (req, res) => { - try { - const { id } = req.params; - const { zoneName, zoneCode, isActive } = req.body; - - const zone = await Zone.findByPk(id); - if (!zone) { - return res.status(404).json({ success: false, message: 'Zone not found' }); - } - - await zone.update({ - name: zoneName || zone.name, - updatedAt: new Date() - }); - - res.json({ success: true, message: 'Zone updated successfully' }); - } catch (error) { - console.error('Update zone error:', error); - res.status(500).json({ success: false, message: 'Error updating zone' }); - } -}; diff --git a/src/modules/master/master.controller.ts b/src/modules/master/master.controller.ts new file mode 100644 index 0000000..187d9f3 --- /dev/null +++ b/src/modules/master/master.controller.ts @@ -0,0 +1,286 @@ +import { Request, Response } from 'express'; +import db from '../../database/models/index.js'; +const { Region, Zone, State, District, Area } = db; + +// --- Regions --- +export const getRegions = async (req: Request, res: Response) => { + try { + const regions = await Region.findAll({ + order: [['name', 'ASC']] + }); + + res.json({ success: true, regions }); + } catch (error) { + console.error('Get regions error:', error); + res.status(500).json({ success: false, message: 'Error fetching regions' }); + } +}; + +export const createRegion = async (req: Request, res: Response) => { + try { + const { regionName } = req.body; + + if (!regionName) { + return res.status(400).json({ success: false, message: 'Region name is required' }); + } + + const region = await Region.create({ name: regionName }); + + res.status(201).json({ success: true, message: 'Region created successfully', data: region }); + } catch (error) { + console.error('Create region error:', error); + res.status(500).json({ success: false, message: 'Error creating region' }); + } +}; + +export const updateRegion = async (req: Request, res: Response) => { + try { + const { id } = req.params; + const { regionName } = req.body; + + const region = await Region.findByPk(id); + if (!region) { + return res.status(404).json({ success: false, message: 'Region not found' }); + } + + await region.update({ + name: regionName || region.name, + updatedAt: new Date() + }); + + res.json({ success: true, message: 'Region updated successfully' }); + } catch (error) { + console.error('Update region error:', error); + res.status(500).json({ success: false, message: 'Error updating region' }); + } +}; + +// --- Zones --- +export const getZones = async (req: Request, res: Response) => { + try { + const { regionId } = req.query as { regionId?: string }; + + const where: any = {}; + if (regionId) { + where.regionId = regionId; + } + + const zones = await Zone.findAll({ + where, + include: [{ + model: Region, + as: 'region', + attributes: ['name'] + }], + order: [['name', 'ASC']] + }); + + res.json({ success: true, zones }); + } catch (error) { + console.error('Get zones error:', error); + res.status(500).json({ success: false, message: 'Error fetching zones' }); + } +}; + +export const createZone = async (req: Request, res: Response) => { + try { + const { regionId, zoneName } = req.body; + + if (!regionId || !zoneName) { + return res.status(400).json({ success: false, message: 'Region ID and zone name are required' }); + } + + const zone = await Zone.create({ + regionId, + name: zoneName + }); + + res.status(201).json({ success: true, message: 'Zone created successfully', data: zone }); + } catch (error) { + console.error('Create zone error:', error); + res.status(500).json({ success: false, message: 'Error creating zone' }); + } +}; + +export const updateZone = async (req: Request, res: Response) => { + try { + const { id } = req.params; + const { zoneName } = req.body; + + const zone = await Zone.findByPk(id); + if (!zone) { + return res.status(404).json({ success: false, message: 'Zone not found' }); + } + + await zone.update({ + name: zoneName || zone.name, + updatedAt: new Date() + }); + + res.json({ success: true, message: 'Zone updated successfully' }); + } catch (error) { + console.error('Update zone error:', error); + res.status(500).json({ success: false, message: 'Error updating zone' }); + } +}; + +// --- States --- +export const getStates = async (req: Request, res: Response) => { + try { + const { zoneId } = req.query as { zoneId?: string }; + const where: any = {}; + if (zoneId) where.zoneId = zoneId; + + const states = await State.findAll({ + where, + include: [{ model: Zone, as: 'zone', attributes: ['name'] }], + order: [['stateName', 'ASC']] + }); + res.json({ success: true, states }); + } catch (error) { + console.error('Get states error:', error); + res.status(500).json({ success: false, message: 'Error fetching states' }); + } +}; + +export const createState = async (req: Request, res: Response) => { + try { + const { zoneId, stateName } = req.body; + if (!zoneId || !stateName) return res.status(400).json({ success: false, message: 'Zone ID and state name required' }); + + const state = await State.create({ zoneId, stateName }); + res.status(201).json({ success: true, message: 'State created', data: state }); + } catch (error) { + console.error('Create state error:', error); + res.status(500).json({ success: false, message: 'Error creating state' }); + } +}; + +export const updateState = async (req: Request, res: Response) => { + try { + const { id } = req.params; + const { stateName, isActive } = req.body; + const state = await State.findByPk(id); + if (!state) return res.status(404).json({ success: false, message: 'State not found' }); + + await state.update({ stateName, isActive }); + res.json({ success: true, message: 'State updated' }); + } catch (error) { + console.error('Update state error:', error); + res.status(500).json({ success: false, message: 'Error updating state' }); + } +}; + +// --- Districts --- +export const getDistricts = async (req: Request, res: Response) => { + try { + const { stateId } = req.query as { stateId?: string }; + const where: any = {}; + if (stateId) where.stateId = stateId; + + const districts = await District.findAll({ + where, + include: [{ model: State, as: 'state', attributes: ['stateName'] }], + order: [['districtName', 'ASC']] + }); + res.json({ success: true, districts }); + } catch (error) { + console.error('Get districts error:', error); + res.status(500).json({ success: false, message: 'Error fetching districts' }); + } +}; + +export const createDistrict = async (req: Request, res: Response) => { + try { + const { stateId, districtName } = req.body; + if (!stateId || !districtName) return res.status(400).json({ success: false, message: 'State ID and district name required' }); + + const district = await District.create({ stateId, districtName }); + res.status(201).json({ success: true, message: 'District created', data: district }); + } catch (error) { + console.error('Create district error:', error); + res.status(500).json({ success: false, message: 'Error creating district' }); + } +}; + +export const updateDistrict = async (req: Request, res: Response) => { + try { + const { id } = req.params; + const { districtName, isActive } = req.body; + const district = await District.findByPk(id); + if (!district) return res.status(404).json({ success: false, message: 'District not found' }); + + await district.update({ districtName, isActive }); + res.json({ success: true, message: 'District updated' }); + } catch (error) { + console.error('Update district error:', error); + res.status(500).json({ success: false, message: 'Error updating district' }); + } +}; + +// --- Areas --- +export const getAreas = async (req: Request, res: Response) => { + try { + const { districtId } = req.query as { districtId?: string }; + const where: any = {}; + if (districtId) where.districtId = districtId; + + const areas = await Area.findAll({ + where, + include: [{ model: District, as: 'district', attributes: ['districtName'] }], + order: [['areaName', 'ASC']] + }); + res.json({ success: true, areas }); + } catch (error) { + console.error('Get areas error:', error); + res.status(500).json({ success: false, message: 'Error fetching areas' }); + } +}; + +export const createArea = async (req: Request, res: Response) => { + try { + const { districtId, areaCode, areaName, city, pincode } = req.body; + if (!districtId || !areaName || !pincode) return res.status(400).json({ success: false, message: 'District ID, area name, and pincode required' }); + + // Need to fetch regionId from district -> state -> zone -> region? + // Or user provides it? + // The Area model has regionId, districtId. + // It's safer to fetch relationships. + const district = await District.findByPk(districtId, { + include: [{ model: State, include: [{ model: Zone, include: [{ model: Region }] }] }] + }); + + let regionId = null; + if (district && district.state && district.state.zone && district.state.zone.region) { + regionId = district.state.zone.region.id; + } + + const area = await Area.create({ + districtId, + regionId, + areaCode, + areaName, + city, + pincode + }); + res.status(201).json({ success: true, message: 'Area created', data: area }); + } catch (error) { + console.error('Create area error:', error); + res.status(500).json({ success: false, message: 'Error creating area' }); + } +}; + +export const updateArea = async (req: Request, res: Response) => { + try { + const { id } = req.params; + const { areaName, city, pincode, isActive } = req.body; + const area = await Area.findByPk(id); + if (!area) return res.status(404).json({ success: false, message: 'Area not found' }); + + await area.update({ areaName, city, pincode, isActive }); + res.json({ success: true, message: 'Area updated' }); + } catch (error) { + console.error('Update area error:', error); + res.status(500).json({ success: false, message: 'Error updating area' }); + } +}; diff --git a/src/modules/master/master.routes.js b/src/modules/master/master.routes.js deleted file mode 100644 index 7d19a1b..0000000 --- a/src/modules/master/master.routes.js +++ /dev/null @@ -1,28 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const masterController = require('./master.controller'); -const outletController = require('./outlet.controller'); -const { authenticate } = require('../../common/middleware/auth'); -const { checkRole } = require('../../common/middleware/roleCheck'); -const { ROLES } = require('../../common/config/constants'); - -// All routes require authentication -router.use(authenticate); - -// Regions -router.get('/regions', masterController.getRegions); -router.post('/regions', checkRole([ROLES.SUPER_ADMIN, ROLES.DD_ADMIN, ROLES.DD_LEAD]), masterController.createRegion); -router.put('/regions/:id', checkRole([ROLES.SUPER_ADMIN, ROLES.DD_ADMIN]), masterController.updateRegion); - -// Zones -router.get('/zones', masterController.getZones); -router.post('/zones', checkRole([ROLES.SUPER_ADMIN, ROLES.DD_ADMIN, ROLES.DD_LEAD]), masterController.createZone); -router.put('/zones/:id', checkRole([ROLES.SUPER_ADMIN, ROLES.DD_ADMIN]), masterController.updateZone); - -// Outlets -router.get('/outlets', outletController.getOutlets); -router.get('/outlets/:id', outletController.getOutletById); -router.post('/outlets', checkRole([ROLES.SUPER_ADMIN, ROLES.DD_ADMIN]), outletController.createOutlet); -router.put('/outlets/:id', checkRole([ROLES.SUPER_ADMIN, ROLES.DD_ADMIN]), outletController.updateOutlet); - -module.exports = router; diff --git a/src/modules/master/master.routes.ts b/src/modules/master/master.routes.ts new file mode 100644 index 0000000..0713832 --- /dev/null +++ b/src/modules/master/master.routes.ts @@ -0,0 +1,43 @@ +import express from 'express'; +const router = express.Router(); +import * as masterController from './master.controller.js'; +import * as outletController from './outlet.controller.js'; +import { authenticate } from '../../common/middleware/auth.js'; +import { checkRole } from '../../common/middleware/roleCheck.js'; +import { ROLES } from '../../common/config/constants.js'; + +// All routes require authentication +router.use(authenticate as any); + +// Regions +router.get('/regions', masterController.getRegions); +router.post('/regions', checkRole([ROLES.SUPER_ADMIN, ROLES.DD_ADMIN, ROLES.DD_LEAD]) as any, masterController.createRegion); +router.put('/regions/:id', checkRole([ROLES.SUPER_ADMIN, ROLES.DD_ADMIN]) as any, masterController.updateRegion); + +// Zones +router.get('/zones', masterController.getZones); +router.post('/zones', checkRole([ROLES.SUPER_ADMIN, ROLES.DD_ADMIN, ROLES.DD_LEAD]) as any, masterController.createZone); +router.put('/zones/:id', checkRole([ROLES.SUPER_ADMIN, ROLES.DD_ADMIN]) as any, masterController.updateZone); + +// States +router.get('/states', masterController.getStates); +router.post('/states', checkRole([ROLES.SUPER_ADMIN, ROLES.DD_ADMIN]) as any, masterController.createState); +router.put('/states/:id', checkRole([ROLES.SUPER_ADMIN, ROLES.DD_ADMIN]) as any, masterController.updateState); + +// Districts +router.get('/districts', masterController.getDistricts); +router.post('/districts', checkRole([ROLES.SUPER_ADMIN, ROLES.DD_ADMIN]) as any, masterController.createDistrict); +router.put('/districts/:id', checkRole([ROLES.SUPER_ADMIN, ROLES.DD_ADMIN]) as any, masterController.updateDistrict); + +// Areas +router.get('/areas', masterController.getAreas); +router.post('/areas', checkRole([ROLES.SUPER_ADMIN, ROLES.DD_ADMIN]) as any, masterController.createArea); +router.put('/areas/:id', checkRole([ROLES.SUPER_ADMIN, ROLES.DD_ADMIN]) as any, masterController.updateArea); + +// Outlets +router.get('/outlets', outletController.getOutlets); +router.get('/outlets/:id', outletController.getOutletById); +router.post('/outlets', checkRole([ROLES.SUPER_ADMIN, ROLES.DD_ADMIN]) as any, outletController.createOutlet); +router.put('/outlets/:id', checkRole([ROLES.SUPER_ADMIN, ROLES.DD_ADMIN]) as any, outletController.updateOutlet); + +export default router; diff --git a/src/modules/master/outlet.controller.js b/src/modules/master/outlet.controller.ts similarity index 86% rename from src/modules/master/outlet.controller.js rename to src/modules/master/outlet.controller.ts index 1a52da2..fb27b16 100644 --- a/src/modules/master/outlet.controller.js +++ b/src/modules/master/outlet.controller.ts @@ -1,11 +1,14 @@ -const { Outlet, User, Resignation } = require('../../database/models'); -const { Op } = require('sequelize'); +import { Response } from 'express'; +import { Op } from 'sequelize'; +import db from '../../database/models/index.js'; +const { Outlet, User, Resignation } = db; +import { AuthRequest } from '../../types/express.types.js'; // Get all outlets for logged-in dealer -exports.getOutlets = async (req, res) => { +export const getOutlets = async (req: AuthRequest, res: Response) => { try { - const where = {}; - if (req.user.role === 'Dealer') { + const where: any = {}; + if (req.user?.role === 'Dealer') { where.dealerId = req.user.id; } @@ -43,7 +46,7 @@ exports.getOutlets = async (req, res) => { }; // Get specific outlet details -exports.getOutletById = async (req, res) => { +export const getOutletById = async (req: AuthRequest, res: Response) => { try { const { id } = req.params; @@ -63,7 +66,7 @@ exports.getOutletById = async (req, res) => { } // Check if dealer can access this outlet - if (req.user.role === 'Dealer' && outlet.dealerId !== req.user.id) { + if (req.user?.role === 'Dealer' && outlet.dealerId !== req.user.id) { return res.status(403).json({ success: false, message: 'Access denied' @@ -84,7 +87,7 @@ exports.getOutletById = async (req, res) => { }; // Create new outlet (admin only) -exports.createOutlet = async (req, res) => { +export const createOutlet = async (req: AuthRequest, res: Response) => { try { const { dealerId, @@ -128,7 +131,8 @@ exports.createOutlet = async (req, res) => { pincode, establishedDate, latitude, - longitude + longitude, + status: 'Active' }); res.status(201).json({ @@ -146,7 +150,7 @@ exports.createOutlet = async (req, res) => { }; // Update outlet -exports.updateOutlet = async (req, res) => { +export const updateOutlet = async (req: AuthRequest, res: Response) => { try { const { id } = req.params; const { name, address, city, state, pincode, status, latitude, longitude } = req.body; diff --git a/src/modules/master/outlet.routes.js b/src/modules/master/outlet.routes.js deleted file mode 100644 index a49c791..0000000 --- a/src/modules/master/outlet.routes.js +++ /dev/null @@ -1,16 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const outletController = require('./outlet.controller'); -const { authenticate } = require('../../common/middleware/auth'); -const { checkRole } = require('../../common/middleware/roleCheck'); -const { ROLES } = require('../../common/config/constants'); - -// All routes require authentication -router.use(authenticate); - -router.get('/', outletController.getOutlets); -router.get('/:id', outletController.getOutletById); -router.post('/', checkRole([ROLES.SUPER_ADMIN, ROLES.DD_ADMIN]), outletController.createOutlet); -router.put('/:id', checkRole([ROLES.SUPER_ADMIN, ROLES.DD_ADMIN]), outletController.updateOutlet); - -module.exports = router; diff --git a/src/modules/master/outlet.routes.ts b/src/modules/master/outlet.routes.ts new file mode 100644 index 0000000..ad50d21 --- /dev/null +++ b/src/modules/master/outlet.routes.ts @@ -0,0 +1,16 @@ +import express from 'express'; +const router = express.Router(); +import * as outletController from './outlet.controller.js'; +import { authenticate } from '../../common/middleware/auth.js'; +import { checkRole } from '../../common/middleware/roleCheck.js'; +import { ROLES } from '../../common/config/constants.js'; + +// All routes require authentication +router.use(authenticate as any); + +router.get('/', outletController.getOutlets); +router.get('/:id', outletController.getOutletById); +router.post('/', checkRole([ROLES.SUPER_ADMIN, ROLES.DD_ADMIN]) as any, outletController.createOutlet); +router.put('/:id', checkRole([ROLES.SUPER_ADMIN, ROLES.DD_ADMIN]) as any, outletController.updateOutlet); + +export default router; diff --git a/src/modules/onboarding/onboarding.controller.js b/src/modules/onboarding/onboarding.controller.js deleted file mode 100644 index 52d811f..0000000 --- a/src/modules/onboarding/onboarding.controller.js +++ /dev/null @@ -1,136 +0,0 @@ -const { Application } = require('../../database/models'); -const { v4: uuidv4 } = require('uuid'); -const { Op } = require('sequelize'); - -exports.submitApplication = async (req, res) => { - try { - const { - applicantName, email, phone, businessType, locationType, - preferredLocation, city, state, experienceYears, investmentCapacity - } = req.body; - - const applicationId = `APP-${new Date().getFullYear()}-${uuidv4().substring(0, 6).toUpperCase()}`; - - const application = await Application.create({ - applicationId, - applicantName, - email, - phone, - businessType, - preferredLocation, - city, - state, - experienceYears, - investmentCapacity, - currentStage: 'DD', - overallStatus: 'Pending', - progressPercentage: 0 - }); - - res.status(201).json({ - success: true, - message: 'Application submitted successfully', - applicationId: application.applicationId - }); - } catch (error) { - console.error('Submit application error:', error); - res.status(500).json({ success: false, message: 'Error submitting application' }); - } -}; - -exports.getApplications = async (req, res) => { - try { - const applications = await Application.findAll({ - order: [['createdAt', 'DESC']] - }); - - res.json({ success: true, applications }); - } catch (error) { - console.error('Get applications error:', error); - res.status(500).json({ success: false, message: 'Error fetching applications' }); - } -}; - -exports.getApplicationById = async (req, res) => { - try { - const { id } = req.params; - - const application = await Application.findOne({ - where: { - [Op.or]: [ - { id }, - { applicationId: id } - ] - } - }); - - if (!application) { - return res.status(404).json({ success: false, message: 'Application not found' }); - } - - res.json({ success: true, application }); - } catch (error) { - console.error('Get application error:', error); - res.status(500).json({ success: false, message: 'Error fetching application' }); - } -}; - -exports.takeAction = async (req, res) => { - try { - const { id } = req.params; - const { action, comments, rating } = req.body; - - const application = await Application.findOne({ - where: { - [Op.or]: [ - { id }, - { applicationId: id } - ] - } - }); - - if (!application) { - return res.status(404).json({ success: false, message: 'Application not found' }); - } - - await application.update({ - overallStatus: action, - updatedAt: new Date() - }); - - res.json({ success: true, message: 'Action taken successfully' }); - } catch (error) { - console.error('Take action error:', error); - res.status(500).json({ success: false, message: 'Error processing action' }); - } -}; - -exports.uploadDocuments = async (req, res) => { - try { - const { id } = req.params; - const { documents } = req.body; - - const application = await Application.findOne({ - where: { - [Op.or]: [ - { id }, - { applicationId: id } - ] - } - }); - - if (!application) { - return res.status(404).json({ success: false, message: 'Application not found' }); - } - - await application.update({ - documents: documents, - updatedAt: new Date() - }); - - res.json({ success: true, message: 'Documents uploaded successfully' }); - } catch (error) { - console.error('Upload documents error:', error); - res.status(500).json({ success: false, message: 'Error uploading documents' }); - } -}; diff --git a/src/modules/onboarding/onboarding.controller.ts b/src/modules/onboarding/onboarding.controller.ts new file mode 100644 index 0000000..4f69bb0 --- /dev/null +++ b/src/modules/onboarding/onboarding.controller.ts @@ -0,0 +1,182 @@ +import { Request, Response } from 'express'; +import db from '../../database/models/index.js'; +const { Application, Opportunity, ApplicationStatusHistory, ApplicationProgress, AuditLog } = db; +import { AUDIT_ACTIONS } from '../../common/config/constants.js'; +import { v4 as uuidv4 } from 'uuid'; +import { Op } from 'sequelize'; +import { AuthRequest } from '../../types/express.types.js'; + +export const submitApplication = async (req: AuthRequest, res: Response) => { + try { + const { + opportunityId, + applicantName, email, phone, businessType, locationType, + preferredLocation, city, state, experienceYears, investmentCapacity + } = req.body; + + // Check for duplicate email + const existingApp = await Application.findOne({ where: { email } }); + if (existingApp) { + return res.status(400).json({ success: false, message: 'Application with this email already exists' }); + } + + const applicationId = `APP-${new Date().getFullYear()}-${uuidv4().substring(0, 6).toUpperCase()}`; + + // Fetch hierarchy from Opportunity if available + let zoneId, regionId, areaId; + if (opportunityId) { + const opportunity = await Opportunity.findByPk(opportunityId); + if (opportunity) { + zoneId = opportunity.zoneId; + regionId = opportunity.regionId; + // areaId might need manual assignment or derived + } + } + + const application = await Application.create({ + opportunityId, + applicationId, + applicantName, + email, + phone, + businessType, + preferredLocation, + city, + state, + experienceYears, + investmentCapacity, + currentStage: 'Application', + overallStatus: 'New', + progressPercentage: 10, + zoneId, + regionId + }); + + // Log Status History + await ApplicationStatusHistory.create({ + applicationId: application.id, + previousStatus: null, + newStatus: 'New', + changedBy: req.user?.id, + reason: 'Initial Submission' + }); + + // Initialize Progress (Optional, or created as needed per stage) + await ApplicationProgress.create({ + applicationId: application.id, + stageName: 'Application', + status: 'Completed', + completionPercentage: 100 + }); + + await AuditLog.create({ + userId: req.user?.id, + action: AUDIT_ACTIONS.CREATED, + entityType: 'application', + entityId: application.id + }); + + res.status(201).json({ + success: true, + message: 'Application submitted successfully', + data: application + }); + } catch (error) { + console.error('Submit application error:', error); + res.status(500).json({ success: false, message: 'Error submitting application' }); + } +}; + +export const getApplications = async (req: Request, res: Response) => { + try { + // Add filtering logic here similar to Opportunity + const applications = await Application.findAll({ + include: [{ model: Opportunity, as: 'opportunity', attributes: ['leadName', 'id'] }], + order: [['createdAt', 'DESC']] + }); + + res.json({ success: true, data: applications }); + } catch (error) { + console.error('Get applications error:', error); + res.status(500).json({ success: false, message: 'Error fetching applications' }); + } +}; + +export const getApplicationById = async (req: Request, res: Response) => { + try { + const { id } = req.params; + + const application = await Application.findOne({ + where: { + [Op.or]: [ + { id }, + { applicationId: id } + ] + }, + include: [ + { model: ApplicationStatusHistory, as: 'statusHistory' }, + { model: ApplicationProgress, as: 'progress' } + ] + }); + + if (!application) { + return res.status(404).json({ success: false, message: 'Application not found' }); + } + + res.json({ success: true, data: application }); + } catch (error) { + console.error('Get application error:', error); + res.status(500).json({ success: false, message: 'Error fetching application' }); + } +}; + +export const updateApplicationStatus = async (req: AuthRequest, res: Response) => { + try { + const { id } = req.params; + const { status, stage, reason } = req.body; + + const application = await Application.findByPk(id); + if (!application) return res.status(404).json({ success: false, message: 'Application not found' }); + + const previousStatus = application.overallStatus; + + await application.update({ + overallStatus: status, + currentStage: stage || application.currentStage, + updatedAt: new Date() + }); + + // Log Status History + await ApplicationStatusHistory.create({ + applicationId: application.id, + previousStatus, + newStatus: status, + changedBy: req.user?.id, + reason + }); + + await AuditLog.create({ + userId: req.user?.id, + action: AUDIT_ACTIONS.UPDATED, + entityType: 'application', + entityId: application.id, + newData: { status, stage } + }); + + res.json({ success: true, message: 'Application status updated successfully' }); + } catch (error) { + console.error('Update application status error:', error); + res.status(500).json({ success: false, message: 'Error updating application status' }); + } +}; + +export const uploadDocuments = async (req: Request, res: Response) => { + // Existing logic or enhanced to use Document model + // For now, keeping simple or stubbing. + try { + // This should likely use the new Document modules/models later + res.json({ success: true, message: 'Use Document module for uploads' }); + } catch (error) { + res.status(500).json({ success: false, message: 'Error' }); + } +}; diff --git a/src/modules/onboarding/onboarding.routes.js b/src/modules/onboarding/onboarding.routes.js deleted file mode 100644 index 483f80c..0000000 --- a/src/modules/onboarding/onboarding.routes.js +++ /dev/null @@ -1,15 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const onboardingController = require('./onboarding.controller'); -const { authenticate } = require('../../common/middleware/auth'); - -// Public route - submit application -router.post('/', onboardingController.submitApplication); - -// Protected routes -router.get('/', authenticate, onboardingController.getApplications); -router.get('/:id', authenticate, onboardingController.getApplicationById); -router.put('/:id/action', authenticate, onboardingController.takeAction); -router.post('/:id/documents', authenticate, onboardingController.uploadDocuments); - -module.exports = router; diff --git a/src/modules/onboarding/onboarding.routes.ts b/src/modules/onboarding/onboarding.routes.ts new file mode 100644 index 0000000..6b93fe9 --- /dev/null +++ b/src/modules/onboarding/onboarding.routes.ts @@ -0,0 +1,15 @@ +import express from 'express'; +const router = express.Router(); +import * as onboardingController from './onboarding.controller.js'; +import { authenticate } from '../../common/middleware/auth.js'; + +// All routes require authentication (or public for submission? Keeping auth for now) +router.use(authenticate as any); + +router.post('/apply', onboardingController.submitApplication); +router.get('/applications', onboardingController.getApplications); +router.get('/applications/:id', onboardingController.getApplicationById); +router.put('/applications/:id/status', onboardingController.updateApplicationStatus); +// router.post('/applications/:id/documents', onboardingController.uploadDocuments); // Moving to DMS module + +export default router; diff --git a/src/modules/opportunity/opportunity.controller.ts b/src/modules/opportunity/opportunity.controller.ts new file mode 100644 index 0000000..922f9dc --- /dev/null +++ b/src/modules/opportunity/opportunity.controller.ts @@ -0,0 +1,109 @@ +import { Request, Response } from 'express'; +import db from '../../database/models/index.js'; +const { Opportunity, Application, User, AuditLog } = db; +import { Op } from 'sequelize'; +import { AuthRequest } from '../../types/express.types.js'; +import { AUDIT_ACTIONS } from '../../common/config/constants.js'; + +export const getOpportunities = async (req: Request, res: Response) => { + try { + const { status, regionId, zoneId } = req.query as any; + const where: any = {}; + + if (status) where.status = status; + if (regionId) where.regionId = regionId; + if (zoneId) where.zoneId = zoneId; + + const opportunities = await Opportunity.findAll({ + where, + include: [ + { model: Application, as: 'application', attributes: ['id', 'applicationId', 'overallStatus'] } + ], + order: [['createdAt', 'DESC']] + }); + + res.json({ success: true, data: opportunities }); + } catch (error) { + console.error('Get opportunities error:', error); + res.status(500).json({ success: false, message: 'Error fetching opportunities' }); + } +}; + +export const createOpportunity = async (req: AuthRequest, res: Response) => { + try { + const { + leadSource, leadName, contactNumber, email, + zoneId, regionId, stateId, districtId, + opportunityType, priority + } = req.body; + + const opportunity = await Opportunity.create({ + leadSource, + leadName, + contactNumber, + email, + zoneId, + regionId, + stateId, + districtId, + opportunityType, + priority, + status: 'New', + assignedTo: req.user?.id + }); + + await AuditLog.create({ + userId: req.user?.id, + action: AUDIT_ACTIONS.CREATED, + entityType: 'opportunity', + entityId: opportunity.id + }); + + res.status(201).json({ success: true, data: opportunity, message: 'Opportunity created successfully' }); + } catch (error) { + console.error('Create opportunity error:', error); + res.status(500).json({ success: false, message: 'Error creating opportunity' }); + } +}; + +export const updateOpportunity = async (req: AuthRequest, res: Response) => { + try { + const { id } = req.params; + const updateData = req.body; + + const opportunity = await Opportunity.findByPk(id); + if (!opportunity) return res.status(404).json({ success: false, message: 'Opportunity not found' }); + + await opportunity.update(updateData); + + res.json({ success: true, message: 'Opportunity updated successfully' }); + } catch (error) { + console.error('Update opportunity error:', error); + res.status(500).json({ success: false, message: 'Error updating opportunity' }); + } +}; + +export const convertToApplication = async (req: AuthRequest, res: Response) => { + try { + const { id } = req.params; // Opportunity ID + + const opportunity = await Opportunity.findByPk(id); + if (!opportunity) return res.status(404).json({ success: false, message: 'Opportunity not found' }); + + if (opportunity.status === 'Converted') { + return res.status(400).json({ success: false, message: 'Opportunity already converted' }); + } + + // Logic to create Application skeleton or return data for frontend to pre-fill + // For API, we might just mark Op as converted if Application creation happens separately, + // OR we create the Application here explicitly. + // Let's assume this action marks it ready and maybe creates a draft Application. + + await opportunity.update({ status: 'Converted' }); + + res.json({ success: true, message: 'Opportunity marked as converted' }); + } catch (error) { + console.error('Convert opportunity error:', error); + res.status(500).json({ success: false, message: 'Error converting opportunity' }); + } +}; diff --git a/src/modules/opportunity/opportunity.routes.ts b/src/modules/opportunity/opportunity.routes.ts new file mode 100644 index 0000000..252f90f --- /dev/null +++ b/src/modules/opportunity/opportunity.routes.ts @@ -0,0 +1,14 @@ +import express from 'express'; +const router = express.Router(); +import * as opportunityController from './opportunity.controller.js'; +import { authenticate } from '../../common/middleware/auth.js'; + +// All routes require authentication +router.use(authenticate as any); + +router.get('/', opportunityController.getOpportunities); +router.post('/', opportunityController.createOpportunity); +router.put('/:id', opportunityController.updateOpportunity); +router.post('/:id/convert', opportunityController.convertToApplication); + +export default router; diff --git a/src/modules/self-service/constitutional.controller.js b/src/modules/self-service/constitutional.controller.ts similarity index 79% rename from src/modules/self-service/constitutional.controller.js rename to src/modules/self-service/constitutional.controller.ts index edd9349..5173272 100644 --- a/src/modules/self-service/constitutional.controller.js +++ b/src/modules/self-service/constitutional.controller.ts @@ -1,12 +1,17 @@ -const { ConstitutionalChange, Outlet, User, Worknote } = require('../../database/models'); -const { v4: uuidv4 } = require('uuid'); -const { Op } = require('sequelize'); +import { Response } from 'express'; +import db from '../../database/models/index.js'; +const { ConstitutionalChange, Outlet, User, Worknote } = db; +import { AUDIT_ACTIONS, ROLES } from '../../common/config/constants.js'; +import { Op, Transaction } from 'sequelize'; +import { v4 as uuidv4 } from 'uuid'; +import { AuthRequest } from '../../types/express.types.js'; -exports.submitRequest = async (req, res) => { +export const submitRequest = async (req: AuthRequest, res: Response) => { try { + if (!req.user) return res.status(401).json({ success: false, message: 'Unauthorized' }); + const { - outletId, changeType, currentConstitution, proposedConstitution, - reason, effectiveDate, newEntityDetails + outletId, changeType, reason, newEntityDetails } = req.body; const requestId = `CC-${Date.now()}-${uuidv4().substring(0, 4).toUpperCase()}`; @@ -17,13 +22,10 @@ exports.submitRequest = async (req, res) => { dealerId: req.user.id, changeType, description: reason, - currentConstitution, - proposedConstitution, - effectiveDate, - newEntityDetails: JSON.stringify(newEntityDetails), - currentStage: 'RBM Review', + currentStage: 'DD_ADMIN_REVIEW' as any, status: 'Pending', progressPercentage: 0, + documents: [], timeline: [{ stage: 'Submitted', timestamp: new Date(), @@ -43,9 +45,11 @@ exports.submitRequest = async (req, res) => { } }; -exports.getRequests = async (req, res) => { +export const getRequests = async (req: AuthRequest, res: Response) => { try { - const where = {}; + if (!req.user) return res.status(401).json({ success: false, message: 'Unauthorized' }); + + const where: any = {}; if (req.user.role === 'Dealer') { where.dealerId = req.user.id; } @@ -74,7 +78,7 @@ exports.getRequests = async (req, res) => { } }; -exports.getRequestById = async (req, res) => { +export const getRequestById = async (req: AuthRequest, res: Response) => { try { const { id } = req.params; @@ -113,8 +117,10 @@ exports.getRequestById = async (req, res) => { } }; -exports.takeAction = async (req, res) => { +export const takeAction = async (req: AuthRequest, res: Response) => { try { + if (!req.user) return res.status(401).json({ success: false, message: 'Unauthorized' }); + const { id } = req.params; const { action, comments } = req.body; @@ -152,7 +158,7 @@ exports.takeAction = async (req, res) => { } }; -exports.uploadDocuments = async (req, res) => { +export const uploadDocuments = async (req: AuthRequest, res: Response) => { try { const { id } = req.params; const { documents } = req.body; diff --git a/src/modules/self-service/relocation.controller.js b/src/modules/self-service/relocation.controller.ts similarity index 74% rename from src/modules/self-service/relocation.controller.js rename to src/modules/self-service/relocation.controller.ts index 522a3b5..90e01ac 100644 --- a/src/modules/self-service/relocation.controller.js +++ b/src/modules/self-service/relocation.controller.ts @@ -1,37 +1,36 @@ -const { RelocationRequest, Outlet, User, Worknote } = require('../../database/models'); -const { Op } = require('sequelize'); -const { v4: uuidv4 } = require('uuid'); +import { Response } from 'express'; +import db from '../../database/models/index.js'; +const { RelocationRequest, Outlet, User, Worknote } = db; +import { AUDIT_ACTIONS, ROLES } from '../../common/config/constants.js'; +import { Op, Transaction } from 'sequelize'; +import { v4 as uuidv4 } from 'uuid'; +import { AuthRequest } from '../../types/express.types.js'; -exports.submitRequest = async (req, res) => { +export const submitRequest = async (req: AuthRequest, res: Response) => { try { + if (!req.user) return res.status(401).json({ success: false, message: 'Unauthorized' }); + const { outletId, relocationType, currentAddress, currentCity, currentState, currentLatitude, currentLongitude, proposedAddress, proposedCity, proposedState, proposedLatitude, proposedLongitude, reason, proposedDate } = req.body; - const requestId = `REL-${Date.now()}-${uuidv4().substring(0, 4).toUpperCase()}`; + const requestId = `REL - ${Date.now()} -${uuidv4().substring(0, 4).toUpperCase()} `; const request = await RelocationRequest.create({ requestId, outletId, dealerId: req.user.id, relocationType, - currentAddress, - currentCity, - currentState, - currentLatitude, - currentLongitude, - proposedAddress, - proposedCity, - proposedState, - proposedLatitude, - proposedLongitude, + newAddress: proposedAddress, + newCity: proposedCity, + newState: proposedState, reason, - proposedDate, - currentStage: 'RBM Review', + currentStage: 'DD_ADMIN_REVIEW' as any, status: 'Pending', progressPercentage: 0, + documents: [], timeline: [{ stage: 'Submitted', timestamp: new Date(), @@ -51,9 +50,11 @@ exports.submitRequest = async (req, res) => { } }; -exports.getRequests = async (req, res) => { +export const getRequests = async (req: AuthRequest, res: Response) => { try { - const where = {}; + if (!req.user) return res.status(401).json({ success: false, message: 'Unauthorized' }); + + const where: any = {}; if (req.user.role === 'Dealer') { where.dealerId = req.user.id; } @@ -82,7 +83,7 @@ exports.getRequests = async (req, res) => { } }; -exports.getRequestById = async (req, res) => { +export const getRequestById = async (req: AuthRequest, res: Response) => { try { const { id } = req.params; @@ -106,11 +107,6 @@ exports.getRequestById = async (req, res) => { { model: Worknote, as: 'worknotes', - include: [{ - model: User, - as: 'actionedBy', - attributes: ['name'] - }], order: [['createdAt', 'ASC']] } ] @@ -120,15 +116,6 @@ exports.getRequestById = async (req, res) => { return res.status(404).json({ success: false, message: 'Request not found' }); } - // Calculate distance between current and proposed location - if (request.currentLatitude && request.proposedLatitude) { - const distance = calculateDistance( - request.currentLatitude, request.currentLongitude, - request.proposedLatitude, request.proposedLongitude - ); - request.dataValues.distance = `${distance.toFixed(2)} km`; - } - res.json({ success: true, request }); } catch (error) { console.error('Get relocation details error:', error); @@ -136,8 +123,10 @@ exports.getRequestById = async (req, res) => { } }; -exports.takeAction = async (req, res) => { +export const takeAction = async (req: AuthRequest, res: Response) => { try { + if (!req.user) return res.status(401).json({ success: false, message: 'Unauthorized' }); + const { id } = req.params; const { action, comments } = req.body; @@ -162,22 +151,15 @@ exports.takeAction = async (req, res) => { newStatus = 'Approved'; } else if (action === 'Rejected') { newStatus = 'Rejected'; - } else if (action === 'Forwarded to RBM') { - newCurrentStage = 'RBM Review'; - } else if (action === 'Forwarded to ZBM') { - newCurrentStage = 'ZBM Review'; - } else if (action === 'Forwarded to HO') { - newCurrentStage = 'HO Review'; } // Create a worknote entry await Worknote.create({ requestId: request.id, - stage: newCurrentStage, - action: action, - comments: comments, - actionedBy: req.user.id, - actionedAt: new Date() + requestType: 'relocation' as any, + userId: req.user.id, + content: comments, + isInternal: true }); // Update the request status and current stage @@ -194,7 +176,7 @@ exports.takeAction = async (req, res) => { } }; -exports.uploadDocuments = async (req, res) => { +export const uploadDocuments = async (req: AuthRequest, res: Response) => { try { const { id } = req.params; const { documents } = req.body; @@ -225,7 +207,7 @@ exports.uploadDocuments = async (req, res) => { }; // Helper function to calculate distance between two coordinates -function calculateDistance(lat1, lon1, lat2, lon2) { +function calculateDistance(lat1: number, lon1: number, lat2: number, lon2: number): number { const R = 6371; // Radius of Earth in km const dLat = (lat2 - lat1) * Math.PI / 180; const dLon = (lon2 - lon1) * Math.PI / 180; diff --git a/src/modules/self-service/resignation.controller.js b/src/modules/self-service/resignation.controller.ts similarity index 81% rename from src/modules/self-service/resignation.controller.js rename to src/modules/self-service/resignation.controller.ts index 77067ff..32288ea 100644 --- a/src/modules/self-service/resignation.controller.js +++ b/src/modules/self-service/resignation.controller.ts @@ -1,17 +1,19 @@ -const db = require('../../database/models'); -const logger = require('../../common/utils/logger'); -const { RESIGNATION_STAGES, AUDIT_ACTIONS, ROLES } = require('../../common/config/constants'); -const { Op } = require('sequelize'); +import { Response, NextFunction } from 'express'; +import db from '../../database/models/index.js'; +import logger from '../../common/utils/logger.js'; +import { RESIGNATION_STAGES, AUDIT_ACTIONS, ROLES } from '../../common/config/constants.js'; +import { Op, Transaction } from 'sequelize'; +import { AuthRequest } from '../../types/express.types.js'; // Generate unique resignation ID -const generateResignationId = async () => { +const generateResignationId = async (): Promise => { const count = await db.Resignation.count(); return `RES-${String(count + 1).padStart(3, '0')}`; }; // Calculate progress percentage based on stage -const calculateProgress = (stage) => { - const stageProgress = { +const calculateProgress = (stage: string): number => { + const stageProgress: Record = { [RESIGNATION_STAGES.ASM]: 15, [RESIGNATION_STAGES.RBM]: 30, [RESIGNATION_STAGES.ZBH]: 45, @@ -27,10 +29,12 @@ const calculateProgress = (stage) => { }; // Create resignation request (Dealer only) -exports.createResignation = async (req, res, next) => { - const transaction = await db.sequelize.transaction(); +export const createResignation = async (req: AuthRequest, res: Response, next: NextFunction) => { + const transaction: Transaction = await db.sequelize.transaction(); try { + if (!req.user) throw new Error('Unauthorized'); + const { outletId, resignationType, lastOperationalDateSales, lastOperationalDateServices, reason, additionalInfo } = req.body; const dealerId = req.user.id; @@ -79,6 +83,8 @@ exports.createResignation = async (req, res, next) => { currentStage: RESIGNATION_STAGES.ASM, status: 'ASM Review', progressPercentage: 15, + submittedOn: new Date(), + documents: [], timeline: [{ stage: 'Submitted', timestamp: new Date(), @@ -95,12 +101,9 @@ exports.createResignation = async (req, res, next) => { // Create audit log await db.AuditLog.create({ userId: req.user.id, - userName: req.user.name, - userRole: req.user.role, action: AUDIT_ACTIONS.CREATED, entityType: 'resignation', - entityId: resignation.id, - changes: { created: resignation.toJSON() } + entityId: resignation.id }, { transaction }); await transaction.commit(); @@ -122,20 +125,17 @@ exports.createResignation = async (req, res, next) => { }; // Get resignations list (role-based filtering) -exports.getResignations = async (req, res, next) => { +export const getResignations = async (req: AuthRequest, res: Response, next: NextFunction) => { try { - const { status, region, zone, page = 1, limit = 10 } = req.query; - const offset = (page - 1) * limit; + if (!req.user) throw new Error('Unauthorized'); + const { status, page = '1', limit = '10' } = req.query as { status?: string, page?: string, limit?: string }; + const offset = (parseInt(page) - 1) * parseInt(limit); // Build where clause based on user role - let where = {}; + let where: any = {}; if (req.user.role === ROLES.DEALER) { - // Dealers see only their resignations where.dealerId = req.user.id; - } else if (req.user.region && ![ROLES.NBH, ROLES.DD_HEAD, ROLES.DD_LEAD, ROLES.SUPER_ADMIN].includes(req.user.role)) { - // Regional users see resignations in their region - where['$outlet.region$'] = req.user.region; } if (status) { @@ -148,8 +148,7 @@ exports.getResignations = async (req, res, next) => { include: [ { model: db.Outlet, - as: 'outlet', - attributes: ['id', 'code', 'name', 'type', 'city', 'state', 'region', 'zone'] + as: 'outlet' }, { model: db.User, @@ -159,7 +158,7 @@ exports.getResignations = async (req, res, next) => { ], order: [['createdAt', 'DESC']], limit: parseInt(limit), - offset: parseInt(offset) + offset: offset }); res.json({ @@ -168,7 +167,7 @@ exports.getResignations = async (req, res, next) => { pagination: { total: count, page: parseInt(page), - pages: Math.ceil(count / limit), + pages: Math.ceil(count / parseInt(limit)), limit: parseInt(limit) } }); @@ -180,8 +179,9 @@ exports.getResignations = async (req, res, next) => { }; // Get resignation details -exports.getResignationById = async (req, res, next) => { +export const getResignationById = async (req: AuthRequest, res: Response, next: NextFunction) => { try { + if (!req.user) throw new Error('Unauthorized'); const { id } = req.params; const resignation = await db.Resignation.findOne({ @@ -200,10 +200,10 @@ exports.getResignationById = async (req, res, next) => { }, { model: db.Worknote, - as: 'worknotes', - order: [['createdAt', 'DESC']] + as: 'worknotes' } - ] + ], + order: [[{ model: db.Worknote, as: 'worknotes' }, 'createdAt', 'DESC']] }); if (!resignation) { @@ -233,10 +233,11 @@ exports.getResignationById = async (req, res, next) => { }; // Approve resignation (move to next stage) -exports.approveResignation = async (req, res, next) => { - const transaction = await db.sequelize.transaction(); +export const approveResignation = async (req: AuthRequest, res: Response, next: NextFunction) => { + const transaction: Transaction = await db.sequelize.transaction(); try { + if (!req.user) throw new Error('Unauthorized'); const { id } = req.params; const { remarks } = req.body; @@ -253,7 +254,7 @@ exports.approveResignation = async (req, res, next) => { } // Determine next stage based on current stage - const stageFlow = { + const stageFlow: Record = { [RESIGNATION_STAGES.ASM]: RESIGNATION_STAGES.RBM, [RESIGNATION_STAGES.RBM]: RESIGNATION_STAGES.ZBH, [RESIGNATION_STAGES.ZBH]: RESIGNATION_STAGES.NBH, @@ -284,7 +285,7 @@ exports.approveResignation = async (req, res, next) => { }]; await resignation.update({ - currentStage: nextStage, + currentStage: nextStage as any, status: nextStage === RESIGNATION_STAGES.COMPLETED ? 'Completed' : `${nextStage} Review`, progressPercentage: calculateProgress(nextStage), timeline @@ -292,7 +293,7 @@ exports.approveResignation = async (req, res, next) => { // If completed, update outlet status if (nextStage === RESIGNATION_STAGES.COMPLETED) { - await resignation.outlet.update({ + await (resignation as any).outlet.update({ status: 'Closed' }, { transaction }); } @@ -300,16 +301,9 @@ exports.approveResignation = async (req, res, next) => { // Create audit log await db.AuditLog.create({ userId: req.user.id, - userName: req.user.name, - userRole: req.user.role, action: AUDIT_ACTIONS.APPROVED, entityType: 'resignation', - entityId: resignation.id, - changes: { - from: resignation.currentStage, - to: nextStage, - remarks - } + entityId: resignation.id }, { transaction }); await transaction.commit(); @@ -331,10 +325,11 @@ exports.approveResignation = async (req, res, next) => { }; // Reject resignation -exports.rejectResignation = async (req, res, next) => { - const transaction = await db.sequelize.transaction(); +export const rejectResignation = async (req: AuthRequest, res: Response, next: NextFunction) => { + const transaction: Transaction = await db.sequelize.transaction(); try { + if (!req.user) throw new Error('Unauthorized'); const { id } = req.params; const { reason } = req.body; @@ -376,19 +371,16 @@ exports.rejectResignation = async (req, res, next) => { }, { transaction }); // Update outlet status back to Active - await resignation.outlet.update({ + await (resignation as any).outlet.update({ status: 'Active' }, { transaction }); // Create audit log await db.AuditLog.create({ userId: req.user.id, - userName: req.user.name, - userRole: req.user.role, action: AUDIT_ACTIONS.REJECTED, entityType: 'resignation', - entityId: resignation.id, - changes: { reason } + entityId: resignation.id }, { transaction }); await transaction.commit(); diff --git a/src/modules/self-service/resignation.routes.js b/src/modules/self-service/resignation.routes.js deleted file mode 100644 index f8b6cdf..0000000 --- a/src/modules/self-service/resignation.routes.js +++ /dev/null @@ -1,13 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const resignationController = require('./resignation.controller'); -const { authenticate } = require('../../common/middleware/auth'); - -// Protected routes -router.post('/', authenticate, resignationController.createResignation); -router.get('/', authenticate, resignationController.getResignations); -router.get('/:id', authenticate, resignationController.getResignationById); -router.put('/:id/approve', authenticate, resignationController.approveResignation); -router.put('/:id/reject', authenticate, resignationController.rejectResignation); - -module.exports = router; diff --git a/src/modules/self-service/resignation.routes.ts b/src/modules/self-service/resignation.routes.ts new file mode 100644 index 0000000..95b553c --- /dev/null +++ b/src/modules/self-service/resignation.routes.ts @@ -0,0 +1,13 @@ +import express from 'express'; +const router = express.Router(); +import * as resignationController from './resignation.controller.js'; +import { authenticate } from '../../common/middleware/auth.js'; + +// Protected routes +router.post('/', authenticate as any, resignationController.createResignation); +router.get('/', authenticate as any, resignationController.getResignations); +router.get('/:id', authenticate as any, resignationController.getResignationById); +router.put('/:id/approve', authenticate as any, resignationController.approveResignation); +router.put('/:id/reject', authenticate as any, resignationController.rejectResignation); + +export default router; diff --git a/src/modules/self-service/self-service.routes.js b/src/modules/self-service/self-service.routes.js deleted file mode 100644 index c9d35ef..0000000 --- a/src/modules/self-service/self-service.routes.js +++ /dev/null @@ -1,26 +0,0 @@ -const express = require('express'); -const router = express.Router(); - -const resignationRoutes = require('./resignation.routes'); -const constitutionalController = require('./constitutional.controller'); -const relocationController = require('./relocation.controller'); -const { authenticate } = require('../../common/middleware/auth'); - -// Resignations submodule -router.use('/resignations', resignationRoutes); - -// Constitutional changes submodule -router.post('/constitutional', authenticate, constitutionalController.submitRequest); -router.get('/constitutional', authenticate, constitutionalController.getRequests); -router.get('/constitutional/:id', authenticate, constitutionalController.getRequestById); -router.put('/constitutional/:id/action', authenticate, constitutionalController.takeAction); -router.post('/constitutional/:id/documents', authenticate, constitutionalController.uploadDocuments); - -// Relocation submodule -router.post('/relocation', authenticate, relocationController.submitRequest); -router.get('/relocation', authenticate, relocationController.getRequests); -router.get('/relocation/:id', authenticate, relocationController.getRequestById); -router.put('/relocation/:id/action', authenticate, relocationController.takeAction); -router.post('/relocation/:id/documents', authenticate, relocationController.uploadDocuments); - -module.exports = router; diff --git a/src/modules/self-service/self-service.routes.ts b/src/modules/self-service/self-service.routes.ts new file mode 100644 index 0000000..cc02c1e --- /dev/null +++ b/src/modules/self-service/self-service.routes.ts @@ -0,0 +1,26 @@ +import express from 'express'; +const router = express.Router(); + +import resignationRoutes from './resignation.routes.js'; +import * as constitutionalController from './constitutional.controller.js'; +import * as relocationController from './relocation.controller.js'; +import { authenticate } from '../../common/middleware/auth.js'; + +// Resignations submodule +router.use('/resignations', resignationRoutes); + +// Constitutional changes submodule +router.post('/constitutional', authenticate as any, constitutionalController.submitRequest); +router.get('/constitutional', authenticate as any, constitutionalController.getRequests); +router.get('/constitutional/:id', authenticate as any, constitutionalController.getRequestById); +router.put('/constitutional/:id/action', authenticate as any, constitutionalController.takeAction); +router.post('/constitutional/:id/documents', authenticate as any, constitutionalController.uploadDocuments); + +// Relocation submodule +router.post('/relocation', authenticate as any, relocationController.submitRequest); +router.get('/relocation', authenticate as any, relocationController.getRequests); +router.get('/relocation/:id', authenticate as any, relocationController.getRequestById); +router.put('/relocation/:id/action', authenticate as any, relocationController.takeAction); +router.post('/relocation/:id/documents', authenticate as any, relocationController.uploadDocuments); + +export default router; diff --git a/src/modules/settlement/settlement.controller.js b/src/modules/settlement/settlement.controller.ts similarity index 84% rename from src/modules/settlement/settlement.controller.js rename to src/modules/settlement/settlement.controller.ts index 9eb34a1..d097dad 100644 --- a/src/modules/settlement/settlement.controller.js +++ b/src/modules/settlement/settlement.controller.ts @@ -1,6 +1,9 @@ -const { FinancePayment, FnF, Application, Resignation, User, Outlet } = require('../../database/models'); +import { Request, Response } from 'express'; +import db from '../../database/models/index.js'; +const { FinancePayment, FnF, Application, Resignation, User, Outlet } = db; +import { AuthRequest } from '../../types/express.types.js'; -exports.getOnboardingPayments = async (req, res) => { +export const getOnboardingPayments = async (req: Request, res: Response) => { try { const payments = await FinancePayment.findAll({ include: [{ @@ -18,7 +21,7 @@ exports.getOnboardingPayments = async (req, res) => { } }; -exports.getFnFSettlements = async (req, res) => { +export const getFnFSettlements = async (req: Request, res: Response) => { try { const settlements = await FnF.findAll({ include: [ @@ -47,10 +50,10 @@ exports.getFnFSettlements = async (req, res) => { } }; -exports.updatePayment = async (req, res) => { +export const updatePayment = async (req: AuthRequest, res: Response) => { try { const { id } = req.params; - const { paidDate, amount, paymentMode, transactionReference, status } = req.body; + const { paidDate, amount, transactionReference, status } = req.body; const payment = await FinancePayment.findByPk(id); if (!payment) { @@ -72,11 +75,10 @@ exports.updatePayment = async (req, res) => { } }; -exports.updateFnF = async (req, res) => { +export const updateFnF = async (req: AuthRequest, res: Response) => { try { const { id } = req.params; const { - inventoryClearance, sparesClearance, accountsClearance, legalClearance, finalSettlementAmount, status } = req.body; diff --git a/src/modules/settlement/settlement.routes.js b/src/modules/settlement/settlement.routes.js deleted file mode 100644 index 37d598f..0000000 --- a/src/modules/settlement/settlement.routes.js +++ /dev/null @@ -1,17 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const settlementController = require('./settlement.controller'); -const { authenticate } = require('../../common/middleware/auth'); -const { checkRole } = require('../../common/middleware/roleCheck'); -const { ROLES } = require('../../common/config/constants'); - -// All routes require authentication -router.use(authenticate); - -// Finance user only routes -router.get('/onboarding', checkRole([ROLES.FINANCE, ROLES.SUPER_ADMIN]), settlementController.getOnboardingPayments); -router.get('/fnf', checkRole([ROLES.FINANCE, ROLES.SUPER_ADMIN]), settlementController.getFnFSettlements); -router.put('/payments/:id', checkRole([ROLES.FINANCE, ROLES.SUPER_ADMIN]), settlementController.updatePayment); -router.put('/fnf/:id', checkRole([ROLES.FINANCE, ROLES.SUPER_ADMIN]), settlementController.updateFnF); - -module.exports = router; diff --git a/src/modules/settlement/settlement.routes.ts b/src/modules/settlement/settlement.routes.ts new file mode 100644 index 0000000..34e01c1 --- /dev/null +++ b/src/modules/settlement/settlement.routes.ts @@ -0,0 +1,17 @@ +import express from 'express'; +const router = express.Router(); +import * as settlementController from './settlement.controller.js'; +import { authenticate } from '../../common/middleware/auth.js'; +import { checkRole } from '../../common/middleware/roleCheck.js'; +import { ROLES } from '../../common/config/constants.js'; + +// All routes require authentication +router.use(authenticate as any); + +// Finance user only routes +router.get('/onboarding', checkRole([ROLES.FINANCE, ROLES.SUPER_ADMIN]) as any, settlementController.getOnboardingPayments); +router.get('/fnf', checkRole([ROLES.FINANCE, ROLES.SUPER_ADMIN]) as any, settlementController.getFnFSettlements); +router.put('/payments/:id', checkRole([ROLES.FINANCE, ROLES.SUPER_ADMIN]) as any, settlementController.updatePayment); +router.put('/fnf/:id', checkRole([ROLES.FINANCE, ROLES.SUPER_ADMIN]) as any, settlementController.updateFnF); + +export default router; diff --git a/src/modules/sla/sla.controller.ts b/src/modules/sla/sla.controller.ts new file mode 100644 index 0000000..73df123 --- /dev/null +++ b/src/modules/sla/sla.controller.ts @@ -0,0 +1,26 @@ +import { Request, Response } from 'express'; +import db from '../../database/models/index.js'; +const { SLAConfiguration, SLATracking, SLABreach } = db; + +export const getConfigs = async (req: Request, res: Response) => { + try { + const configs = await SLAConfiguration.findAll(); + res.json({ success: true, data: configs }); + } catch (error) { + console.error('Get SLA configs error:', error); + res.status(500).json({ success: false, message: 'Error fetching SLA configs' }); + } +}; + +export const getTracking = async (req: Request, res: Response) => { + try { + const { applicationId } = req.params; + const tracking = await SLATracking.findAll({ + where: { applicationId }, + include: [{ model: SLABreach, as: 'breaches' }] + }); + res.json({ success: true, data: tracking }); + } catch (error) { + res.status(500).json({ success: false, message: 'Error fetching SLA tracking' }); + } +}; diff --git a/src/modules/sla/sla.routes.ts b/src/modules/sla/sla.routes.ts new file mode 100644 index 0000000..85647bb --- /dev/null +++ b/src/modules/sla/sla.routes.ts @@ -0,0 +1,11 @@ +import express from 'express'; +const router = express.Router(); +import * as slaController from './sla.controller.js'; +import { authenticate } from '../../common/middleware/auth.js'; + +router.use(authenticate as any); + +router.get('/configs', slaController.getConfigs); +router.get('/tracking/:applicationId', slaController.getTracking); + +export default router; diff --git a/src/server.ts b/src/server.ts new file mode 100644 index 0000000..246bb24 --- /dev/null +++ b/src/server.ts @@ -0,0 +1,184 @@ +import 'dotenv/config'; +import express, { Request, Response, NextFunction } from 'express'; +import cors from 'cors'; +import helmet from 'helmet'; +import compression from 'compression'; +import rateLimit from 'express-rate-limit'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +// Import database +import db from './database/models/index.js'; + +// Import routes (Modular Monolith Structure) +// Import routes (Modular Monolith Structure) +import authRoutes from './modules/auth/auth.routes.js'; +import onboardingRoutes from './modules/onboarding/onboarding.routes.js'; +import selfServiceRoutes from './modules/self-service/self-service.routes.js'; +import masterRoutes from './modules/master/master.routes.js'; +import settlementRoutes from './modules/settlement/settlement.routes.js'; +import collaborationRoutes from './modules/collaboration/collaboration.routes.js'; +import resignationRoutes from './modules/self-service/resignation.routes.js'; +import outletRoutes from './modules/master/outlet.routes.js'; +// New Modules +import adminRoutes from './modules/admin/admin.routes.js'; +import opportunityRoutes from './modules/opportunity/opportunity.routes.js'; +import assessmentRoutes from './modules/assessment/assessment.routes.js'; +import fddRoutes from './modules/fdd/fdd.routes.js'; +import loiRoutes from './modules/loi/loi.routes.js'; +import loaRoutes from './modules/loa/loa.routes.js'; +import eorRoutes from './modules/eor/eor.routes.js'; +import dealerRoutes from './modules/dealer/dealer.routes.js'; +import slaRoutes from './modules/sla/sla.routes.js'; +import communicationRoutes from './modules/communication/communication.routes.js'; + +// Import common middleware & utils +import errorHandler from './common/middleware/errorHandler.js'; +import logger from './common/utils/logger.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// Initialize Express app +const app = express(); + +// Security middleware +app.use(helmet()); +app.use(cors({ + origin: process.env.FRONTEND_URL || 'http://localhost:5173', + credentials: true +})); + +// Rate limiting +const limiter = rateLimit({ + windowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS || '900000'), // 15 minutes + max: parseInt(process.env.RATE_LIMIT_MAX_REQUESTS || '100'), + message: 'Too many requests from this IP, please try again later.' +}); +app.use('/api/', limiter); + +// Body parsing middleware +app.use(express.json({ limit: '10mb' })); +app.use(express.urlencoded({ extended: true, limit: '10mb' })); + +// Compression +app.use(compression()); + +// Static files (uploaded documents) +app.use('/uploads', express.static(path.join(__dirname, '..', 'uploads'))); + +// Request logging +app.use((req: Request, res: Response, next: NextFunction) => { + logger.info(`${req.method} ${req.path}`, { + ip: req.ip, + userAgent: req.get('user-agent') + }); + next(); +}); + +// Health check endpoint +app.get('/health', (req: Request, res: Response) => { + res.json({ + status: 'OK', + timestamp: new Date().toISOString(), + uptime: process.uptime(), + environment: process.env.NODE_ENV + }); +}); + +// API Routes (Modular) +app.use('/api/auth', authRoutes); +app.use('/api/onboarding', onboardingRoutes); +app.use('/api/self-service', selfServiceRoutes); +app.use('/api/master', masterRoutes); +app.use('/api/settlement', settlementRoutes); +app.use('/api/collaboration', collaborationRoutes); +// New Routes +app.use('/api/admin', adminRoutes); +app.use('/api/opportunity', opportunityRoutes); +app.use('/api/assessment', assessmentRoutes); +app.use('/api/fdd', fddRoutes); +app.use('/api/loi', loiRoutes); +app.use('/api/loa', loaRoutes); +app.use('/api/eor', eorRoutes); +app.use('/api/dealer', dealerRoutes); +app.use('/api/sla', slaRoutes); +app.use('/api/communication', communicationRoutes); + +// Backward Compatibility Aliases +app.use('/api/applications', onboardingRoutes); +app.use('/api/resignations', resignationRoutes); +app.use('/api/constitutional', (req: Request, res: Response, next: NextFunction) => { + // Map /api/constitutional to /api/self-service/constitutional + req.url = '/constitutional' + req.url; + next(); +}, selfServiceRoutes); +app.use('/api/relocations', (req: Request, res: Response, next: NextFunction) => { + // Map /api/relocations to /api/self-service/relocation + req.url = '/relocation' + req.url; + next(); +}, selfServiceRoutes); +app.use('/api/outlets', outletRoutes); +app.use('/api/finance', settlementRoutes); +app.use('/api/worknotes', collaborationRoutes); + +// 404 handler +app.use((req: Request, res: Response) => { + res.status(404).json({ + success: false, + message: 'Route not found' + }); +}); + +// Global error handler +app.use(errorHandler as any); + +// Database connection and server start +const PORT = process.env.PORT || 5000; + +const startServer = async () => { + try { + // Test database connection + await db.sequelize.authenticate(); + logger.info('Database connection established successfully'); + + // Sync database (in development only) + if (process.env.NODE_ENV === 'development') { + await db.sequelize.sync({ alter: false }); + logger.info('Database models synchronized'); + } + + // Start server + app.listen(PORT, () => { + logger.info(`🚀 Server running on port ${PORT}`); + logger.info(`📍 Environment: ${process.env.NODE_ENV}`); + logger.info(`🔗 API Base URL: http://localhost:${PORT}/api`); + }); + } catch (error) { + logger.error('Unable to start server:', error); + process.exit(1); + } +}; + +// Handle unhandled promise rejections +process.on('unhandledRejection', (err) => { + logger.error('Unhandled Promise Rejection:', err); + process.exit(1); +}); + +// Handle uncaught exceptions +process.on('uncaughtException', (err) => { + logger.error('Uncaught Exception:', err); + process.exit(1); +}); + +// Graceful shutdown +process.on('SIGTERM', async () => { + logger.info('SIGTERM signal received: closing HTTP server'); + await db.sequelize.close(); + process.exit(0); +}); + +startServer(); + +export default app; diff --git a/src/types/auth.types.ts b/src/types/auth.types.ts new file mode 100644 index 0000000..eea988a --- /dev/null +++ b/src/types/auth.types.ts @@ -0,0 +1,7 @@ +export interface TokenPayload { + userId: string; + email: string; + role: string; + region: string | null; + zone: string | null; +} diff --git a/src/types/common.types.ts b/src/types/common.types.ts new file mode 100644 index 0000000..be0e259 --- /dev/null +++ b/src/types/common.types.ts @@ -0,0 +1,12 @@ +import { Options } from 'sequelize'; + +export interface DbConfig { + [key: string]: Options & { username?: string; password?: string; database?: string }; +} + +export interface AppError extends Error { + statusCode?: number; + name: string; + errors?: any[]; + code?: string; +} diff --git a/src/types/constants.types.ts b/src/types/constants.types.ts new file mode 100644 index 0000000..84d24ee --- /dev/null +++ b/src/types/constants.types.ts @@ -0,0 +1,41 @@ +import { + ROLES, + REGIONS, + APPLICATION_STAGES, + APPLICATION_STATUS, + RESIGNATION_STAGES, + RESIGNATION_TYPES, + CONSTITUTIONAL_CHANGE_TYPES, + CONSTITUTIONAL_STAGES, + RELOCATION_TYPES, + RELOCATION_STAGES, + OUTLET_TYPES, + OUTLET_STATUS, + BUSINESS_TYPES, + PAYMENT_TYPES, + PAYMENT_STATUS, + FNF_STATUS, + AUDIT_ACTIONS, + DOCUMENT_TYPES, + REQUEST_TYPES +} from '../common/config/constants.js'; + +export type Role = typeof ROLES[keyof typeof ROLES]; +export type Region = typeof REGIONS[keyof typeof REGIONS]; +export type ApplicationStage = typeof APPLICATION_STAGES[keyof typeof APPLICATION_STAGES]; +export type ApplicationStatus = typeof APPLICATION_STATUS[keyof typeof APPLICATION_STATUS]; +export type ResignationStage = typeof RESIGNATION_STAGES[keyof typeof RESIGNATION_STAGES]; +export type ResignationType = typeof RESIGNATION_TYPES[keyof typeof RESIGNATION_TYPES]; +export type ConstitutionalChangeType = typeof CONSTITUTIONAL_CHANGE_TYPES[keyof typeof CONSTITUTIONAL_CHANGE_TYPES]; +export type ConstitutionalStage = typeof CONSTITUTIONAL_STAGES[keyof typeof CONSTITUTIONAL_STAGES]; +export type RelocationType = typeof RELOCATION_TYPES[keyof typeof RELOCATION_TYPES]; +export type RelocationStage = typeof RELOCATION_STAGES[keyof typeof RELOCATION_STAGES]; +export type OutletType = typeof OUTLET_TYPES[keyof typeof OUTLET_TYPES]; +export type OutletStatus = typeof OUTLET_STATUS[keyof typeof OUTLET_STATUS]; +export type BusinessType = typeof BUSINESS_TYPES[keyof typeof BUSINESS_TYPES]; +export type PaymentType = typeof PAYMENT_TYPES[keyof typeof PAYMENT_TYPES]; +export type PaymentStatus = typeof PAYMENT_STATUS[keyof typeof PAYMENT_STATUS]; +export type FnfStatus = typeof FNF_STATUS[keyof typeof FNF_STATUS]; +export type AuditAction = typeof AUDIT_ACTIONS[keyof typeof AUDIT_ACTIONS]; +export type DocumentType = typeof DOCUMENT_TYPES[keyof typeof DOCUMENT_TYPES]; +export type RequestType = typeof REQUEST_TYPES[keyof typeof REQUEST_TYPES]; diff --git a/src/types/express.types.ts b/src/types/express.types.ts new file mode 100644 index 0000000..5c127b8 --- /dev/null +++ b/src/types/express.types.ts @@ -0,0 +1,15 @@ +import { Request } from 'express'; +import { ROLES } from '../common/config/constants.js'; + +export interface AuthenticatedRequest extends Request { + user?: { + id: string; + email: string; + name: string; + role: typeof ROLES[keyof typeof ROLES]; + }; +} + +export interface AuthRequest extends AuthenticatedRequest { + token?: string; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..deb2ded --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,39 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "outDir": "./dist", + "rootDir": "./", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "allowJs": true, + "types": [ + "node", + "express", + "multer", + "cors", + "jsonwebtoken", + "bcryptjs", + "compression", + "pg", + "validator", + "uuid", + "nodemailer", + "supertest", + "jest" + ] + }, + "include": [ + "**/*.ts" + ], + "exclude": [ + "node_modules", + "dist", + "tests", + "scripts" + ] +} \ No newline at end of file