diff --git a/package-lock.json b/package-lock.json index 55245ad..9a26cf6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,9 +9,9 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "@aws-sdk/client-s3": "^3.954.0", + "@aws-sdk/client-s3": "^3.962.0", "@aws-sdk/lib-storage": "^3.954.0", - "@aws-sdk/s3-request-presigner": "^3.954.0", + "@aws-sdk/s3-request-presigner": "^3.962.0", "@keyv/redis": "^5.1.5", "@nestjs-modules/mailer": "^2.0.2", "@nestjs/axios": "^4.0.1", @@ -463,65 +463,575 @@ } }, "node_modules/@aws-sdk/client-s3": { - "version": "3.954.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.954.0.tgz", - "integrity": "sha512-DoeySsljzjuWRzqoETLszHGKKOOWlzuGZh3oAF7TkYRsrwbuYYmttrWomb9koogaF0S5YSPwCMCUbKbpF0lbTA==", + "version": "3.962.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.962.0.tgz", + "integrity": "sha512-I2/1McBZCcM3PfM4ck8D6gnZR3K7+yl1fGkwTq/3ThEn9tdLjNwcdgTbPfxfX6LoecLrH9Ekoo+D9nmQ0T261w==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.954.0", - "@aws-sdk/credential-provider-node": "3.954.0", - "@aws-sdk/middleware-bucket-endpoint": "3.953.0", - "@aws-sdk/middleware-expect-continue": "3.953.0", - "@aws-sdk/middleware-flexible-checksums": "3.954.0", - "@aws-sdk/middleware-host-header": "3.953.0", - "@aws-sdk/middleware-location-constraint": "3.953.0", - "@aws-sdk/middleware-logger": "3.953.0", - "@aws-sdk/middleware-recursion-detection": "3.953.0", - "@aws-sdk/middleware-sdk-s3": "3.954.0", - "@aws-sdk/middleware-ssec": "3.953.0", - "@aws-sdk/middleware-user-agent": "3.954.0", - "@aws-sdk/region-config-resolver": "3.953.0", - "@aws-sdk/signature-v4-multi-region": "3.954.0", - "@aws-sdk/types": "3.953.0", - "@aws-sdk/util-endpoints": "3.953.0", - "@aws-sdk/util-user-agent-browser": "3.953.0", - "@aws-sdk/util-user-agent-node": "3.954.0", - "@smithy/config-resolver": "^4.4.4", - "@smithy/core": "^3.19.0", - "@smithy/eventstream-serde-browser": "^4.2.6", - "@smithy/eventstream-serde-config-resolver": "^4.3.6", - "@smithy/eventstream-serde-node": "^4.2.6", - "@smithy/fetch-http-handler": "^5.3.7", - "@smithy/hash-blob-browser": "^4.2.7", - "@smithy/hash-node": "^4.2.6", - "@smithy/hash-stream-node": "^4.2.6", - "@smithy/invalid-dependency": "^4.2.6", - "@smithy/md5-js": "^4.2.6", - "@smithy/middleware-content-length": "^4.2.6", - "@smithy/middleware-endpoint": "^4.4.0", - "@smithy/middleware-retry": "^4.4.16", - "@smithy/middleware-serde": "^4.2.7", - "@smithy/middleware-stack": "^4.2.6", - "@smithy/node-config-provider": "^4.3.6", - "@smithy/node-http-handler": "^4.4.6", - "@smithy/protocol-http": "^5.3.6", - "@smithy/smithy-client": "^4.10.1", - "@smithy/types": "^4.10.0", - "@smithy/url-parser": "^4.2.6", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/credential-provider-node": "3.962.0", + "@aws-sdk/middleware-bucket-endpoint": "3.957.0", + "@aws-sdk/middleware-expect-continue": "3.957.0", + "@aws-sdk/middleware-flexible-checksums": "3.957.0", + "@aws-sdk/middleware-host-header": "3.957.0", + "@aws-sdk/middleware-location-constraint": "3.957.0", + "@aws-sdk/middleware-logger": "3.957.0", + "@aws-sdk/middleware-recursion-detection": "3.957.0", + "@aws-sdk/middleware-sdk-s3": "3.957.0", + "@aws-sdk/middleware-ssec": "3.957.0", + "@aws-sdk/middleware-user-agent": "3.957.0", + "@aws-sdk/region-config-resolver": "3.957.0", + "@aws-sdk/signature-v4-multi-region": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@aws-sdk/util-endpoints": "3.957.0", + "@aws-sdk/util-user-agent-browser": "3.957.0", + "@aws-sdk/util-user-agent-node": "3.957.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/core": "^3.20.0", + "@smithy/eventstream-serde-browser": "^4.2.7", + "@smithy/eventstream-serde-config-resolver": "^4.3.7", + "@smithy/eventstream-serde-node": "^4.2.7", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/hash-blob-browser": "^4.2.8", + "@smithy/hash-node": "^4.2.7", + "@smithy/hash-stream-node": "^4.2.7", + "@smithy/invalid-dependency": "^4.2.7", + "@smithy/md5-js": "^4.2.7", + "@smithy/middleware-content-length": "^4.2.7", + "@smithy/middleware-endpoint": "^4.4.1", + "@smithy/middleware-retry": "^4.4.17", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/middleware-stack": "^4.2.7", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.15", - "@smithy/util-defaults-mode-node": "^4.2.18", - "@smithy/util-endpoints": "^3.2.6", - "@smithy/util-middleware": "^4.2.6", - "@smithy/util-retry": "^4.2.6", - "@smithy/util-stream": "^4.5.7", + "@smithy/util-defaults-mode-browser": "^4.3.16", + "@smithy/util-defaults-mode-node": "^4.2.19", + "@smithy/util-endpoints": "^3.2.7", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-retry": "^4.2.7", + "@smithy/util-stream": "^4.5.8", "@smithy/util-utf8": "^4.2.0", - "@smithy/util-waiter": "^4.2.6", + "@smithy/util-waiter": "^4.2.7", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/client-sso": { + "version": "3.958.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.958.0.tgz", + "integrity": "sha512-6qNCIeaMzKzfqasy2nNRuYnMuaMebCcCPP4J2CVGkA8QYMbIVKPlkn9bpB20Vxe6H/r3jtCCLQaOJjVTx/6dXg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/middleware-host-header": "3.957.0", + "@aws-sdk/middleware-logger": "3.957.0", + "@aws-sdk/middleware-recursion-detection": "3.957.0", + "@aws-sdk/middleware-user-agent": "3.957.0", + "@aws-sdk/region-config-resolver": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@aws-sdk/util-endpoints": "3.957.0", + "@aws-sdk/util-user-agent-browser": "3.957.0", + "@aws-sdk/util-user-agent-node": "3.957.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/core": "^3.20.0", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/hash-node": "^4.2.7", + "@smithy/invalid-dependency": "^4.2.7", + "@smithy/middleware-content-length": "^4.2.7", + "@smithy/middleware-endpoint": "^4.4.1", + "@smithy/middleware-retry": "^4.4.17", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/middleware-stack": "^4.2.7", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.16", + "@smithy/util-defaults-mode-node": "^4.2.19", + "@smithy/util-endpoints": "^3.2.7", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-retry": "^4.2.7", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/core": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.957.0.tgz", + "integrity": "sha512-DrZgDnF1lQZv75a52nFWs6MExihJF2GZB6ETZRqr6jMwhrk2kbJPUtvgbifwcL7AYmVqHQDJBrR/MqkwwFCpiw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.957.0", + "@aws-sdk/xml-builder": "3.957.0", + "@smithy/core": "^3.20.0", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/signature-v4": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.957.0.tgz", + "integrity": "sha512-475mkhGaWCr+Z52fOOVb/q2VHuNvqEDixlYIkeaO6xJ6t9qR0wpLt4hOQaR6zR1wfZV0SlE7d8RErdYq/PByog==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.957.0.tgz", + "integrity": "sha512-8dS55QHRxXgJlHkEYaCGZIhieCs9NU1HU1BcqQ4RfUdSsfRdxxktqUKgCnBnOOn0oD3PPA8cQOCAVgIyRb3Rfw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/util-stream": "^4.5.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.962.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.962.0.tgz", + "integrity": "sha512-h0kVnXLW2d3nxbcrR/Pfg3W/+YoCguasWz7/3nYzVqmdKarGrpJzaFdoZtLgvDSZ8VgWUC4lWOTcsDMV0UNqUQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.957.0", + "@aws-sdk/credential-provider-env": "3.957.0", + "@aws-sdk/credential-provider-http": "3.957.0", + "@aws-sdk/credential-provider-login": "3.962.0", + "@aws-sdk/credential-provider-process": "3.957.0", + "@aws-sdk/credential-provider-sso": "3.958.0", + "@aws-sdk/credential-provider-web-identity": "3.958.0", + "@aws-sdk/nested-clients": "3.958.0", + "@aws-sdk/types": "3.957.0", + "@smithy/credential-provider-imds": "^4.2.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-login": { + "version": "3.962.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.962.0.tgz", + "integrity": "sha512-kHYH6Av2UifG3mPkpPUNRh/PuX6adaAcpmsclJdHdxlixMCRdh8GNeEihq480DC0GmfqdpoSf1w2CLmLLPIS6w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.957.0", + "@aws-sdk/nested-clients": "3.958.0", + "@aws-sdk/types": "3.957.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.962.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.962.0.tgz", + "integrity": "sha512-CS78NsWRxLa+nWqeWBEYMZTLacMFIXs1C5WJuM9kD05LLiWL32ksljoPsvNN24Bc7rCSQIIMx/U3KGvkDVZMVg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.957.0", + "@aws-sdk/credential-provider-http": "3.957.0", + "@aws-sdk/credential-provider-ini": "3.962.0", + "@aws-sdk/credential-provider-process": "3.957.0", + "@aws-sdk/credential-provider-sso": "3.958.0", + "@aws-sdk/credential-provider-web-identity": "3.958.0", + "@aws-sdk/types": "3.957.0", + "@smithy/credential-provider-imds": "^4.2.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.957.0.tgz", + "integrity": "sha512-/KIz9kadwbeLy6SKvT79W81Y+hb/8LMDyeloA2zhouE28hmne+hLn0wNCQXAAupFFlYOAtZR2NTBs7HBAReJlg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.958.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.958.0.tgz", + "integrity": "sha512-CBYHJ5ufp8HC4q+o7IJejCUctJXWaksgpmoFpXerbjAso7/Fg7LLUu9inXVOxlHKLlvYekDXjIUBXDJS2WYdgg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.958.0", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/token-providers": "3.958.0", + "@aws-sdk/types": "3.957.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.958.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.958.0.tgz", + "integrity": "sha512-dgnvwjMq5Y66WozzUzxNkCFap+umHUtqMMKlr8z/vl9NYMLem/WUbWNpFFOVFWquXikc+ewtpBMR4KEDXfZ+KA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.957.0", + "@aws-sdk/nested-clients": "3.958.0", + "@aws-sdk/types": "3.957.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.957.0.tgz", + "integrity": "sha512-BBgKawVyfQZglEkNTuBBdC3azlyqNXsvvN4jPkWAiNYcY0x1BasaJFl+7u/HisfULstryweJq/dAvIZIxzlZaA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.957.0", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-logger": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.957.0.tgz", + "integrity": "sha512-w1qfKrSKHf9b5a8O76yQ1t69u6NWuBjr5kBX+jRWFx/5mu6RLpqERXRpVJxfosbep7k3B+DSB5tZMZ82GKcJtQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.957.0", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.957.0.tgz", + "integrity": "sha512-D2H/WoxhAZNYX+IjkKTdOhOkWQaK0jjJrDBj56hKjU5c9ltQiaX/1PqJ4dfjHntEshJfu0w+E6XJ+/6A6ILBBA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.957.0", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.957.0.tgz", + "integrity": "sha512-5B2qY2nR2LYpxoQP0xUum5A1UNvH2JQpLHDH1nWFNF/XetV7ipFHksMxPNhtJJ6ARaWhQIDXfOUj0jcnkJxXUg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@aws-sdk/util-arn-parser": "3.957.0", + "@smithy/core": "^3.20.0", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/signature-v4": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-stream": "^4.5.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.957.0.tgz", + "integrity": "sha512-50vcHu96XakQnIvlKJ1UoltrFODjsq2KvtTgHiPFteUS884lQnK5VC/8xd1Msz/1ONpLMzdCVproCQqhDTtMPQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@aws-sdk/util-endpoints": "3.957.0", + "@smithy/core": "^3.20.0", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/nested-clients": { + "version": "3.958.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.958.0.tgz", + "integrity": "sha512-/KuCcS8b5TpQXkYOrPLYytrgxBhv81+5pChkOlhegbeHttjM69pyUpQVJqyfDM/A7wPLnDrzCAnk4zaAOkY0Nw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/middleware-host-header": "3.957.0", + "@aws-sdk/middleware-logger": "3.957.0", + "@aws-sdk/middleware-recursion-detection": "3.957.0", + "@aws-sdk/middleware-user-agent": "3.957.0", + "@aws-sdk/region-config-resolver": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@aws-sdk/util-endpoints": "3.957.0", + "@aws-sdk/util-user-agent-browser": "3.957.0", + "@aws-sdk/util-user-agent-node": "3.957.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/core": "^3.20.0", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/hash-node": "^4.2.7", + "@smithy/invalid-dependency": "^4.2.7", + "@smithy/middleware-content-length": "^4.2.7", + "@smithy/middleware-endpoint": "^4.4.1", + "@smithy/middleware-retry": "^4.4.17", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/middleware-stack": "^4.2.7", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.16", + "@smithy/util-defaults-mode-node": "^4.2.19", + "@smithy/util-endpoints": "^3.2.7", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-retry": "^4.2.7", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.957.0.tgz", + "integrity": "sha512-V8iY3blh8l2iaOqXWW88HbkY5jDoWjH56jonprG/cpyqqCnprvpMUZWPWYJoI8rHRf2bqzZeql1slxG6EnKI7A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.957.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.957.0.tgz", + "integrity": "sha512-t6UfP1xMUigMMzHcb7vaZcjv7dA2DQkk9C/OAP1dKyrE0vb4lFGDaTApi17GN6Km9zFxJthEMUbBc7DL0hq1Bg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-sdk-s3": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@smithy/protocol-http": "^5.3.7", + "@smithy/signature-v4": "^5.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/token-providers": { + "version": "3.958.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.958.0.tgz", + "integrity": "sha512-UCj7lQXODduD1myNJQkV+LYcGYJ9iiMggR8ow8Hva1g3A/Na5imNXzz6O67k7DAee0TYpy+gkNw+SizC6min8Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.957.0", + "@aws-sdk/nested-clients": "3.958.0", + "@aws-sdk/types": "3.957.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/types": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.957.0.tgz", + "integrity": "sha512-wzWC2Nrt859ABk6UCAVY/WYEbAd7FjkdrQL6m24+tfmWYDNRByTJ9uOgU/kw9zqLCAwb//CPvrJdhqjTznWXAg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-arn-parser": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.957.0.tgz", + "integrity": "sha512-Aj6m+AyrhWyg8YQ4LDPg2/gIfGHCEcoQdBt5DeSFogN5k9mmJPOJ+IAmNSWmWRjpOxEy6eY813RNDI6qS97M0g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-endpoints": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.957.0.tgz", + "integrity": "sha512-xwF9K24mZSxcxKS3UKQFeX/dPYkEps9wF1b+MGON7EvnbcucrJGyQyK1v1xFPn1aqXkBTFi+SZaMRx5E5YCVFw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.957.0", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "@smithy/util-endpoints": "^3.2.7", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.957.0.tgz", + "integrity": "sha512-exueuwxef0lUJRnGaVkNSC674eAiWU07ORhxBnevFFZEKisln+09Qrtw823iyv5I1N8T+wKfh95xvtWQrNKNQw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.957.0", + "@smithy/types": "^4.11.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.957.0.tgz", + "integrity": "sha512-ycbYCwqXk4gJGp0Oxkzf2KBeeGBdTxz559D41NJP8FlzSej1Gh7Rk40Zo6AyTfsNWkrl/kVi1t937OIzC5t+9Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/xml-builder": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.957.0.tgz", + "integrity": "sha512-Ai5iiQqS8kJ5PjzMhWcLKN0G2yasAkvpnPlq2EnqlIMdB48HsizElt62qcktdxp4neRMyGkFq4NzgmDbXnhRiA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0", + "fast-xml-parser": "5.2.5", "tslib": "^2.6.2" }, "engines": { @@ -584,6 +1094,7 @@ "version": "3.954.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.954.0.tgz", "integrity": "sha512-FVyMAvlFhLK68DHWB1lSkCRTm25xl38bIZDd+jKt5+yDolCrG5+n9aIN8AA8jNO1HNGhZuMjSIQm9r5rGmJH8g==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", @@ -633,6 +1144,7 @@ "version": "3.954.0", "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.954.0.tgz", "integrity": "sha512-5oYO5RP+mvCNXNj8XnF9jZo0EP0LTseYOJVNQYcii1D9DJqzHL3HJWurYh7cXxz7G7eDyvVYA01O9Xpt34TdoA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.953.0", @@ -653,10 +1165,24 @@ "node": ">=18.0.0" } }, + "node_modules/@aws-sdk/crc64-nvme": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/crc64-nvme/-/crc64-nvme-3.957.0.tgz", + "integrity": "sha512-qSwSfI+qBU9HDsd6/4fM9faCxYJx2yDuHtj+NVOQ6XYDWQzFab/hUdwuKZ77Pi6goLF1pBZhJ2azaC2w7LbnTA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@aws-sdk/credential-provider-env": { "version": "3.954.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.954.0.tgz", "integrity": "sha512-2HNkqBjfsvyoRuPAiFh86JBFMFyaCNhL4VyH6XqwTGKZffjG7hdBmzXPy7AT7G3oFh1k/1Zc27v0qxaKoK7mBA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "3.954.0", @@ -673,6 +1199,7 @@ "version": "3.954.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.954.0.tgz", "integrity": "sha512-CrWD5300+NE1OYRnSVDxoG7G0b5cLIZb7yp+rNQ5Jq/kqnTmyJXpVAsivq+bQIDaGzPXhadzpAMIoo7K/aHaag==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "3.954.0", @@ -694,6 +1221,7 @@ "version": "3.954.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.954.0.tgz", "integrity": "sha512-WAFD8pVwRSoBsuXcoD+s/hrdsP9Z0PNUedSgkOGExuJVAabpM2cIIMzYNsdHio9XFZUSqHkv8mF5mQXuIZvuzg==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "3.954.0", @@ -719,6 +1247,7 @@ "version": "3.954.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.954.0.tgz", "integrity": "sha512-EYqaBWwdVbVK7prmsmgTWLPptoWREplPkFMFscOpVmseDvf/0IjYNbNLLtfuhy/6L7ZBGI9wat2k4u0MRivvxA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "3.954.0", @@ -738,6 +1267,7 @@ "version": "3.954.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.954.0.tgz", "integrity": "sha512-UPBjw7Lnly5i+/rES8Z5U+nPaumzEUYOE/wrHkxyH6JjwFWn8w7R07fE5Z5cgYlIq1U1lQ7sxYwB3wHPpQ65Aw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/credential-provider-env": "3.954.0", @@ -761,6 +1291,7 @@ "version": "3.954.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.954.0.tgz", "integrity": "sha512-Y1/0O2LgbKM8iIgcVj/GNEQW6p90LVTCOzF2CI1pouoKqxmZ/1F7F66WHoa6XUOfKaCRj/R6nuMR3om9ThaM5A==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "3.954.0", @@ -778,6 +1309,7 @@ "version": "3.954.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.954.0.tgz", "integrity": "sha512-UXxGfkp/plFRdyidMLvNul5zoLKmHhVQOCrD2OgR/lg9jNqNmJ7abF+Qu8abo902iDkhU21Qj4M398cx6l8Kng==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/client-sso": "3.954.0", @@ -797,6 +1329,7 @@ "version": "3.954.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.954.0.tgz", "integrity": "sha512-XEyf1T08q1tG4zkTS4Dnf1cAQyrJUo/xlvi6XNpqGhY3bOmKUYE2h/K6eITIdytDL9VuCpWYQ6YRcIVtL29E0w==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "3.954.0", @@ -833,16 +1366,16 @@ } }, "node_modules/@aws-sdk/middleware-bucket-endpoint": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.953.0.tgz", - "integrity": "sha512-YHVRIOowtGIl/L2WuS83FgRlm31tU0aL1yryWaFtF+AFjA5BIeiFkxIZqaRGxJpJvFEBdohsyq6Ipv5mgWfezg==", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.957.0.tgz", + "integrity": "sha512-iczcn/QRIBSpvsdAS/rbzmoBpleX1JBjXvCynMbDceVLBIcVrwT1hXECrhtIC2cjh4HaLo9ClAbiOiWuqt+6MA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.953.0", - "@aws-sdk/util-arn-parser": "3.953.0", - "@smithy/node-config-provider": "^4.3.6", - "@smithy/protocol-http": "^5.3.6", - "@smithy/types": "^4.10.0", + "@aws-sdk/types": "3.957.0", + "@aws-sdk/util-arn-parser": "3.957.0", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", "@smithy/util-config-provider": "^4.2.0", "tslib": "^2.6.2" }, @@ -850,15 +1383,53 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-expect-continue": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.953.0.tgz", - "integrity": "sha512-BQTVXrypQ0rbb7au/Hk4IS5GaJZlwk6O44Rjk6Kxb0IvGQhSurNTuesFiJx1sLbf+w+T31saPtODcfQQERqhCQ==", + "node_modules/@aws-sdk/middleware-bucket-endpoint/node_modules/@aws-sdk/types": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.957.0.tgz", + "integrity": "sha512-wzWC2Nrt859ABk6UCAVY/WYEbAd7FjkdrQL6m24+tfmWYDNRByTJ9uOgU/kw9zqLCAwb//CPvrJdhqjTznWXAg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.953.0", - "@smithy/protocol-http": "^5.3.6", - "@smithy/types": "^4.10.0", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-bucket-endpoint/node_modules/@aws-sdk/util-arn-parser": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.957.0.tgz", + "integrity": "sha512-Aj6m+AyrhWyg8YQ4LDPg2/gIfGHCEcoQdBt5DeSFogN5k9mmJPOJ+IAmNSWmWRjpOxEy6eY813RNDI6qS97M0g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-expect-continue": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.957.0.tgz", + "integrity": "sha512-AlbK3OeVNwZZil0wlClgeI/ISlOt/SPUxBsIns876IFaVu/Pj3DgImnYhpcJuFRek4r4XM51xzIaGQXM6GDHGg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.957.0", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-expect-continue/node_modules/@aws-sdk/types": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.957.0.tgz", + "integrity": "sha512-wzWC2Nrt859ABk6UCAVY/WYEbAd7FjkdrQL6m24+tfmWYDNRByTJ9uOgU/kw9zqLCAwb//CPvrJdhqjTznWXAg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -866,22 +1437,23 @@ } }, "node_modules/@aws-sdk/middleware-flexible-checksums": { - "version": "3.954.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.954.0.tgz", - "integrity": "sha512-hHOPDJyxucNodkgapLhA0VdwDBwVYN9DX20aA6j+3nwutAlZ5skaV7Bw0W3YC7Fh/ieDKKhcSZulONd4lVTwMg==", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.957.0.tgz", + "integrity": "sha512-iJpeVR5V8se1hl2pt+k8bF/e9JO4KWgPCMjg8BtRspNtKIUGy7j6msYvbDixaKZaF2Veg9+HoYcOhwnZumjXSA==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", - "@aws-sdk/core": "3.954.0", - "@aws-sdk/types": "3.953.0", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/crc64-nvme": "3.957.0", + "@aws-sdk/types": "3.957.0", "@smithy/is-array-buffer": "^4.2.0", - "@smithy/node-config-provider": "^4.3.6", - "@smithy/protocol-http": "^5.3.6", - "@smithy/types": "^4.10.0", - "@smithy/util-middleware": "^4.2.6", - "@smithy/util-stream": "^4.5.7", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-stream": "^4.5.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, @@ -889,10 +1461,62 @@ "node": ">=18.0.0" } }, + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@aws-sdk/core": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.957.0.tgz", + "integrity": "sha512-DrZgDnF1lQZv75a52nFWs6MExihJF2GZB6ETZRqr6jMwhrk2kbJPUtvgbifwcL7AYmVqHQDJBrR/MqkwwFCpiw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.957.0", + "@aws-sdk/xml-builder": "3.957.0", + "@smithy/core": "^3.20.0", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/signature-v4": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@aws-sdk/types": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.957.0.tgz", + "integrity": "sha512-wzWC2Nrt859ABk6UCAVY/WYEbAd7FjkdrQL6m24+tfmWYDNRByTJ9uOgU/kw9zqLCAwb//CPvrJdhqjTznWXAg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@aws-sdk/xml-builder": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.957.0.tgz", + "integrity": "sha512-Ai5iiQqS8kJ5PjzMhWcLKN0G2yasAkvpnPlq2EnqlIMdB48HsizElt62qcktdxp4neRMyGkFq4NzgmDbXnhRiA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0", + "fast-xml-parser": "5.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@aws-sdk/middleware-host-header": { "version": "3.953.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.953.0.tgz", "integrity": "sha512-jTGhfkONav+r4E6HLOrl5SzBqDmPByUYCkyB/c/3TVb8jX3wAZx8/q9bphKpCh+G5ARi3IdbSisgkZrJYqQ19Q==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.953.0", @@ -905,13 +1529,26 @@ } }, "node_modules/@aws-sdk/middleware-location-constraint": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.953.0.tgz", - "integrity": "sha512-h0urrbteIQEybyIISaJfQLZ/+/lJPRzPWAQT4epvzfgv/4MKZI7K83dK7SfTwAooVKFBHiCMok2Cf0iHDt07Kw==", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.957.0.tgz", + "integrity": "sha512-y8/W7TOQpmDJg/fPYlqAhwA4+I15LrS7TwgUEoxogtkD8gfur9wFMRLT8LCyc9o4NMEcAnK50hSb4+wB0qv6tQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.953.0", - "@smithy/types": "^4.10.0", + "@aws-sdk/types": "3.957.0", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-location-constraint/node_modules/@aws-sdk/types": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.957.0.tgz", + "integrity": "sha512-wzWC2Nrt859ABk6UCAVY/WYEbAd7FjkdrQL6m24+tfmWYDNRByTJ9uOgU/kw9zqLCAwb//CPvrJdhqjTznWXAg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -922,6 +1559,7 @@ "version": "3.953.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.953.0.tgz", "integrity": "sha512-PlWdVYgcuptkIC0ZKqVUhWNtSHXJSx7U9V8J7dJjRmsXC40X7zpEycvrkzDMJjeTDGcCceYbyYAg/4X1lkcIMw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.953.0", @@ -936,6 +1574,7 @@ "version": "3.953.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.953.0.tgz", "integrity": "sha512-cmIJx0gWeesUKK4YwgE+VQL3mpACr3/J24fbwnc1Z5tntC86b+HQFzU5vsBDw6lLwyD46dBgWdsXFh1jL+ZaFw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.953.0", @@ -952,6 +1591,7 @@ "version": "3.954.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.954.0.tgz", "integrity": "sha512-274CNmnRjknmfFb2o0Azxic54fnujaA8AYSeRUOho3lN48TVzx85eAFWj2kLgvUJO88pE3jBDPWboKQiQdXeUQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "3.954.0", @@ -974,13 +1614,26 @@ } }, "node_modules/@aws-sdk/middleware-ssec": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.953.0.tgz", - "integrity": "sha512-OrhG1kcQ9zZh3NS3RovR028N0+UndQ957zF1k5HPLeFLwFwQN1uPOufzzPzAyXIIKtR69ARFsQI4mstZS4DMvw==", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.957.0.tgz", + "integrity": "sha512-qwkmrK0lizdjNt5qxl4tHYfASh8DFpHXM1iDVo+qHe+zuslfMqQEGRkzxS8tJq/I+8F0c6v3IKOveKJAfIvfqQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.953.0", - "@smithy/types": "^4.10.0", + "@aws-sdk/types": "3.957.0", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-ssec/node_modules/@aws-sdk/types": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.957.0.tgz", + "integrity": "sha512-wzWC2Nrt859ABk6UCAVY/WYEbAd7FjkdrQL6m24+tfmWYDNRByTJ9uOgU/kw9zqLCAwb//CPvrJdhqjTznWXAg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -991,6 +1644,7 @@ "version": "3.954.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.954.0.tgz", "integrity": "sha512-5PX8JDe3dB2+MqXeGIhmgFnm2rbVsSxhz+Xyuu1oxLtbOn+a9UDA+sNBufEBjt3UxWy5qwEEY1fxdbXXayjlGg==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "3.954.0", @@ -1009,6 +1663,7 @@ "version": "3.954.0", "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.954.0.tgz", "integrity": "sha512-JLUhf35fTQIDPLk6G5KPggL9tV//Hjhy6+N2zZeis76LuBRNhKDq8z1CFyKhjf00vXi/tDYdn9D7y9emI+5Y/g==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", @@ -1058,6 +1713,7 @@ "version": "3.953.0", "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.953.0.tgz", "integrity": "sha512-5MJgnsc+HLO+le0EK1cy92yrC7kyhGZSpaq8PcQvKs9qtXCXT5Tb6tMdkr5Y07JxYsYOV1omWBynvL6PWh08tQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.953.0", @@ -1071,18 +1727,123 @@ } }, "node_modules/@aws-sdk/s3-request-presigner": { - "version": "3.954.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.954.0.tgz", - "integrity": "sha512-ypFOt8JK/x6p2hD9Jd5ctStw8h32KdgSBV+eXAMvcMP4DFt7wp6f7StwbjAblehZnPwNolFJE4Hz2F08nh4VdA==", + "version": "3.962.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.962.0.tgz", + "integrity": "sha512-tyxsGfLY4NSohLrJsFGXbE3j8jguWK+hdGaUQSD1gJPvmC0B82qOyJ7WBIJLWgTabU3fiF/I9EGXjzR2rKr8jQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/signature-v4-multi-region": "3.954.0", - "@aws-sdk/types": "3.953.0", - "@aws-sdk/util-format-url": "3.953.0", - "@smithy/middleware-endpoint": "^4.4.0", - "@smithy/protocol-http": "^5.3.6", - "@smithy/smithy-client": "^4.10.1", - "@smithy/types": "^4.10.0", + "@aws-sdk/signature-v4-multi-region": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@aws-sdk/util-format-url": "3.957.0", + "@smithy/middleware-endpoint": "^4.4.1", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@aws-sdk/core": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.957.0.tgz", + "integrity": "sha512-DrZgDnF1lQZv75a52nFWs6MExihJF2GZB6ETZRqr6jMwhrk2kbJPUtvgbifwcL7AYmVqHQDJBrR/MqkwwFCpiw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.957.0", + "@aws-sdk/xml-builder": "3.957.0", + "@smithy/core": "^3.20.0", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/signature-v4": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.957.0.tgz", + "integrity": "sha512-5B2qY2nR2LYpxoQP0xUum5A1UNvH2JQpLHDH1nWFNF/XetV7ipFHksMxPNhtJJ6ARaWhQIDXfOUj0jcnkJxXUg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@aws-sdk/util-arn-parser": "3.957.0", + "@smithy/core": "^3.20.0", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/signature-v4": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-stream": "^4.5.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.957.0.tgz", + "integrity": "sha512-t6UfP1xMUigMMzHcb7vaZcjv7dA2DQkk9C/OAP1dKyrE0vb4lFGDaTApi17GN6Km9zFxJthEMUbBc7DL0hq1Bg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-sdk-s3": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@smithy/protocol-http": "^5.3.7", + "@smithy/signature-v4": "^5.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@aws-sdk/types": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.957.0.tgz", + "integrity": "sha512-wzWC2Nrt859ABk6UCAVY/WYEbAd7FjkdrQL6m24+tfmWYDNRByTJ9uOgU/kw9zqLCAwb//CPvrJdhqjTznWXAg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@aws-sdk/util-arn-parser": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.957.0.tgz", + "integrity": "sha512-Aj6m+AyrhWyg8YQ4LDPg2/gIfGHCEcoQdBt5DeSFogN5k9mmJPOJ+IAmNSWmWRjpOxEy6eY813RNDI6qS97M0g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@aws-sdk/xml-builder": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.957.0.tgz", + "integrity": "sha512-Ai5iiQqS8kJ5PjzMhWcLKN0G2yasAkvpnPlq2EnqlIMdB48HsizElt62qcktdxp4neRMyGkFq4NzgmDbXnhRiA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0", + "fast-xml-parser": "5.2.5", "tslib": "^2.6.2" }, "engines": { @@ -1093,6 +1854,7 @@ "version": "3.954.0", "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.954.0.tgz", "integrity": "sha512-GJJbUaSlGrMSRWui3Oz8ByygpQlzDGm195yTKirgGyu4tfYrFr/QWrWT42EUktY/L4Irev1pdHTuLS+AGHO1gw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/middleware-sdk-s3": "3.954.0", @@ -1110,6 +1872,7 @@ "version": "3.954.0", "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.954.0.tgz", "integrity": "sha512-rDyN3oQQKMOJgyQ9/LNbh4fAGAj8ePMGOAQzSP/kyzizmViI6STpBW1o/VRqiTgMNi1bvA9ZasDtfrJqcVt0iA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "3.954.0", @@ -1141,6 +1904,7 @@ "version": "3.953.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.953.0.tgz", "integrity": "sha512-9hqdKkn4OvYzzaLryq2xnwcrPc8ziY34i9szUdgBfSqEC6pBxbY9/lLXmrgzfwMSL2Z7/v2go4Od0p5eukKLMQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -1153,6 +1917,7 @@ "version": "3.953.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.953.0.tgz", "integrity": "sha512-rjaS6jrFksopXvNg6YeN+D1lYwhcByORNlFuYesFvaQNtPOufbE5tJL4GJ3TMXyaY0uFR28N5BHHITPyWWfH/g==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.953.0", @@ -1166,14 +1931,27 @@ } }, "node_modules/@aws-sdk/util-format-url": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.953.0.tgz", - "integrity": "sha512-fs70vtTiBhp/T9ss52OuW2LGJqPoNBbd1+wxqh82CMdzkOvCzI3qa/cK8tR0jCFeIjGeiV74lAskImRxu/V4lg==", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.957.0.tgz", + "integrity": "sha512-Yyo/tlc0iGFGTPPkuxub1uRAv6XrnVnvSNjslZh5jIYA8GZoeEFPgJa3Qdu0GUS/YwoK8GOLnnaL9h/eH5LDJQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.953.0", - "@smithy/querystring-builder": "^4.2.6", - "@smithy/types": "^4.10.0", + "@aws-sdk/types": "3.957.0", + "@smithy/querystring-builder": "^4.2.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-format-url/node_modules/@aws-sdk/types": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.957.0.tgz", + "integrity": "sha512-wzWC2Nrt859ABk6UCAVY/WYEbAd7FjkdrQL6m24+tfmWYDNRByTJ9uOgU/kw9zqLCAwb//CPvrJdhqjTznWXAg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -1196,6 +1974,7 @@ "version": "3.953.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.953.0.tgz", "integrity": "sha512-UF5NeqYesWuFao+u7LJvpV1SJCaLml5BtFZKUdTnNNMeN6jvV+dW/eQoFGpXF94RCqguX0XESmRuRRPQp+/rzQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.953.0", @@ -1208,6 +1987,7 @@ "version": "3.954.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.954.0.tgz", "integrity": "sha512-fB5S5VOu7OFkeNzcblQlez4AjO5hgDFaa7phYt7716YWisY3RjAaQPlxgv+G3GltHHDJIfzEC5aRxdf62B9zMg==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/middleware-user-agent": "3.954.0", @@ -1232,6 +2012,7 @@ "version": "3.953.0", "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.953.0.tgz", "integrity": "sha512-Zmrj21jQ2OeOJGr9spPiN00aQvXa/WUqRXcTVENhrMt+OFoSOfDFpYhUj9NQ09QmQ8KMWFoWuWW6iKurNqLvAA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.10.0", @@ -5501,12 +6282,12 @@ } }, "node_modules/@smithy/abort-controller": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.6.tgz", - "integrity": "sha512-P7JD4J+wxHMpGxqIg6SHno2tPkZbBUBLbPpR5/T1DEUvw/mEaINBMaPFZNM7lA+ToSCZ36j6nMHa+5kej+fhGg==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.7.tgz", + "integrity": "sha512-rzMY6CaKx2qxrbYbqjXWS0plqEy7LOdKHS0bg4ixJ6aoGDPNUcLWk/FRNuCILh7GKLG9TFUXYYeQQldMBBwuyw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.10.0", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -5539,16 +6320,16 @@ } }, "node_modules/@smithy/config-resolver": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.4.tgz", - "integrity": "sha512-s3U5ChS21DwU54kMmZ0UJumoS5cg0+rGVZvN6f5Lp6EbAVi0ZyP+qDSHdewfmXKUgNK1j3z45JyzulkDukrjAA==", + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.5.tgz", + "integrity": "sha512-HAGoUAFYsUkoSckuKbCPayECeMim8pOu+yLy1zOxt1sifzEbrsRpYa+mKcMdiHKMeiqOibyPG0sFJnmaV/OGEg==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.6", - "@smithy/types": "^4.10.0", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/types": "^4.11.0", "@smithy/util-config-provider": "^4.2.0", - "@smithy/util-endpoints": "^3.2.6", - "@smithy/util-middleware": "^4.2.6", + "@smithy/util-endpoints": "^3.2.7", + "@smithy/util-middleware": "^4.2.7", "tslib": "^2.6.2" }, "engines": { @@ -5556,18 +6337,18 @@ } }, "node_modules/@smithy/core": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.19.0.tgz", - "integrity": "sha512-Y9oHXpBcXQgYHOcAEmxjkDilUbSTkgKjoHYed3WaYUH8jngq8lPWDBSpjHblJ9uOgBdy5mh3pzebrScDdYr29w==", + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.20.0.tgz", + "integrity": "sha512-WsSHCPq/neD5G/MkK4csLI5Y5Pkd9c1NMfpYEKeghSGaD4Ja1qLIohRQf2D5c1Uy5aXp76DeKHkzWZ9KAlHroQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/middleware-serde": "^4.2.7", - "@smithy/protocol-http": "^5.3.6", - "@smithy/types": "^4.10.0", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-middleware": "^4.2.6", - "@smithy/util-stream": "^4.5.7", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-stream": "^4.5.8", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" @@ -5577,15 +6358,15 @@ } }, "node_modules/@smithy/credential-provider-imds": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.6.tgz", - "integrity": "sha512-xBmawExyTzOjbhzkZwg+vVm/khg28kG+rj2sbGlULjFd1jI70sv/cbpaR0Ev4Yfd6CpDUDRMe64cTqR//wAOyA==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.7.tgz", + "integrity": "sha512-CmduWdCiILCRNbQWFR0OcZlUPVtyE49Sr8yYL0rZQ4D/wKxiNzBNS/YHemvnbkIWj623fplgkexUd/c9CAKdoA==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.6", - "@smithy/property-provider": "^4.2.6", - "@smithy/types": "^4.10.0", - "@smithy/url-parser": "^4.2.6", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", "tslib": "^2.6.2" }, "engines": { @@ -5593,13 +6374,13 @@ } }, "node_modules/@smithy/eventstream-codec": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.6.tgz", - "integrity": "sha512-OZfsI+YRG26XZik/jKMMg37acnBSbUiK/8nETW3uM3mLj+0tMmFXdHQw1e5WEd/IHN8BGOh3te91SNDe2o4RHg==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.7.tgz", + "integrity": "sha512-DrpkEoM3j9cBBWhufqBwnbbn+3nf1N9FP6xuVJ+e220jbactKuQgaZwjwP5CP1t+O94brm2JgVMD2atMGX3xIQ==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/crc32": "5.2.0", - "@smithy/types": "^4.10.0", + "@smithy/types": "^4.11.0", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" }, @@ -5608,13 +6389,13 @@ } }, "node_modules/@smithy/eventstream-serde-browser": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.6.tgz", - "integrity": "sha512-6OiaAaEbLB6dEkRbQyNzFSJv5HDvly3Mc6q/qcPd2uS/g3szR8wAIkh7UndAFKfMypNSTuZ6eCBmgCLR5LacTg==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.7.tgz", + "integrity": "sha512-ujzPk8seYoDBmABDE5YqlhQZAXLOrtxtJLrbhHMKjBoG5b4dK4i6/mEU+6/7yXIAkqOO8sJ6YxZl+h0QQ1IJ7g==", "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-serde-universal": "^4.2.6", - "@smithy/types": "^4.10.0", + "@smithy/eventstream-serde-universal": "^4.2.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -5622,12 +6403,12 @@ } }, "node_modules/@smithy/eventstream-serde-config-resolver": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.6.tgz", - "integrity": "sha512-xP5YXbOVRVN8A4pDnSUkEUsL9fYFU6VNhxo8tgr13YnMbf3Pn4xVr+hSyLVjS1Frfi1Uk03ET5Bwml4+0CeYEw==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.7.tgz", + "integrity": "sha512-x7BtAiIPSaNaWuzm24Q/mtSkv+BrISO/fmheiJ39PKRNH3RmH2Hph/bUKSOBOBC9unqfIYDhKTHwpyZycLGPVQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.10.0", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -5635,13 +6416,13 @@ } }, "node_modules/@smithy/eventstream-serde-node": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.6.tgz", - "integrity": "sha512-jhH7nJuaOpnTFcuZpWK9dqb6Ge2yGi1okTo0W6wkJrfwAm2vwmO74tF1v07JmrSyHBcKLQATEexclJw9K1Vj7w==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.7.tgz", + "integrity": "sha512-roySCtHC5+pQq5lK4be1fZ/WR6s/AxnPaLfCODIPArtN2du8s5Ot4mKVK3pPtijL/L654ws592JHJ1PbZFF6+A==", "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-serde-universal": "^4.2.6", - "@smithy/types": "^4.10.0", + "@smithy/eventstream-serde-universal": "^4.2.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -5649,13 +6430,13 @@ } }, "node_modules/@smithy/eventstream-serde-universal": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.6.tgz", - "integrity": "sha512-olIfZ230B64TvPD6b0tPvrEp2eB0FkyL3KvDlqF4RVmIc/kn3orzXnV6DTQdOOW5UU+M5zKY3/BU47X420/oPw==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.7.tgz", + "integrity": "sha512-QVD+g3+icFkThoy4r8wVFZMsIP08taHVKjE6Jpmz8h5CgX/kk6pTODq5cht0OMtcapUx+xrPzUTQdA+TmO0m1g==", "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-codec": "^4.2.6", - "@smithy/types": "^4.10.0", + "@smithy/eventstream-codec": "^4.2.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -5663,14 +6444,14 @@ } }, "node_modules/@smithy/fetch-http-handler": { - "version": "5.3.7", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.7.tgz", - "integrity": "sha512-fcVap4QwqmzQwQK9QU3keeEpCzTjnP9NJ171vI7GnD7nbkAIcP9biZhDUx88uRH9BabSsQDS0unUps88uZvFIQ==", + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.8.tgz", + "integrity": "sha512-h/Fi+o7mti4n8wx1SR6UHWLaakwHRx29sizvp8OOm7iqwKGFneT06GCSFhml6Bha5BT6ot5pj3CYZnCHhGC2Rg==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.6", - "@smithy/querystring-builder": "^4.2.6", - "@smithy/types": "^4.10.0", + "@smithy/protocol-http": "^5.3.7", + "@smithy/querystring-builder": "^4.2.7", + "@smithy/types": "^4.11.0", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" }, @@ -5679,14 +6460,14 @@ } }, "node_modules/@smithy/hash-blob-browser": { - "version": "4.2.7", - "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.2.7.tgz", - "integrity": "sha512-CIbCTGGX5CI7tfewBPSYD9ycp2Vb2GW5xnXD1n7GcO9mu37EN7A6DvCHM9MX7pOeS1adMn5D+1yRwI3eABVbcA==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.2.8.tgz", + "integrity": "sha512-07InZontqsM1ggTCPSRgI7d8DirqRrnpL7nIACT4PW0AWrgDiHhjGZzbAE5UtRSiU0NISGUYe7/rri9ZeWyDpw==", "license": "Apache-2.0", "dependencies": { "@smithy/chunked-blob-reader": "^5.2.0", "@smithy/chunked-blob-reader-native": "^4.2.1", - "@smithy/types": "^4.10.0", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -5694,12 +6475,12 @@ } }, "node_modules/@smithy/hash-node": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.6.tgz", - "integrity": "sha512-k3Dy9VNR37wfMh2/1RHkFf/e0rMyN0pjY0FdyY6ItJRjENYyVPRMwad6ZR1S9HFm6tTuIOd9pqKBmtJ4VHxvxg==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.7.tgz", + "integrity": "sha512-PU/JWLTBCV1c8FtB8tEFnY4eV1tSfBc7bDBADHfn1K+uRbPgSJ9jnJp0hyjiFN2PMdPzxsf1Fdu0eo9fJ760Xw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.10.0", + "@smithy/types": "^4.11.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" @@ -5709,12 +6490,12 @@ } }, "node_modules/@smithy/hash-stream-node": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.2.6.tgz", - "integrity": "sha512-+3T8LkH39YIhYHsv/Ec8lF+92nykZpU+XMBvAyXF/uLcTp86pxa5oSJk1vzaRY9N++qgDLYjzJ6OVbtAgDGwfw==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.2.7.tgz", + "integrity": "sha512-ZQVoAwNYnFMIbd4DUc517HuwNelJUY6YOzwqrbcAgCnVn+79/OK7UjwA93SPpdTOpKDVkLIzavWm/Ck7SmnDPQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.10.0", + "@smithy/types": "^4.11.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, @@ -5723,12 +6504,12 @@ } }, "node_modules/@smithy/invalid-dependency": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.6.tgz", - "integrity": "sha512-E4t/V/q2T46RY21fpfznd1iSLTvCXKNKo4zJ1QuEFN4SE9gKfu2vb6bgq35LpufkQ+SETWIC7ZAf2GGvTlBaMQ==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.7.tgz", + "integrity": "sha512-ncvgCr9a15nPlkhIUx3CU4d7E7WEuVJOV7fS7nnK2hLtPK9tYRBkMHQbhXU1VvvKeBm/O0x26OEoBq+ngFpOEQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.10.0", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -5748,12 +6529,12 @@ } }, "node_modules/@smithy/md5-js": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.2.6.tgz", - "integrity": "sha512-ZXeh8UmH31JdcNsrQ1o9v1IVuva9JFwxIc6zTMxWX7wcmWvVR7Ai9aUEw5LraNKqdkAsb06clpM2sRH4Iy55Sg==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.2.7.tgz", + "integrity": "sha512-Wv6JcUxtOLTnxvNjDnAiATUsk8gvA6EeS8zzHig07dotpByYsLot+m0AaQEniUBjx97AC41MQR4hW0baraD1Xw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.10.0", + "@smithy/types": "^4.11.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, @@ -5762,13 +6543,13 @@ } }, "node_modules/@smithy/middleware-content-length": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.6.tgz", - "integrity": "sha512-0cjqjyfj+Gls30ntq45SsBtqF3dfJQCeqQPyGz58Pk8OgrAr5YiB7ZvDzjCA94p4r6DCI4qLm7FKobqBjf515w==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.7.tgz", + "integrity": "sha512-GszfBfCcvt7kIbJ41LuNa5f0wvQCHhnGx/aDaZJCCT05Ld6x6U2s0xsc/0mBFONBZjQJp2U/0uSJ178OXOwbhg==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.6", - "@smithy/types": "^4.10.0", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -5776,18 +6557,18 @@ } }, "node_modules/@smithy/middleware-endpoint": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.0.tgz", - "integrity": "sha512-M6qWfUNny6NFNy8amrCGIb9TfOMUkHVtg9bHtEFGRgfH7A7AtPpn/fcrToGPjVDK1ECuMVvqGQOXcZxmu9K+7A==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.1.tgz", + "integrity": "sha512-gpLspUAoe6f1M6H0u4cVuFzxZBrsGZmjx2O9SigurTx4PbntYa4AJ+o0G0oGm1L2oSX6oBhcGHwrfJHup2JnJg==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.19.0", - "@smithy/middleware-serde": "^4.2.7", - "@smithy/node-config-provider": "^4.3.6", - "@smithy/shared-ini-file-loader": "^4.4.1", - "@smithy/types": "^4.10.0", - "@smithy/url-parser": "^4.2.6", - "@smithy/util-middleware": "^4.2.6", + "@smithy/core": "^3.20.0", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "@smithy/util-middleware": "^4.2.7", "tslib": "^2.6.2" }, "engines": { @@ -5795,18 +6576,18 @@ } }, "node_modules/@smithy/middleware-retry": { - "version": "4.4.16", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.16.tgz", - "integrity": "sha512-XPpNhNRzm3vhYm7YCsyw3AtmWggJbg1wNGAoqb7NBYr5XA5isMRv14jgbYyUV6IvbTBFZQdf2QpeW43LrRdStQ==", + "version": "4.4.17", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.17.tgz", + "integrity": "sha512-MqbXK6Y9uq17h+4r0ogu/sBT6V/rdV+5NvYL7ZV444BKfQygYe8wAhDrVXagVebN6w2RE0Fm245l69mOsPGZzg==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.6", - "@smithy/protocol-http": "^5.3.6", - "@smithy/service-error-classification": "^4.2.6", - "@smithy/smithy-client": "^4.10.1", - "@smithy/types": "^4.10.0", - "@smithy/util-middleware": "^4.2.6", - "@smithy/util-retry": "^4.2.6", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/service-error-classification": "^4.2.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-retry": "^4.2.7", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" }, @@ -5815,13 +6596,13 @@ } }, "node_modules/@smithy/middleware-serde": { - "version": "4.2.7", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.7.tgz", - "integrity": "sha512-PFMVHVPgtFECeu4iZ+4SX6VOQT0+dIpm4jSPLLL6JLSkp9RohGqKBKD0cbiXdeIFS08Forp0UHI6kc0gIHenSA==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.8.tgz", + "integrity": "sha512-8rDGYen5m5+NV9eHv9ry0sqm2gI6W7mc1VSFMtn6Igo25S507/HaOX9LTHAS2/J32VXD0xSzrY0H5FJtOMS4/w==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.6", - "@smithy/types": "^4.10.0", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -5829,12 +6610,12 @@ } }, "node_modules/@smithy/middleware-stack": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.6.tgz", - "integrity": "sha512-JSbALU3G+JS4kyBZPqnJ3hxIYwOVRV7r9GNQMS6j5VsQDo5+Es5nddLfr9TQlxZLNHPvKSh+XSB0OuWGfSWFcA==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.7.tgz", + "integrity": "sha512-bsOT0rJ+HHlZd9crHoS37mt8qRRN/h9jRve1SXUhVbkRzu0QaNYZp1i1jha4n098tsvROjcwfLlfvcFuJSXEsw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.10.0", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -5842,14 +6623,14 @@ } }, "node_modules/@smithy/node-config-provider": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.6.tgz", - "integrity": "sha512-fYEyL59Qe82Ha1p97YQTMEQPJYmBS+ux76foqluaTVWoG9Px5J53w6NvXZNE3wP7lIicLDF7Vj1Em18XTX7fsA==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.7.tgz", + "integrity": "sha512-7r58wq8sdOcrwWe+klL9y3bc4GW1gnlfnFOuL7CXa7UzfhzhxKuzNdtqgzmTV+53lEp9NXh5hY/S4UgjLOzPfw==", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.2.6", - "@smithy/shared-ini-file-loader": "^4.4.1", - "@smithy/types": "^4.10.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -5857,15 +6638,15 @@ } }, "node_modules/@smithy/node-http-handler": { - "version": "4.4.6", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.6.tgz", - "integrity": "sha512-Gsb9jf4ido5BhPfani4ggyrKDd3ZK+vTFWmUaZeFg5G3E5nhFmqiTzAIbHqmPs1sARuJawDiGMGR/nY+Gw6+aQ==", + "version": "4.4.7", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.7.tgz", + "integrity": "sha512-NELpdmBOO6EpZtWgQiHjoShs1kmweaiNuETUpuup+cmm/xJYjT4eUjfhrXRP4jCOaAsS3c3yPsP3B+K+/fyPCQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^4.2.6", - "@smithy/protocol-http": "^5.3.6", - "@smithy/querystring-builder": "^4.2.6", - "@smithy/types": "^4.10.0", + "@smithy/abort-controller": "^4.2.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/querystring-builder": "^4.2.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -5873,12 +6654,12 @@ } }, "node_modules/@smithy/property-provider": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.6.tgz", - "integrity": "sha512-a/tGSLPtaia2krbRdwR4xbZKO8lU67DjMk/jfY4QKt4PRlKML+2tL/gmAuhNdFDioO6wOq0sXkfnddNFH9mNUA==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.7.tgz", + "integrity": "sha512-jmNYKe9MGGPoSl/D7JDDs1C8b3dC8f/w78LbaVfoTtWy4xAd5dfjaFG9c9PWPihY4ggMQNQSMtzU77CNgAJwmA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.10.0", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -5886,12 +6667,12 @@ } }, "node_modules/@smithy/protocol-http": { - "version": "5.3.6", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.6.tgz", - "integrity": "sha512-qLRZzP2+PqhE3OSwvY2jpBbP0WKTZ9opTsn+6IWYI0SKVpbG+imcfNxXPq9fj5XeaUTr7odpsNpK6dmoiM1gJQ==", + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.7.tgz", + "integrity": "sha512-1r07pb994I20dD/c2seaZhoCuNYm0rWrvBxhCQ70brNh11M5Ml2ew6qJVo0lclB3jMIXirD4s2XRXRe7QEi0xA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.10.0", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -5899,12 +6680,12 @@ } }, "node_modules/@smithy/querystring-builder": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.6.tgz", - "integrity": "sha512-MeM9fTAiD3HvoInK/aA8mgJaKQDvm8N0dKy6EiFaCfgpovQr4CaOkJC28XqlSRABM+sHdSQXbC8NZ0DShBMHqg==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.7.tgz", + "integrity": "sha512-eKONSywHZxK4tBxe2lXEysh8wbBdvDWiA+RIuaxZSgCMmA0zMgoDpGLJhnyj+c0leOQprVnXOmcB4m+W9Rw7sg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.10.0", + "@smithy/types": "^4.11.0", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" }, @@ -5913,12 +6694,12 @@ } }, "node_modules/@smithy/querystring-parser": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.6.tgz", - "integrity": "sha512-YmWxl32SQRw/kIRccSOxzS/Ib8/b5/f9ex0r5PR40jRJg8X1wgM3KrR2In+8zvOGVhRSXgvyQpw9yOSlmfmSnA==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.7.tgz", + "integrity": "sha512-3X5ZvzUHmlSTHAXFlswrS6EGt8fMSIxX/c3Rm1Pni3+wYWB6cjGocmRIoqcQF9nU5OgGmL0u7l9m44tSUpfj9w==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.10.0", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -5926,24 +6707,24 @@ } }, "node_modules/@smithy/service-error-classification": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.6.tgz", - "integrity": "sha512-Q73XBrzJlGTut2nf5RglSntHKgAG0+KiTJdO5QQblLfr4TdliGwIAha1iZIjwisc3rA5ulzqwwsYC6xrclxVQg==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.7.tgz", + "integrity": "sha512-YB7oCbukqEb2Dlh3340/8g8vNGbs/QsNNRms+gv3N2AtZz9/1vSBx6/6tpwQpZMEJFs7Uq8h4mmOn48ZZ72MkA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.10.0" + "@smithy/types": "^4.11.0" }, "engines": { "node": ">=18.0.0" } }, "node_modules/@smithy/shared-ini-file-loader": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.1.tgz", - "integrity": "sha512-tph+oQYPbpN6NamF030hx1gb5YN2Plog+GLaRHpoEDwp8+ZPG26rIJvStG9hkWzN2HBn3HcWg0sHeB0tmkYzqA==", + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.2.tgz", + "integrity": "sha512-M7iUUff/KwfNunmrgtqBfvZSzh3bmFgv/j/t1Y1dQ+8dNo34br1cqVEqy6v0mYEgi0DkGO7Xig0AnuOaEGVlcg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.10.0", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -5951,16 +6732,16 @@ } }, "node_modules/@smithy/signature-v4": { - "version": "5.3.6", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.6.tgz", - "integrity": "sha512-P1TXDHuQMadTMTOBv4oElZMURU4uyEhxhHfn+qOc2iofW9Rd4sZtBGx58Lzk112rIGVEYZT8eUMK4NftpewpRA==", + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.7.tgz", + "integrity": "sha512-9oNUlqBlFZFOSdxgImA6X5GFuzE7V2H7VG/7E70cdLhidFbdtvxxt81EHgykGK5vq5D3FafH//X+Oy31j3CKOg==", "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^4.2.0", - "@smithy/protocol-http": "^5.3.6", - "@smithy/types": "^4.10.0", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", "@smithy/util-hex-encoding": "^4.2.0", - "@smithy/util-middleware": "^4.2.6", + "@smithy/util-middleware": "^4.2.7", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" @@ -5970,17 +6751,17 @@ } }, "node_modules/@smithy/smithy-client": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.10.1.tgz", - "integrity": "sha512-1ovWdxzYprhq+mWqiGZlt3kF69LJthuQcfY9BIyHx9MywTFKzFapluku1QXoaBB43GCsLDxNqS+1v30ure69AA==", + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.10.2.tgz", + "integrity": "sha512-D5z79xQWpgrGpAHb054Fn2CCTQZpog7JELbVQ6XAvXs5MNKWf28U9gzSBlJkOyMl9LA1TZEjRtwvGXfP0Sl90g==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.19.0", - "@smithy/middleware-endpoint": "^4.4.0", - "@smithy/middleware-stack": "^4.2.6", - "@smithy/protocol-http": "^5.3.6", - "@smithy/types": "^4.10.0", - "@smithy/util-stream": "^4.5.7", + "@smithy/core": "^3.20.0", + "@smithy/middleware-endpoint": "^4.4.1", + "@smithy/middleware-stack": "^4.2.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "@smithy/util-stream": "^4.5.8", "tslib": "^2.6.2" }, "engines": { @@ -5988,9 +6769,9 @@ } }, "node_modules/@smithy/types": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.10.0.tgz", - "integrity": "sha512-K9mY7V/f3Ul+/Gz4LJANZ3vJ/yiBIwCyxe0sPT4vNJK63Srvd+Yk1IzP0t+nE7XFSpIGtzR71yljtnqpUTYFlQ==", + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.11.0.tgz", + "integrity": "sha512-mlrmL0DRDVe3mNrjTcVcZEgkFmufITfUAPBEA+AHYiIeYyJebso/He1qLbP3PssRe22KUzLRpQSdBPbXdgZ2VA==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -6000,13 +6781,13 @@ } }, "node_modules/@smithy/url-parser": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.6.tgz", - "integrity": "sha512-tVoyzJ2vXp4R3/aeV4EQjBDmCuWxRa8eo3KybL7Xv4wEM16nObYh7H1sNfcuLWHAAAzb0RVyxUz1S3sGj4X+Tg==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.7.tgz", + "integrity": "sha512-/RLtVsRV4uY3qPWhBDsjwahAtt3x2IsMGnP5W1b2VZIe+qgCqkLxI1UOHDZp1Q1QSOrdOR32MF3Ph2JfWT1VHg==", "license": "Apache-2.0", "dependencies": { - "@smithy/querystring-parser": "^4.2.6", - "@smithy/types": "^4.10.0", + "@smithy/querystring-parser": "^4.2.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -6077,14 +6858,14 @@ } }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.3.15", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.15.tgz", - "integrity": "sha512-LiZQVAg/oO8kueX4c+oMls5njaD2cRLXRfcjlTYjhIqmwHnCwkQO5B3dMQH0c5PACILxGAQf6Mxsq7CjlDc76A==", + "version": "4.3.16", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.16.tgz", + "integrity": "sha512-/eiSP3mzY3TsvUOYMeL4EqUX6fgUOj2eUOU4rMMgVbq67TiRLyxT7Xsjxq0bW3OwuzK009qOwF0L2OgJqperAQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.2.6", - "@smithy/smithy-client": "^4.10.1", - "@smithy/types": "^4.10.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -6092,17 +6873,17 @@ } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.2.18", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.18.tgz", - "integrity": "sha512-Kw2J+KzYm9C9Z9nY6+W0tEnoZOofstVCMTshli9jhQbQCy64rueGfKzPfuFBnVUqZD9JobxTh2DzHmPkp/Va/Q==", + "version": "4.2.19", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.19.tgz", + "integrity": "sha512-3a4+4mhf6VycEJyHIQLypRbiwG6aJvbQAeRAVXydMmfweEPnLLabRbdyo/Pjw8Rew9vjsh5WCdhmDaHkQnhhhA==", "license": "Apache-2.0", "dependencies": { - "@smithy/config-resolver": "^4.4.4", - "@smithy/credential-provider-imds": "^4.2.6", - "@smithy/node-config-provider": "^4.3.6", - "@smithy/property-provider": "^4.2.6", - "@smithy/smithy-client": "^4.10.1", - "@smithy/types": "^4.10.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/credential-provider-imds": "^4.2.7", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -6110,13 +6891,13 @@ } }, "node_modules/@smithy/util-endpoints": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.6.tgz", - "integrity": "sha512-v60VNM2+mPvgHCBXEfMCYrQ0RepP6u6xvbAkMenfe4Mi872CqNkJzgcnQL837e8NdeDxBgrWQRTluKq5Lqdhfg==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.7.tgz", + "integrity": "sha512-s4ILhyAvVqhMDYREeTS68R43B1V5aenV5q/V1QpRQJkCXib5BPRo4s7uNdzGtIKxaPHCfU/8YkvPAEvTpxgspg==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.6", - "@smithy/types": "^4.10.0", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -6136,12 +6917,12 @@ } }, "node_modules/@smithy/util-middleware": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.6.tgz", - "integrity": "sha512-qrvXUkxBSAFomM3/OEMuDVwjh4wtqK8D2uDZPShzIqOylPst6gor2Cdp6+XrH4dyksAWq/bE2aSDYBTTnj0Rxg==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.7.tgz", + "integrity": "sha512-i1IkpbOae6NvIKsEeLLM9/2q4X+M90KV3oCFgWQI4q0Qz+yUZvsr+gZPdAEAtFhWQhAHpTsJO8DRJPuwVyln+w==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.10.0", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -6149,13 +6930,13 @@ } }, "node_modules/@smithy/util-retry": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.6.tgz", - "integrity": "sha512-x7CeDQLPQ9cb6xN7fRJEjlP9NyGW/YeXWc4j/RUhg4I+H60F0PEeRc2c/z3rm9zmsdiMFzpV/rT+4UHW6KM1SA==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.7.tgz", + "integrity": "sha512-SvDdsQyF5CIASa4EYVT02LukPHVzAgUA4kMAuZ97QJc2BpAqZfA4PINB8/KOoCXEw9tsuv/jQjMeaHFvxdLNGg==", "license": "Apache-2.0", "dependencies": { - "@smithy/service-error-classification": "^4.2.6", - "@smithy/types": "^4.10.0", + "@smithy/service-error-classification": "^4.2.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -6163,14 +6944,14 @@ } }, "node_modules/@smithy/util-stream": { - "version": "4.5.7", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.7.tgz", - "integrity": "sha512-Uuy4S5Aj4oF6k1z+i2OtIBJUns4mlg29Ph4S+CqjR+f4XXpSFVgTCYLzMszHJTicYDBxKFtwq2/QSEDSS5l02A==", + "version": "4.5.8", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.8.tgz", + "integrity": "sha512-ZnnBhTapjM0YPGUSmOs0Mcg/Gg87k503qG4zU2v/+Js2Gu+daKOJMeqcQns8ajepY8tgzzfYxl6kQyZKml6O2w==", "license": "Apache-2.0", "dependencies": { - "@smithy/fetch-http-handler": "^5.3.7", - "@smithy/node-http-handler": "^4.4.6", - "@smithy/types": "^4.10.0", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/types": "^4.11.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", @@ -6207,13 +6988,13 @@ } }, "node_modules/@smithy/util-waiter": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.6.tgz", - "integrity": "sha512-xU9HwUSik9UUCJmm530yvBy0AwlQFICveKmqvaaTukKkXEAhyiBdHtSrhPrH3rH+uz0ykyaE3LdgsX86C6mDCQ==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.7.tgz", + "integrity": "sha512-vHJFXi9b7kUEpHWUCY3Twl+9NPOZvQ0SAi+Ewtn48mbiJk4JY9MZmKQjGB4SCvVb9WPiSphZJYY6RIbs+grrzw==", "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^4.2.6", - "@smithy/types": "^4.10.0", + "@smithy/abort-controller": "^4.2.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { diff --git a/package.json b/package.json index 15e7b33..feb465d 100644 --- a/package.json +++ b/package.json @@ -27,9 +27,9 @@ "db:reset": "npx prisma migrate reset" }, "dependencies": { - "@aws-sdk/client-s3": "^3.954.0", + "@aws-sdk/client-s3": "^3.962.0", "@aws-sdk/lib-storage": "^3.954.0", - "@aws-sdk/s3-request-presigner": "^3.954.0", + "@aws-sdk/s3-request-presigner": "^3.962.0", "@keyv/redis": "^5.1.5", "@nestjs-modules/mailer": "^2.0.2", "@nestjs/axios": "^4.0.1", diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 6ebefeb..b05c034 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -1,5 +1,5 @@ // Real Estate Agent Platform - Database Schema -// Milestone 1: Foundation & Authentication +// Milestone 1 & 2: Foundation, Authentication & Agent Features generator client { provider = "prisma-client-js" @@ -33,6 +33,19 @@ enum AuthProvider { TWITTER } +enum VerificationStatus { + PENDING + APPROVED + REJECTED +} + +enum VerificationDocType { + LICENSE + ID_CARD + CERTIFICATE + OTHER +} + // =========================================== // USER - Authentication Only // =========================================== @@ -45,6 +58,7 @@ model User { status UserStatus @default(ACTIVE) emailVerified Boolean @default(false) emailVerifiedAt DateTime? + avatar String? // Profile picture URL // Social Auth authProvider AuthProvider @default(LOCAL) @@ -112,7 +126,7 @@ model AgentProfile { lastName String? phone String? avatar String? - bio String? + bio String? @db.Text headline String? // Location @@ -120,29 +134,162 @@ model AgentProfile { state String? country String? address String? + zipCode String? + latitude Float? + longitude Float? // Professional Info yearsOfExperience Int? licenseNumber String? companyName String? + website String? + + // Social Links + facebookUrl String? + twitterUrl String? + linkedinUrl String? + instagramUrl String? + + // Verification Status + isVerified Boolean @default(false) + verifiedAt DateTime? // Profile Status isProfileComplete Boolean @default(false) profileCompleteness Int @default(0) + isPublic Boolean @default(true) + isFeatured Boolean @default(false) + + // Stats (denormalized for performance) + totalReviews Int @default(0) + averageRating Float @default(0) // Timestamps createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations - user User @relation(fields: [userId], references: [id], onDelete: Cascade) + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + specializations AgentSpecialization[] + verificationRequests VerificationRequest[] @@index([userId]) @@index([slug]) @@index([city, state]) + @@index([isVerified]) + @@index([isPublic]) + @@index([isFeatured]) @@map("agent_profiles") } +// =========================================== +// CATEGORY - Main categories for agents +// =========================================== + +model Category { + id String @id @default(uuid()) + name String @unique + slug String @unique + description String? + icon String? + image String? + isActive Boolean @default(true) + sortOrder Int @default(0) + + // Timestamps + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + specializations Specialization[] + + @@index([slug]) + @@index([isActive]) + @@map("categories") +} + +// =========================================== +// SPECIALIZATION - Sub-categories under Category +// =========================================== + +model Specialization { + id String @id @default(uuid()) + categoryId String + name String + slug String @unique + description String? + isActive Boolean @default(true) + sortOrder Int @default(0) + + // Timestamps + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + category Category @relation(fields: [categoryId], references: [id], onDelete: Cascade) + agents AgentSpecialization[] + + @@unique([categoryId, name]) + @@index([categoryId]) + @@index([slug]) + @@map("specializations") +} + +// =========================================== +// AGENT SPECIALIZATION - Many-to-Many pivot +// =========================================== + +model AgentSpecialization { + id String @id @default(uuid()) + agentProfileId String + specializationId String + + // Timestamps + createdAt DateTime @default(now()) + + // Relations + agentProfile AgentProfile @relation(fields: [agentProfileId], references: [id], onDelete: Cascade) + specialization Specialization @relation(fields: [specializationId], references: [id], onDelete: Cascade) + + @@unique([agentProfileId, specializationId]) + @@index([agentProfileId]) + @@index([specializationId]) + @@map("agent_specializations") +} + +// =========================================== +// VERIFICATION REQUEST - Agent verification +// =========================================== + +model VerificationRequest { + id String @id @default(uuid()) + agentProfileId String + + // Document Info + documentType VerificationDocType + documentUrl String + documentName String? + notes String? @db.Text + + // Status + status VerificationStatus @default(PENDING) + reviewedAt DateTime? + reviewedBy String? // Admin user ID + adminNotes String? @db.Text + rejectionReason String? + + // Timestamps + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + agentProfile AgentProfile @relation(fields: [agentProfileId], references: [id], onDelete: Cascade) + + @@index([agentProfileId]) + @@index([status]) + @@map("verification_requests") +} + // =========================================== // SESSION - JWT Token Management // =========================================== diff --git a/src/agents/agents.controller.ts b/src/agents/agents.controller.ts new file mode 100644 index 0000000..b40f460 --- /dev/null +++ b/src/agents/agents.controller.ts @@ -0,0 +1,176 @@ +import { + Controller, + Get, + Post, + Put, + Delete, + Body, + Param, + Query, + UseGuards, + ParseUUIDPipe, +} from '@nestjs/common'; +import { + ApiTags, + ApiOperation, + ApiResponse, + ApiBearerAuth, +} from '@nestjs/swagger'; +import { AgentsService } from './agents.service'; +import { + CreateAgentProfileDto, + UpdateAgentProfileDto, + SearchAgentsDto, +} from './dto'; +import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; +import { RolesGuard } from '../auth/guards/roles.guard'; +import { Roles } from '../auth/decorators/roles.decorator'; +import { CurrentUser } from '../auth/decorators/current-user.decorator'; +import { UserRole } from '@prisma/client'; + +@ApiTags('agents') +@Controller('agents') +export class AgentsController { + constructor(private readonly agentsService: AgentsService) {} + + // Public endpoints + + @Get() + @ApiOperation({ summary: 'Search and list agents' }) + @ApiResponse({ status: 200, description: 'Agents retrieved successfully' }) + async searchAgents(@Query() dto: SearchAgentsDto) { + return this.agentsService.searchAgents(dto); + } + + @Get('featured') + @ApiOperation({ summary: 'Get featured agents' }) + @ApiResponse({ + status: 200, + description: 'Featured agents retrieved successfully', + }) + async getFeaturedAgents(@Query('limit') limit?: number) { + return this.agentsService.getFeaturedAgents(limit || 10); + } + + @Get('top-rated') + @ApiOperation({ summary: 'Get top rated agents' }) + @ApiResponse({ + status: 200, + description: 'Top rated agents retrieved successfully', + }) + async getTopRatedAgents(@Query('limit') limit?: number) { + return this.agentsService.getTopRatedAgents(limit || 10); + } + + // Authenticated endpoints - must come before :id route + + @Get('profile/me') + @UseGuards(JwtAuthGuard) + @ApiBearerAuth() + @ApiOperation({ summary: 'Get current user agent profile' }) + @ApiResponse({ status: 200, description: 'Profile retrieved successfully' }) + @ApiResponse({ status: 404, description: 'Profile not found' }) + async getMyProfile(@CurrentUser('id') userId: string) { + return this.agentsService.getProfile(userId); + } + + // Parameterized route - must come after specific routes + @Get(':id') + @ApiOperation({ summary: 'Get agent profile by ID' }) + @ApiResponse({ status: 200, description: 'Agent profile retrieved successfully' }) + @ApiResponse({ status: 404, description: 'Agent profile not found' }) + async getAgentById(@Param('id', ParseUUIDPipe) id: string) { + return this.agentsService.getProfileById(id); + } + + @Post('profile') + @UseGuards(JwtAuthGuard) + @ApiBearerAuth() + @ApiOperation({ summary: 'Create agent profile' }) + @ApiResponse({ status: 201, description: 'Profile created successfully' }) + @ApiResponse({ status: 409, description: 'Profile already exists' }) + async createProfile( + @CurrentUser('id') userId: string, + @Body() dto: CreateAgentProfileDto, + ) { + return this.agentsService.createProfile(userId, dto); + } + + @Put('profile') + @UseGuards(JwtAuthGuard) + @ApiBearerAuth() + @ApiOperation({ summary: 'Update current user agent profile' }) + @ApiResponse({ status: 200, description: 'Profile updated successfully' }) + @ApiResponse({ status: 404, description: 'Profile not found' }) + async updateProfile( + @CurrentUser('id') userId: string, + @Body() dto: UpdateAgentProfileDto, + ) { + return this.agentsService.updateProfile(userId, dto); + } + + // Admin endpoints + + @Get('admin/list') + @UseGuards(JwtAuthGuard, RolesGuard) + @Roles(UserRole.ADMIN) + @ApiBearerAuth() + @ApiOperation({ summary: 'Admin: List all agents' }) + @ApiResponse({ status: 200, description: 'Agents retrieved successfully' }) + async adminListAgents(@Query() dto: SearchAgentsDto) { + return this.agentsService.getAllAgentsAdmin(dto); + } + + @Put('admin/:id') + @UseGuards(JwtAuthGuard, RolesGuard) + @Roles(UserRole.ADMIN) + @ApiBearerAuth() + @ApiOperation({ summary: 'Admin: Update agent profile' }) + @ApiResponse({ status: 200, description: 'Profile updated successfully' }) + @ApiResponse({ status: 404, description: 'Profile not found' }) + async adminUpdateProfile( + @Param('id', ParseUUIDPipe) id: string, + @Body() dto: UpdateAgentProfileDto, + ) { + return this.agentsService.adminUpdateProfile(id, dto); + } + + @Delete('admin/:id') + @UseGuards(JwtAuthGuard, RolesGuard) + @Roles(UserRole.ADMIN) + @ApiBearerAuth() + @ApiOperation({ summary: 'Admin: Delete agent profile' }) + @ApiResponse({ status: 200, description: 'Profile deleted successfully' }) + @ApiResponse({ status: 404, description: 'Profile not found' }) + async adminDeleteProfile(@Param('id', ParseUUIDPipe) id: string) { + return this.agentsService.adminDeleteProfile(id); + } + + @Put('admin/:id/verify') + @UseGuards(JwtAuthGuard, RolesGuard) + @Roles(UserRole.ADMIN) + @ApiBearerAuth() + @ApiOperation({ summary: 'Admin: Verify/unverify agent' }) + @ApiResponse({ status: 200, description: 'Verification status updated' }) + @ApiResponse({ status: 404, description: 'Profile not found' }) + async adminVerifyAgent( + @Param('id', ParseUUIDPipe) id: string, + @Body('isVerified') isVerified: boolean, + ) { + return this.agentsService.adminVerifyAgent(id, isVerified); + } + + @Put('admin/:id/featured') + @UseGuards(JwtAuthGuard, RolesGuard) + @Roles(UserRole.ADMIN) + @ApiBearerAuth() + @ApiOperation({ summary: 'Admin: Set agent as featured' }) + @ApiResponse({ status: 200, description: 'Featured status updated' }) + @ApiResponse({ status: 404, description: 'Profile not found' }) + async adminSetFeatured( + @Param('id', ParseUUIDPipe) id: string, + @Body('isFeatured') isFeatured: boolean, + ) { + return this.agentsService.adminSetFeatured(id, isFeatured); + } +} diff --git a/src/agents/agents.module.ts b/src/agents/agents.module.ts new file mode 100644 index 0000000..0a93f5c --- /dev/null +++ b/src/agents/agents.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { AgentsController } from './agents.controller'; +import { AgentsService } from './agents.service'; +import { PrismaModule } from '../prisma/prisma.module'; + +@Module({ + imports: [PrismaModule], + controllers: [AgentsController], + providers: [AgentsService], + exports: [AgentsService], +}) +export class AgentsModule {} diff --git a/src/agents/agents.service.ts b/src/agents/agents.service.ts new file mode 100644 index 0000000..451709e --- /dev/null +++ b/src/agents/agents.service.ts @@ -0,0 +1,509 @@ +import { + Injectable, + NotFoundException, + ForbiddenException, + ConflictException, +} from '@nestjs/common'; +import { PrismaService } from '../prisma/prisma.service'; +import { + CreateAgentProfileDto, + UpdateAgentProfileDto, + SearchAgentsDto, +} from './dto'; +import { Prisma } from '@prisma/client'; + +function generateSlug(firstName: string, lastName: string): string { + const base = `${firstName}-${lastName}` + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-|-$/g, ''); + const uniqueSuffix = Math.random().toString(36).substring(2, 8); + return `${base}-${uniqueSuffix}`; +} + +@Injectable() +export class AgentsService { + constructor(private prisma: PrismaService) {} + + async createProfile(userId: string, dto: CreateAgentProfileDto) { + // Check if user already has an agent profile + const existingProfile = await this.prisma.agentProfile.findUnique({ + where: { userId }, + }); + + if (existingProfile) { + throw new ConflictException('User already has an agent profile'); + } + + const { specializationIds, ...profileData } = dto; + const slug = generateSlug(dto.firstName, dto.lastName); + + const profile = await this.prisma.agentProfile.create({ + data: { + ...profileData, + userId, + slug, + specializations: specializationIds?.length + ? { + create: specializationIds.map((specId) => ({ + specializationId: specId, + })), + } + : undefined, + }, + include: { + user: { + select: { + id: true, + email: true, + avatar: true, + }, + }, + specializations: { + include: { + specialization: { + include: { + category: true, + }, + }, + }, + }, + }, + }); + + return profile; + } + + async getProfile(userId: string) { + const profile = await this.prisma.agentProfile.findUnique({ + where: { userId }, + include: { + user: { + select: { + id: true, + email: true, + avatar: true, + }, + }, + specializations: { + include: { + specialization: { + include: { + category: true, + }, + }, + }, + }, + verificationRequests: { + orderBy: { createdAt: 'desc' }, + take: 5, + }, + }, + }); + + if (!profile) { + throw new NotFoundException('Agent profile not found'); + } + + return profile; + } + + async getProfileById(profileId: string) { + const profile = await this.prisma.agentProfile.findUnique({ + where: { id: profileId }, + include: { + user: { + select: { + id: true, + email: true, + avatar: true, + }, + }, + specializations: { + include: { + specialization: { + include: { + category: true, + }, + }, + }, + }, + }, + }); + + if (!profile) { + throw new NotFoundException('Agent profile not found'); + } + + return profile; + } + + async updateProfile(userId: string, dto: UpdateAgentProfileDto) { + const profile = await this.prisma.agentProfile.findUnique({ + where: { userId }, + }); + + if (!profile) { + throw new NotFoundException('Agent profile not found'); + } + + const { specializationIds, ...profileData } = dto; + + // If specializationIds is provided, update the specializations + if (specializationIds !== undefined) { + // Delete existing specializations + await this.prisma.agentSpecialization.deleteMany({ + where: { agentProfileId: profile.id }, + }); + + // Create new specializations + if (specializationIds.length > 0) { + await this.prisma.agentSpecialization.createMany({ + data: specializationIds.map((specId) => ({ + agentProfileId: profile.id, + specializationId: specId, + })), + }); + } + } + + const updatedProfile = await this.prisma.agentProfile.update({ + where: { userId }, + data: profileData, + include: { + user: { + select: { + id: true, + email: true, + avatar: true, + }, + }, + specializations: { + include: { + specialization: { + include: { + category: true, + }, + }, + }, + }, + }, + }); + + return updatedProfile; + } + + async searchAgents(dto: SearchAgentsDto) { + const { + search, + city, + state, + country, + categoryId, + specializationIds, + isVerified, + page = 1, + limit = 10, + sortBy = 'createdAt', + sortOrder = 'desc', + } = dto; + + const skip = (page - 1) * limit; + + // Build where clause + const where: Prisma.AgentProfileWhereInput = {}; + + if (search) { + where.OR = [ + { firstName: { contains: search, mode: 'insensitive' } }, + { lastName: { contains: search, mode: 'insensitive' } }, + { bio: { contains: search, mode: 'insensitive' } }, + { headline: { contains: search, mode: 'insensitive' } }, + { companyName: { contains: search, mode: 'insensitive' } }, + ]; + } + + if (city) { + where.city = { contains: city, mode: 'insensitive' }; + } + + if (state) { + where.state = { contains: state, mode: 'insensitive' }; + } + + if (country) { + where.country = { contains: country, mode: 'insensitive' }; + } + + if (isVerified !== undefined) { + where.isVerified = isVerified; + } + + // Filter by category + if (categoryId) { + where.specializations = { + some: { + specialization: { + categoryId, + }, + }, + }; + } + + // Filter by specializations + if (specializationIds?.length) { + where.specializations = { + some: { + specializationId: { in: specializationIds }, + }, + }; + } + + // Build orderBy + const orderBy: Prisma.AgentProfileOrderByWithRelationInput = {}; + if (sortBy === 'averageRating') { + orderBy.averageRating = sortOrder; + } else if (sortBy === 'totalReviews') { + orderBy.totalReviews = sortOrder; + } else if (sortBy === 'firstName') { + orderBy.firstName = sortOrder; + } else { + orderBy.createdAt = sortOrder; + } + + const [agents, total] = await Promise.all([ + this.prisma.agentProfile.findMany({ + where, + skip, + take: limit, + orderBy, + include: { + user: { + select: { + id: true, + email: true, + avatar: true, + }, + }, + specializations: { + include: { + specialization: { + include: { + category: true, + }, + }, + }, + }, + }, + }), + this.prisma.agentProfile.count({ where }), + ]); + + return { + data: agents, + meta: { + total, + page, + limit, + totalPages: Math.ceil(total / limit), + }, + }; + } + + async getFeaturedAgents(limit = 10) { + const agents = await this.prisma.agentProfile.findMany({ + where: { + isVerified: true, + isFeatured: true, + }, + take: limit, + orderBy: [{ averageRating: 'desc' }, { totalReviews: 'desc' }], + include: { + user: { + select: { + id: true, + email: true, + avatar: true, + }, + }, + specializations: { + include: { + specialization: { + include: { + category: true, + }, + }, + }, + }, + }, + }); + + return agents; + } + + async getTopRatedAgents(limit = 10) { + const agents = await this.prisma.agentProfile.findMany({ + where: { + isVerified: true, + totalReviews: { gte: 5 }, + }, + take: limit, + orderBy: [{ averageRating: 'desc' }, { totalReviews: 'desc' }], + include: { + user: { + select: { + id: true, + email: true, + avatar: true, + }, + }, + specializations: { + include: { + specialization: { + include: { + category: true, + }, + }, + }, + }, + }, + }); + + return agents; + } + + // Admin methods + async adminUpdateProfile(profileId: string, dto: UpdateAgentProfileDto) { + const profile = await this.prisma.agentProfile.findUnique({ + where: { id: profileId }, + }); + + if (!profile) { + throw new NotFoundException('Agent profile not found'); + } + + const { specializationIds, ...profileData } = dto; + + if (specializationIds !== undefined) { + await this.prisma.agentSpecialization.deleteMany({ + where: { agentProfileId: profileId }, + }); + + if (specializationIds.length > 0) { + await this.prisma.agentSpecialization.createMany({ + data: specializationIds.map((specId) => ({ + agentProfileId: profileId, + specializationId: specId, + })), + }); + } + } + + const updatedProfile = await this.prisma.agentProfile.update({ + where: { id: profileId }, + data: profileData, + include: { + user: { + select: { + id: true, + email: true, + avatar: true, + }, + }, + specializations: { + include: { + specialization: { + include: { + category: true, + }, + }, + }, + }, + }, + }); + + return updatedProfile; + } + + async adminDeleteProfile(profileId: string) { + const profile = await this.prisma.agentProfile.findUnique({ + where: { id: profileId }, + }); + + if (!profile) { + throw new NotFoundException('Agent profile not found'); + } + + // Delete related records first + await this.prisma.agentSpecialization.deleteMany({ + where: { agentProfileId: profileId }, + }); + + await this.prisma.verificationRequest.deleteMany({ + where: { agentProfileId: profileId }, + }); + + await this.prisma.agentProfile.delete({ + where: { id: profileId }, + }); + + return { message: 'Agent profile deleted successfully' }; + } + + async adminVerifyAgent(profileId: string, isVerified: boolean) { + const profile = await this.prisma.agentProfile.findUnique({ + where: { id: profileId }, + }); + + if (!profile) { + throw new NotFoundException('Agent profile not found'); + } + + const updatedProfile = await this.prisma.agentProfile.update({ + where: { id: profileId }, + data: { isVerified }, + include: { + user: { + select: { + id: true, + email: true, + avatar: true, + }, + }, + }, + }); + + return updatedProfile; + } + + async adminSetFeatured(profileId: string, isFeatured: boolean) { + const profile = await this.prisma.agentProfile.findUnique({ + where: { id: profileId }, + }); + + if (!profile) { + throw new NotFoundException('Agent profile not found'); + } + + const updatedProfile = await this.prisma.agentProfile.update({ + where: { id: profileId }, + data: { isFeatured }, + include: { + user: { + select: { + id: true, + email: true, + avatar: true, + }, + }, + }, + }); + + return updatedProfile; + } + + async getAllAgentsAdmin(dto: SearchAgentsDto) { + // Same as searchAgents but returns all data for admin + return this.searchAgents(dto); + } +} diff --git a/src/agents/dto/create-agent-profile.dto.ts b/src/agents/dto/create-agent-profile.dto.ts new file mode 100644 index 0000000..281eb4c --- /dev/null +++ b/src/agents/dto/create-agent-profile.dto.ts @@ -0,0 +1,128 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsString, + IsOptional, + IsArray, + IsInt, + IsUrl, + IsUUID, + MinLength, + MaxLength, + Min, + Max, +} from 'class-validator'; + +export class CreateAgentProfileDto { + @ApiProperty({ example: 'John' }) + @IsString() + @MinLength(1) + @MaxLength(100) + firstName: string; + + @ApiProperty({ example: 'Doe' }) + @IsString() + @MinLength(1) + @MaxLength(100) + lastName: string; + + @ApiPropertyOptional({ example: '+1234567890' }) + @IsOptional() + @IsString() + @MaxLength(20) + phone?: string; + + @ApiPropertyOptional({ example: 'Experienced real estate agent...' }) + @IsOptional() + @IsString() + @MaxLength(2000) + bio?: string; + + @ApiPropertyOptional({ example: 'Top Real Estate Agent in California' }) + @IsOptional() + @IsString() + @MaxLength(200) + headline?: string; + + @ApiPropertyOptional({ example: 'Los Angeles' }) + @IsOptional() + @IsString() + @MaxLength(100) + city?: string; + + @ApiPropertyOptional({ example: 'California' }) + @IsOptional() + @IsString() + @MaxLength(100) + state?: string; + + @ApiPropertyOptional({ example: 'USA' }) + @IsOptional() + @IsString() + @MaxLength(100) + country?: string; + + @ApiPropertyOptional({ example: '123 Main St' }) + @IsOptional() + @IsString() + @MaxLength(200) + address?: string; + + @ApiPropertyOptional({ example: '90001' }) + @IsOptional() + @IsString() + @MaxLength(20) + zipCode?: string; + + @ApiPropertyOptional({ example: 10 }) + @IsOptional() + @IsInt() + @Min(0) + @Max(100) + yearsOfExperience?: number; + + @ApiPropertyOptional({ example: 'LIC-12345' }) + @IsOptional() + @IsString() + @MaxLength(50) + licenseNumber?: string; + + @ApiPropertyOptional({ example: 'ABC Realty' }) + @IsOptional() + @IsString() + @MaxLength(200) + companyName?: string; + + @ApiPropertyOptional({ example: 'https://mywebsite.com' }) + @IsOptional() + @IsUrl() + website?: string; + + @ApiPropertyOptional({ example: 'https://facebook.com/johndoe' }) + @IsOptional() + @IsUrl() + facebookUrl?: string; + + @ApiPropertyOptional({ example: 'https://twitter.com/johndoe' }) + @IsOptional() + @IsUrl() + twitterUrl?: string; + + @ApiPropertyOptional({ example: 'https://linkedin.com/in/johndoe' }) + @IsOptional() + @IsUrl() + linkedinUrl?: string; + + @ApiPropertyOptional({ example: 'https://instagram.com/johndoe' }) + @IsOptional() + @IsUrl() + instagramUrl?: string; + + @ApiPropertyOptional({ + example: ['uuid-1', 'uuid-2'], + description: 'Array of specialization IDs', + }) + @IsOptional() + @IsArray() + @IsUUID('4', { each: true }) + specializationIds?: string[]; +} diff --git a/src/agents/dto/index.ts b/src/agents/dto/index.ts new file mode 100644 index 0000000..babc822 --- /dev/null +++ b/src/agents/dto/index.ts @@ -0,0 +1,3 @@ +export * from './create-agent-profile.dto'; +export * from './update-agent-profile.dto'; +export * from './search-agents.dto'; diff --git a/src/agents/dto/search-agents.dto.ts b/src/agents/dto/search-agents.dto.ts new file mode 100644 index 0000000..3aa2b44 --- /dev/null +++ b/src/agents/dto/search-agents.dto.ts @@ -0,0 +1,83 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsString, + IsOptional, + IsBoolean, + IsInt, + IsUUID, + IsArray, + Min, + Max, +} from 'class-validator'; +import { Transform, Type } from 'class-transformer'; + +export class SearchAgentsDto { + @ApiPropertyOptional({ example: 'John' }) + @IsOptional() + @IsString() + search?: string; + + @ApiPropertyOptional({ example: 'Los Angeles' }) + @IsOptional() + @IsString() + city?: string; + + @ApiPropertyOptional({ example: 'California' }) + @IsOptional() + @IsString() + state?: string; + + @ApiPropertyOptional({ example: 'USA' }) + @IsOptional() + @IsString() + country?: string; + + @ApiPropertyOptional({ example: 'uuid-of-category' }) + @IsOptional() + @IsUUID() + categoryId?: string; + + @ApiPropertyOptional({ + example: ['uuid-1', 'uuid-2'], + description: 'Filter by specialization IDs', + }) + @IsOptional() + @IsArray() + @IsUUID('4', { each: true }) + @Transform(({ value }) => (typeof value === 'string' ? [value] : value)) + specializationIds?: string[]; + + @ApiPropertyOptional({ example: true }) + @IsOptional() + @Transform(({ value }) => value === 'true' || value === true) + @IsBoolean() + isVerified?: boolean; + + @ApiPropertyOptional({ example: 1, default: 1 }) + @IsOptional() + @Type(() => Number) + @IsInt() + @Min(1) + page?: number = 1; + + @ApiPropertyOptional({ example: 10, default: 10 }) + @IsOptional() + @Type(() => Number) + @IsInt() + @Min(1) + @Max(100) + limit?: number = 10; + + @ApiPropertyOptional({ + example: 'createdAt', + enum: ['createdAt', 'averageRating', 'totalReviews', 'firstName'], + }) + @IsOptional() + @IsString() + sortBy?: string = 'createdAt'; + + @ApiPropertyOptional({ example: 'desc', enum: ['asc', 'desc'] }) + @IsOptional() + @IsString() + sortOrder?: 'asc' | 'desc' = 'desc'; +} diff --git a/src/agents/dto/update-agent-profile.dto.ts b/src/agents/dto/update-agent-profile.dto.ts new file mode 100644 index 0000000..6053735 --- /dev/null +++ b/src/agents/dto/update-agent-profile.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/swagger'; +import { CreateAgentProfileDto } from './create-agent-profile.dto'; + +export class UpdateAgentProfileDto extends PartialType(CreateAgentProfileDto) {} diff --git a/src/agents/index.ts b/src/agents/index.ts new file mode 100644 index 0000000..4df1513 --- /dev/null +++ b/src/agents/index.ts @@ -0,0 +1,4 @@ +export * from './agents.module'; +export * from './agents.service'; +export * from './agents.controller'; +export * from './dto'; diff --git a/src/app.module.ts b/src/app.module.ts index 3bc32f4..da76567 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -10,6 +10,10 @@ import { PrismaModule } from './prisma'; import { AuthModule, JwtAuthGuard, RolesGuard } from './auth'; import { UsersModule } from './users'; import { EmailModule } from './email'; +import { CategoriesModule } from './categories'; +import { AgentsModule } from './agents'; +import { VerificationModule } from './verification'; +import { UploadModule } from './upload'; import { AppController } from './app.controller'; import { AppService } from './app.service'; @@ -43,6 +47,10 @@ import { AppService } from './app.service'; AuthModule, UsersModule, EmailModule, + CategoriesModule, + AgentsModule, + VerificationModule, + UploadModule, ], controllers: [AppController], providers: [ diff --git a/src/categories/categories.controller.ts b/src/categories/categories.controller.ts new file mode 100644 index 0000000..22f1a6a --- /dev/null +++ b/src/categories/categories.controller.ts @@ -0,0 +1,197 @@ +import { + Controller, + Get, + Post, + Put, + Delete, + Body, + Param, + Query, + UseGuards, + HttpCode, + HttpStatus, +} from '@nestjs/common'; +import { + ApiTags, + ApiOperation, + ApiResponse, + ApiBearerAuth, + ApiQuery, +} from '@nestjs/swagger'; +import { CategoriesService } from './categories.service'; +import { + CreateCategoryDto, + UpdateCategoryDto, + CreateSpecializationDto, + UpdateSpecializationDto, +} from './dto'; +import { JwtAuthGuard, RolesGuard } from '../auth/guards'; +import { Public, Roles } from '../auth/decorators'; +import { UserRole } from '@prisma/client'; + +@ApiTags('Categories') +@Controller('categories') +export class CategoriesController { + constructor(private readonly categoriesService: CategoriesService) {} + + // =========================================== + // CATEGORY ENDPOINTS + // =========================================== + + @Public() + @Get() + @ApiOperation({ summary: 'Get all categories with specializations' }) + @ApiQuery({ + name: 'includeInactive', + required: false, + type: Boolean, + description: 'Include inactive categories (admin only)', + }) + @ApiResponse({ + status: 200, + description: 'List of categories', + schema: { + example: { + success: true, + data: [ + { + id: 'uuid', + name: 'Real Estate', + slug: 'real-estate', + description: 'Real estate agents', + icon: 'home-icon', + isActive: true, + specializations: [ + { + id: 'uuid', + name: 'Residential Sales', + slug: 'real-estate-residential-sales', + }, + ], + }, + ], + }, + }, + }) + async findAll(@Query('includeInactive') includeInactive?: string) { + return this.categoriesService.findAllCategories( + includeInactive === 'true', + ); + } + + @Public() + @Get(':id') + @ApiOperation({ summary: 'Get category by ID' }) + @ApiResponse({ status: 200, description: 'Category details' }) + @ApiResponse({ status: 404, description: 'Category not found' }) + async findOne(@Param('id') id: string) { + return this.categoriesService.findCategoryById(id); + } + + @Public() + @Get('slug/:slug') + @ApiOperation({ summary: 'Get category by slug' }) + @ApiResponse({ status: 200, description: 'Category details' }) + @ApiResponse({ status: 404, description: 'Category not found' }) + async findBySlug(@Param('slug') slug: string) { + return this.categoriesService.findCategoryBySlug(slug); + } + + @Post() + @UseGuards(JwtAuthGuard, RolesGuard) + @Roles(UserRole.ADMIN) + @ApiBearerAuth('JWT-auth') + @ApiOperation({ summary: 'Create a new category (Admin only)' }) + @ApiResponse({ status: 201, description: 'Category created' }) + @ApiResponse({ status: 409, description: 'Category already exists' }) + async create(@Body() dto: CreateCategoryDto) { + return this.categoriesService.createCategory(dto); + } + + @Put(':id') + @UseGuards(JwtAuthGuard, RolesGuard) + @Roles(UserRole.ADMIN) + @ApiBearerAuth('JWT-auth') + @ApiOperation({ summary: 'Update a category (Admin only)' }) + @ApiResponse({ status: 200, description: 'Category updated' }) + @ApiResponse({ status: 404, description: 'Category not found' }) + async update(@Param('id') id: string, @Body() dto: UpdateCategoryDto) { + return this.categoriesService.updateCategory(id, dto); + } + + @Delete(':id') + @UseGuards(JwtAuthGuard, RolesGuard) + @Roles(UserRole.ADMIN) + @HttpCode(HttpStatus.OK) + @ApiBearerAuth('JWT-auth') + @ApiOperation({ summary: 'Delete a category (Admin only)' }) + @ApiResponse({ status: 200, description: 'Category deleted' }) + @ApiResponse({ status: 404, description: 'Category not found' }) + async remove(@Param('id') id: string) { + return this.categoriesService.deleteCategory(id); + } + + // =========================================== + // SPECIALIZATION ENDPOINTS + // =========================================== + + @Public() + @Get('specializations/all') + @ApiOperation({ summary: 'Get all specializations' }) + @ApiQuery({ + name: 'categoryId', + required: false, + description: 'Filter by category ID', + }) + @ApiResponse({ status: 200, description: 'List of specializations' }) + async findAllSpecializations(@Query('categoryId') categoryId?: string) { + return this.categoriesService.findAllSpecializations(categoryId); + } + + @Public() + @Get('specializations/:id') + @ApiOperation({ summary: 'Get specialization by ID' }) + @ApiResponse({ status: 200, description: 'Specialization details' }) + @ApiResponse({ status: 404, description: 'Specialization not found' }) + async findSpecialization(@Param('id') id: string) { + return this.categoriesService.findSpecializationById(id); + } + + @Post('specializations') + @UseGuards(JwtAuthGuard, RolesGuard) + @Roles(UserRole.ADMIN) + @ApiBearerAuth('JWT-auth') + @ApiOperation({ summary: 'Create a new specialization (Admin only)' }) + @ApiResponse({ status: 201, description: 'Specialization created' }) + @ApiResponse({ status: 404, description: 'Category not found' }) + @ApiResponse({ status: 409, description: 'Specialization already exists' }) + async createSpecialization(@Body() dto: CreateSpecializationDto) { + return this.categoriesService.createSpecialization(dto); + } + + @Put('specializations/:id') + @UseGuards(JwtAuthGuard, RolesGuard) + @Roles(UserRole.ADMIN) + @ApiBearerAuth('JWT-auth') + @ApiOperation({ summary: 'Update a specialization (Admin only)' }) + @ApiResponse({ status: 200, description: 'Specialization updated' }) + @ApiResponse({ status: 404, description: 'Specialization not found' }) + async updateSpecialization( + @Param('id') id: string, + @Body() dto: UpdateSpecializationDto, + ) { + return this.categoriesService.updateSpecialization(id, dto); + } + + @Delete('specializations/:id') + @UseGuards(JwtAuthGuard, RolesGuard) + @Roles(UserRole.ADMIN) + @HttpCode(HttpStatus.OK) + @ApiBearerAuth('JWT-auth') + @ApiOperation({ summary: 'Delete a specialization (Admin only)' }) + @ApiResponse({ status: 200, description: 'Specialization deleted' }) + @ApiResponse({ status: 404, description: 'Specialization not found' }) + async removeSpecialization(@Param('id') id: string) { + return this.categoriesService.deleteSpecialization(id); + } +} diff --git a/src/categories/categories.module.ts b/src/categories/categories.module.ts new file mode 100644 index 0000000..0339875 --- /dev/null +++ b/src/categories/categories.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { CategoriesController } from './categories.controller'; +import { CategoriesService } from './categories.service'; +import { PrismaModule } from '../prisma/prisma.module'; + +@Module({ + imports: [PrismaModule], + controllers: [CategoriesController], + providers: [CategoriesService], + exports: [CategoriesService], +}) +export class CategoriesModule {} diff --git a/src/categories/categories.service.ts b/src/categories/categories.service.ts new file mode 100644 index 0000000..f58fa81 --- /dev/null +++ b/src/categories/categories.service.ts @@ -0,0 +1,328 @@ +import { + Injectable, + NotFoundException, + ConflictException, +} from '@nestjs/common'; +import { PrismaService } from '../prisma/prisma.service'; +import { + CreateCategoryDto, + UpdateCategoryDto, + CreateSpecializationDto, + UpdateSpecializationDto, +} from './dto'; + +@Injectable() +export class CategoriesService { + constructor(private prisma: PrismaService) {} + + // =========================================== + // CATEGORY METHODS + // =========================================== + + private generateSlug(name: string): string { + return name + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/(^-|-$)/g, ''); + } + + async createCategory(dto: CreateCategoryDto) { + const slug = this.generateSlug(dto.name); + + // Check if category with same name or slug exists + const existing = await this.prisma.category.findFirst({ + where: { + OR: [{ name: dto.name }, { slug }], + }, + }); + + if (existing) { + throw new ConflictException('Category with this name already exists'); + } + + const category = await this.prisma.category.create({ + data: { + ...dto, + slug, + }, + include: { + specializations: true, + }, + }); + + return { + success: true, + data: category, + }; + } + + async findAllCategories(includeInactive = false) { + const categories = await this.prisma.category.findMany({ + where: includeInactive ? {} : { isActive: true }, + include: { + specializations: { + where: includeInactive ? {} : { isActive: true }, + orderBy: { sortOrder: 'asc' }, + }, + }, + orderBy: { sortOrder: 'asc' }, + }); + + return { + success: true, + data: categories, + }; + } + + async findCategoryById(id: string) { + const category = await this.prisma.category.findUnique({ + where: { id }, + include: { + specializations: { + orderBy: { sortOrder: 'asc' }, + }, + }, + }); + + if (!category) { + throw new NotFoundException('Category not found'); + } + + return { + success: true, + data: category, + }; + } + + async findCategoryBySlug(slug: string) { + const category = await this.prisma.category.findUnique({ + where: { slug }, + include: { + specializations: { + where: { isActive: true }, + orderBy: { sortOrder: 'asc' }, + }, + }, + }); + + if (!category) { + throw new NotFoundException('Category not found'); + } + + return { + success: true, + data: category, + }; + } + + async updateCategory(id: string, dto: UpdateCategoryDto) { + const category = await this.prisma.category.findUnique({ + where: { id }, + }); + + if (!category) { + throw new NotFoundException('Category not found'); + } + + // If name is being updated, generate new slug + let slug = category.slug; + if (dto.name && dto.name !== category.name) { + slug = this.generateSlug(dto.name); + + // Check if new name/slug conflicts + const existing = await this.prisma.category.findFirst({ + where: { + OR: [{ name: dto.name }, { slug }], + NOT: { id }, + }, + }); + + if (existing) { + throw new ConflictException('Category with this name already exists'); + } + } + + const updated = await this.prisma.category.update({ + where: { id }, + data: { + ...dto, + slug, + }, + include: { + specializations: true, + }, + }); + + return { + success: true, + data: updated, + }; + } + + async deleteCategory(id: string) { + const category = await this.prisma.category.findUnique({ + where: { id }, + }); + + if (!category) { + throw new NotFoundException('Category not found'); + } + + await this.prisma.category.delete({ + where: { id }, + }); + + return { + success: true, + data: { message: 'Category deleted successfully' }, + }; + } + + // =========================================== + // SPECIALIZATION METHODS + // =========================================== + + async createSpecialization(dto: CreateSpecializationDto) { + // Check if category exists + const category = await this.prisma.category.findUnique({ + where: { id: dto.categoryId }, + }); + + if (!category) { + throw new NotFoundException('Category not found'); + } + + const slug = this.generateSlug(`${category.slug}-${dto.name}`); + + // Check if specialization with same name exists in category + const existing = await this.prisma.specialization.findFirst({ + where: { + categoryId: dto.categoryId, + name: dto.name, + }, + }); + + if (existing) { + throw new ConflictException( + 'Specialization with this name already exists in this category', + ); + } + + const specialization = await this.prisma.specialization.create({ + data: { + ...dto, + slug, + }, + include: { + category: true, + }, + }); + + return { + success: true, + data: specialization, + }; + } + + async findAllSpecializations(categoryId?: string) { + const specializations = await this.prisma.specialization.findMany({ + where: categoryId ? { categoryId, isActive: true } : { isActive: true }, + include: { + category: true, + }, + orderBy: { sortOrder: 'asc' }, + }); + + return { + success: true, + data: specializations, + }; + } + + async findSpecializationById(id: string) { + const specialization = await this.prisma.specialization.findUnique({ + where: { id }, + include: { + category: true, + }, + }); + + if (!specialization) { + throw new NotFoundException('Specialization not found'); + } + + return { + success: true, + data: specialization, + }; + } + + async updateSpecialization(id: string, dto: UpdateSpecializationDto) { + const specialization = await this.prisma.specialization.findUnique({ + where: { id }, + include: { category: true }, + }); + + if (!specialization) { + throw new NotFoundException('Specialization not found'); + } + + // If name is being updated, generate new slug + let slug = specialization.slug; + if (dto.name && dto.name !== specialization.name) { + slug = this.generateSlug( + `${specialization.category.slug}-${dto.name}`, + ); + + // Check if new name conflicts + const existing = await this.prisma.specialization.findFirst({ + where: { + categoryId: specialization.categoryId, + name: dto.name, + NOT: { id }, + }, + }); + + if (existing) { + throw new ConflictException( + 'Specialization with this name already exists in this category', + ); + } + } + + const updated = await this.prisma.specialization.update({ + where: { id }, + data: { + ...dto, + slug, + }, + include: { + category: true, + }, + }); + + return { + success: true, + data: updated, + }; + } + + async deleteSpecialization(id: string) { + const specialization = await this.prisma.specialization.findUnique({ + where: { id }, + }); + + if (!specialization) { + throw new NotFoundException('Specialization not found'); + } + + await this.prisma.specialization.delete({ + where: { id }, + }); + + return { + success: true, + data: { message: 'Specialization deleted successfully' }, + }; + } +} diff --git a/src/categories/dto/create-category.dto.ts b/src/categories/dto/create-category.dto.ts new file mode 100644 index 0000000..2b27117 --- /dev/null +++ b/src/categories/dto/create-category.dto.ts @@ -0,0 +1,43 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsString, + IsOptional, + IsBoolean, + IsInt, + MinLength, + MaxLength, +} from 'class-validator'; + +export class CreateCategoryDto { + @ApiProperty({ example: 'Real Estate' }) + @IsString() + @MinLength(2) + @MaxLength(100) + name: string; + + @ApiPropertyOptional({ example: 'Agents specializing in real estate' }) + @IsOptional() + @IsString() + @MaxLength(500) + description?: string; + + @ApiPropertyOptional({ example: 'home-icon' }) + @IsOptional() + @IsString() + icon?: string; + + @ApiPropertyOptional({ example: 'https://example.com/image.jpg' }) + @IsOptional() + @IsString() + image?: string; + + @ApiPropertyOptional({ example: true, default: true }) + @IsOptional() + @IsBoolean() + isActive?: boolean; + + @ApiPropertyOptional({ example: 0 }) + @IsOptional() + @IsInt() + sortOrder?: number; +} diff --git a/src/categories/dto/create-specialization.dto.ts b/src/categories/dto/create-specialization.dto.ts new file mode 100644 index 0000000..1cc0a97 --- /dev/null +++ b/src/categories/dto/create-specialization.dto.ts @@ -0,0 +1,38 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsString, + IsOptional, + IsBoolean, + IsInt, + IsUUID, + MinLength, + MaxLength, +} from 'class-validator'; + +export class CreateSpecializationDto { + @ApiProperty({ example: 'uuid-of-category' }) + @IsUUID() + categoryId: string; + + @ApiProperty({ example: 'Residential Sales' }) + @IsString() + @MinLength(2) + @MaxLength(100) + name: string; + + @ApiPropertyOptional({ example: 'Specializing in residential property sales' }) + @IsOptional() + @IsString() + @MaxLength(500) + description?: string; + + @ApiPropertyOptional({ example: true, default: true }) + @IsOptional() + @IsBoolean() + isActive?: boolean; + + @ApiPropertyOptional({ example: 0 }) + @IsOptional() + @IsInt() + sortOrder?: number; +} diff --git a/src/categories/dto/index.ts b/src/categories/dto/index.ts new file mode 100644 index 0000000..0ab907d --- /dev/null +++ b/src/categories/dto/index.ts @@ -0,0 +1,4 @@ +export * from './create-category.dto'; +export * from './update-category.dto'; +export * from './create-specialization.dto'; +export * from './update-specialization.dto'; diff --git a/src/categories/dto/update-category.dto.ts b/src/categories/dto/update-category.dto.ts new file mode 100644 index 0000000..d713b9b --- /dev/null +++ b/src/categories/dto/update-category.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/swagger'; +import { CreateCategoryDto } from './create-category.dto'; + +export class UpdateCategoryDto extends PartialType(CreateCategoryDto) {} diff --git a/src/categories/dto/update-specialization.dto.ts b/src/categories/dto/update-specialization.dto.ts new file mode 100644 index 0000000..65f120f --- /dev/null +++ b/src/categories/dto/update-specialization.dto.ts @@ -0,0 +1,6 @@ +import { PartialType, OmitType } from '@nestjs/swagger'; +import { CreateSpecializationDto } from './create-specialization.dto'; + +export class UpdateSpecializationDto extends PartialType( + OmitType(CreateSpecializationDto, ['categoryId'] as const), +) {} diff --git a/src/categories/index.ts b/src/categories/index.ts new file mode 100644 index 0000000..6d82bce --- /dev/null +++ b/src/categories/index.ts @@ -0,0 +1,4 @@ +export * from './categories.module'; +export * from './categories.service'; +export * from './categories.controller'; +export * from './dto'; diff --git a/src/upload/dto/index.ts b/src/upload/dto/index.ts new file mode 100644 index 0000000..514077e --- /dev/null +++ b/src/upload/dto/index.ts @@ -0,0 +1 @@ +export * from './upload.dto'; diff --git a/src/upload/dto/upload.dto.ts b/src/upload/dto/upload.dto.ts new file mode 100644 index 0000000..7221369 --- /dev/null +++ b/src/upload/dto/upload.dto.ts @@ -0,0 +1,53 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsString, IsEnum, IsOptional } from 'class-validator'; + +export enum UploadFolder { + AVATARS = 'avatars', + DOCUMENTS = 'documents', + PROPERTIES = 'properties', + VERIFICATION = 'verification', +} + +export class PresignedUrlDto { + @ApiProperty({ + example: 'document.pdf', + description: 'Original filename', + }) + @IsString() + filename: string; + + @ApiProperty({ + example: 'application/pdf', + description: 'MIME type of the file', + }) + @IsString() + contentType: string; + + @ApiProperty({ + example: 'documents', + enum: UploadFolder, + description: 'Folder to upload to', + }) + @IsEnum(UploadFolder) + folder: UploadFolder; +} + +export class PresignedUrlResponseDto { + @ApiProperty({ + example: 'https://s3.amazonaws.com/bucket/...', + description: 'Pre-signed URL for uploading', + }) + uploadUrl: string; + + @ApiProperty({ + example: 'https://cdn.example.com/documents/abc123.pdf', + description: 'Public URL after upload completes', + }) + publicUrl: string; + + @ApiProperty({ + example: 'documents/abc123.pdf', + description: 'File key in storage', + }) + key: string; +} diff --git a/src/upload/index.ts b/src/upload/index.ts new file mode 100644 index 0000000..df3c79c --- /dev/null +++ b/src/upload/index.ts @@ -0,0 +1,4 @@ +export * from './upload.module'; +export * from './upload.service'; +export * from './upload.controller'; +export * from './dto'; diff --git a/src/upload/upload.controller.ts b/src/upload/upload.controller.ts new file mode 100644 index 0000000..840e01d --- /dev/null +++ b/src/upload/upload.controller.ts @@ -0,0 +1,47 @@ +import { + Controller, + Post, + Delete, + Body, + Param, + UseGuards, +} from '@nestjs/common'; +import { + ApiTags, + ApiOperation, + ApiResponse, + ApiBearerAuth, +} from '@nestjs/swagger'; +import { UploadService } from './upload.service'; +import { PresignedUrlDto, PresignedUrlResponseDto } from './dto'; +import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; + +@ApiTags('upload') +@Controller('upload') +@UseGuards(JwtAuthGuard) +@ApiBearerAuth() +export class UploadController { + constructor(private readonly uploadService: UploadService) {} + + @Post('presigned-url') + @ApiOperation({ summary: 'Get a pre-signed URL for file upload' }) + @ApiResponse({ + status: 200, + description: 'Pre-signed URL generated successfully', + type: PresignedUrlResponseDto, + }) + @ApiResponse({ status: 400, description: 'Invalid content type' }) + async getPresignedUrl( + @Body() dto: PresignedUrlDto, + ): Promise { + return this.uploadService.getPresignedUrl(dto); + } + + @Delete(':key') + @ApiOperation({ summary: 'Delete an uploaded file' }) + @ApiResponse({ status: 200, description: 'File deleted successfully' }) + async deleteFile(@Param('key') key: string) { + await this.uploadService.deleteFile(key); + return { message: 'File deleted successfully' }; + } +} diff --git a/src/upload/upload.module.ts b/src/upload/upload.module.ts new file mode 100644 index 0000000..e4baecb --- /dev/null +++ b/src/upload/upload.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { UploadController } from './upload.controller'; +import { UploadService } from './upload.service'; + +@Module({ + controllers: [UploadController], + providers: [UploadService], + exports: [UploadService], +}) +export class UploadModule {} diff --git a/src/upload/upload.service.ts b/src/upload/upload.service.ts new file mode 100644 index 0000000..591689b --- /dev/null +++ b/src/upload/upload.service.ts @@ -0,0 +1,119 @@ +import { Injectable, BadRequestException } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { + S3Client, + PutObjectCommand, + DeleteObjectCommand, + GetObjectCommand, +} from '@aws-sdk/client-s3'; +import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; +import { v4 as uuidv4 } from 'uuid'; +import { PresignedUrlDto, PresignedUrlResponseDto, UploadFolder } from './dto'; + +@Injectable() +export class UploadService { + private s3Client: S3Client; + private bucket: string; + private region: string; + + constructor(private configService: ConfigService) { + this.region = this.configService.get('AWS_REGION') || 'us-east-1'; + this.bucket = this.configService.get('AWS_S3_BUCKET') || ''; + + this.s3Client = new S3Client({ + region: this.region, + credentials: { + accessKeyId: this.configService.get('AWS_ACCESS_KEY_ID') || '', + secretAccessKey: + this.configService.get('AWS_SECRET_ACCESS_KEY') || '', + }, + }); + } + + private getFileExtension(filename: string): string { + const parts = filename.split('.'); + return parts.length > 1 ? parts.pop()!.toLowerCase() : ''; + } + + private validateContentType(contentType: string, folder: UploadFolder): void { + const allowedTypes: Record = { + [UploadFolder.AVATARS]: ['image/jpeg', 'image/png', 'image/webp'], + [UploadFolder.DOCUMENTS]: [ + 'application/pdf', + 'image/jpeg', + 'image/png', + 'application/msword', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + ], + [UploadFolder.PROPERTIES]: [ + 'image/jpeg', + 'image/png', + 'image/webp', + 'video/mp4', + 'video/quicktime', + ], + [UploadFolder.VERIFICATION]: [ + 'application/pdf', + 'image/jpeg', + 'image/png', + ], + }; + + const allowed = allowedTypes[folder]; + if (!allowed.includes(contentType)) { + throw new BadRequestException( + `Content type ${contentType} is not allowed for ${folder}. Allowed types: ${allowed.join(', ')}`, + ); + } + } + + async getPresignedUrl(dto: PresignedUrlDto): Promise { + this.validateContentType(dto.contentType, dto.folder); + + const extension = this.getFileExtension(dto.filename); + const uniqueId = uuidv4(); + const key = `${dto.folder}/${uniqueId}${extension ? `.${extension}` : ''}`; + + const command = new PutObjectCommand({ + Bucket: this.bucket, + Key: key, + ContentType: dto.contentType, + }); + + const uploadUrl = await getSignedUrl(this.s3Client, command, { + expiresIn: 3600, // 1 hour + }); + + const publicUrl = `https://${this.bucket}.s3.${this.region}.amazonaws.com/${key}`; + + return { + uploadUrl, + publicUrl, + key, + }; + } + + async deleteFile(key: string): Promise { + const command = new DeleteObjectCommand({ + Bucket: this.bucket, + Key: key, + }); + + await this.s3Client.send(command); + } + + async getSignedDownloadUrl(key: string): Promise { + const command = new GetObjectCommand({ + Bucket: this.bucket, + Key: key, + }); + + return getSignedUrl(this.s3Client, command, { + expiresIn: 3600, // 1 hour + }); + } + + getPublicUrl(key: string): string { + return `https://${this.bucket}.s3.${this.region}.amazonaws.com/${key}`; + } +} diff --git a/src/verification/dto/create-verification-request.dto.ts b/src/verification/dto/create-verification-request.dto.ts new file mode 100644 index 0000000..19a9586 --- /dev/null +++ b/src/verification/dto/create-verification-request.dto.ts @@ -0,0 +1,30 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsString, IsEnum, IsOptional, IsUrl, MaxLength } from 'class-validator'; +import { VerificationDocType } from '@prisma/client'; + +export class CreateVerificationRequestDto { + @ApiProperty({ + example: 'LICENSE', + enum: VerificationDocType, + description: 'Type of verification document', + }) + @IsEnum(VerificationDocType) + documentType: VerificationDocType; + + @ApiProperty({ + example: 'https://s3.amazonaws.com/bucket/license.pdf', + description: 'URL of the uploaded document', + }) + @IsString() + @IsUrl() + documentUrl: string; + + @ApiPropertyOptional({ + example: 'My real estate license from California', + description: 'Additional notes about the verification request', + }) + @IsOptional() + @IsString() + @MaxLength(500) + notes?: string; +} diff --git a/src/verification/dto/index.ts b/src/verification/dto/index.ts new file mode 100644 index 0000000..014cdd2 --- /dev/null +++ b/src/verification/dto/index.ts @@ -0,0 +1,3 @@ +export * from './create-verification-request.dto'; +export * from './review-verification-request.dto'; +export * from './search-verification-requests.dto'; diff --git a/src/verification/dto/review-verification-request.dto.ts b/src/verification/dto/review-verification-request.dto.ts new file mode 100644 index 0000000..f441757 --- /dev/null +++ b/src/verification/dto/review-verification-request.dto.ts @@ -0,0 +1,31 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsString, IsEnum, IsOptional, MaxLength } from 'class-validator'; +import { VerificationStatus } from '@prisma/client'; + +export class ReviewVerificationRequestDto { + @ApiProperty({ + example: 'APPROVED', + enum: ['APPROVED', 'REJECTED'], + description: 'New status for the verification request', + }) + @IsEnum(['APPROVED', 'REJECTED'] as const) + status: 'APPROVED' | 'REJECTED'; + + @ApiPropertyOptional({ + example: 'License verified successfully', + description: 'Admin notes about the review decision', + }) + @IsOptional() + @IsString() + @MaxLength(500) + adminNotes?: string; + + @ApiPropertyOptional({ + example: 'Document is blurry, please resubmit', + description: 'Rejection reason (required if status is REJECTED)', + }) + @IsOptional() + @IsString() + @MaxLength(500) + rejectionReason?: string; +} diff --git a/src/verification/dto/search-verification-requests.dto.ts b/src/verification/dto/search-verification-requests.dto.ts new file mode 100644 index 0000000..a15c5c2 --- /dev/null +++ b/src/verification/dto/search-verification-requests.dto.ts @@ -0,0 +1,57 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsOptional, IsEnum, IsInt, Min, Max, IsString } from 'class-validator'; +import { Type, Transform } from 'class-transformer'; +import { VerificationStatus, VerificationDocType } from '@prisma/client'; + +export class SearchVerificationRequestsDto { + @ApiPropertyOptional({ + example: 'PENDING', + enum: VerificationStatus, + description: 'Filter by status', + }) + @IsOptional() + @IsEnum(VerificationStatus) + status?: VerificationStatus; + + @ApiPropertyOptional({ + example: 'LICENSE', + enum: VerificationDocType, + description: 'Filter by document type', + }) + @IsOptional() + @IsEnum(VerificationDocType) + documentType?: VerificationDocType; + + @ApiPropertyOptional({ example: 'uuid-of-agent-profile' }) + @IsOptional() + @IsString() + agentProfileId?: string; + + @ApiPropertyOptional({ example: 1, default: 1 }) + @IsOptional() + @Type(() => Number) + @IsInt() + @Min(1) + page?: number = 1; + + @ApiPropertyOptional({ example: 10, default: 10 }) + @IsOptional() + @Type(() => Number) + @IsInt() + @Min(1) + @Max(100) + limit?: number = 10; + + @ApiPropertyOptional({ + example: 'createdAt', + enum: ['createdAt', 'updatedAt', 'status'], + }) + @IsOptional() + @IsString() + sortBy?: string = 'createdAt'; + + @ApiPropertyOptional({ example: 'desc', enum: ['asc', 'desc'] }) + @IsOptional() + @IsString() + sortOrder?: 'asc' | 'desc' = 'desc'; +} diff --git a/src/verification/index.ts b/src/verification/index.ts new file mode 100644 index 0000000..23a17ec --- /dev/null +++ b/src/verification/index.ts @@ -0,0 +1,4 @@ +export * from './verification.module'; +export * from './verification.service'; +export * from './verification.controller'; +export * from './dto'; diff --git a/src/verification/verification.controller.ts b/src/verification/verification.controller.ts new file mode 100644 index 0000000..ef8aa0c --- /dev/null +++ b/src/verification/verification.controller.ts @@ -0,0 +1,150 @@ +import { + Controller, + Get, + Post, + Put, + Delete, + Body, + Param, + Query, + UseGuards, + ParseUUIDPipe, +} from '@nestjs/common'; +import { + ApiTags, + ApiOperation, + ApiResponse, + ApiBearerAuth, +} from '@nestjs/swagger'; +import { VerificationService } from './verification.service'; +import { + CreateVerificationRequestDto, + ReviewVerificationRequestDto, + SearchVerificationRequestsDto, +} from './dto'; +import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; +import { RolesGuard } from '../auth/guards/roles.guard'; +import { Roles } from '../auth/decorators/roles.decorator'; +import { CurrentUser } from '../auth/decorators/current-user.decorator'; +import { UserRole } from '@prisma/client'; + +@ApiTags('verification') +@Controller('verification') +export class VerificationController { + constructor(private readonly verificationService: VerificationService) {} + + // Agent endpoints + + @Post('request') + @UseGuards(JwtAuthGuard) + @ApiBearerAuth() + @ApiOperation({ summary: 'Submit a verification request' }) + @ApiResponse({ status: 201, description: 'Request submitted successfully' }) + @ApiResponse({ status: 400, description: 'Pending request already exists' }) + @ApiResponse({ status: 404, description: 'Agent profile not found' }) + async createRequest( + @CurrentUser('id') userId: string, + @Body() dto: CreateVerificationRequestDto, + ) { + return this.verificationService.createRequest(userId, dto); + } + + @Get('my-requests') + @UseGuards(JwtAuthGuard) + @ApiBearerAuth() + @ApiOperation({ summary: 'Get my verification requests' }) + @ApiResponse({ status: 200, description: 'Requests retrieved successfully' }) + async getMyRequests(@CurrentUser('id') userId: string) { + return this.verificationService.getMyRequests(userId); + } + + @Get('request/:id') + @UseGuards(JwtAuthGuard) + @ApiBearerAuth() + @ApiOperation({ summary: 'Get verification request by ID' }) + @ApiResponse({ status: 200, description: 'Request retrieved successfully' }) + @ApiResponse({ status: 404, description: 'Request not found' }) + @ApiResponse({ status: 403, description: 'Access denied' }) + async getRequestById( + @Param('id', ParseUUIDPipe) id: string, + @CurrentUser('id') userId: string, + ) { + return this.verificationService.getRequestById(id, userId); + } + + @Delete('request/:id') + @UseGuards(JwtAuthGuard) + @ApiBearerAuth() + @ApiOperation({ summary: 'Cancel a pending verification request' }) + @ApiResponse({ status: 200, description: 'Request cancelled successfully' }) + @ApiResponse({ status: 400, description: 'Request is not pending' }) + @ApiResponse({ status: 404, description: 'Request not found' }) + async cancelRequest( + @Param('id', ParseUUIDPipe) id: string, + @CurrentUser('id') userId: string, + ) { + return this.verificationService.cancelRequest(id, userId); + } + + // Admin endpoints + + @Get('admin/list') + @UseGuards(JwtAuthGuard, RolesGuard) + @Roles(UserRole.ADMIN) + @ApiBearerAuth() + @ApiOperation({ summary: 'Admin: List all verification requests' }) + @ApiResponse({ status: 200, description: 'Requests retrieved successfully' }) + async searchRequests(@Query() dto: SearchVerificationRequestsDto) { + return this.verificationService.searchRequests(dto); + } + + @Get('admin/pending') + @UseGuards(JwtAuthGuard, RolesGuard) + @Roles(UserRole.ADMIN) + @ApiBearerAuth() + @ApiOperation({ summary: 'Admin: Get all pending verification requests' }) + @ApiResponse({ + status: 200, + description: 'Pending requests retrieved successfully', + }) + async getPendingRequests() { + return this.verificationService.getPendingRequests(); + } + + @Get('admin/stats') + @UseGuards(JwtAuthGuard, RolesGuard) + @Roles(UserRole.ADMIN) + @ApiBearerAuth() + @ApiOperation({ summary: 'Admin: Get verification statistics' }) + @ApiResponse({ status: 200, description: 'Stats retrieved successfully' }) + async getStats() { + return this.verificationService.getStats(); + } + + @Get('admin/:id') + @UseGuards(JwtAuthGuard, RolesGuard) + @Roles(UserRole.ADMIN) + @ApiBearerAuth() + @ApiOperation({ summary: 'Admin: Get verification request by ID' }) + @ApiResponse({ status: 200, description: 'Request retrieved successfully' }) + @ApiResponse({ status: 404, description: 'Request not found' }) + async adminGetRequestById(@Param('id', ParseUUIDPipe) id: string) { + return this.verificationService.getRequestById(id); + } + + @Put('admin/:id/review') + @UseGuards(JwtAuthGuard, RolesGuard) + @Roles(UserRole.ADMIN) + @ApiBearerAuth() + @ApiOperation({ summary: 'Admin: Review a verification request' }) + @ApiResponse({ status: 200, description: 'Request reviewed successfully' }) + @ApiResponse({ status: 400, description: 'Request already reviewed' }) + @ApiResponse({ status: 404, description: 'Request not found' }) + async reviewRequest( + @Param('id', ParseUUIDPipe) id: string, + @CurrentUser('id') adminId: string, + @Body() dto: ReviewVerificationRequestDto, + ) { + return this.verificationService.reviewRequest(id, adminId, dto); + } +} diff --git a/src/verification/verification.module.ts b/src/verification/verification.module.ts new file mode 100644 index 0000000..8cbae9a --- /dev/null +++ b/src/verification/verification.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { VerificationController } from './verification.controller'; +import { VerificationService } from './verification.service'; +import { PrismaModule } from '../prisma/prisma.module'; + +@Module({ + imports: [PrismaModule], + controllers: [VerificationController], + providers: [VerificationService], + exports: [VerificationService], +}) +export class VerificationModule {} diff --git a/src/verification/verification.service.ts b/src/verification/verification.service.ts new file mode 100644 index 0000000..23b647f --- /dev/null +++ b/src/verification/verification.service.ts @@ -0,0 +1,336 @@ +import { + Injectable, + NotFoundException, + ForbiddenException, + BadRequestException, +} from '@nestjs/common'; +import { PrismaService } from '../prisma/prisma.service'; +import { + CreateVerificationRequestDto, + ReviewVerificationRequestDto, + SearchVerificationRequestsDto, +} from './dto'; +import { Prisma, VerificationStatus } from '@prisma/client'; + +@Injectable() +export class VerificationService { + constructor(private prisma: PrismaService) {} + + async createRequest(userId: string, dto: CreateVerificationRequestDto) { + // Get the agent profile for this user + const agentProfile = await this.prisma.agentProfile.findUnique({ + where: { userId }, + }); + + if (!agentProfile) { + throw new NotFoundException( + 'Agent profile not found. Create an agent profile first.', + ); + } + + // Check if there's already a pending request for this document type + const existingRequest = await this.prisma.verificationRequest.findFirst({ + where: { + agentProfileId: agentProfile.id, + documentType: dto.documentType, + status: VerificationStatus.PENDING, + }, + }); + + if (existingRequest) { + throw new BadRequestException( + `A pending verification request for ${dto.documentType} already exists`, + ); + } + + const request = await this.prisma.verificationRequest.create({ + data: { + agentProfileId: agentProfile.id, + documentType: dto.documentType, + documentUrl: dto.documentUrl, + notes: dto.notes, + }, + include: { + agentProfile: { + include: { + user: { + select: { + id: true, + email: true, + }, + }, + }, + }, + }, + }); + + return request; + } + + async getMyRequests(userId: string) { + const agentProfile = await this.prisma.agentProfile.findUnique({ + where: { userId }, + }); + + if (!agentProfile) { + throw new NotFoundException('Agent profile not found'); + } + + const requests = await this.prisma.verificationRequest.findMany({ + where: { agentProfileId: agentProfile.id }, + orderBy: { createdAt: 'desc' }, + }); + + return requests; + } + + async getRequestById(id: string, userId?: string) { + const request = await this.prisma.verificationRequest.findUnique({ + where: { id }, + include: { + agentProfile: { + include: { + user: { + select: { + id: true, + email: true, + avatar: true, + }, + }, + }, + }, + }, + }); + + if (!request) { + throw new NotFoundException('Verification request not found'); + } + + // If userId is provided, check ownership (for non-admin access) + if (userId) { + const agentProfile = await this.prisma.agentProfile.findUnique({ + where: { userId }, + }); + + if (!agentProfile || agentProfile.id !== request.agentProfileId) { + throw new ForbiddenException( + 'You do not have access to this verification request', + ); + } + } + + return request; + } + + async cancelRequest(id: string, userId: string) { + const request = await this.getRequestById(id, userId); + + if (request.status !== VerificationStatus.PENDING) { + throw new BadRequestException( + 'Only pending requests can be cancelled', + ); + } + + await this.prisma.verificationRequest.delete({ + where: { id }, + }); + + return { message: 'Verification request cancelled successfully' }; + } + + // Admin methods + + async searchRequests(dto: SearchVerificationRequestsDto) { + const { + status, + documentType, + agentProfileId, + page = 1, + limit = 10, + sortBy = 'createdAt', + sortOrder = 'desc', + } = dto; + + const skip = (page - 1) * limit; + + const where: Prisma.VerificationRequestWhereInput = {}; + + if (status) { + where.status = status; + } + + if (documentType) { + where.documentType = documentType; + } + + if (agentProfileId) { + where.agentProfileId = agentProfileId; + } + + const orderBy: Prisma.VerificationRequestOrderByWithRelationInput = {}; + if (sortBy === 'status') { + orderBy.status = sortOrder; + } else if (sortBy === 'updatedAt') { + orderBy.updatedAt = sortOrder; + } else { + orderBy.createdAt = sortOrder; + } + + const [requests, total] = await Promise.all([ + this.prisma.verificationRequest.findMany({ + where, + skip, + take: limit, + orderBy, + include: { + agentProfile: { + include: { + user: { + select: { + id: true, + email: true, + avatar: true, + }, + }, + }, + }, + }, + }), + this.prisma.verificationRequest.count({ where }), + ]); + + return { + data: requests, + meta: { + total, + page, + limit, + totalPages: Math.ceil(total / limit), + }, + }; + } + + async getPendingRequests() { + const requests = await this.prisma.verificationRequest.findMany({ + where: { status: VerificationStatus.PENDING }, + orderBy: { createdAt: 'asc' }, + include: { + agentProfile: { + include: { + user: { + select: { + id: true, + email: true, + avatar: true, + }, + }, + }, + }, + }, + }); + + return requests; + } + + async reviewRequest( + id: string, + adminId: string, + dto: ReviewVerificationRequestDto, + ) { + const request = await this.prisma.verificationRequest.findUnique({ + where: { id }, + include: { + agentProfile: true, + }, + }); + + if (!request) { + throw new NotFoundException('Verification request not found'); + } + + if (request.status !== VerificationStatus.PENDING) { + throw new BadRequestException( + 'This verification request has already been reviewed', + ); + } + + if (dto.status === 'REJECTED' && !dto.rejectionReason) { + throw new BadRequestException( + 'Rejection reason is required when rejecting a request', + ); + } + + const updatedRequest = await this.prisma.verificationRequest.update({ + where: { id }, + data: { + status: dto.status as VerificationStatus, + reviewedBy: adminId, + reviewedAt: new Date(), + adminNotes: dto.adminNotes, + rejectionReason: dto.rejectionReason, + }, + include: { + agentProfile: { + include: { + user: { + select: { + id: true, + email: true, + }, + }, + }, + }, + }, + }); + + // If approved, check if all required documents are verified + // and auto-verify the agent profile + if (dto.status === 'APPROVED') { + await this.checkAndUpdateAgentVerification(request.agentProfileId); + } + + return updatedRequest; + } + + private async checkAndUpdateAgentVerification(agentProfileId: string) { + // Get all verification requests for this agent + const requests = await this.prisma.verificationRequest.findMany({ + where: { agentProfileId }, + }); + + // Check if at least one LICENSE document is approved + const hasApprovedLicense = requests.some( + (r) => + r.documentType === 'LICENSE' && r.status === VerificationStatus.APPROVED, + ); + + if (hasApprovedLicense) { + await this.prisma.agentProfile.update({ + where: { id: agentProfileId }, + data: { isVerified: true }, + }); + } + } + + async getStats() { + const [pending, approved, rejected, total] = await Promise.all([ + this.prisma.verificationRequest.count({ + where: { status: VerificationStatus.PENDING }, + }), + this.prisma.verificationRequest.count({ + where: { status: VerificationStatus.APPROVED }, + }), + this.prisma.verificationRequest.count({ + where: { status: VerificationStatus.REJECTED }, + }), + this.prisma.verificationRequest.count(), + ]); + + return { + pending, + approved, + rejected, + total, + }; + } +}