From 29e82bc7464b4090738fa48a662b1d54ca8d2e92 Mon Sep 17 00:00:00 2001 From: "Mamadou Khoussa [028918 DSI/DAC/DIF/DS]" Date: Mon, 1 Dec 2025 00:40:15 +0000 Subject: [PATCH] fix it --- eslint.config.mjs | 15 +- package-lock.json | 704 ++++++--------------- package.json | 7 +- prisma/schema.prisma | 6 +- src/Dockerfile | 76 +++ src/app.module.ts | 51 +- src/main.ts | 46 +- src/mongodb/mongodb.module.ts | 21 + src/mongodb/schemas/subscription.schema.ts | 51 ++ src/mongodb/schemas/transaction.schema.ts | 45 ++ src/prisma/prisma.module.ts | 9 + src/prisma/prisma.service.ts | 18 + src/reporting/dto/report-query.dto.ts | 34 + src/reporting/dto/report-response.dto.ts | 29 + src/reporting/reporting.controller.ts | 150 +++++ src/reporting/reporting.module.ts | 12 + src/reporting/reporting.module.ts | 0 src/reporting/reporting.service.ts | 225 +++++++ src/sync/sync.module.ts | 11 + src/sync/sync.scheduler.ts | 21 + src/sync/sync.service.ts | 165 +++++ 21 files changed, 1169 insertions(+), 527 deletions(-) create mode 100644 src/reporting/reporting.module.ts delete mode 100644 src/reporting/reporting.module.ts diff --git a/eslint.config.mjs b/eslint.config.mjs index caebf6e..8bb661b 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -2,6 +2,7 @@ import eslint from '@eslint/js'; import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; import globals from 'globals'; +import { off } from 'process'; import tseslint from 'typescript-eslint'; export default tseslint.config( @@ -17,9 +18,9 @@ export default tseslint.config( ...globals.node, ...globals.jest, }, - sourceType: 'commonjs', + sourceType: 'module', // Changé de 'commonjs' à 'module' parserOptions: { - projectService: true, + project: './tsconfig.json', // Ajout du chemin vers tsconfig tsconfigRootDir: import.meta.dirname, }, }, @@ -27,8 +28,16 @@ export default tseslint.config( { rules: { '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-unsafe-assignment':'off', + 'eslint-disable-next-line @typescript-eslint/no-unsafe-call':'off', '@typescript-eslint/no-floating-promises': 'warn', - '@typescript-eslint/no-unsafe-argument': 'warn' + '@typescript-eslint/no-unsafe-argument': 'warn', + 'prettier/prettier': ['error', { + singleQuote: true, + trailingComma: 'all', + printWidth: 100, + tabWidth: 2, + }], }, }, ); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 4dba064..59bcfba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,12 +15,12 @@ "@nestjs/mongoose": "^11.0.3", "@nestjs/platform-express": "^11.0.1", "@nestjs/schedule": "^6.0.1", - "@prisma/client": "^7.0.1", + "@nestjs/swagger": "^11.2.3", + "@prisma/client": "^6.17.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.3", "date-fns": "^4.1.0", - "mongoose": "^9.0.0", - "prisma": "^7.0.1", + "prisma": "^6.17.1", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1" }, @@ -720,39 +720,6 @@ "url": "https://github.com/sponsors/Borewit" } }, - "node_modules/@chevrotain/cst-dts-gen": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-10.5.0.tgz", - "integrity": "sha512-lhmC/FyqQ2o7pGK4Om+hzuDrm9rhFYIJ/AXoQBeongmn870Xeb0L6oGEiuR8nohFNL5sMaQEJWCxr1oIVIVXrw==", - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/gast": "10.5.0", - "@chevrotain/types": "10.5.0", - "lodash": "4.17.21" - } - }, - "node_modules/@chevrotain/gast": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-10.5.0.tgz", - "integrity": "sha512-pXdMJ9XeDAbgOWKuD1Fldz4ieCs6+nLNmyVhe2gZVqoO7v8HXuHYs5OV2EzUtbuai37TlOAQHrTDvxMnvMJz3A==", - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/types": "10.5.0", - "lodash": "4.17.21" - } - }, - "node_modules/@chevrotain/types": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-10.5.0.tgz", - "integrity": "sha512-f1MAia0x/pAVPWH/T73BJVyO2XU5tI4/iE7cnxb7tqdNTNhQI3Uq3XkqcoteTmD4t1aM0LbHCJOhgIDn07kl2A==", - "license": "Apache-2.0" - }, - "node_modules/@chevrotain/utils": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-10.5.0.tgz", - "integrity": "sha512-hBzuU5+JjB2cqNZyszkDHZgOSrUUT8V3dhgRl8Q9Gp6dAj/H5+KILGjbhDpc3Iy9qmqlm/akuOI2ut9VUtzJxQ==", - "license": "Apache-2.0" - }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -788,33 +755,6 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/@electric-sql/pglite": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.3.2.tgz", - "integrity": "sha512-zfWWa+V2ViDCY/cmUfRqeWY1yLto+EpxjXnZzenB1TyxsTiXaTWeZFIZw6mac52BsuQm0RjCnisjBtdBaXOI6w==", - "license": "Apache-2.0" - }, - "node_modules/@electric-sql/pglite-socket": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/@electric-sql/pglite-socket/-/pglite-socket-0.0.6.tgz", - "integrity": "sha512-6RjmgzphIHIBA4NrMGJsjNWK4pu+bCWJlEWlwcxFTVY3WT86dFpKwbZaGWZV6C5Rd7sCk1Z0CI76QEfukLAUXw==", - "license": "Apache-2.0", - "bin": { - "pglite-server": "dist/scripts/server.js" - }, - "peerDependencies": { - "@electric-sql/pglite": "0.3.2" - } - }, - "node_modules/@electric-sql/pglite-tools": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@electric-sql/pglite-tools/-/pglite-tools-0.2.7.tgz", - "integrity": "sha512-9dAccClqxx4cZB+Ar9B+FZ5WgxDc/Xvl9DPrTWv+dYTf0YNubLzi4wHHRGRGhrJv15XwnyKcGOZAP1VXSneSUg==", - "license": "Apache-2.0", - "peerDependencies": { - "@electric-sql/pglite": "0.3.2" - } - }, "node_modules/@emnapi/core": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz", @@ -1006,18 +946,6 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@hono/node-server": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.14.2.tgz", - "integrity": "sha512-GHjpOeHYbr9d1vkID2sNUYkl5IxumyhDrUJB7wBp7jvqYwPFt+oNKsAPBRcdSbV7kIrXhouLE199ks1QcK4r7A==", - "license": "MIT", - "engines": { - "node": ">=18.14.1" - }, - "peerDependencies": { - "hono": "^4" - } - }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -2146,28 +2074,22 @@ "node": ">=8" } }, + "node_modules/@microsoft/tsdoc": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.16.0.tgz", + "integrity": "sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA==", + "license": "MIT" + }, "node_modules/@mongodb-js/saslprep": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.2.tgz", "integrity": "sha512-QgA5AySqB27cGTXBFmnpifAi7HxoGUeezwo6p9dI03MuDB6Pp33zgclqVb6oVK3j6I9Vesg0+oojW2XxB59SGg==", "license": "MIT", + "peer": true, "dependencies": { "sparse-bitfield": "^3.0.3" } }, - "node_modules/@mrleebo/prisma-ast": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/@mrleebo/prisma-ast/-/prisma-ast-0.12.1.tgz", - "integrity": "sha512-JwqeCQ1U3fvccttHZq7Tk0m/TMC6WcFAQZdukypW3AzlJYKYTGNVd1ANU2GuhKnv4UQuOFj3oAl0LLG/gxFN1w==", - "license": "MIT", - "dependencies": { - "chevrotain": "^10.5.0", - "lilconfig": "^2.1.0" - }, - "engines": { - "node": ">=16" - } - }, "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", @@ -2313,6 +2235,26 @@ } } }, + "node_modules/@nestjs/mapped-types": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.1.0.tgz", + "integrity": "sha512-W+n+rM69XsFdwORF11UqJahn4J3xi4g/ZEOlJNL6KoW5ygWSmBB2p0S2BZ4FQeS/NDH72e6xIcu35SfJnE8bXw==", + "license": "MIT", + "peerDependencies": { + "@nestjs/common": "^10.0.0 || ^11.0.0", + "class-transformer": "^0.4.0 || ^0.5.0", + "class-validator": "^0.13.0 || ^0.14.0", + "reflect-metadata": "^0.1.12 || ^0.2.0" + }, + "peerDependenciesMeta": { + "class-transformer": { + "optional": true + }, + "class-validator": { + "optional": true + } + } + }, "node_modules/@nestjs/mongoose": { "version": "11.0.3", "resolved": "https://registry.npmjs.org/@nestjs/mongoose/-/mongoose-11.0.3.tgz", @@ -2457,6 +2399,39 @@ "tslib": "^2.1.0" } }, + "node_modules/@nestjs/swagger": { + "version": "11.2.3", + "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-11.2.3.tgz", + "integrity": "sha512-a0xFfjeqk69uHIUpP8u0ryn4cKuHdra2Ug96L858i0N200Hxho+n3j+TlQXyOF4EstLSGjTfxI1Xb2E1lUxeNg==", + "license": "MIT", + "dependencies": { + "@microsoft/tsdoc": "0.16.0", + "@nestjs/mapped-types": "2.1.0", + "js-yaml": "4.1.1", + "lodash": "4.17.21", + "path-to-regexp": "8.3.0", + "swagger-ui-dist": "5.30.2" + }, + "peerDependencies": { + "@fastify/static": "^8.0.0", + "@nestjs/common": "^11.0.1", + "@nestjs/core": "^11.0.1", + "class-transformer": "*", + "class-validator": "*", + "reflect-metadata": "^0.1.12 || ^0.2.0" + }, + "peerDependenciesMeta": { + "@fastify/static": { + "optional": true + }, + "class-transformer": { + "optional": true + }, + "class-validator": { + "optional": true + } + } + }, "node_modules/@nestjs/testing": { "version": "11.1.9", "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-11.1.9.tgz", @@ -2549,19 +2524,17 @@ } }, "node_modules/@prisma/client": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.0.1.tgz", - "integrity": "sha512-O74T6xcfaGAq5gXwCAvfTLvI6fmC3and2g5yLRMkNjri1K8mSpEgclDNuUWs9xj5AwNEMQ88NeD3asI+sovm1g==", + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.19.0.tgz", + "integrity": "sha512-QXFT+N/bva/QI2qoXmjBzL7D6aliPffIwP+81AdTGq0FXDoLxLkWivGMawG8iM5B9BKfxLIXxfWWAF6wbuJU6g==", + "hasInstallScript": true, "license": "Apache-2.0", - "dependencies": { - "@prisma/client-runtime-utils": "7.0.1" - }, "engines": { - "node": "^20.19 || ^22.12 || >=24.0" + "node": ">=18.18" }, "peerDependencies": { "prisma": "*", - "typescript": ">=5.4.0" + "typescript": ">=5.1.0" }, "peerDependenciesMeta": { "prisma": { @@ -2572,16 +2545,10 @@ } } }, - "node_modules/@prisma/client-runtime-utils": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@prisma/client-runtime-utils/-/client-runtime-utils-7.0.1.tgz", - "integrity": "sha512-R26BVX9D/iw4toUmZKZf3jniM/9pMGHHdZN5LVP2L7HNiCQKNQQx/9LuMtjepbgRqSqQO3oHN0yzojHLnKTGEw==", - "license": "Apache-2.0" - }, "node_modules/@prisma/config": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.0.1.tgz", - "integrity": "sha512-MacIjXdo+hNKxPvtMzDXykIIc8HCRWoyjQ2nguJTFqLDzJBD5L6QRaANGTLOqbGtJ3sFvLRmfXhrFg3pWoK1BA==", + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.19.0.tgz", + "integrity": "sha512-zwCayme+NzI/WfrvFEtkFhhOaZb/hI+X8TTjzjJ252VbPxAl2hWHK5NMczmnG9sXck2lsXrxIZuK524E25UNmg==", "license": "Apache-2.0", "dependencies": { "c12": "3.1.0", @@ -2591,116 +2558,57 @@ } }, "node_modules/@prisma/debug": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.0.1.tgz", - "integrity": "sha512-5+25XokVeAK2Z2C9W457AFw7Hk032Q3QI3G58KYKXPlpgxy+9FvV1+S1jqfJ2d4Nmq9LP/uACrM6OVhpJMSr8w==", + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.19.0.tgz", + "integrity": "sha512-8hAdGG7JmxrzFcTzXZajlQCidX0XNkMJkpqtfbLV54wC6LSSX6Vni25W/G+nAANwLnZ2TmwkfIuWetA7jJxJFA==", "license": "Apache-2.0" }, - "node_modules/@prisma/dev": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@prisma/dev/-/dev-0.13.0.tgz", - "integrity": "sha512-QMmF6zFeUF78yv1HYbHvod83AQnl7u6NtKyDhTRZOJup3h1icWs8R7RUVxBJZvM2tBXNAMpLQYYM/8kPlOPegA==", - "license": "ISC", - "dependencies": { - "@electric-sql/pglite": "0.3.2", - "@electric-sql/pglite-socket": "0.0.6", - "@electric-sql/pglite-tools": "0.2.7", - "@hono/node-server": "1.14.2", - "@mrleebo/prisma-ast": "0.12.1", - "@prisma/get-platform": "6.8.2", - "@prisma/query-plan-executor": "6.18.0", - "foreground-child": "3.3.1", - "get-port-please": "3.1.2", - "hono": "4.7.10", - "http-status-codes": "2.3.0", - "pathe": "2.0.3", - "proper-lockfile": "4.1.2", - "remeda": "2.21.3", - "std-env": "3.9.0", - "valibot": "1.1.0", - "zeptomatch": "2.0.2" - } - }, "node_modules/@prisma/engines": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.0.1.tgz", - "integrity": "sha512-f+D/vdKeImqUHysd5Bgv8LQ1whl4sbLepHyYMQQMK61cp4WjwJVryophleLUrfEJRpBLGTBI/7fnLVENxxMFPQ==", + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.19.0.tgz", + "integrity": "sha512-pMRJ+1S6NVdXoB8QJAPIGpKZevFjxhKt0paCkRDTZiczKb7F4yTgRP8M4JdVkpQwmaD4EoJf6qA+p61godDokw==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.0.1", - "@prisma/engines-version": "7.1.0-2.f09f2815f091dbba658cdcd2264306d88bb5bda6", - "@prisma/fetch-engine": "7.0.1", - "@prisma/get-platform": "7.0.1" + "@prisma/debug": "6.19.0", + "@prisma/engines-version": "6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773", + "@prisma/fetch-engine": "6.19.0", + "@prisma/get-platform": "6.19.0" } }, "node_modules/@prisma/engines-version": { - "version": "7.1.0-2.f09f2815f091dbba658cdcd2264306d88bb5bda6", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.1.0-2.f09f2815f091dbba658cdcd2264306d88bb5bda6.tgz", - "integrity": "sha512-RA7pShKvijHib4USRB3YuLTQamHKJPkTRDc45AwxfahUQngiGVMlIj4ix4emUxkrum4o/jwn82WIwlG57EtgiQ==", + "version": "6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773.tgz", + "integrity": "sha512-gV7uOBQfAFlWDvPJdQxMT1aSRur3a0EkU/6cfbAC5isV67tKDWUrPauyaHNpB+wN1ebM4A9jn/f4gH+3iHSYSQ==", "license": "Apache-2.0" }, - "node_modules/@prisma/engines/node_modules/@prisma/get-platform": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.0.1.tgz", - "integrity": "sha512-DrsGnZOsF7PlAE7UtqmJenWti87RQtg7v9qW9alS71Pj0P6ZQV0RuzRQaql9dCWoo6qKAaF5U/L4kI826MmiZg==", - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "7.0.1" - } - }, "node_modules/@prisma/fetch-engine": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.0.1.tgz", - "integrity": "sha512-5DnSairYIYU7dcv/9pb1KCwIRHZfhVOd34855d01lUI5QdF9rdCkMywPQbBM67YP7iCgQoEZO0/COtOMpR4i9A==", + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.19.0.tgz", + "integrity": "sha512-OOx2Lda0DGrZ1rodADT06ZGqHzr7HY7LNMaFE2Vp8dp146uJld58sRuasdX0OiwpHgl8SqDTUKHNUyzEq7pDdQ==", "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.0.1", - "@prisma/engines-version": "7.1.0-2.f09f2815f091dbba658cdcd2264306d88bb5bda6", - "@prisma/get-platform": "7.0.1" - } - }, - "node_modules/@prisma/fetch-engine/node_modules/@prisma/get-platform": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.0.1.tgz", - "integrity": "sha512-DrsGnZOsF7PlAE7UtqmJenWti87RQtg7v9qW9alS71Pj0P6ZQV0RuzRQaql9dCWoo6qKAaF5U/L4kI826MmiZg==", - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "7.0.1" + "@prisma/debug": "6.19.0", + "@prisma/engines-version": "6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773", + "@prisma/get-platform": "6.19.0" } }, "node_modules/@prisma/get-platform": { - "version": "6.8.2", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.8.2.tgz", - "integrity": "sha512-vXSxyUgX3vm1Q70QwzwkjeYfRryIvKno1SXbIqwSptKwqKzskINnDUcx85oX+ys6ooN2ATGSD0xN2UTfg6Zcow==", + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.19.0.tgz", + "integrity": "sha512-ym85WDO2yDhC3fIXHWYpG3kVMBA49cL1XD2GCsCF8xbwoy2OkDQY44gEbAt2X46IQ4Apq9H6g0Ex1iFfPqEkHA==", "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.8.2" + "@prisma/debug": "6.19.0" } }, - "node_modules/@prisma/get-platform/node_modules/@prisma/debug": { - "version": "6.8.2", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.8.2.tgz", - "integrity": "sha512-4muBSSUwJJ9BYth5N8tqts8JtiLT8QI/RSAzEogwEfpbYGFo9mYsInsVo8dqXdPO2+Rm5OG5q0qWDDE3nyUbVg==", + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true, "license": "Apache-2.0" }, - "node_modules/@prisma/query-plan-executor": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@prisma/query-plan-executor/-/query-plan-executor-6.18.0.tgz", - "integrity": "sha512-jZ8cfzFgL0jReE1R10gT8JLHtQxjWYLiQ//wHmVYZ2rVkFHoh0DT8IXsxcKcFlfKN7ak7k6j0XMNn2xVNyr5cA==", - "license": "Apache-2.0" - }, - "node_modules/@prisma/studio-core": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/@prisma/studio-core/-/studio-core-0.8.2.tgz", - "integrity": "sha512-/iAEWEUpTja+7gVMu1LtR2pPlvDmveAwMHdTWbDeGlT7yiv0ZTCPpmeAGdq/Y9aJ9Zj1cEGBXGRbmmNPj022PQ==", - "license": "UNLICENSED", - "peerDependencies": { - "@types/react": "^18.0.0 || ^19.0.0", - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, "node_modules/@sinclair/typebox": { "version": "0.34.41", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz", @@ -3094,13 +3002,15 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/whatwg-url": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-13.0.0.tgz", - "integrity": "sha512-N8WXpbE6Wgri7KUSvrmQcqrMllKZ9uxkYWMt+mCSGwNc0Hsw9VQTW7ApqI4XNrx6/SaM2QQJCzMPDEXE058s+Q==", + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", "license": "MIT", + "peer": true, "dependencies": { "@types/webidl-conversions": "*" } @@ -4067,7 +3977,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, "license": "Python-2.0" }, "node_modules/array-timsort": { @@ -4091,15 +4000,6 @@ "dev": true, "license": "MIT" }, - "node_modules/aws-ssl-profiles": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", - "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==", - "license": "MIT", - "engines": { - "node": ">= 6.0.0" - } - }, "node_modules/babel-jest": { "version": "30.2.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.2.0.tgz", @@ -4355,12 +4255,13 @@ } }, "node_modules/bson": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/bson/-/bson-7.0.0.tgz", - "integrity": "sha512-Kwc6Wh4lQ5OmkqqKhYGKIuELXl+EPYSCObVE6bWsp1T/cGkOCBN0I8wF/T44BiuhHyNi1mmKVPXk60d41xZ7kw==", + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", + "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==", "license": "Apache-2.0", + "peer": true, "engines": { - "node": ">=20.19.0" + "node": ">=16.20.1" } }, "node_modules/buffer": { @@ -4558,20 +4459,6 @@ "dev": true, "license": "MIT" }, - "node_modules/chevrotain": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-10.5.0.tgz", - "integrity": "sha512-Pkv5rBY3+CsHOYfV5g/Vs5JY9WTHHDEKOlohI2XeygaZhUeqhAlldZ8Hz9cRmxu709bvS08YzxHdTPHhffc13A==", - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/cst-dts-gen": "10.5.0", - "@chevrotain/gast": "10.5.0", - "@chevrotain/types": "10.5.0", - "@chevrotain/utils": "10.5.0", - "lodash": "4.17.21", - "regexp-to-ast": "0.5.0" - } - }, "node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -4989,6 +4876,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -5096,15 +4984,6 @@ "node": ">=0.4.0" } }, - "node_modules/denque": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", - "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.10" - } - }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -5933,6 +5812,7 @@ "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", @@ -6102,15 +5982,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/generate-function": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", - "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", - "license": "MIT", - "dependencies": { - "is-property": "^1.0.2" - } - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -6165,12 +6036,6 @@ "node": ">=8.0.0" } }, - "node_modules/get-port-please": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/get-port-please/-/get-port-please-3.1.2.tgz", - "integrity": "sha512-Gxc29eLs1fbn6LQ4jSU4vXjlwyZhF5HsGuMAa7gqBP4Rw4yxxltyDUuF5MBclFzDTXO+ACchGQoeela4DSfzdQ==", - "license": "MIT" - }, "node_modules/get-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", @@ -6297,14 +6162,9 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, "license": "ISC" }, - "node_modules/grammex": { - "version": "3.1.12", - "resolved": "https://registry.npmjs.org/grammex/-/grammex-3.1.12.tgz", - "integrity": "sha512-6ufJOsSA7LcQehIJNCO7HIBykfM7DXQual0Ny780/DEcJIpBlHRvcqEBWGPYd7hrXL2GJ3oJI1MIhaXjWmLQOQ==", - "license": "MIT" - }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -6394,15 +6254,6 @@ "node": ">= 0.4" } }, - "node_modules/hono": { - "version": "4.7.10", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.7.10.tgz", - "integrity": "sha512-QkACju9MiN59CKSY5JsGZCYmPZkA6sIW6OFCUp7qDjZu6S6KHtJHhAc9Uy9mV9F8PJ1/HQ3ybZF2yjCa/73fvQ==", - "license": "MIT", - "engines": { - "node": ">=16.9.0" - } - }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -6430,12 +6281,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/http-status-codes": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.3.0.tgz", - "integrity": "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==", - "license": "MIT" - }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -6642,12 +6487,6 @@ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "license": "MIT" }, - "node_modules/is-property": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", - "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", - "license": "MIT" - }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -6678,6 +6517,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, "license": "ISC" }, "node_modules/istanbul-lib-coverage": { @@ -7539,7 +7379,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -7623,12 +7462,13 @@ } }, "node_modules/kareem": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/kareem/-/kareem-3.0.0.tgz", - "integrity": "sha512-RKhaOBSPN8L7y4yAgNhDT2602G5FD6QbOIISbjN9D6mjHPeqeg7K+EB5IGSU5o81/X2Gzm3ICnAvQW3x3OP8HA==", + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz", + "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==", "license": "Apache-2.0", + "peer": true, "engines": { - "node": ">=18.0.0" + "node": ">=12.0.0" } }, "node_modules/keyv": { @@ -7671,15 +7511,6 @@ "integrity": "sha512-KxH7uIJFD6+cR6nhdh+wY6prFiH26A3W/W1gTMXnng2PXSwVfi5MhYkdq3Z2Y7vhBVa1/5VJgpNtI76UM2njGA==", "license": "MIT" }, - "node_modules/lilconfig": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", - "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -7773,12 +7604,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/long": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", - "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", - "license": "Apache-2.0" - }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -7789,21 +7614,6 @@ "yallist": "^3.0.2" } }, - "node_modules/lru.min": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.3.tgz", - "integrity": "sha512-Lkk/vx6ak3rYkRR0Nhu4lFUT2VDnQSxBe8Hbl7f36358p6ow8Bnvr8lrLt98H8J1aGxfhbX4Fs5tYg2+FTwr5Q==", - "license": "MIT", - "engines": { - "bun": ">=1.0.0", - "deno": ">=1.30.0", - "node": ">=8.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wellwelwel" - } - }, "node_modules/luxon": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz", @@ -7891,7 +7701,8 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/merge-descriptors": { "version": "2.0.0", @@ -8042,26 +7853,27 @@ } }, "node_modules/mongodb": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.0.0.tgz", - "integrity": "sha512-vG/A5cQrvGGvZm2mTnCSz1LUcbOPl83hfB6bxULKQ8oFZauyox/2xbZOoGNl+64m8VBrETkdGCDBdOsCr3F3jg==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.20.0.tgz", + "integrity": "sha512-Tl6MEIU3K4Rq3TSHd+sZQqRBoGlFsOgNrH5ltAcFBV62Re3Fd+FcaVf8uSEQFOJ51SDowDVttBTONMfoYWrWlQ==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@mongodb-js/saslprep": "^1.3.0", - "bson": "^7.0.0", - "mongodb-connection-string-url": "^7.0.0" + "bson": "^6.10.4", + "mongodb-connection-string-url": "^3.0.2" }, "engines": { - "node": ">=20.19.0" + "node": ">=16.20.1" }, "peerDependencies": { - "@aws-sdk/credential-providers": "^3.806.0", - "@mongodb-js/zstd": "^7.0.0", - "gcp-metadata": "^7.0.1", - "kerberos": "^7.0.0", - "mongodb-client-encryption": ">=7.0.0 <7.1.0", + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", "snappy": "^7.3.2", - "socks": "^2.8.6" + "socks": "^2.7.1" }, "peerDependenciesMeta": { "@aws-sdk/credential-providers": { @@ -8088,33 +7900,33 @@ } }, "node_modules/mongodb-connection-string-url": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-7.0.0.tgz", - "integrity": "sha512-irhhjRVLE20hbkRl4zpAYLnDMM+zIZnp0IDB9akAFFUZp/3XdOfwwddc7y6cNvF2WCEtfTYRwYbIfYa2kVY0og==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", "license": "Apache-2.0", + "peer": true, "dependencies": { - "@types/whatwg-url": "^13.0.0", - "whatwg-url": "^14.1.0" - }, - "engines": { - "node": ">=20.19.0" + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" } }, "node_modules/mongoose": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-9.0.0.tgz", - "integrity": "sha512-J6wTGC8/hSFpy9K6kPbKDnBNoUcX4iOGCasUrRzlSKdwdVNxdscLA4oHKOQwJlHqh/TxDNg+Uzg6koCyCKP0wA==", + "version": "8.20.1", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.20.1.tgz", + "integrity": "sha512-G+n3maddlqkQrP1nXxsI0q20144OSo+pe+HzRRGqaC4yK3FLYKqejqB9cbIi+SX7eoRsnG23LHGYNp8n7mWL2Q==", "license": "MIT", + "peer": true, "dependencies": { - "kareem": "3.0.0", - "mongodb": "~7.0", + "bson": "^6.10.4", + "kareem": "2.6.3", + "mongodb": "~6.20.0", "mpath": "0.9.0", - "mquery": "6.0.0", + "mquery": "5.0.0", "ms": "2.1.3", "sift": "17.1.3" }, "engines": { - "node": ">=20.19.0" + "node": ">=16.20.1" }, "funding": { "type": "opencollective", @@ -8126,17 +7938,22 @@ "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", "license": "MIT", + "peer": true, "engines": { "node": ">=4.0.0" } }, "node_modules/mquery": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/mquery/-/mquery-6.0.0.tgz", - "integrity": "sha512-b2KQNsmgtkscfeDgkYMcWGn9vZI9YoXh802VDEwE6qc50zxBFQ0Oo8ROkawbPAsXCY1/Z1yp0MagqsZStPWJjw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", + "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", "license": "MIT", + "peer": true, + "dependencies": { + "debug": "4.x" + }, "engines": { - "node": ">=20.19.0" + "node": ">=14.0.0" } }, "node_modules/ms": { @@ -8216,47 +8033,6 @@ "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/mysql2": { - "version": "3.15.3", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.15.3.tgz", - "integrity": "sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg==", - "license": "MIT", - "dependencies": { - "aws-ssl-profiles": "^1.1.1", - "denque": "^2.1.0", - "generate-function": "^2.3.1", - "iconv-lite": "^0.7.0", - "long": "^5.2.1", - "lru.min": "^1.0.0", - "named-placeholders": "^1.1.3", - "seq-queue": "^0.0.5", - "sqlstring": "^2.3.2" - }, - "engines": { - "node": ">= 8.0" - } - }, - "node_modules/named-placeholders": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz", - "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==", - "license": "MIT", - "dependencies": { - "lru-cache": "^7.14.1" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/named-placeholders/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, "node_modules/napi-postinstall": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", @@ -8595,6 +8371,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -8618,11 +8395,11 @@ } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "11.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", - "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", + "version": "11.2.4", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", + "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { "node": "20 || >=22" } @@ -8779,19 +8556,6 @@ "node": ">=4" } }, - "node_modules/postgres": { - "version": "3.4.7", - "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.7.tgz", - "integrity": "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==", - "license": "Unlicense", - "engines": { - "node": ">=12" - }, - "funding": { - "type": "individual", - "url": "https://github.com/sponsors/porsager" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -8860,55 +8624,30 @@ } }, "node_modules/prisma": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.0.1.tgz", - "integrity": "sha512-zp93MdFMSU1IHPEXbUHVUuD8wauh2BUm14OVxhxGrWJQQpXpda0rW4VSST2bci4raoldX64/wQxHKkl/wqDskQ==", + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.19.0.tgz", + "integrity": "sha512-F3eX7K+tWpkbhl3l4+VkFtrwJlLXbAM+f9jolgoUZbFcm1DgHZ4cq9AgVEgUym2au5Ad/TDLN8lg83D+M10ycw==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/config": "7.0.1", - "@prisma/dev": "0.13.0", - "@prisma/engines": "7.0.1", - "@prisma/studio-core": "0.8.2", - "mysql2": "3.15.3", - "postgres": "3.4.7" + "@prisma/config": "6.19.0", + "@prisma/engines": "6.19.0" }, "bin": { "prisma": "build/index.js" }, "engines": { - "node": "^20.19 || ^22.12 || >=24.0" + "node": ">=18.18" }, "peerDependencies": { - "better-sqlite3": ">=9.0.0", - "typescript": ">=5.4.0" + "typescript": ">=5.1.0" }, "peerDependenciesMeta": { - "better-sqlite3": { - "optional": true - }, "typescript": { "optional": true } } }, - "node_modules/proper-lockfile": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", - "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "retry": "^0.12.0", - "signal-exit": "^3.0.2" - } - }, - "node_modules/proper-lockfile/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==", - "license": "ISC" - }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -9047,33 +8786,6 @@ "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", "license": "Apache-2.0" }, - "node_modules/regexp-to-ast": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/regexp-to-ast/-/regexp-to-ast-0.5.0.tgz", - "integrity": "sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==", - "license": "MIT" - }, - "node_modules/remeda": { - "version": "2.21.3", - "resolved": "https://registry.npmjs.org/remeda/-/remeda-2.21.3.tgz", - "integrity": "sha512-XXrZdLA10oEOQhLLzEJEiFFSKi21REGAkHdImIb4rt/XXy8ORGXh5HCcpUOsElfPNDb+X6TA/+wkh+p2KffYmg==", - "license": "MIT", - "dependencies": { - "type-fest": "^4.39.1" - } - }, - "node_modules/remeda/node_modules/type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -9148,15 +8860,6 @@ "dev": true, "license": "ISC" }, - "node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, "node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", @@ -9262,11 +8965,6 @@ "node": ">= 18" } }, - "node_modules/seq-queue": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", - "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==" - }, "node_modules/serialize-javascript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", @@ -9302,6 +9000,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -9314,6 +9013,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -9395,12 +9095,14 @@ "version": "17.1.3", "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz", "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/signal-exit": { "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", "engines": { "node": ">=14" @@ -9455,6 +9157,7 @@ "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", "license": "MIT", + "peer": true, "dependencies": { "memory-pager": "^1.0.2" } @@ -9466,15 +9169,6 @@ "dev": true, "license": "BSD-3-Clause" }, - "node_modules/sqlstring": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", - "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -9507,12 +9201,6 @@ "node": ">= 0.8" } }, - "node_modules/std-env": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", - "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", - "license": "MIT" - }, "node_modules/streamsearch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", @@ -9699,6 +9387,15 @@ "node": ">=8" } }, + "node_modules/swagger-ui-dist": { + "version": "5.30.2", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.30.2.tgz", + "integrity": "sha512-HWCg1DTNE/Nmapt+0m2EPXFwNKNeKK4PwMjkwveN/zn1cV2Kxi9SURd+m0SpdcSgWEK/O64sf8bzXdtUhigtHA==", + "license": "Apache-2.0", + "dependencies": { + "@scarf/scarf": "=1.4.0" + } + }, "node_modules/symbol-observable": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", @@ -10034,6 +9731,7 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", "license": "MIT", + "peer": true, "dependencies": { "punycode": "^2.3.1" }, @@ -10292,7 +9990,7 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -10494,20 +10192,6 @@ "node": ">=10.12.0" } }, - "node_modules/valibot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.1.0.tgz", - "integrity": "sha512-Nk8lX30Qhu+9txPYTwM0cFlWLdPFsFr6LblzqIySfbZph9+BFsAHsNvHOymEviUepeIW6KFHzpX8TKhbptBXXw==", - "license": "MIT", - "peerDependencies": { - "typescript": ">=5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, "node_modules/validator": { "version": "13.15.23", "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.23.tgz", @@ -10565,6 +10249,7 @@ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", "license": "BSD-2-Clause", + "peer": true, "engines": { "node": ">=12" } @@ -10765,6 +10450,7 @@ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", "license": "MIT", + "peer": true, "dependencies": { "tr46": "^5.1.0", "webidl-conversions": "^7.0.0" @@ -10777,6 +10463,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -10949,15 +10636,6 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } - }, - "node_modules/zeptomatch": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/zeptomatch/-/zeptomatch-2.0.2.tgz", - "integrity": "sha512-H33jtSKf8Ijtb5BW6wua3G5DhnFjbFML36eFu+VdOoVY4HD9e7ggjqdM6639B+L87rjnR6Y+XeRzBXZdy52B/g==", - "license": "MIT", - "dependencies": { - "grammex": "^3.1.10" - } } } } diff --git a/package.json b/package.json index 9e0198d..9cbf424 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "reporting-api", "version": "0.0.1", "description": "", + "type": "commonjs", "author": "", "private": true, "license": "UNLICENSED", @@ -26,12 +27,12 @@ "@nestjs/mongoose": "^11.0.3", "@nestjs/platform-express": "^11.0.1", "@nestjs/schedule": "^6.0.1", - "@prisma/client": "^7.0.1", + "@nestjs/swagger": "^11.2.3", + "@prisma/client": "^6.17.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.3", "date-fns": "^4.1.0", - "mongoose": "^9.0.0", - "prisma": "^7.0.1", + "prisma": "^6.17.1", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1" }, diff --git a/prisma/schema.prisma b/prisma/schema.prisma index fe02085..7bc6b1b 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -5,15 +5,17 @@ // Try Prisma Accelerate: https://pris.ly/cli/accelerate-init generator client { - provider = "prisma-client" - output = "../generated/prisma" + provider = "prisma-client-js" } datasource db { provider = "postgresql" + url = env("DATABASE_URL") // ← AJOUTEZ CETTE LIGNE } + + model Transaction { id Int @id @default(autoincrement()) date DateTime @default(now()) diff --git a/src/Dockerfile b/src/Dockerfile index e69de29..13812d3 100644 --- a/src/Dockerfile +++ b/src/Dockerfile @@ -0,0 +1,76 @@ +# Stage 1: Build +FROM node:20-alpine AS builder + +# Définir le répertoire de travail +WORKDIR /app + +# Copier les fichiers de dépendances +COPY package*.json ./ + +# Copier le schema Prisma AVANT npm ci +COPY prisma ./prisma/ + +# Installer les dépendances avec --legacy-peer-deps pour résoudre les conflits +RUN npm ci --legacy-peer-deps + +# Copier le code source +COPY . . + +# Générer Prisma Client +RUN npx prisma generate + +# Builder l'application +RUN npm run build + +# Stage 2: Production +FROM node:20-alpine AS production + +# Installer dumb-init pour une meilleure gestion des signaux +RUN apk add --no-cache dumb-init + +# Créer un utilisateur non-root +RUN addgroup -g 1001 -S nodejs && \ + adduser -S nestjs -u 1001 + +# Définir le répertoire de travail +WORKDIR /app + +# Copier package.json et package-lock.json +COPY package*.json ./ + +# Copier le schema Prisma +COPY prisma ./prisma/ + +# Installer UNIQUEMENT les dépendances de production avec --legacy-peer-deps +RUN npm ci --omit=dev --legacy-peer-deps && \ + npm cache clean --force + +# 🔥 IMPORTANT: Générer Prisma Client en production +RUN npx prisma generate + +# Copier le code buildé depuis le builder +COPY --from=builder --chown=nestjs:nodejs /app/dist ./dist + +# 🔥 IMPORTANT: Copier les fichiers générés de Prisma depuis le builder +COPY --from=builder --chown=nestjs:nodejs /app/node_modules/.prisma ./node_modules/.prisma +COPY --from=builder --chown=nestjs:nodejs /app/node_modules/@prisma ./node_modules/@prisma + +# Si vous utilisez un output personnalisé dans schema.prisma, copiez aussi: +# COPY --from=builder --chown=nestjs:nodejs /app/generated ./generated + +# Changer le propriétaire des fichiers +RUN chown -R nestjs:nodejs /app + +# Utiliser l'utilisateur non-root +USER nestjs + +# Exposer le port +EXPOSE 3000 + +# Healthcheck +#HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \ +# CMD node -e "require('http').get('http://localhost:3000/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})" + +# Démarrer l'application avec dumb-init +ENTRYPOINT ["dumb-init", "--"] +CMD ["node", "dist/main"] \ No newline at end of file diff --git a/src/app.module.ts b/src/app.module.ts index 8662803..1490335 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,10 +1,51 @@ import { Module } from '@nestjs/common'; -import { AppController } from './app.controller'; -import { AppService } from './app.service'; +import { ConfigModule } from '@nestjs/config'; +import { ScheduleModule } from '@nestjs/schedule'; +import { MongooseModule } from '@nestjs/mongoose'; +import { PrismaModule } from './prisma/prisma.module'; +import { MongodbModule } from './mongodb/mongodb.module'; +import { SyncModule } from './sync/sync.module'; +import { ReportingModule } from './reporting/reporting.module'; @Module({ - imports: [], - controllers: [AppController], - providers: [AppService], + imports: [ + // Configuration globale + ConfigModule.forRoot({ + isGlobal: true, + envFilePath: '.env', + }), + + // Scheduler pour la synchronisation automatique + ScheduleModule.forRoot(), + + // Connexion MongoDB + MongooseModule.forRootAsync({ + useFactory: () => ({ + uri: process.env.MONGODB_URI, + /** + + + connectionFactory: (connection) => { + connection.on('connected', () => { + console.log('✅ MongoDB connected'); + }); + connection.on('error', (error) => { + console.error('❌ MongoDB connection error:', error); + }); + connection.on('disconnected', () => { + console.log('❌ MongoDB disconnected'); + }); + return connection; + }, + */ + }), + }), + + // Modules de l'application + PrismaModule, + MongodbModule, + SyncModule, + ReportingModule, + ], }) export class AppModule {} diff --git a/src/main.ts b/src/main.ts index f76bc8d..65ec096 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,8 +1,52 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { NestFactory } from '@nestjs/core'; +import { ValidationPipe } from '@nestjs/common'; +import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); - await app.listen(process.env.PORT ?? 3000); + + // Global prefix + app.setGlobalPrefix('api/v1'); + + // Validation + app.useGlobalPipes( + new ValidationPipe({ + whitelist: true, + transform: true, + forbidNonWhitelisted: true, + }), + ); + + // CORS + app.enableCors({ + origin: process.env.CORS_ORIGINS?.split(',') || '*', + credentials: true, + }); + + // Swagger + const config = new DocumentBuilder() + .setTitle('Payment Hub API') + .setDescription('Unified DCB Payment Aggregation Platform') + .setVersion('1.0.0') + .addBearerAuth() + .addTag('payments') + .addTag('subscriptions') + .build(); + + const document = SwaggerModule.createDocument(app, config); + SwaggerModule.setup('api/docs', app, document); + + app.getHttpAdapter().get('/api/swagger-json', (req, res) => { + res.json(document); + }); + + const port = process.env.PORT || 3004; + await app.listen(port); + console.log(`Application is running on: http://localhost:${port}`); + console.log(`Swagger docs: http://localhost:${port}/api/docs`); + console.log(`Swagger docs: http://localhost:${port}/api/swagger-json`); } bootstrap(); diff --git a/src/mongodb/mongodb.module.ts b/src/mongodb/mongodb.module.ts index e69de29..ca1d03c 100644 --- a/src/mongodb/mongodb.module.ts +++ b/src/mongodb/mongodb.module.ts @@ -0,0 +1,21 @@ +import { Module } from '@nestjs/common'; +import { MongooseModule } from '@nestjs/mongoose'; +import { + TransactionDoc, + TransactionSchema, +} from './schemas/transaction.schema'; +import { + SubscriptionDoc, + SubscriptionSchema, +} from './schemas/subscription.schema'; + +@Module({ + imports: [ + MongooseModule.forFeature([ + { name: TransactionDoc.name, schema: TransactionSchema }, + { name: SubscriptionDoc.name, schema: SubscriptionSchema }, + ]), + ], + exports: [MongooseModule], +}) +export class MongodbModule {} diff --git a/src/mongodb/schemas/subscription.schema.ts b/src/mongodb/schemas/subscription.schema.ts index e69de29..9cc4277 100644 --- a/src/mongodb/schemas/subscription.schema.ts +++ b/src/mongodb/schemas/subscription.schema.ts @@ -0,0 +1,51 @@ +import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; +import { Document } from 'mongoose'; + +@Schema({ collection: 'subscriptions', timestamps: true }) +export class SubscriptionDoc extends Document { + @Prop({ required: true, unique: true }) + subscriptionId: number; + + @Prop({ required: true }) + startDate: Date; + + @Prop() + endDate: Date; + + @Prop({ required: true }) + amount: number; + + @Prop({ required: true }) + currency: string; + + @Prop({ required: true }) + status: string; + + @Prop({ required: true }) + periodicity: string; + + @Prop({ required: true }) + merchantPartnerId: number; + + @Prop() + year: number; + + @Prop() + month: number; + + @Prop() + week: number; + + @Prop() + day: string; // Format: YYYY-MM-DD +} + +export const SubscriptionSchema = SchemaFactory.createForClass(SubscriptionDoc); + +// Index pour optimiser les requêtes +SubscriptionSchema.index({ merchantPartnerId: 1, startDate: -1 }); +SubscriptionSchema.index({ startDate: -1 }); +SubscriptionSchema.index({ day: 1, merchantPartnerId: 1 }); +SubscriptionSchema.index({ year: 1, month: 1 }); +SubscriptionSchema.index({ year: 1, week: 1 }); +SubscriptionSchema.index({ status: 1 }); diff --git a/src/mongodb/schemas/transaction.schema.ts b/src/mongodb/schemas/transaction.schema.ts index e69de29..677c8f4 100644 --- a/src/mongodb/schemas/transaction.schema.ts +++ b/src/mongodb/schemas/transaction.schema.ts @@ -0,0 +1,45 @@ +import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; +import { Document } from 'mongoose'; + +@Schema({ collection: 'transactions', timestamps: true }) +export class TransactionDoc extends Document { + @Prop({ required: true, unique: true }) + transactionId: number; + + @Prop({ required: true }) + date: Date; + + @Prop({ required: true }) + amount: number; + + @Prop({ required: true }) + tax: number; + + @Prop({ required: true }) + status: string; + + @Prop({ required: true }) + merchantPartnerId: number; + + @Prop() + year: number; + + @Prop() + month: number; + + @Prop() + week: number; + + @Prop() + day: string; // Format: YYYY-MM-DD +} + +export const TransactionSchema = SchemaFactory.createForClass(TransactionDoc); + +// Index pour optimiser les requêtes +TransactionSchema.index({ merchantPartnerId: 1, date: -1 }); +TransactionSchema.index({ date: -1 }); +TransactionSchema.index({ day: 1, merchantPartnerId: 1 }); +TransactionSchema.index({ year: 1, month: 1 }); +TransactionSchema.index({ year: 1, week: 1 }); +TransactionSchema.index({ status: 1 }); diff --git a/src/prisma/prisma.module.ts b/src/prisma/prisma.module.ts index e69de29..23c626e 100644 --- a/src/prisma/prisma.module.ts +++ b/src/prisma/prisma.module.ts @@ -0,0 +1,9 @@ +import { Module, Global } from '@nestjs/common'; +import { PrismaService } from './prisma.service'; + +@Global() +@Module({ + providers: [PrismaService], + exports: [PrismaService], +}) +export class PrismaModule {} diff --git a/src/prisma/prisma.service.ts b/src/prisma/prisma.service.ts index e69de29..d047a40 100644 --- a/src/prisma/prisma.service.ts +++ b/src/prisma/prisma.service.ts @@ -0,0 +1,18 @@ +import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common'; +import { PrismaClient } from '@prisma/client'; + //import { PrismaClient } from 'generated/prisma/client'; + @Injectable() +export class PrismaService + extends PrismaClient + implements OnModuleInit, OnModuleDestroy +{ + async onModuleInit() { + await this.$connect(); + console.log('✅ PostgreSQL connected via Prisma'); + } + + async onModuleDestroy() { + await this.$disconnect(); + console.log('❌ PostgreSQL disconnected'); + } +} diff --git a/src/reporting/dto/report-query.dto.ts b/src/reporting/dto/report-query.dto.ts index e69de29..dd38235 100644 --- a/src/reporting/dto/report-query.dto.ts +++ b/src/reporting/dto/report-query.dto.ts @@ -0,0 +1,34 @@ +import { IsEnum, IsOptional, IsInt, IsDateString } from 'class-validator'; +import { Transform } from 'class-transformer'; + +export enum ReportPeriod { + DAILY = 'daily', + WEEKLY = 'weekly', + MONTHLY = 'monthly', +} + +export enum ReportType { + TRANSACTION = 'transaction', + SUBSCRIPTION = 'subscription', +} + +export class ReportQueryDto { + @IsEnum(ReportPeriod) + period: ReportPeriod; + + @IsEnum(ReportType) + type: ReportType; + + @IsOptional() + @IsInt() + @Transform(({ value }) => parseInt(value)) + merchantPartnerId?: number; + + @IsOptional() + @IsDateString() + startDate?: string; + + @IsOptional() + @IsDateString() + endDate?: string; +} diff --git a/src/reporting/dto/report-response.dto.ts b/src/reporting/dto/report-response.dto.ts index e69de29..ad34df2 100644 --- a/src/reporting/dto/report-response.dto.ts +++ b/src/reporting/dto/report-response.dto.ts @@ -0,0 +1,29 @@ +export class ReportItemDto { + period: string; // "2024-01-15", "2024-W03", "2024-01" + totalAmount: number; + totalTax?: number; + count: number; + successCount?: number; + failedCount?: number; + pendingCount?: number; + activeCount?: number; + cancelledCount?: number; + merchantPartnerId?: number; +} + +export class ReportResponseDto { + type: string; // 'transaction' | 'subscription' + period: string; // 'daily' | 'weekly' | 'monthly' + startDate: string; + endDate: string; + merchantPartnerId?: number; + totalAmount: number; + totalCount: number; + items: ReportItemDto[]; + generatedAt: Date; + summary?: { + avgAmount?: number; + minAmount?: number; + maxAmount?: number; + }; +} diff --git a/src/reporting/reporting.controller.ts b/src/reporting/reporting.controller.ts index e69de29..cced5db 100644 --- a/src/reporting/reporting.controller.ts +++ b/src/reporting/reporting.controller.ts @@ -0,0 +1,150 @@ +import { + Controller, + Get, + Query, + Post, + HttpCode, + HttpStatus, + Logger, +} from '@nestjs/common'; +import { ReportingService } from './reporting.service'; +import { ReportPeriod, ReportType } from './dto/report-query.dto'; +import { ReportResponseDto } from './dto/report-response.dto'; +import { SyncService } from '../sync/sync.service'; + +@Controller('reporting') +export class ReportingController { + private readonly logger = new Logger(ReportingController.name); + + constructor( + private reportingService: ReportingService, + private syncService: SyncService, + ) {} + + @Get('transactions/daily') + async getTransactionsDaily( + @Query('merchantPartnerId') merchantPartnerId?: string, + @Query('startDate') startDate?: string, + @Query('endDate') endDate?: string, + ): Promise { + return this.reportingService.generateReport({ + period: ReportPeriod.DAILY, + type: ReportType.TRANSACTION, + merchantPartnerId: merchantPartnerId + ? parseInt(merchantPartnerId) + : undefined, + startDate, + endDate, + }); + } + + @Get('transactions/weekly') + async getTransactionsWeekly( + @Query('merchantPartnerId') merchantPartnerId?: string, + @Query('startDate') startDate?: string, + @Query('endDate') endDate?: string, + ): Promise { + return this.reportingService.generateReport({ + period: ReportPeriod.WEEKLY, + type: ReportType.TRANSACTION, + merchantPartnerId: merchantPartnerId + ? parseInt(merchantPartnerId) + : undefined, + startDate, + endDate, + }); + } + + @Get('transactions/monthly') + async getTransactionsMonthly( + @Query('merchantPartnerId') merchantPartnerId?: string, + @Query('startDate') startDate?: string, + @Query('endDate') endDate?: string, + ): Promise { + return this.reportingService.generateReport({ + period: ReportPeriod.MONTHLY, + type: ReportType.TRANSACTION, + merchantPartnerId: merchantPartnerId + ? parseInt(merchantPartnerId) + : undefined, + startDate, + endDate, + }); + } + + @Get('subscriptions/daily') + async getSubscriptionsDaily( + @Query('merchantPartnerId') merchantPartnerId?: string, + @Query('startDate') startDate?: string, + @Query('endDate') endDate?: string, + ): Promise { + return this.reportingService.generateReport({ + period: ReportPeriod.DAILY, + type: ReportType.SUBSCRIPTION, + merchantPartnerId: merchantPartnerId + ? parseInt(merchantPartnerId) + : undefined, + startDate, + endDate, + }); + } + + @Get('subscriptions/weekly') + async getSubscriptionsWeekly( + @Query('merchantPartnerId') merchantPartnerId?: string, + @Query('startDate') startDate?: string, + @Query('endDate') endDate?: string, + ): Promise { + return this.reportingService.generateReport({ + period: ReportPeriod.WEEKLY, + type: ReportType.SUBSCRIPTION, + merchantPartnerId: merchantPartnerId + ? parseInt(merchantPartnerId) + : undefined, + startDate, + endDate, + }); + } + + @Get('subscriptions/monthly') + async getSubscriptionsMonthly( + @Query('merchantPartnerId') merchantPartnerId?: string, + @Query('startDate') startDate?: string, + @Query('endDate') endDate?: string, + ): Promise { + return this.reportingService.generateReport({ + period: ReportPeriod.MONTHLY, + type: ReportType.SUBSCRIPTION, + merchantPartnerId: merchantPartnerId + ? parseInt(merchantPartnerId) + : undefined, + startDate, + endDate, + }); + } + + @Post('sync/full') + @HttpCode(HttpStatus.OK) + async triggerFullSync(): Promise<{ message: string; timestamp: Date }> { + this.logger.log('🔄 Manual full sync triggered'); + await this.syncService.fullSync(); + return { + message: 'Full sync completed successfully', + timestamp: new Date(), + }; + } + + @Post('sync/incremental') + @HttpCode(HttpStatus.OK) + async triggerIncrementalSync(): Promise<{ + message: string; + timestamp: Date; + }> { + this.logger.log('🔄 Manual incremental sync triggered'); + await this.syncService.incrementalSync(); + return { + message: 'Incremental sync completed successfully', + timestamp: new Date(), + }; + } +} diff --git a/src/reporting/reporting.module.ts b/src/reporting/reporting.module.ts new file mode 100644 index 0000000..c747e1c --- /dev/null +++ b/src/reporting/reporting.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { MongodbModule } from '../mongodb/mongodb.module'; +import { SyncModule } from '../sync/sync.module'; +import { ReportingController } from './reporting.controller'; +import { ReportingService } from './reporting.service'; + +@Module({ + imports: [MongodbModule, SyncModule], + controllers: [ReportingController], + providers: [ReportingService], +}) +export class ReportingModule {} diff --git a/src/reporting/reporting.module.ts b/src/reporting/reporting.module.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/reporting/reporting.service.ts b/src/reporting/reporting.service.ts index e69de29..eaa3374 100644 --- a/src/reporting/reporting.service.ts +++ b/src/reporting/reporting.service.ts @@ -0,0 +1,225 @@ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +import { Injectable, Logger } from '@nestjs/common'; +import { InjectModel } from '@nestjs/mongoose'; +import { Model } from 'mongoose'; +import { TransactionDoc } from '../mongodb/schemas/transaction.schema'; +import { SubscriptionDoc } from '../mongodb/schemas/subscription.schema'; +import { + ReportQueryDto, + ReportPeriod, + ReportType, +} from './dto/report-query.dto'; +import { ReportResponseDto, ReportItemDto } from './dto/report-response.dto'; + +@Injectable() +export class ReportingService { + private readonly logger = new Logger(ReportingService.name); + + constructor( + @InjectModel(TransactionDoc.name) + private transactionModel: Model, + @InjectModel(SubscriptionDoc.name) + private subscriptionModel: Model, + ) {} + + async generateReport(query: ReportQueryDto): Promise { + this.logger.log(`📊 Generating ${query.type} report - ${query.period}`); + + const { period, type, merchantPartnerId, startDate, endDate } = query; + const dateFilter = this.buildDateFilter(startDate, endDate, type); + const merchantFilter = merchantPartnerId ? { merchantPartnerId } : {}; + const filter = { ...dateFilter, ...merchantFilter }; + + if (type === ReportType.TRANSACTION) { + return this.generateTransactionReport(period, filter, query); + } else { + return this.generateSubscriptionReport(period, filter, query); + } + } + + private async generateTransactionReport( + period: ReportPeriod, + filter: any, + query: ReportQueryDto, + ): Promise { + const groupBy = this.getGroupByField(period); + + const pipeline: any[] = [ + { $match: filter }, + { + $group: { + _id: groupBy, + totalAmount: { $sum: '$amount' }, + totalTax: { $sum: '$tax' }, + count: { $sum: 1 }, + successCount: { + $sum: { $cond: [{ $eq: ['$status', 'SUCCESS'] }, 1, 0] }, + }, + failedCount: { + $sum: { $cond: [{ $eq: ['$status', 'FAILED'] }, 1, 0] }, + }, + pendingCount: { + $sum: { $cond: [{ $eq: ['$status', 'PENDING'] }, 1, 0] }, + }, + }, + }, + { $sort: { _id: 1 } }, + ]; + + if (filter.merchantPartnerId) { + pipeline[1].$group['merchantPartnerId'] = { + $first: '$merchantPartnerId', + }; + } + + const results = await this.transactionModel.aggregate(pipeline); + + const items: ReportItemDto[] = results.map((item) => ({ + period: this.formatPeriod(item._id, period), + totalAmount: Math.round(item.totalAmount * 100) / 100, + totalTax: Math.round(item.totalTax * 100) / 100, + count: item.count, + successCount: item.successCount, + failedCount: item.failedCount, + pendingCount: item.pendingCount, + merchantPartnerId: item.merchantPartnerId, + })); + + const totalAmount = items.reduce((sum, item) => sum + item.totalAmount, 0); + const totalCount = items.reduce((sum, item) => sum + item.count, 0); + + return { + type: 'transaction', + period: period, + startDate: query.startDate || '', + endDate: query.endDate || '', + merchantPartnerId: query.merchantPartnerId, + totalAmount: Math.round(totalAmount * 100) / 100, + totalCount, + items, + summary: { + avgAmount: + totalCount > 0 + ? Math.round((totalAmount / totalCount) * 100) / 100 + : 0, + minAmount: + items.length > 0 ? Math.min(...items.map((i) => i.totalAmount)) : 0, + maxAmount: + items.length > 0 ? Math.max(...items.map((i) => i.totalAmount)) : 0, + }, + generatedAt: new Date(), + }; + } + + private async generateSubscriptionReport( + period: ReportPeriod, + filter: any, + query: ReportQueryDto, + ): Promise { + const groupBy = this.getGroupByField(period); + + const pipeline: any[] = [ + { $match: filter }, + { + $group: { + _id: groupBy, + totalAmount: { $sum: '$amount' }, + count: { $sum: 1 }, + activeCount: { + $sum: { $cond: [{ $eq: ['$status', 'ACTIVE'] }, 1, 0] }, + }, + cancelledCount: { + $sum: { $cond: [{ $eq: ['$status', 'CANCELLED'] }, 1, 0] }, + }, + }, + }, + { $sort: { _id: 1 } }, + ]; + + if (filter.merchantPartnerId) { + pipeline[1].$group['merchantPartnerId'] = { + $first: '$merchantPartnerId', + }; + } + + const results = await this.subscriptionModel.aggregate(pipeline); + + const items: ReportItemDto[] = results.map((item) => ({ + period: this.formatPeriod(item._id, period), + totalAmount: Math.round(item.totalAmount * 100) / 100, + count: item.count, + activeCount: item.activeCount, + cancelledCount: item.cancelledCount, + merchantPartnerId: item.merchantPartnerId, + })); + + const totalAmount = items.reduce((sum, item) => sum + item.totalAmount, 0); + const totalCount = items.reduce((sum, item) => sum + item.count, 0); + + return { + type: 'subscription', + period: period, + startDate: query.startDate || '', + endDate: query.endDate || '', + merchantPartnerId: query.merchantPartnerId, + totalAmount: Math.round(totalAmount * 100) / 100, + totalCount, + items, + summary: { + avgAmount: + totalCount > 0 + ? Math.round((totalAmount / totalCount) * 100) / 100 + : 0, + minAmount: + items.length > 0 ? Math.min(...items.map((i) => i.totalAmount)) : 0, + maxAmount: + items.length > 0 ? Math.max(...items.map((i) => i.totalAmount)) : 0, + }, + generatedAt: new Date(), + }; + } + + private buildDateFilter( + startDate?: string, + endDate?: string, + type?: ReportType, + ): any { + if (!startDate && !endDate) return {}; + + const filter: any = {}; + const dateField = type === ReportType.TRANSACTION ? 'date' : 'startDate'; + + if (startDate) { + filter[dateField] = { $gte: new Date(startDate) }; + } + if (endDate) { + const end = new Date(endDate); + end.setHours(23, 59, 59, 999); + filter[dateField] = { ...filter[dateField], $lte: end }; + } + + return filter; + } + + private getGroupByField(period: ReportPeriod): any { + switch (period) { + case ReportPeriod.DAILY: + return '$day'; + case ReportPeriod.WEEKLY: + return { year: '$year', week: '$week' }; + case ReportPeriod.MONTHLY: + return { year: '$year', month: '$month' }; + } + } + + private formatPeriod(value: any, period: ReportPeriod): string { + if (period === ReportPeriod.DAILY) return value; + if (period === ReportPeriod.WEEKLY) { + return `${value.year}-W${String(value.week).padStart(2, '0')}`; + } + if (period === ReportPeriod.MONTHLY) { + return `${value.year}-${String(value.month).padStart(2, '0')}`; + } + return ''; + } +} diff --git a/src/sync/sync.module.ts b/src/sync/sync.module.ts index e69de29..038110d 100644 --- a/src/sync/sync.module.ts +++ b/src/sync/sync.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { MongodbModule } from '../mongodb/mongodb.module'; +import { SyncService } from './sync.service'; +import { SyncScheduler } from './sync.scheduler'; + +@Module({ + imports: [MongodbModule], + providers: [SyncService, SyncScheduler], + exports: [SyncService], +}) +export class SyncModule {} diff --git a/src/sync/sync.scheduler.ts b/src/sync/sync.scheduler.ts index e69de29..8f4eb2c 100644 --- a/src/sync/sync.scheduler.ts +++ b/src/sync/sync.scheduler.ts @@ -0,0 +1,21 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { Cron, CronExpression } from '@nestjs/schedule'; +import { SyncService } from './sync.service'; + +@Injectable() +export class SyncScheduler { + private readonly logger = new Logger(SyncScheduler.name); + + constructor(private syncService: SyncService) {} + + @Cron(CronExpression.EVERY_5_MINUTES) + async handleIncrementalSync() { + this.logger.log('⏰ Running scheduled incremental sync'); + try { + await this.syncService.incrementalSync(); + } catch (error) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + this.logger.error('❌ Scheduled sync failed', error.stack); + } + } +} diff --git a/src/sync/sync.service.ts b/src/sync/sync.service.ts index e69de29..554d9a7 100644 --- a/src/sync/sync.service.ts +++ b/src/sync/sync.service.ts @@ -0,0 +1,165 @@ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +import { Injectable, Logger } from '@nestjs/common'; +import { InjectModel } from '@nestjs/mongoose'; +import { Model } from 'mongoose'; +import { PrismaService } from '../prisma/prisma.service'; +import { TransactionDoc } from '../mongodb/schemas/transaction.schema'; +import { SubscriptionDoc } from '../mongodb/schemas/subscription.schema'; +import { getYear, getMonth, getWeek, format } from 'date-fns'; + +@Injectable() +export class SyncService { + private readonly logger = new Logger(SyncService.name); + + constructor( + private prisma: PrismaService, + @InjectModel(TransactionDoc.name) + private transactionModel: Model, + @InjectModel(SubscriptionDoc.name) + private subscriptionModel: Model, + ) {} + + async syncTransactions(fromDate?: Date): Promise { + this.logger.log('🔄 Starting transaction sync...'); + + try { + const whereClause = fromDate ? { updatedAt: { gte: fromDate } } : {}; + + const transactions = await this.prisma.payment.findMany({ + where: {}, + orderBy: { id: 'asc' }, + }); + + this.logger.log(`📊 Found ${transactions.length} transactions to sync`); + + if (transactions.length === 0) { + this.logger.log('✅ No transactions to sync'); + return; + } + + const batchSize = 100; + for (let i = 0; i < transactions.length; i += batchSize) { + const batch = transactions.slice(i, i + batchSize); + + const operations = batch.map((transaction) => { + const date = new Date(transaction.createdAt); + + return { + updateOne: { + filter: { transactionId: transaction.id }, + update: { + $set: { + transactionId: transaction.id, + date: transaction.createdAt, + amount: transaction.amount, + //todo add tax field in prisma payment model + tax: transaction.amount, + status: transaction.status, + merchantPartnerId: transaction.merchantPartnerId, + year: getYear(date), + month: getMonth(date) + 1, + week: getWeek(date), + day: format(date, 'yyyy-MM-dd'), + }, + }, + upsert: true, + }, + }; + }); + + await this.transactionModel.bulkWrite(operations); + this.logger.log( + `✅ Synced ${Math.min(i + batchSize, transactions.length)}/${transactions.length}`, + ); + } + + this.logger.log('✅ Transaction sync completed'); + } catch (error) { + this.logger.error('❌ Error syncing transactions', error.stack); + throw error; + } + } + + async syncSubscriptions(fromDate?: Date): Promise { + this.logger.log('🔄 Starting subscription sync...'); + + try { + const whereClause = fromDate ? { updatedAt: { gte: fromDate } } : {}; + + const subscriptions = await this.prisma.subscription.findMany({ + where: whereClause, + orderBy: { id: 'asc' }, + }); + + this.logger.log(`📊 Found ${subscriptions.length} subscriptions to sync`); + + if (subscriptions.length === 0) { + this.logger.log('✅ No subscriptions to sync'); + return; + } + + const batchSize = 100; + for (let i = 0; i < subscriptions.length; i += batchSize) { + const batch = subscriptions.slice(i, i + batchSize); + + const operations = batch.map((subscription) => { + const date = new Date(subscription.startDate); + + return { + updateOne: { + filter: { subscriptionId: subscription.id }, + update: { + $set: { + subscriptionId: subscription.id, + startDate: subscription.startDate, + endDate: subscription.endDate, + amount: subscription.amount, + currency: subscription.currency, + status: subscription.status, + periodicity: subscription.periodicity, + merchantPartnerId: subscription.merchantPartnerId, + year: getYear(date), + month: getMonth(date) + 1, + week: getWeek(date), + day: format(date, 'yyyy-MM-dd'), + }, + }, + upsert: true, + }, + }; + }); + + await this.subscriptionModel.bulkWrite(operations); + this.logger.log( + `✅ Synced ${Math.min(i + batchSize, subscriptions.length)}/${subscriptions.length}`, + ); + } + + this.logger.log('✅ Subscription sync completed'); + } catch (error) { + this.logger.error('❌ Error syncing subscriptions', error.stack); + throw error; + } + } + + async fullSync(): Promise { + this.logger.log('🚀 Starting full sync...'); + const startTime = Date.now(); + + await this.syncTransactions(); + await this.syncSubscriptions(); + + const duration = ((Date.now() - startTime) / 1000).toFixed(2); + this.logger.log(`🎉 Full sync completed in ${duration}s`); + } + + async incrementalSync(): Promise { + const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000); + this.logger.log(`🔄 Starting incremental sync...`); + + await this.syncTransactions(fiveMinutesAgo); + await this.syncSubscriptions(fiveMinutesAgo); + + this.logger.log('✅ Incremental sync completed'); + } +}