From 5dcd2d8a9fab9840111d6e987c61030cafa28731 Mon Sep 17 00:00:00 2001 From: realrafi Date: Fri, 31 Jul 2026 23:30:39 +0330 Subject: [PATCH] feat: added upload file module --- package.json | 9 + pnpm-lock.yaml | 847 +++++++++++++++++- src/common/enums/messages.enum.ts | 38 +- src/config/s3.config.ts | 29 + src/main.ts | 12 +- src/modules/uploader/DTO/upload-file.dto.ts | 19 + src/modules/uploader/constants.ts | 1 + .../uploader/interfaces/file.interface.ts | 41 + .../uploader/interfaces/s3.interface.ts | 37 + .../uploader/interfaces/service.interface.ts | 10 + src/modules/uploader/services/s3.service.ts | 132 +++ .../uploader/services/uploader.service.ts | 131 +++ src/modules/uploader/uploader.controller.ts | 32 + src/modules/uploader/uploader.module.ts | 13 + 14 files changed, 1333 insertions(+), 18 deletions(-) create mode 100644 src/config/s3.config.ts create mode 100755 src/modules/uploader/DTO/upload-file.dto.ts create mode 100644 src/modules/uploader/constants.ts create mode 100644 src/modules/uploader/interfaces/file.interface.ts create mode 100644 src/modules/uploader/interfaces/s3.interface.ts create mode 100644 src/modules/uploader/interfaces/service.interface.ts create mode 100644 src/modules/uploader/services/s3.service.ts create mode 100644 src/modules/uploader/services/uploader.service.ts create mode 100644 src/modules/uploader/uploader.controller.ts create mode 100644 src/modules/uploader/uploader.module.ts diff --git a/package.json b/package.json index aea9098..6c19e45 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,12 @@ "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { + "@aws-sdk/client-s3": "^3.1099.0", + "@aws-sdk/s3-request-presigner": "^3.1099.0", + "@fastify/multipart": "^10.1.0", + "@fastify/static": "^10.1.2", "@keyv/redis": "^5.1.6", + "@nest-lab/fastify-multer": "^1.3.0", "@nestjs/axios": "^4.0.1", "@nestjs/bullmq": "^11.0.4", "@nestjs/cache-manager": "^3.1.3", @@ -30,6 +35,7 @@ "@nestjs/jwt": "^11.0.2", "@nestjs/passport": "^11.0.5", "@nestjs/platform-express": "^11.1.28", + "@nestjs/platform-fastify": "^11.1.28", "@nestjs/swagger": "^11.4.6", "@nestjs/typeorm": "^11.0.3", "axios": "^1.18.1", @@ -38,6 +44,8 @@ "cache-manager": "^7.2.9", "class-transformer": "^0.5.1", "class-validator": "^0.15.1", + "fastify": "^5.11.0", + "fastify-multer": "^2.0.3", "ioredis": "^5.11.1", "joi": "^18.2.3", "keyv": "^5.6.0", @@ -59,6 +67,7 @@ "@types/express": "^5.0.0", "@types/jest": "^30.0.0", "@types/ms": "^2.1.0", + "@types/multer": "^2.2.0", "@types/node": "^22.10.7", "@types/passport-jwt": "^4.0.1", "@types/supertest": "^6.0.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 210dff8..85e03c6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,9 +8,24 @@ importers: .: dependencies: + '@aws-sdk/client-s3': + specifier: ^3.1099.0 + version: 3.1099.0 + '@aws-sdk/s3-request-presigner': + specifier: ^3.1099.0 + version: 3.1099.0 + '@fastify/multipart': + specifier: ^10.1.0 + version: 10.1.0 + '@fastify/static': + specifier: ^10.1.2 + version: 10.1.2 '@keyv/redis': specifier: ^5.1.6 version: 5.1.6(keyv@5.6.0) + '@nest-lab/fastify-multer': + specifier: ^1.3.0 + version: 1.3.0(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-fastify@11.1.28(@fastify/static@10.1.2)(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.28))(rxjs@7.8.2) '@nestjs/axios': specifier: ^4.0.1 version: 4.0.1(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.18.1)(rxjs@7.8.2) @@ -38,9 +53,12 @@ importers: '@nestjs/platform-express': specifier: ^11.1.28 version: 11.1.28(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.28) + '@nestjs/platform-fastify': + specifier: ^11.1.28 + version: 11.1.28(@fastify/static@10.1.2)(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.28) '@nestjs/swagger': specifier: ^11.4.6 - version: 11.4.6(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.28)(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2) + version: 11.4.6(@fastify/static@10.1.2)(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.28)(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2) '@nestjs/typeorm': specifier: ^11.0.3 version: 11.0.3(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.28)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@1.1.0(ioredis@5.11.1)(pg@8.22.0)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.9.3))) @@ -62,6 +80,12 @@ importers: class-validator: specifier: ^0.15.1 version: 0.15.1 + fastify: + specifier: ^5.11.0 + version: 5.11.0 + fastify-multer: + specifier: ^2.0.3 + version: 2.0.3 ioredis: specifier: ^5.11.1 version: 5.11.1 @@ -120,6 +144,9 @@ importers: '@types/ms': specifier: ^2.1.0 version: 2.1.0 + '@types/multer': + specifier: ^2.2.0 + version: 2.2.0 '@types/node': specifier: ^22.10.7 version: 22.20.1 @@ -205,6 +232,82 @@ packages: resolution: {integrity: sha512-/PZmyAlb2NGWPikRRuiWLdfHQd8Wrx6lX4HqvTcaDhlU43M3T0ud4PH2T3QDp7BzHYY92xtD8iPxX2asg67G1A==} engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + '@aws-sdk/checksums@3.1000.23': + resolution: {integrity: sha512-Hv4VX5+IdDYmAbOdiwZguz8fLh3WnE4VtyJwVNEHLulA+OYy7Z5L0ETGUlX2T4g8DLO8XxCV8CyWrtVL4uwqkg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/client-s3@3.1099.0': + resolution: {integrity: sha512-d6R4bTZQTlMKxXFj8kIf/KZj2xXAozL9ai94yCXQILrvm4jja2yaiqi/ktTD835G3z3FuFoYK+wNWANADilU7A==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/core@3.977.3': + resolution: {integrity: sha512-6YZTpF5Zzl0KdSrztM0l6ErKdnXrnnodIDYfayHE9SFfZU8Ubxls7H2XnfWT121jgwiG+WP1z5kyfVDsH5V4qA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-env@3.972.64': + resolution: {integrity: sha512-14kkR5aj1c7D+cYCrEcG2W3xw87wMYAEUeB/tMiQ2j0RVKzzdewd4mJw1+Q0JVLr4IFU1JHGDCyj0WqLrtIixg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-http@3.972.66': + resolution: {integrity: sha512-BrZxoXScBZ+nTOYy388zHkz1n5cS8C+uGFMozD4w9OKEWMq39Cu/9l/k385Hb54dtv0VIeh12f8h67o3xDNH9g==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-ini@3.973.9': + resolution: {integrity: sha512-d/mMvS+sQjSWNA6+JCldK1YB/jQmtoWM8Izj10mrOZdRCeWNVb8IYQuuyHHKKKXcpGo5Pd0LIK6onHbLvlhuVQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-login@3.972.71': + resolution: {integrity: sha512-l93922lXnTs5RjM3QrMCXSmXiEgLFLaQ0Lco9F3tJ08o0mzGdAj6m2nAXDuTMHoUuL0u7RKmrhm+Z8/7GCilyg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-node@3.972.75': + resolution: {integrity: sha512-sdFhR4E3HWZefGL6OsBhxqXdqtFvxGOh8VLfiu9yttPgVNcpEozEDPmrTYmXA3CIf6npUETVLveQPGQ7GBzVhg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-process@3.972.64': + resolution: {integrity: sha512-Q5noG5vfFo++bUaLGcjj8OHX5tmwa1O/5nDVMeaSSZgjzgvWbW6tgu/gSa+aENwcJXjoXDQh+8TRZR2zHds/aw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-sso@3.973.8': + resolution: {integrity: sha512-37okeZyRdVkGrU6nyKBPGqQvyl/7CEiMpUVOb0+BCNCIR0VAuBxNgjMb0mnsR1EXm8dqQFfQZOJ5YW/0sBfa1w==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-web-identity@3.972.70': + resolution: {integrity: sha512-Ps/f9USafScb6CSQTRPNMzdKL5x5XRwDIRgFvnuFWfClGZe7/fI7iCFqXdxKjc9LBxP28E7iUgNloGgfgw406w==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-sdk-s3@3.972.69': + resolution: {integrity: sha512-ziSQA8AkB+u4K/5t/SStFbeoiNTG04reUeddCLNCAgmdRJGQ3+MJcpZOXXLdD6uyfCqSK1/R6004gRdXvHbYQA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/nested-clients@3.997.38': + resolution: {integrity: sha512-jQDs+nxnmgo4+WPOmY373HIC8jx4KyYYMYDI74FWrzX3Ba9fYsV0dSA+7PNYNkhRbbRCRLre83tQ31F1ACCHkA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/s3-request-presigner@3.1099.0': + resolution: {integrity: sha512-BE8hClKx6hv7qNS17+0UQeaLQgcZoPGhskycUoMgBFkOl+N4+eEJCaVsiiyU2Sv5NrP/NVt4BgsXbNW0/hVlkQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/signature-v4-multi-region@3.996.43': + resolution: {integrity: sha512-lKekx8bLBXSv4O+cslk9Zfnw2XKSkWBs3uWL5QGhH2ZAQfNS7FE0vcSSN2vD/AhxX54ZTywWxR4STThoeOXlBA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/token-providers@3.1098.0': + resolution: {integrity: sha512-6cFAviffeqYGjrXn4FqGbWcTxB3nbpBpQXUm6MvnrWZThJaVBTHHnespHgSUh4yVLfzhipSh9H/XjzkHQd9P4g==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/types@3.974.2': + resolution: {integrity: sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/xml-builder@3.972.37': + resolution: {integrity: sha512-zKq4HQum8JwDyEuyfuI4bbiAcU0KxP6qy+9PR/IsR92IyE/DaBAikzAS50tjxip4bqIIANpCcG+Yyj6CVhXupg==} + engines: {node: '>=20.0.0'} + + '@aws/lambda-invoke-store@0.3.0': + resolution: {integrity: sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==} + engines: {node: '>=18.0.0'} + '@babel/code-frame@7.29.7': resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} engines: {node: '>=6.9.0'} @@ -431,6 +534,52 @@ packages: resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@fastify/accept-negotiator@2.0.1': + resolution: {integrity: sha512-/c/TW2bO/v9JeEgoD/g1G5GxGeCF1Hafdf79WPmUlgYiBXummY0oX3VVq4yFkKKVBKDNlaDUYoab7g38RpPqCQ==} + + '@fastify/ajv-compiler@4.0.5': + resolution: {integrity: sha512-KoWKW+MhvfTRWL4qrhUwAAZoaChluo0m0vbiJlGMt2GXvL4LVPQEjt8kSpHI3IBq5Rez8fg+XeH3cneztq+C7A==} + + '@fastify/busboy@1.2.1': + resolution: {integrity: sha512-7PQA7EH43S0CxcOa9OeAnaeA0oQ+e/DHNPZwSQM9CQHW76jle5+OvLdibRp/Aafs9KXbLhxyjOTkRjWUbQEd3Q==} + engines: {node: '>=14'} + + '@fastify/busboy@3.2.0': + resolution: {integrity: sha512-m9FVDXU3GT2ITSe0UaMA5rU3QkfC/UXtCU8y0gSN/GugTqtVldOBWIB5V6V3sbmenVZUIpU6f+mPEO2+m5iTaA==} + + '@fastify/cors@11.2.0': + resolution: {integrity: sha512-LbLHBuSAdGdSFZYTLVA3+Ch2t+sA6nq3Ejc6XLAKiQ6ViS2qFnvicpj0htsx03FyYeLs04HfRNBsz/a8SvbcUw==} + + '@fastify/deepmerge@3.2.1': + resolution: {integrity: sha512-N5Oqvltoa2r9z1tbx4xjky0oRR60v+T47Ic4J1ukoVQcptLOrIdRnCSdTGmOmajZuHVKlTnfcmrjyqsGEW1ztA==} + + '@fastify/error@4.2.0': + resolution: {integrity: sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==} + + '@fastify/fast-json-stringify-compiler@5.1.0': + resolution: {integrity: sha512-PxcYtKLbQ8Z+yApiqjK8FwxIwvEj38k2OiLc17u8dkJSlmfi2wHHPaSnaoqBPQqtvF8YVsDgDpP2snDCfFrpfw==} + + '@fastify/formbody@8.0.2': + resolution: {integrity: sha512-84v5J2KrkXzjgBpYnaNRPqwgMsmY7ZDjuj0YVuMR3NXCJRCgKEZy/taSP1wUYGn0onfxJpLyRGDLa+NMaDJtnA==} + + '@fastify/forwarded@3.0.2': + resolution: {integrity: sha512-NE8HgKLgYejV9lDpqkEFaDKMLYelJBVfHekhB0UKvX0ghagXRJqg68feg8er1NPXxG4N9i6vPxzt8E+3wHfcmA==} + + '@fastify/merge-json-schemas@0.2.1': + resolution: {integrity: sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==} + + '@fastify/multipart@10.1.0': + resolution: {integrity: sha512-b2r6CovmLQvLFJ5HJDtxVigZjcO9TwkRGslhiNQxsGJwilm5B3eqvXPOVLudC44zXMXJITpQ/iEATIE7zoXqcQ==} + + '@fastify/proxy-addr@5.1.0': + resolution: {integrity: sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw==} + + '@fastify/send@4.1.0': + resolution: {integrity: sha512-TMYeQLCBSy2TOFmV95hQWkiTYgC/SEx7vMdV+wnZVX4tt8VBLKzmH8vV9OzJehV0+XBfg+WxPMt5wp+JBUKsVw==} + + '@fastify/static@10.1.2': + resolution: {integrity: sha512-G/g18cG9tLutT/OVyN1AIsHIl9L1UwmJ+S3dkyhVpplIx0nEMicd7RGQ+uJLyhKKF4a3tTcQydccn3Mop1fX+Q==} + '@hapi/address@5.1.1': resolution: {integrity: sha512-A+po2d/dVoY7cYajycYI43ZbYMXukuopIsqCjh5QzsBCipDtdofHntljDlpccMjIfTy6UOkg+5KPriwYch2bXA==} engines: {node: '>=14.0.0'} @@ -746,6 +895,10 @@ packages: resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==} engines: {node: '>=8'} + '@lukeed/ms@2.0.2': + resolution: {integrity: sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==} + engines: {node: '>=8'} + '@microsoft/tsdoc@0.16.0': resolution: {integrity: sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA==} @@ -785,6 +938,13 @@ packages: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 + '@nest-lab/fastify-multer@1.3.0': + resolution: {integrity: sha512-/4LocG7pBhwwbqN33YiPbv+93NdxlL/z/B2EL+hpulIr+qn2eLs/CxMg8jHCmtGCodA7NFHFERqIh75Fc2fryQ==} + peerDependencies: + '@nestjs/common': ^9.0.0 || ^10.0.0 || ^11.0.0 + '@nestjs/platform-fastify': ^9.0.0 || ^10.0.0 || ^11.0.0 + rxjs: ^7.0.0 + '@nestjs/axios@4.0.1': resolution: {integrity: sha512-68pFJgu+/AZbWkGu65Z3r55bTsCPlgyKaV4BSG8yUAD72q1PPuyVRgUwFv6BxdnibTUHlyxm06FmYWNC+bjN7A==} peerDependencies: @@ -894,6 +1054,19 @@ packages: '@nestjs/common': ^11.0.0 '@nestjs/core': ^11.0.0 + '@nestjs/platform-fastify@11.1.28': + resolution: {integrity: sha512-utUfyxRzZsoFxz1GU3Z0OthHpijB605iqtxwFnk9yKowLrU1t1ZkxMUuad/csemImY6AsuaTpTjCecKbmfl+pQ==} + peerDependencies: + '@fastify/static': ^8.0.0 || ^9.0.0 + '@fastify/view': ^10.0.0 || ^11.0.0 || ^12.0.0 + '@nestjs/common': ^11.0.0 + '@nestjs/core': ^11.0.0 + peerDependenciesMeta: + '@fastify/static': + optional: true + '@fastify/view': + optional: true + '@nestjs/schematics@11.1.0': resolution: {integrity: sha512-lVxGZ46tcdItFMoXr6vyKWlnOsm1SZm/GUqAEDvy2RL4Q4O+3bkziAhrO7Y8JLssFUUvNFEGqAizI52WAxhjDw==} peerDependencies: @@ -949,6 +1122,9 @@ packages: '@paralleldrive/cuid2@2.3.1': resolution: {integrity: sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==} + '@pinojs/redact@0.4.0': + resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -981,6 +1157,30 @@ packages: '@sinonjs/fake-timers@15.4.0': resolution: {integrity: sha512-DsG+8/LscQIQg68J6Ef3dv10u6nVyetYn923s3/sus5eaGfTo1of5WMZSLf0UJc9KDuKPilPH0UDJCjvNbDNCA==} + '@smithy/core@3.31.1': + resolution: {integrity: sha512-CyogUINxvi7C7LDsh8Syo6hVJOT9ckz4rG8dRZfTJ8r91HkMY59PnNooaj7WcHyxEkxPfBAmbgztZU+xTo76lg==} + engines: {node: '>=18.0.0'} + + '@smithy/credential-provider-imds@4.4.16': + resolution: {integrity: sha512-QfuLWAkLzptffFW980AFeHZFdqds2B64rpEd3uJ6lgs3xVn9QegGMUgUcj+4d7dRrAsya3r58ZKpku97WcFb4w==} + engines: {node: '>=18.0.0'} + + '@smithy/fetch-http-handler@5.6.13': + resolution: {integrity: sha512-4fW86pEUOMbrD5nkbyl/tTvPHHWJFbuB2odl6ps9lWfHoXf9HWh3Q/Smh59qH1g7+c/BSZghX6bbUk4gsiMs8A==} + engines: {node: '>=18.0.0'} + + '@smithy/node-http-handler@4.9.13': + resolution: {integrity: sha512-Nmd/Nl35zfYrd+a6OO2cDJb3GPh9bgTjIUhcM+JFfjpp8/osCgboDV5nCT1I01Pv6R13eSKDKLSoVa5ZB6Zsfw==} + engines: {node: '>=18.0.0'} + + '@smithy/signature-v4@5.6.12': + resolution: {integrity: sha512-I6KLtq3H0qqSuV9vLglfi8puHqzygzWHOnI4z/Rdoo+q50vvo18vBRdPAvvEtcaKROz7Zn6qnPa14kRfPH6PcQ==} + engines: {node: '>=18.0.0'} + + '@smithy/types@4.16.1': + resolution: {integrity: sha512-0JFs3V2y2M9tKW5na/qxe69Zv+uxLMO7QBbhxF/FHu/Gp2NFZAAL9tWl9PU02xxo07pb3G9FTyjNc6D5uZrJIg==} + engines: {node: '>=18.0.0'} + '@sqltools/formatter@1.2.5': resolution: {integrity: sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==} @@ -1075,6 +1275,9 @@ packages: '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + '@types/multer@2.2.0': + resolution: {integrity: sha512-3U1troeqGV8Ntp7Q3klwf4zr23VEoqYVocYXaswm9+8z3O9UHDYAqLxjJ/h550iRADTjKdOdhhasXw6gD6kYtg==} + '@types/node@22.20.1': resolution: {integrity: sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==} @@ -1350,6 +1553,9 @@ packages: '@xtuc/long@4.2.2': resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} + abstract-logging@2.0.1: + resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==} + accepts@2.0.0: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} @@ -1474,6 +1680,13 @@ packages: asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + + avvio@9.3.0: + resolution: {integrity: sha512-g2tQ7LE7oOSqDfwEm3M+ZCMTJc7KiZCdJ4UwyZJb5ckTKyYu50OYmvv0mCFXPuYXoM4zkSt8zM9XQ9KCvxA74A==} + axios@1.18.1: resolution: {integrity: sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==} @@ -1528,6 +1741,9 @@ packages: resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==} engines: {node: '>=18'} + bowser@2.14.1: + resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==} + brace-expansion@1.1.16: resolution: {integrity: sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==} @@ -1713,6 +1929,10 @@ packages: resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} engines: {node: '>=18'} + content-disposition@2.0.1: + resolution: {integrity: sha512-e+H0ZXHSWYrENhQzw1LPuP4oF5MzVKmDU6d3hxlvaPEYLLg62MxtQNPRx4SYSuYJSBUgnQIG4HIN2tEtNv7Dog==} + engines: {node: '>=18'} + content-type@1.0.5: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} @@ -1732,6 +1952,10 @@ packages: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} + cookiejar@2.1.4: resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} @@ -1801,6 +2025,10 @@ packages: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} @@ -2005,6 +2233,9 @@ packages: resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} engines: {node: '>= 18'} + fast-decode-uri-component@1.0.1: + resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -2014,15 +2245,46 @@ packages: fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + fast-json-stringify@7.0.1: + resolution: {integrity: sha512-eRSayARSbbwlBjpP4vnTTIRD5QPcIrmihPxDeN1DtKnHPg66UuJLx+8hlK1kaFdjvzyQ/dzALoi4vwAQ+T+iZA==} + fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-querystring@1.1.2: + resolution: {integrity: sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==} + fast-safe-stringify@2.1.1: resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} fast-uri@3.1.3: resolution: {integrity: sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==} + fast-uri@4.1.1: + resolution: {integrity: sha512-YPOs1zD5TG2+EZt+r88LwF6mclA7TPkpwMP7ZN3TO2HiHS8TXvq7QA/17iJsV9dubcLo/f8eEYqMBruyQV21hQ==} + + fastify-multer@2.0.3: + resolution: {integrity: sha512-QnFqrRgxmUwWHTgX9uyQSu0C/hmVCfcxopqjApZ4uaZD5W9MJ+nHUlW4+9q7Yd3BRxDIuHvgiM5mjrh6XG8cAA==} + engines: {node: '>=10.17.0'} + + fastify-plugin@2.3.4: + resolution: {integrity: sha512-I+Oaj6p9oiRozbam30sh39BiuiqBda7yK2nmSPVwDCfIBlKnT8YB3MY+pRQc2Fcd07bf6KPGklHJaQ2Qu81TYQ==} + + fastify-plugin@5.1.0: + resolution: {integrity: sha512-FAIDA8eovSt5qcDgcBvDuX/v0Cjz0ohGhENZ/wpc3y+oZCY2afZ9Baqql3g/lC+OHRnciQol4ww7tuthOb9idw==} + + fastify-plugin@6.0.0: + resolution: {integrity: sha512-fZOty7z3O7vOliF6d8bHE3wiEh1KcNnKEQensSgTk9C1DvN6nRLS++XVd86v33Hw/8u9Un8A1zDrQ8ujcQDHEg==} + + fastify@5.10.0: + resolution: {integrity: sha512-A9L0ziuWGQHgEEVgF3davQ9vbD93IuX+lo2IsxapQmu5b/Y/ynn9m9K5JHt9dvyJXOFc5iN0Zk5GHEOqnzhWjg==} + + fastify@5.11.0: + resolution: {integrity: sha512-Y/Ecx1yt0hYzrQR+QVLbxaUN8wCX+MQzMevh29r5RRvlOIArmi4+WAXXaGl5Hw5gBr2wduZpZaT3gRCnXoyVgA==} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + fb-watchman@2.0.2: resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} @@ -2047,6 +2309,10 @@ packages: resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} engines: {node: '>= 18.0.0'} + find-my-way@9.6.0: + resolution: {integrity: sha512-Zf4Xve4RymLl7NgaavNebZ01joJ8MfVerOG43wy7SHLO+r+K0C6d/SE0BiR7AV5V1VOCFlOP7ecdo+I4qmiHrQ==} + engines: {node: '>=20'} + find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} @@ -2265,6 +2531,10 @@ packages: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} + ipaddr.js@2.4.0: + resolution: {integrity: sha512-9VGk3HGanVE6JoZXHiCpnGy5X0jYDnN4EA4lntFPj+1vIWlFhIylq2CrrCOJH9EAhc5CYhq18F2Av2tgoAPsYQ==} + engines: {node: '>= 10'} + is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} @@ -2491,6 +2761,9 @@ packages: json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + json-schema-ref-resolver@3.0.0: + resolution: {integrity: sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A==} + json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} @@ -2538,6 +2811,9 @@ packages: libphonenumber-js@1.13.9: resolution: {integrity: sha512-VNS5vWMM7r0P66BYv+TQJATxExEgLxN+34hfHDVhDkUsGAE4cRg0shCNSLTXNKm7nIUscC7AfB51TjxEeF7msQ==} + light-my-request@6.6.0: + resolution: {integrity: sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==} + lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -2666,6 +2942,11 @@ packages: engines: {node: '>=4.0.0'} hasBin: true + mime@3.0.0: + resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} + engines: {node: '>=10.0.0'} + hasBin: true + mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} @@ -2688,6 +2969,11 @@ packages: resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} engines: {node: '>=16 || 14 >=14.17'} + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -2762,6 +3048,10 @@ packages: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} + on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} + on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} @@ -2906,6 +3196,16 @@ packages: resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} engines: {node: '>=12'} + pino-abstract-transport@3.0.0: + resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==} + + pino-std-serializers@7.1.0: + resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==} + + pino@10.3.1: + resolution: {integrity: sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==} + hasBin: true + pirates@4.0.7: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} @@ -2951,6 +3251,12 @@ packages: resolution: {integrity: sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + process-warning@4.0.1: + resolution: {integrity: sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==} + + process-warning@5.1.0: + resolution: {integrity: sha512-jQSaVHsPgtyw60e1rQ/A+/ArPEj/S8pS/vFnyGa/gYFXrKk/6RuDkoqVDQ5NI5MmS01698ltlAk0NoDBNLujRw==} + proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -2970,6 +3276,9 @@ packages: resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==} engines: {node: '>=0.6'} + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + range-parser@1.3.0: resolution: {integrity: sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==} engines: {node: '>= 0.6'} @@ -2992,6 +3301,13 @@ packages: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} + real-require@0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + + real-require@1.0.0: + resolution: {integrity: sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g==} + redis-errors@1.2.0: resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} engines: {node: '>=4'} @@ -3027,6 +3343,17 @@ packages: resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} engines: {node: '>=8'} + ret@0.5.0: + resolution: {integrity: sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==} + engines: {node: '>=10'} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + router@2.2.0: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} @@ -3040,6 +3367,14 @@ packages: safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + safe-regex2@5.1.1: + resolution: {integrity: sha512-mOSBvHGDZMuIEZMdOz/aCEYDCv0E7nfcNsIhUF+/P+xC7Hyf3FkvymqgPbg9D1EdSGu+uKbJgy09K/RKKc7kJA==} + hasBin: true + + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -3051,6 +3386,9 @@ packages: resolution: {integrity: sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==} engines: {node: '>= 10.13.0'} + secure-json-parse@4.1.0: + resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==} + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -3068,6 +3406,9 @@ packages: resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} engines: {node: '>= 18'} + set-cookie-parser@2.7.2: + resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} + setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} @@ -3106,6 +3447,9 @@ packages: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} + sonic-boom@4.2.1: + resolution: {integrity: sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==} + source-map-support@0.5.13: resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} @@ -3280,6 +3624,13 @@ packages: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} + text-decoding@1.0.0: + resolution: {integrity: sha512-/0TJD42KDnVwKmDK6jj3xP7E2MG7SHAOG4tyTgyUCRPdHwvkquYNLEQltmdMa3owq3TkddCVcTsoctJI8VQNKA==} + + thread-stream@4.2.0: + resolution: {integrity: sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ==} + engines: {node: '>=20'} + tinyglobby@0.2.17: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} @@ -3287,6 +3638,10 @@ packages: tmpl@1.0.5: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} + toad-cache@3.7.4: + resolution: {integrity: sha512-m1TdR/rvT7kgGJZhspNtXdsdYk0fddFpJJFlG5s+UkPFo6lkLoZ3YLOaovPYjq1R75NP5JfeTlSHaOsE09peCg==} + engines: {node: '>=20'} + toidentifier@1.0.1: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} @@ -3671,6 +4026,180 @@ snapshots: transitivePeerDependencies: - chokidar + '@aws-sdk/checksums@3.1000.23': + dependencies: + '@aws-sdk/core': 3.977.3 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.31.1 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@aws-sdk/client-s3@3.1099.0': + dependencies: + '@aws-sdk/checksums': 3.1000.23 + '@aws-sdk/core': 3.977.3 + '@aws-sdk/credential-provider-node': 3.972.75 + '@aws-sdk/middleware-sdk-s3': 3.972.69 + '@aws-sdk/signature-v4-multi-region': 3.996.43 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.31.1 + '@smithy/fetch-http-handler': 5.6.13 + '@smithy/node-http-handler': 4.9.13 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@aws-sdk/core@3.977.3': + dependencies: + '@aws-sdk/types': 3.974.2 + '@aws-sdk/xml-builder': 3.972.37 + '@aws/lambda-invoke-store': 0.3.0 + '@smithy/core': 3.31.1 + '@smithy/signature-v4': 5.6.12 + '@smithy/types': 4.16.1 + bowser: 2.14.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-env@3.972.64': + dependencies: + '@aws-sdk/core': 3.977.3 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.31.1 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-http@3.972.66': + dependencies: + '@aws-sdk/core': 3.977.3 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.31.1 + '@smithy/fetch-http-handler': 5.6.13 + '@smithy/node-http-handler': 4.9.13 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-ini@3.973.9': + dependencies: + '@aws-sdk/core': 3.977.3 + '@aws-sdk/credential-provider-env': 3.972.64 + '@aws-sdk/credential-provider-http': 3.972.66 + '@aws-sdk/credential-provider-login': 3.972.71 + '@aws-sdk/credential-provider-process': 3.972.64 + '@aws-sdk/credential-provider-sso': 3.973.8 + '@aws-sdk/credential-provider-web-identity': 3.972.70 + '@aws-sdk/nested-clients': 3.997.38 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.31.1 + '@smithy/credential-provider-imds': 4.4.16 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-login@3.972.71': + dependencies: + '@aws-sdk/core': 3.977.3 + '@aws-sdk/nested-clients': 3.997.38 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.31.1 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-node@3.972.75': + dependencies: + '@aws-sdk/credential-provider-env': 3.972.64 + '@aws-sdk/credential-provider-http': 3.972.66 + '@aws-sdk/credential-provider-ini': 3.973.9 + '@aws-sdk/credential-provider-process': 3.972.64 + '@aws-sdk/credential-provider-sso': 3.973.8 + '@aws-sdk/credential-provider-web-identity': 3.972.70 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.31.1 + '@smithy/credential-provider-imds': 4.4.16 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-process@3.972.64': + dependencies: + '@aws-sdk/core': 3.977.3 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.31.1 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-sso@3.973.8': + dependencies: + '@aws-sdk/core': 3.977.3 + '@aws-sdk/nested-clients': 3.997.38 + '@aws-sdk/token-providers': 3.1098.0 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.31.1 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-web-identity@3.972.70': + dependencies: + '@aws-sdk/core': 3.977.3 + '@aws-sdk/nested-clients': 3.997.38 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.31.1 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-sdk-s3@3.972.69': + dependencies: + '@aws-sdk/core': 3.977.3 + '@aws-sdk/signature-v4-multi-region': 3.996.43 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.31.1 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@aws-sdk/nested-clients@3.997.38': + dependencies: + '@aws-sdk/core': 3.977.3 + '@aws-sdk/signature-v4-multi-region': 3.996.43 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.31.1 + '@smithy/fetch-http-handler': 5.6.13 + '@smithy/node-http-handler': 4.9.13 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@aws-sdk/s3-request-presigner@3.1099.0': + dependencies: + '@aws-sdk/core': 3.977.3 + '@aws-sdk/signature-v4-multi-region': 3.996.43 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.31.1 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@aws-sdk/signature-v4-multi-region@3.996.43': + dependencies: + '@aws-sdk/types': 3.974.2 + '@smithy/signature-v4': 5.6.12 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@aws-sdk/token-providers@3.1098.0': + dependencies: + '@aws-sdk/core': 3.977.3 + '@aws-sdk/nested-clients': 3.997.38 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.31.1 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@aws-sdk/types@3.974.2': + dependencies: + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@aws-sdk/xml-builder@3.972.37': + dependencies: + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@aws/lambda-invoke-store@0.3.0': {} + '@babel/code-frame@7.29.7': dependencies: '@babel/helper-validator-identifier': 7.29.7 @@ -3936,6 +4465,75 @@ snapshots: '@eslint/core': 0.17.0 levn: 0.4.1 + '@fastify/accept-negotiator@2.0.1': {} + + '@fastify/ajv-compiler@4.0.5': + dependencies: + ajv: 8.20.0 + ajv-formats: 3.0.1(ajv@8.20.0) + fast-uri: 3.1.3 + + '@fastify/busboy@1.2.1': + dependencies: + text-decoding: 1.0.0 + + '@fastify/busboy@3.2.0': {} + + '@fastify/cors@11.2.0': + dependencies: + fastify-plugin: 5.1.0 + toad-cache: 3.7.4 + + '@fastify/deepmerge@3.2.1': {} + + '@fastify/error@4.2.0': {} + + '@fastify/fast-json-stringify-compiler@5.1.0': + dependencies: + fast-json-stringify: 7.0.1 + + '@fastify/formbody@8.0.2': + dependencies: + fast-querystring: 1.1.2 + fastify-plugin: 5.1.0 + + '@fastify/forwarded@3.0.2': {} + + '@fastify/merge-json-schemas@0.2.1': + dependencies: + dequal: 2.0.3 + + '@fastify/multipart@10.1.0': + dependencies: + '@fastify/busboy': 3.2.0 + '@fastify/deepmerge': 3.2.1 + '@fastify/error': 4.2.0 + fastify-plugin: 6.0.0 + secure-json-parse: 4.1.0 + + '@fastify/proxy-addr@5.1.0': + dependencies: + '@fastify/forwarded': 3.0.2 + ipaddr.js: 2.4.0 + + '@fastify/send@4.1.0': + dependencies: + '@lukeed/ms': 2.0.2 + escape-html: 1.0.3 + fast-decode-uri-component: 1.0.1 + http-errors: 2.0.1 + mime: 3.0.0 + + '@fastify/static@10.1.2': + dependencies: + '@fastify/accept-negotiator': 2.0.1 + '@fastify/error': 4.2.0 + '@fastify/send': 4.1.0 + content-disposition: 2.0.1 + fastify-plugin: 6.0.0 + fastq: 1.20.1 + glob: 13.0.6 + '@hapi/address@5.1.1': dependencies: '@hapi/hoek': 11.0.7 @@ -4350,6 +4948,8 @@ snapshots: '@lukeed/csprng@1.1.0': {} + '@lukeed/ms@2.0.2': {} + '@microsoft/tsdoc@0.16.0': {} '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4': @@ -4377,6 +4977,13 @@ snapshots: '@tybys/wasm-util': 0.10.3 optional: true + '@nest-lab/fastify-multer@1.3.0(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-fastify@11.1.28(@fastify/static@10.1.2)(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.28))(rxjs@7.8.2)': + dependencies: + '@nestjs/common': 11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/platform-fastify': 11.1.28(@fastify/static@10.1.2)(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.28) + fastify-multer: 2.0.3 + rxjs: 7.8.2 + '@nestjs/axios@4.0.1(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.18.1)(rxjs@7.8.2)': dependencies: '@nestjs/common': 11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -4508,6 +5115,23 @@ snapshots: transitivePeerDependencies: - supports-color + '@nestjs/platform-fastify@11.1.28(@fastify/static@10.1.2)(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.28)': + dependencies: + '@fastify/cors': 11.2.0 + '@fastify/formbody': 8.0.2 + '@nestjs/common': 11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.28(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.28)(reflect-metadata@0.2.2)(rxjs@7.8.2) + fast-querystring: 1.1.2 + fastify: 5.10.0 + fastify-plugin: 6.0.0 + find-my-way: 9.6.0 + light-my-request: 6.6.0 + path-to-regexp: 8.4.2 + reusify: 1.1.0 + tslib: 2.8.1 + optionalDependencies: + '@fastify/static': 10.1.2 + '@nestjs/schematics@11.1.0(chokidar@4.0.3)(prettier@3.9.5)(typescript@5.9.3)': dependencies: '@angular-devkit/core': 19.2.24(chokidar@4.0.3) @@ -4521,7 +5145,7 @@ snapshots: transitivePeerDependencies: - chokidar - '@nestjs/swagger@11.4.6(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.28)(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)': + '@nestjs/swagger@11.4.6(@fastify/static@10.1.2)(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.28)(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)': dependencies: '@microsoft/tsdoc': 0.16.0 '@nestjs/common': 11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -4533,6 +5157,7 @@ snapshots: reflect-metadata: 0.2.2 swagger-ui-dist: 5.32.8 optionalDependencies: + '@fastify/static': 10.1.2 class-transformer: 0.5.1 class-validator: 0.15.1 @@ -4558,6 +5183,8 @@ snapshots: dependencies: '@noble/hashes': 1.8.0 + '@pinojs/redact@0.4.0': {} + '@pkgjs/parseargs@0.11.0': optional: true @@ -4579,6 +5206,39 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.1 + '@smithy/core@3.31.1': + dependencies: + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@smithy/credential-provider-imds@4.4.16': + dependencies: + '@smithy/core': 3.31.1 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@smithy/fetch-http-handler@5.6.13': + dependencies: + '@smithy/core': 3.31.1 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@smithy/node-http-handler@4.9.13': + dependencies: + '@smithy/core': 3.31.1 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@smithy/signature-v4@5.6.12': + dependencies: + '@smithy/core': 3.31.1 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@smithy/types@4.16.1': + dependencies: + tslib: 2.8.1 + '@sqltools/formatter@1.2.5': {} '@standard-schema/spec@1.1.0': {} @@ -4694,6 +5354,10 @@ snapshots: '@types/ms@2.1.0': {} + '@types/multer@2.2.0': + dependencies: + '@types/express': 5.0.6 + '@types/node@22.20.1': dependencies: undici-types: 6.21.0 @@ -4990,6 +5654,8 @@ snapshots: '@xtuc/long@4.2.2': {} + abstract-logging@2.0.1: {} + accepts@2.0.0: dependencies: mime-types: 3.0.2 @@ -5023,6 +5689,10 @@ snapshots: optionalDependencies: ajv: 8.18.0 + ajv-formats@3.0.1(ajv@8.20.0): + optionalDependencies: + ajv: 8.20.0 + ajv-keywords@3.5.2(ajv@6.15.0): dependencies: ajv: 6.15.0 @@ -5096,6 +5766,13 @@ snapshots: asynckit@0.4.0: {} + atomic-sleep@1.0.0: {} + + avvio@9.3.0: + dependencies: + '@fastify/error': 4.2.0 + fastq: 1.20.1 + axios@1.18.1: dependencies: follow-redirects: 1.16.0 @@ -5191,6 +5868,8 @@ snapshots: transitivePeerDependencies: - supports-color + bowser@2.14.1: {} + brace-expansion@1.1.16: dependencies: balanced-match: 1.0.2 @@ -5364,6 +6043,8 @@ snapshots: content-disposition@1.1.0: {} + content-disposition@2.0.1: {} + content-type@1.0.5: {} content-type@2.0.0: {} @@ -5374,6 +6055,8 @@ snapshots: cookie@0.7.2: {} + cookie@1.1.1: {} + cookiejar@2.1.4: {} cors@2.8.6: @@ -5424,6 +6107,8 @@ snapshots: depd@2.0.0: {} + dequal@2.0.3: {} + detect-libc@2.1.2: optional: true @@ -5655,18 +6340,94 @@ snapshots: transitivePeerDependencies: - supports-color + fast-decode-uri-component@1.0.1: {} + fast-deep-equal@3.1.3: {} fast-diff@1.3.0: {} fast-json-stable-stringify@2.1.0: {} + fast-json-stringify@7.0.1: + dependencies: + '@fastify/merge-json-schemas': 0.2.1 + ajv: 8.20.0 + ajv-formats: 3.0.1(ajv@8.20.0) + fast-uri: 4.1.1 + json-schema-ref-resolver: 3.0.0 + rfdc: 1.4.1 + fast-levenshtein@2.0.6: {} + fast-querystring@1.1.2: + dependencies: + fast-decode-uri-component: 1.0.1 + fast-safe-stringify@2.1.1: {} fast-uri@3.1.3: {} + fast-uri@4.1.1: {} + + fastify-multer@2.0.3: + dependencies: + '@fastify/busboy': 1.2.1 + append-field: 1.0.0 + concat-stream: 2.0.0 + fastify-plugin: 2.3.4 + mkdirp: 1.0.4 + on-finished: 2.4.1 + type-is: 1.6.18 + xtend: 4.0.2 + + fastify-plugin@2.3.4: + dependencies: + semver: 7.8.5 + + fastify-plugin@5.1.0: {} + + fastify-plugin@6.0.0: {} + + fastify@5.10.0: + dependencies: + '@fastify/ajv-compiler': 4.0.5 + '@fastify/error': 4.2.0 + '@fastify/fast-json-stringify-compiler': 5.1.0 + '@fastify/proxy-addr': 5.1.0 + abstract-logging: 2.0.1 + avvio: 9.3.0 + fast-json-stringify: 7.0.1 + find-my-way: 9.6.0 + light-my-request: 6.6.0 + pino: 10.3.1 + process-warning: 5.1.0 + rfdc: 1.4.1 + secure-json-parse: 4.1.0 + semver: 7.8.5 + toad-cache: 3.7.4 + + fastify@5.11.0: + dependencies: + '@fastify/ajv-compiler': 4.0.5 + '@fastify/error': 4.2.0 + '@fastify/fast-json-stringify-compiler': 5.1.0 + '@fastify/proxy-addr': 5.1.0 + abstract-logging: 2.0.1 + avvio: 9.3.0 + fast-json-stringify: 7.0.1 + find-my-way: 9.6.0 + light-my-request: 6.6.0 + pino: 10.3.1 + process-warning: 5.1.0 + rfdc: 1.4.1 + secure-json-parse: 4.1.0 + semver: 7.8.5 + toad-cache: 3.7.4 + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + fb-watchman@2.0.2: dependencies: bser: 2.1.1 @@ -5699,6 +6460,12 @@ snapshots: transitivePeerDependencies: - supports-color + find-my-way@9.6.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-querystring: 1.1.2 + safe-regex2: 5.1.1 + find-up@4.1.0: dependencies: locate-path: 5.0.0 @@ -5928,6 +6695,8 @@ snapshots: ipaddr.js@1.9.1: {} + ipaddr.js@2.4.0: {} + is-arrayish@0.2.1: {} is-extglob@2.1.1: {} @@ -6338,6 +7107,10 @@ snapshots: json-parse-even-better-errors@2.3.1: {} + json-schema-ref-resolver@3.0.0: + dependencies: + dequal: 2.0.3 + json-schema-traverse@0.4.1: {} json-schema-traverse@1.0.0: {} @@ -6395,6 +7168,12 @@ snapshots: libphonenumber-js@1.13.9: {} + light-my-request@6.6.0: + dependencies: + cookie: 1.1.1 + process-warning: 4.0.1 + set-cookie-parser: 2.7.2 + lines-and-columns@1.2.4: {} load-esm@1.0.3: {} @@ -6488,6 +7267,8 @@ snapshots: mime@2.6.0: {} + mime@3.0.0: {} + mimic-fn@2.1.0: {} minimatch@10.2.5: @@ -6506,6 +7287,8 @@ snapshots: minipass@7.1.3: {} + mkdirp@1.0.4: {} + ms@2.1.3: {} msgpackr-extract@3.0.4: @@ -6570,6 +7353,8 @@ snapshots: object-inspect@1.13.4: {} + on-exit-leak-free@2.1.2: {} + on-finished@2.4.1: dependencies: ee-first: 1.1.1 @@ -6714,6 +7499,26 @@ snapshots: picomatch@4.0.5: {} + pino-abstract-transport@3.0.0: + dependencies: + split2: 4.2.0 + + pino-std-serializers@7.1.0: {} + + pino@10.3.1: + dependencies: + '@pinojs/redact': 0.4.0 + atomic-sleep: 1.0.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 3.0.0 + pino-std-serializers: 7.1.0 + process-warning: 5.1.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 4.2.1 + thread-stream: 4.2.0 + pirates@4.0.7: {} pkg-dir@4.2.0: @@ -6747,6 +7552,10 @@ snapshots: react-is-18: react-is@18.3.1 react-is-19: react-is@19.2.7 + process-warning@4.0.1: {} + + process-warning@5.1.0: {} + proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 @@ -6763,6 +7572,8 @@ snapshots: es-define-property: 1.0.1 side-channel: 1.1.1 + quick-format-unescaped@4.0.4: {} + range-parser@1.3.0: {} raw-body@3.0.2: @@ -6784,6 +7595,10 @@ snapshots: readdirp@4.1.2: {} + real-require@0.2.0: {} + + real-require@1.0.0: {} + redis-errors@1.2.0: {} redis-parser@3.0.0: @@ -6809,6 +7624,12 @@ snapshots: onetime: 5.1.2 signal-exit: 3.0.7 + ret@0.5.0: {} + + reusify@1.1.0: {} + + rfdc@1.4.1: {} + router@2.2.0: dependencies: debug: 4.4.3 @@ -6829,6 +7650,12 @@ snapshots: safe-buffer@5.2.1: {} + safe-regex2@5.1.1: + dependencies: + ret: 0.5.0 + + safe-stable-stringify@2.5.0: {} + safer-buffer@2.1.2: {} schema-utils@3.3.0: @@ -6844,6 +7671,8 @@ snapshots: ajv-formats: 2.1.1(ajv@8.20.0) ajv-keywords: 5.1.0(ajv@8.20.0) + secure-json-parse@4.1.0: {} + semver@6.3.1: {} semver@7.8.5: {} @@ -6873,6 +7702,8 @@ snapshots: transitivePeerDependencies: - supports-color + set-cookie-parser@2.7.2: {} + setprototypeof@1.2.0: {} shebang-command@2.0.0: @@ -6915,6 +7746,10 @@ snapshots: slash@3.0.0: {} + sonic-boom@4.2.1: + dependencies: + atomic-sleep: 1.0.0 + source-map-support@0.5.13: dependencies: buffer-from: 1.1.2 @@ -7057,6 +7892,12 @@ snapshots: glob: 7.2.3 minimatch: 3.1.5 + text-decoding@1.0.0: {} + + thread-stream@4.2.0: + dependencies: + real-require: 1.0.0 + tinyglobby@0.2.17: dependencies: fdir: 6.5.0(picomatch@4.0.5) @@ -7064,6 +7905,8 @@ snapshots: tmpl@1.0.5: {} + toad-cache@3.7.4: {} + toidentifier@1.0.1: {} token-types@6.1.2: diff --git a/src/common/enums/messages.enum.ts b/src/common/enums/messages.enum.ts index 63147cf..ad48f34 100644 --- a/src/common/enums/messages.enum.ts +++ b/src/common/enums/messages.enum.ts @@ -1,37 +1,45 @@ export enum UserMessages { - USER_ALREADY_EXISTS = 'کاربر مورد نظر با این ایمیل یا شماره همراه در حال حاضر وجود دارد. لطفا وارد حساب شوید!', - USER_NOT_FOUND = 'کاربر مورد نظر پیدا نشد!', - USER_PASSWORD_INVALID = 'رمز وارد شده اشتباده است!', - USER_VALID_CREDENTIALS = 'اطلاعات وارد شده صحیح میباشد!' + USER_ALREADY_EXISTS = 'کاربر مورد نظر با این ایمیل یا شماره همراه در حال حاضر وجود دارد. لطفا وارد حساب شوید!', + USER_NOT_FOUND = 'کاربر مورد نظر پیدا نشد!', + USER_PASSWORD_INVALID = 'رمز وارد شده اشتباده است!', + USER_VALID_CREDENTIALS = 'اطلاعات وارد شده صحیح میباشد!', } export enum OTPMessages { - OTP_NOT_FOUND = 'این کد پیدا نشد! لطفا دوباره درخواست بدهید!', - OTP_EXPIRED = 'این کد منسوخ شده است لطفا دوباره درخواست بدهید!', - OTP_TOO_MANY_TRIES = 'تعداد تلاش ها بیش از حد مجاز است لطفا دوباره درخواست کد بدهید!', - OTP_INVALID = 'کد وارد شده نا معتبر است!' + OTP_NOT_FOUND = 'این کد پیدا نشد! لطفا دوباره درخواست بدهید!', + OTP_EXPIRED = 'این کد منسوخ شده است لطفا دوباره درخواست بدهید!', + OTP_TOO_MANY_TRIES = 'تعداد تلاش ها بیش از حد مجاز است لطفا دوباره درخواست کد بدهید!', + OTP_INVALID = 'کد وارد شده نا معتبر است!', } export enum UserRoleMessages { - USER_ROLE_ALREADY_EXISTS = 'نقش مورد نظر برای کاربر مد نظر وجود دارد!' + USER_ROLE_ALREADY_EXISTS = 'نقش مورد نظر برای کاربر مد نظر وجود دارد!', } export enum AuthMessages { - TOKEN_EXPIRED_OR_INVALID = 'توکن منسوخ شده یا نامعتبر است!', + TOKEN_EXPIRED_OR_INVALID = 'توکن منسوخ شده یا نامعتبر است!', } export enum OrganMessages { - ORGAN_NOT_FOUND = 'ارگان مورد نظر پیدا نشد!' + ORGAN_NOT_FOUND = 'ارگان مورد نظر پیدا نشد!', } export enum ComplaintMessages { - COMPLAINT_NOT_FOUND = 'شکایت مورد نظر پیدا نشد!' + COMPLAINT_NOT_FOUND = 'شکایت مورد نظر پیدا نشد!', } export enum AttachmentMessages { - ATTACHMENT_NOT_FOUND = 'ضمیمه مورد نظر پیدا نشد!' + ATTACHMENT_NOT_FOUND = 'ضمیمه مورد نظر پیدا نشد!', } export enum ReplyMessages { - REPLY_NOT_FOUND = 'پاسخ مورد نظر پیدا نشد!' -} \ No newline at end of file + REPLY_NOT_FOUND = 'پاسخ مورد نظر پیدا نشد!', +} + +export const enum UploaderMessage { + FILE_TYPE_NOT_ALLOWED = 'نوع فایل مجاز نیست', + FILE_SIZE_TOO_SMALL = 'فایل کوچکتر از حد مجاز است', + UPLOAD_FILE_INVALID = 'فایل معتبر نیست', + UPLOAD_FILE_TOO_LARGE = '[MAX_FILE_SIZE] فایل بزرگتر از حد مجاز است', + UPLOAD_FILE_TYPE_NOT_SUPPORTED = '[MIME_TYPES] نوع فایل مجاز نیست', +} diff --git a/src/config/s3.config.ts b/src/config/s3.config.ts new file mode 100644 index 0000000..e492ea4 --- /dev/null +++ b/src/config/s3.config.ts @@ -0,0 +1,29 @@ +import { FactoryProvider } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +import { S3_CONFIG } from 'src/modules/uploader/constants'; + +export const S3ConfigsProvider: FactoryProvider = { + provide: S3_CONFIG, + + inject: [ConfigService], + useFactory(configService: ConfigService) { + return { + accessKeyId: configService.getOrThrow('BUCKET_ACCESS_KEY'), + secretAccessKey: configService.getOrThrow('BUCKET_SECRET_KEY'), + endpoint: configService.getOrThrow('BUCKET_URL'), + region: configService.getOrThrow('BUCKET_REGION'), + bucketName: configService.getOrThrow('BUCKET_NAME'), + bucketUploadedUrl: configService.getOrThrow('BUCKET_UPLOAD_URL'), + }; + }, +}; + +export interface IS3Configs { + accessKeyId: string; + secretAccessKey: string; + endpoint: string; + region: string; + bucketName: string; + bucketUploadedUrl: string; +} diff --git a/src/main.ts b/src/main.ts index e646119..f49b573 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,5 +1,9 @@ import { NestFactory, Reflector } from '@nestjs/core'; import { AppModule } from './app.module'; +import { + FastifyAdapter, + NestFastifyApplication, +} from '@nestjs/platform-fastify'; import { ClassSerializerInterceptor, Logger, @@ -9,11 +13,17 @@ import { setupSwagger } from './config/swagger.config'; import { ConfigService } from '@nestjs/config'; import { AllExceptionFilter } from './common/filters/all-exception.filter'; import { ResponseInterceptor } from './common/interceptors/response.interceptor'; +import multipart from '@fastify/multipart'; async function bootstrap() { const logger = new Logger('APP'); - const app = await NestFactory.create(AppModule); + const app = await NestFactory.create( + AppModule, + new FastifyAdapter(), + ); + + await app.register(multipart); app.useGlobalPipes( new ValidationPipe({ diff --git a/src/modules/uploader/DTO/upload-file.dto.ts b/src/modules/uploader/DTO/upload-file.dto.ts new file mode 100755 index 0000000..dac523c --- /dev/null +++ b/src/modules/uploader/DTO/upload-file.dto.ts @@ -0,0 +1,19 @@ +import type { File } from "@nest-lab/fastify-multer"; +import { ApiProperty } from "@nestjs/swagger"; + +export class UploadSingleFileDto { + @ApiProperty({ type: "string", format: "binary", nullable: false }) + file: File; +} + +export class UploadMultipleFileDto { + @ApiProperty({ + required: true, + type: "array", + items: { + type: "string", + format: "binary", + }, + }) + files: File[]; +} diff --git a/src/modules/uploader/constants.ts b/src/modules/uploader/constants.ts new file mode 100644 index 0000000..89b6606 --- /dev/null +++ b/src/modules/uploader/constants.ts @@ -0,0 +1 @@ +export const S3_CONFIG = "S3_CONFIG"; \ No newline at end of file diff --git a/src/modules/uploader/interfaces/file.interface.ts b/src/modules/uploader/interfaces/file.interface.ts new file mode 100644 index 0000000..bc8a5ff --- /dev/null +++ b/src/modules/uploader/interfaces/file.interface.ts @@ -0,0 +1,41 @@ +import { File } from "@nest-lab/fastify-multer"; + +export interface FileUploadResult { + file: File; + url: string; +} + +export interface FileStreamResult { + stream: NodeJS.ReadableStream; + file: File; +} + +export interface FileMetadata { + width?: number; + height?: number; + duration?: number; + bitrate?: number; + codec?: string; + language?: string; +} + +export type FileType = "video" | "audio" | "image" | "document" | "text" | "unknown"; + +export interface FileValidationOptions { + maxFileSize: number; + allowedMimeTypes: string[]; +} + +export interface FileUploadOptions { + roomId?: string; + metadata?: Record; +} + +// File processing status +export type FileProcessingStatus = "pending" | "processing" | "completed" | "failed"; + +export interface FileProcessingResult { + status: FileProcessingStatus; + metadata?: FileMetadata; + error?: string; +} diff --git a/src/modules/uploader/interfaces/s3.interface.ts b/src/modules/uploader/interfaces/s3.interface.ts new file mode 100644 index 0000000..f510a4c --- /dev/null +++ b/src/modules/uploader/interfaces/s3.interface.ts @@ -0,0 +1,37 @@ +export interface S3UploadResult { + key: string; + url: string; + bucket: string; +} + +export interface S3UploadOptions { + contentType: string; + metadata?: Record; + acl?: "private" | "public-read" | "public-read-write"; +} + +export interface S3DownloadOptions { + expiresIn?: number; + responseContentType?: string; + responseContentDisposition?: string; +} + +export interface S3FileInfo { + key: string; + bucket: string; + size: number; + lastModified: Date; + contentType: string; + etag: string; +} + +export interface S3DeleteResult { + deleted: boolean; + key: string; +} + +export interface S3ListResult { + files: S3FileInfo[]; + continuationToken?: string; + isTruncated: boolean; +} diff --git a/src/modules/uploader/interfaces/service.interface.ts b/src/modules/uploader/interfaces/service.interface.ts new file mode 100644 index 0000000..c76fc37 --- /dev/null +++ b/src/modules/uploader/interfaces/service.interface.ts @@ -0,0 +1,10 @@ +export interface FileSearchOptions { + userId?: string; + roomId?: string; + fileType?: "video" | "audio" | "subtitle"; + isActive?: boolean; + limit?: number; + offset?: number; + sortBy?: "uploadedAt" | "size" | "originalName"; + sortOrder?: "ASC" | "DESC"; +} diff --git a/src/modules/uploader/services/s3.service.ts b/src/modules/uploader/services/s3.service.ts new file mode 100644 index 0000000..3cff1b0 --- /dev/null +++ b/src/modules/uploader/services/s3.service.ts @@ -0,0 +1,132 @@ +import { Readable } from "stream"; + +import { DeleteObjectCommand, GetObjectCommand, PutObjectCommand, S3Client } from "@aws-sdk/client-s3"; +import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; +import { Inject, Injectable, Logger } from "@nestjs/common"; + +import type { IS3Configs } from "src/config/s3.config"; +import { S3_CONFIG } from "../constants"; +import { FileType } from "../interfaces/file.interface"; +import { S3UploadResult } from "../interfaces/s3.interface"; + +@Injectable() +export class S3Service { + private readonly logger = new Logger(S3Service.name); + private readonly s3Client: S3Client; + + constructor(@Inject(S3_CONFIG) private readonly s3Configs: IS3Configs) { + this.s3Client = new S3Client({ + region: this.s3Configs.region, + endpoint: this.s3Configs.endpoint, + credentials: { + accessKeyId: this.s3Configs.accessKeyId, + secretAccessKey: this.s3Configs.secretAccessKey, + }, + }); + } + + /** + * Upload file to S3 + */ + async uploadFile(buffer: Buffer, key: string, contentType: string, metadata?: Record): Promise { + try { + const command = new PutObjectCommand({ + Bucket: this.s3Configs.bucketName, + Key: key, + Body: buffer, + ACL: "public-read", + ContentType: contentType, + Metadata: metadata, + }); + + await this.s3Client.send(command); + + const url = `${this.s3Configs.bucketUploadedUrl}/${key}`; + + this.logger.log(`File uploaded to S3: ${key}`); + + return { + key, + url, + bucket: this.s3Configs.bucketName, + }; + } catch (error: unknown) { + this.logger.error(`Failed to upload file to S3: ${(error as Error).message}`, (error as Error).stack); + throw error; + } + } + + /** + * Get file stream from S3 + */ + async getFileStream(key: string): Promise { + try { + const command = new GetObjectCommand({ + Bucket: this.s3Configs.bucketName, + Key: key, + }); + + const response = await this.s3Client.send(command); + return response.Body as Readable; + } catch (error: unknown) { + this.logger.error(`Failed to get file from S3: ${(error as Error).message}`, (error as Error).stack); + throw error; + } + } + + /** + * Generate presigned URL for file download + */ + async getPresignedUrl(key: string, expiresIn: number = 3600): Promise { + try { + const command = new GetObjectCommand({ + Bucket: this.s3Configs.bucketName, + Key: key, + }); + + return await getSignedUrl(this.s3Client, command, { expiresIn }); + } catch (error: unknown) { + this.logger.error(`Failed to generate presigned URL: ${(error as Error).message}`, (error as Error).stack); + throw error; + } + } + + /** + * Delete file from S3 + */ + async deleteFile(key: string): Promise { + try { + const command = new DeleteObjectCommand({ + Bucket: this.s3Configs.bucketName, + Key: key, + }); + + await this.s3Client.send(command); + this.logger.log(`File deleted from S3: ${key}`); + } catch (error: unknown) { + this.logger.error(`Failed to delete file from S3: ${(error as Error).message}`, (error as Error).stack); + throw error; + } + } + + /** + * Generate S3 key for file + */ + generateFileKey(originalName: string, fileType: FileType): string { + const timestamp = Date.now(); + const randomSuffix = Math.round(Math.random() * 1e9); + const extension = originalName.split(".").pop(); + + const basePathMap: Record = { + image: "images", + video: "videos", + audio: "audio", + document: "documents", + text: "texts", + unknown: "unknowns", + }; + + const basePath = basePathMap[fileType]; + return `${basePath}/${timestamp}-${randomSuffix}.${extension}`; + } +} diff --git a/src/modules/uploader/services/uploader.service.ts b/src/modules/uploader/services/uploader.service.ts new file mode 100644 index 0000000..750e416 --- /dev/null +++ b/src/modules/uploader/services/uploader.service.ts @@ -0,0 +1,131 @@ +import { File } from "@nest-lab/fastify-multer"; +import { BadRequestException, Injectable, Logger } from "@nestjs/common"; +import { ConfigService } from "@nestjs/config"; + +import { S3Service } from "./s3.service"; +import { UploaderMessage } from "src/common/enums/messages.enum"; +import { FileType } from "../interfaces/file.interface"; + +@Injectable() +export class UploaderService { + private readonly logger = new Logger(UploaderService.name); + private readonly maxFileSize: number; + private readonly allowedMimeTypes: string[]; + + constructor( + private readonly configService: ConfigService, + private readonly s3Service: S3Service, + ) { + this.maxFileSize = this.configService.get("MAX_FILE_SIZE", 1024 * 1024 * 100); // 100MB + this.allowedMimeTypes = [ + "video/mp4", + "video/webm", + "video/ogg", + "video/avi", + "video/mov", + "video/mkv", + "audio/mp3", + "audio/wav", + "audio/ogg", + "audio/webm", + "audio/m4a", + "audio/aac", + "audio/flac", + "audio/mpeg", + "image/jpeg", + "image/png", + "image/gif", + "image/webp", + "image/svg+xml", + "image/tiff", + "image/bmp", + "application/pdf", + "text/plain", + "text/csv", + ]; + } + + async uploadMultiple(files: File[]) { + return await Promise.all(files.map((file) => this.uploadFile(file))); + } + + /** + * Handle file upload + */ + async uploadFile(file: File) { + if (!file || !file.buffer || !file.size) { + throw new BadRequestException(UploaderMessage.UPLOAD_FILE_INVALID); + } + this.logger.log(`Uploading file: ${file.originalname}`); + + this.validateFile(file); + + const fileType = await this.determineFileType(file.mimetype); + + const s3Key = this.s3Service.generateFileKey(file.originalname, fileType); + + // Sanitize filename for HTTP headers (S3 metadata) + const sanitizedOriginalName = this.sanitizeFilenameForHeader(file.originalname); + + // Upload to S3 + const s3Result = await this.s3Service.uploadFile(file.buffer, s3Key, file.mimetype, { + originalName: sanitizedOriginalName, + uploadedBy: "admin", + }); + + this.logger.log(`File uploaded successfully: `); + + return { + file: s3Result, + url: s3Result.url, + }; + } + + /** + * Validate uploaded file + */ + private validateFile(file: File): void { + if (!file) throw new BadRequestException(UploaderMessage.UPLOAD_FILE_INVALID); + + if (file.size && file.size > this.maxFileSize) + throw new BadRequestException(UploaderMessage.UPLOAD_FILE_TOO_LARGE.replace("[MAX_FILE_SIZE]", this.maxFileSize.toString())); + + if (!this.allowedMimeTypes.includes(file.mimetype)) + throw new BadRequestException(UploaderMessage.UPLOAD_FILE_TYPE_NOT_SUPPORTED.replace("[MIME_TYPES]", file.mimetype)); + } + + /** + * Determine file type based on mimetype + */ + private async determineFileType(mimetype: string): Promise { + if (mimetype.startsWith("video/")) { + return "video"; + } else if (mimetype.startsWith("audio/")) { + return "audio"; + } else if (mimetype.startsWith("image/")) { + return "image"; + } else if (mimetype.startsWith("text/")) { + return "text"; + } else if (mimetype.startsWith("application/")) { + return "document"; + } + return "unknown"; + } + + /** + * Sanitize filename for use in HTTP headers + * Removes invalid characters that are not allowed in HTTP header values + * HTTP headers only allow ASCII printable characters (0x20-0x7E) excluding certain special chars + */ + private sanitizeFilenameForHeader(filename: string): string { + if (!filename) return ""; + + // Remove control characters (0x00-0x1F, 0x7F) and newlines/tabs + // Replace non-ASCII characters with underscore to keep the filename readable + // This ensures the header value is valid while preserving as much of the original name as possible + return filename + .replace(/[\x00-\x1F\x7F\r\n\t]/g, "") // Remove control characters, newlines, tabs + .replace(/[^\x20-\x7E]/g, "_") // Replace non-ASCII characters with underscore + .trim(); + } +} diff --git a/src/modules/uploader/uploader.controller.ts b/src/modules/uploader/uploader.controller.ts new file mode 100644 index 0000000..88cd218 --- /dev/null +++ b/src/modules/uploader/uploader.controller.ts @@ -0,0 +1,32 @@ +import { FileInterceptor, FilesInterceptor } from "@nest-lab/fastify-multer"; +import type { File } from "@nest-lab/fastify-multer"; +import { Controller, Post, UploadedFile, UploadedFiles, UseInterceptors } from "@nestjs/common"; +import { ApiBody, ApiConsumes, ApiOperation } from "@nestjs/swagger"; + +import { UploadMultipleFileDto, UploadSingleFileDto } from "./DTO/upload-file.dto"; +import { UploaderService } from "./services/uploader.service"; +import { AuthGuard } from "src/common/decorators/auth-guard.decorator"; + +@Controller("uploader") +@AuthGuard() +export class UploaderController { + constructor(private readonly uploaderService: UploaderService) {} + + @ApiOperation({ summary: "Upload a file (admin)" }) + @ApiConsumes("multipart/form-data") + @UseInterceptors(FileInterceptor("file")) + @ApiBody({ type: UploadSingleFileDto }) + @Post("single-file") + uploadFile(@UploadedFile() file: File) { + return this.uploaderService.uploadFile(file); + } + + @ApiOperation({ summary: "Uploads multiple files" }) + @ApiConsumes("multipart/form-data") + @UseInterceptors(FilesInterceptor("files")) + @ApiBody({ type: UploadMultipleFileDto }) + @Post("multi-file") + uploadFiles(@UploadedFiles() files: File[]) { + return this.uploaderService.uploadMultiple(files); + } +} diff --git a/src/modules/uploader/uploader.module.ts b/src/modules/uploader/uploader.module.ts new file mode 100644 index 0000000..2685f70 --- /dev/null +++ b/src/modules/uploader/uploader.module.ts @@ -0,0 +1,13 @@ +import { Module } from "@nestjs/common"; + +import { S3Service } from "./services/s3.service"; +import { UploaderService } from "./services/uploader.service"; +import { UploaderController } from "./uploader.controller"; +import { S3ConfigsProvider } from "src/config/s3.config"; + +@Module({ + controllers: [UploaderController], + providers: [UploaderService, S3Service, S3ConfigsProvider], + exports: [UploaderService], +}) +export class UploaderModule {}