diff --git a/.gitignore b/.gitignore index 11ddd8db..3c2dda3c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules # Keep environment variables out of version control .env +.txt diff --git a/1751001599793.png b/1751001599793.png new file mode 100644 index 00000000..81f05678 Binary files /dev/null and b/1751001599793.png differ diff --git a/1751001954026.png b/1751001954026.png new file mode 100644 index 00000000..945b1348 Binary files /dev/null and b/1751001954026.png differ diff --git a/http/product.http b/http/product.http index 9896d21b..b082f049 100644 --- a/http/product.http +++ b/http/product.http @@ -1,14 +1,14 @@ ### 상품 등록 (여러 이미지 한번에 업로드 추후 수정) POST http://localhost:3000/products Content-Type: application/json -Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsImlhdCI6MTc0OTY5NDc1MCwiZXhwIjoxNzQ5Njk4MzUwfQ.jpaGwiILt7NgO-LMfxIPoAmdekCXe70vlAR-8QVjLmM +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsImlhdCI6MTc1MTgwODY0NywiZXhwIjoxNzUxODA5NTQ3fQ.XNVotR2b66WhNcbZe4SA1FPwvqAcdAglueM1QP3o134 { - "name": "장원영 굿즈 모음", - "description": "레어템 다수 보유", - "price": 50000, + "name": "냐옹이 굿즈", + "description": "10장씩 묶음 판매", + "price": 10000, "tags": ["한정판", "인기"], - "images": ["/uploads/1749694617772.png"] + "images": ["1751001954026.png"] } ### 상품 목록 조회 @@ -19,3 +19,10 @@ GET http://localhost:3000/products/1 Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsImlhdCI6MTc0OTcwNTUyOCwiZXhwIjoxNzQ5NzA5MTI4fQ.abynpN2pe7-5OW_TXnYtQxhgnhkY70fY6pcqZJRBb6g ### 수정, 삭제는 프론트에서 연결하고 확인함 + +### 좋아요 등록 및 취소 +POST http://localhost:3000/products/6/favorite +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsImlhdCI6MTc1MDY1NTI5NiwiZXhwIjoxNzUwNjU4ODk2fQ.IzxGWOV0uczEBwLLg7WCKClKZVJ1denJgkjycljM3uI + +### 이미지 업로드 (S3 연결) +POST http://localhost:3000/api/upload \ No newline at end of file diff --git a/http/user.http b/http/user.http index 5cfb7c2d..232097b3 100644 --- a/http/user.http +++ b/http/user.http @@ -19,7 +19,7 @@ Content-Type: application/json ### 토큰 재발급 POST http://localhost:3000/token/refresh -Cookie: refreshToken=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsImlhdCI6MTc0OTc3NTY5MiwiZXhwIjoxNzUwOTg1MjkyfQ.3jh8-9OnSBU9bC-vdrLIZEz1WwALMyXbta89yAJIvzo +Cookie: refreshToken=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjIsImlhdCI6MTc1MTI5NDAyMSwiZXhwIjoxNzUxMjk3NjIxfQ.m6wz5Be9UwVQdou1J3tUb7miIx0jpsAD8f3dDK7sa0k ### http://localhost:3000/uploads/1746979915826.png diff --git a/package-lock.json b/package-lock.json index ac5ef42d..5313d61c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,15 +9,19 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "@aws-sdk/client-s3": "^3.842.0", + "@aws-sdk/s3-request-presigner": "^3.842.0", "@prisma/client": "^6.7.0", "bcrypt": "^5.1.1", "cookie": "^1.0.2", "cookie-parser": "^1.4.7", "cors": "^2.8.5", + "dotenv": "^16.5.0", "express": "^5.1.0", "express-jwt": "^8.5.1", "jsonwebtoken": "^9.0.2", "multer": "^1.4.5-lts.2", + "multer-s3": "^3.0.1", "prisma": "^6.7.0" }, "devDependencies": { @@ -27,548 +31,2181 @@ "@types/express": "^5.0.3", "@types/jsonwebtoken": "^9.0.9", "@types/multer": "^1.4.13", + "dotenv-cli": "^8.0.0", "nodemon": "^3.1.10", "ts-node": "^10.9.2", "ts-node-dev": "^2.0.0", + "tsconfig-paths": "^4.2.0", "typescript": "^5.8.3" } }, + "node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/crc32c": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz", + "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha1-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz", + "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-s3": { + "version": "3.842.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.842.0.tgz", + "integrity": "sha512-T5Rh72Rcq1xIaM8KkTr1Wpr7/WPCYO++KrM+/Em0rq2jxpjMMhj77ITpgH7eEmNxWmwIndTwqpgfmbpNfk7Gbw==", + "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.840.0", + "@aws-sdk/credential-provider-node": "3.840.0", + "@aws-sdk/middleware-bucket-endpoint": "3.840.0", + "@aws-sdk/middleware-expect-continue": "3.840.0", + "@aws-sdk/middleware-flexible-checksums": "3.840.0", + "@aws-sdk/middleware-host-header": "3.840.0", + "@aws-sdk/middleware-location-constraint": "3.840.0", + "@aws-sdk/middleware-logger": "3.840.0", + "@aws-sdk/middleware-recursion-detection": "3.840.0", + "@aws-sdk/middleware-sdk-s3": "3.840.0", + "@aws-sdk/middleware-ssec": "3.840.0", + "@aws-sdk/middleware-user-agent": "3.840.0", + "@aws-sdk/region-config-resolver": "3.840.0", + "@aws-sdk/signature-v4-multi-region": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-endpoints": "3.840.0", + "@aws-sdk/util-user-agent-browser": "3.840.0", + "@aws-sdk/util-user-agent-node": "3.840.0", + "@aws-sdk/xml-builder": "3.821.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.6.0", + "@smithy/eventstream-serde-browser": "^4.0.4", + "@smithy/eventstream-serde-config-resolver": "^4.1.2", + "@smithy/eventstream-serde-node": "^4.0.4", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-blob-browser": "^4.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/hash-stream-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/md5-js": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.13", + "@smithy/middleware-retry": "^4.1.14", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.5", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.21", + "@smithy/util-defaults-mode-node": "^4.0.21", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "@smithy/util-stream": "^4.2.2", + "@smithy/util-utf8": "^4.0.0", + "@smithy/util-waiter": "^4.0.6", + "@types/uuid": "^9.0.1", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.840.0.tgz", + "integrity": "sha512-3Zp+FWN2hhmKdpS0Ragi5V2ZPsZNScE3jlbgoJjzjI/roHZqO+e3/+XFN4TlM0DsPKYJNp+1TAjmhxN6rOnfYA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.840.0", + "@aws-sdk/middleware-host-header": "3.840.0", + "@aws-sdk/middleware-logger": "3.840.0", + "@aws-sdk/middleware-recursion-detection": "3.840.0", + "@aws-sdk/middleware-user-agent": "3.840.0", + "@aws-sdk/region-config-resolver": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-endpoints": "3.840.0", + "@aws-sdk/util-user-agent-browser": "3.840.0", + "@aws-sdk/util-user-agent-node": "3.840.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.6.0", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.13", + "@smithy/middleware-retry": "^4.1.14", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.5", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.21", + "@smithy/util-defaults-mode-node": "^4.0.21", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.840.0.tgz", + "integrity": "sha512-x3Zgb39tF1h2XpU+yA4OAAQlW6LVEfXNlSedSYJ7HGKXqA/E9h3rWQVpYfhXXVVsLdYXdNw5KBUkoAoruoZSZA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.840.0", + "@aws-sdk/xml-builder": "3.821.0", + "@smithy/core": "^3.6.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/signature-v4": "^5.1.2", + "@smithy/smithy-client": "^4.4.5", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-utf8": "^4.0.0", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.840.0.tgz", + "integrity": "sha512-EzF6VcJK7XvQ/G15AVEfJzN2mNXU8fcVpXo4bRyr1S6t2q5zx6UPH/XjDbn18xyUmOq01t+r8gG+TmHEVo18fA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.840.0.tgz", + "integrity": "sha512-wbnUiPGLVea6mXbUh04fu+VJmGkQvmToPeTYdHE8eRZq3NRDi3t3WltT+jArLBKD/4NppRpMjf2ju4coMCz91g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.5", + "@smithy/types": "^4.3.1", + "@smithy/util-stream": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.840.0.tgz", + "integrity": "sha512-7F290BsWydShHb+7InXd+IjJc3mlEIm9I0R57F/Pjl1xZB69MdkhVGCnuETWoBt4g53ktJd6NEjzm/iAhFXFmw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.840.0", + "@aws-sdk/credential-provider-env": "3.840.0", + "@aws-sdk/credential-provider-http": "3.840.0", + "@aws-sdk/credential-provider-process": "3.840.0", + "@aws-sdk/credential-provider-sso": "3.840.0", + "@aws-sdk/credential-provider-web-identity": "3.840.0", + "@aws-sdk/nested-clients": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.840.0.tgz", + "integrity": "sha512-KufP8JnxA31wxklLm63evUPSFApGcH8X86z3mv9SRbpCm5ycgWIGVCTXpTOdgq6rPZrwT9pftzv2/b4mV/9clg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.840.0", + "@aws-sdk/credential-provider-http": "3.840.0", + "@aws-sdk/credential-provider-ini": "3.840.0", + "@aws-sdk/credential-provider-process": "3.840.0", + "@aws-sdk/credential-provider-sso": "3.840.0", + "@aws-sdk/credential-provider-web-identity": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.840.0.tgz", + "integrity": "sha512-HkDQWHy8tCI4A0Ps2NVtuVYMv9cB4y/IuD/TdOsqeRIAT12h8jDb98BwQPNLAImAOwOWzZJ8Cu0xtSpX7CQhMw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.840.0.tgz", + "integrity": "sha512-2qgdtdd6R0Z1y0KL8gzzwFUGmhBHSUx4zy85L2XV1CXhpRNwV71SVWJqLDVV5RVWVf9mg50Pm3AWrUC0xb0pcA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.840.0", + "@aws-sdk/core": "3.840.0", + "@aws-sdk/token-providers": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.840.0.tgz", + "integrity": "sha512-dpEeVXG8uNZSmVXReE4WP0lwoioX2gstk4RnUgrdUE3YaPq8A+hJiVAyc3h+cjDeIqfbsQbZm9qFetKC2LF9dQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.840.0", + "@aws-sdk/nested-clients": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/lib-storage": { + "version": "3.842.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.842.0.tgz", + "integrity": "sha512-QRWu+/I6Sgy4Kj2gq9srdw0/YSDiybSiMiQMUgcw4iI4mgWUcEwRPw7rGUFRWwdZUAhYGUv1T37Ydz2GXDFnJQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.13", + "@smithy/smithy-client": "^4.4.5", + "buffer": "5.6.0", + "events": "3.3.0", + "stream-browserify": "3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-s3": "^3.842.0" + } + }, + "node_modules/@aws-sdk/middleware-bucket-endpoint": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.840.0.tgz", + "integrity": "sha512-+gkQNtPwcSMmlwBHFd4saVVS11In6ID1HczNzpM3MXKXRBfSlbZJbCt6wN//AZ8HMklZEik4tcEOG0qa9UY8SQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-arn-parser": "3.804.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-expect-continue": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.840.0.tgz", + "integrity": "sha512-iJg2r6FKsKKvdiU4oCOuCf7Ro/YE0Q2BT/QyEZN3/Rt8Nr4SAZiQOlcBXOCpGvuIKOEAhvDOUnW3aDHL01PdVw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.840.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.840.0.tgz", + "integrity": "sha512-Kg/o2G6o72sdoRH0J+avdcf668gM1bp6O4VeEXpXwUj/urQnV5qiB2q1EYT110INHUKWOLXPND3sQAqh6sTqHw==", + "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.840.0", + "@aws-sdk/types": "3.840.0", + "@smithy/is-array-buffer": "^4.0.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-stream": "^4.2.2", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.840.0.tgz", + "integrity": "sha512-ub+hXJAbAje94+Ya6c6eL7sYujoE8D4Bumu1NUI8TXjUhVVn0HzVWQjpRLshdLsUp1AW7XyeJaxyajRaJQ8+Xg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.840.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-location-constraint": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.840.0.tgz", + "integrity": "sha512-KVLD0u0YMF3aQkVF8bdyHAGWSUY6N1Du89htTLgqCcIhSxxAJ9qifrosVZ9jkAzqRW99hcufyt2LylcVU2yoKQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.840.0", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.840.0.tgz", + "integrity": "sha512-lSV8FvjpdllpGaRspywss4CtXV8M7NNNH+2/j86vMH+YCOZ6fu2T/TyFd/tHwZ92vDfHctWkRbQxg0bagqwovA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.840.0", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.840.0.tgz", + "integrity": "sha512-Gu7lGDyfddyhIkj1Z1JtrY5NHb5+x/CRiB87GjaSrKxkDaydtX2CU977JIABtt69l9wLbcGDIQ+W0uJ5xPof7g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.840.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.840.0.tgz", + "integrity": "sha512-rOUji7CayWN3O09zvvgLzDVQe0HiJdZkxoTS6vzOS3WbbdT7joGdVtAJHtn+x776QT3hHzbKU5gnfhel0o6gQA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-arn-parser": "3.804.0", + "@smithy/core": "^3.6.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/signature-v4": "^5.1.2", + "@smithy/smithy-client": "^4.4.5", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-stream": "^4.2.2", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-ssec": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.840.0.tgz", + "integrity": "sha512-CBZP9t1QbjDFGOrtnUEHL1oAvmnCUUm7p0aPNbIdSzNtH42TNKjPRN3TuEIJDGjkrqpL3MXyDSmNayDcw/XW7Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.840.0", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.840.0.tgz", + "integrity": "sha512-hiiMf7BP5ZkAFAvWRcK67Mw/g55ar7OCrvrynC92hunx/xhMkrgSLM0EXIZ1oTn3uql9kH/qqGF0nqsK6K555A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-endpoints": "3.840.0", + "@smithy/core": "^3.6.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.840.0.tgz", + "integrity": "sha512-LXYYo9+n4hRqnRSIMXLBb+BLz+cEmjMtTudwK1BF6Bn2RfdDv29KuyeDRrPCS3TwKl7ZKmXUmE9n5UuHAPfBpA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.840.0", + "@aws-sdk/middleware-host-header": "3.840.0", + "@aws-sdk/middleware-logger": "3.840.0", + "@aws-sdk/middleware-recursion-detection": "3.840.0", + "@aws-sdk/middleware-user-agent": "3.840.0", + "@aws-sdk/region-config-resolver": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-endpoints": "3.840.0", + "@aws-sdk/util-user-agent-browser": "3.840.0", + "@aws-sdk/util-user-agent-node": "3.840.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.6.0", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.13", + "@smithy/middleware-retry": "^4.1.14", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.5", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.21", + "@smithy/util-defaults-mode-node": "^4.0.21", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.840.0.tgz", + "integrity": "sha512-Qjnxd/yDv9KpIMWr90ZDPtRj0v75AqGC92Lm9+oHXZ8p1MjG5JE2CW0HL8JRgK9iKzgKBL7pPQRXI8FkvEVfrA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.840.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner": { + "version": "3.842.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.842.0.tgz", + "integrity": "sha512-daS69IJ20X+BzsiEtj3XuyyM765iFOdZ648lrptHncQHRWdpzahk67/nP/SKYhWvnNrQ4pw2vYlVxpOs9vl1yg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/signature-v4-multi-region": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-format-url": "3.840.0", + "@smithy/middleware-endpoint": "^4.1.13", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.5", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.840.0.tgz", + "integrity": "sha512-8AoVgHrkSfhvGPtwx23hIUO4MmMnux2pjnso1lrLZGqxfElM6jm2w4jTNLlNXk8uKHGyX89HaAIuT0lL6dJj9g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-sdk-s3": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/signature-v4": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.840.0.tgz", + "integrity": "sha512-6BuTOLTXvmgwjK7ve7aTg9JaWFdM5UoMolLVPMyh3wTv9Ufalh8oklxYHUBIxsKkBGO2WiHXytveuxH6tAgTYg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.840.0", + "@aws-sdk/nested-clients": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.840.0.tgz", + "integrity": "sha512-xliuHaUFZxEx1NSXeLLZ9Dyu6+EJVQKEoD+yM+zqUo3YDZ7medKJWY6fIOKiPX/N7XbLdBYwajb15Q7IL8KkeA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-arn-parser": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.804.0.tgz", + "integrity": "sha512-wmBJqn1DRXnZu3b4EkE6CWnoWMo1ZMvlfkqU5zPz67xx1GMaXlDCchFvKAXMjk4jn/L1O3tKnoFDNsoLV1kgNQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.840.0.tgz", + "integrity": "sha512-eqE9ROdg/Kk0rj3poutyRCFauPDXIf/WSvCqFiRDDVi6QOnCv/M0g2XW8/jSvkJlOyaXkNCptapIp6BeeFFGYw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.840.0", + "@smithy/types": "^4.3.1", + "@smithy/util-endpoints": "^3.0.6", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-format-url": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.840.0.tgz", + "integrity": "sha512-VB1PWyI1TQPiPvg4w7tgUGGQER1xxXPNUqfh3baxUSFi1Oh8wHrDnFywkxLm3NMmgDmnLnSZ5Q326qAoyqKLSg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.840.0", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.804.0.tgz", + "integrity": "sha512-zVoRfpmBVPodYlnMjgVjfGoEZagyRF5IPn3Uo6ZvOZp24chnW/FRstH7ESDHDDRga4z3V+ElUQHKpFDXWyBW5A==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.840.0.tgz", + "integrity": "sha512-JdyZM3EhhL4PqwFpttZu1afDpPJCCc3eyZOLi+srpX11LsGj6sThf47TYQN75HT1CarZ7cCdQHGzP2uy3/xHfQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.840.0", + "@smithy/types": "^4.3.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.840.0.tgz", + "integrity": "sha512-Fy5JUEDQU1tPm2Yw/YqRYYc27W5+QD/J4mYvQvdWjUGZLB5q3eLFMGD35Uc28ZFoGMufPr4OCxK/bRfWROBRHQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.821.0.tgz", + "integrity": "sha512-DIIotRnefVL6DiaHtO6/21DhJ4JZnnIwdNbpwiAhdt/AVbttcE4yw925gsjur0OGv5BTYXQXU3YnANBYnZjuQA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", "dev": true, "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz", + "integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.4.tgz", + "integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz", + "integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.4.tgz", + "integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz", + "integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz", + "integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz", + "integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz", + "integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz", + "integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz", + "integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz", + "integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz", + "integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz", + "integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz", + "integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz", + "integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz", + "integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz", + "integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz", + "integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz", + "integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz", + "integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz", + "integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz", + "integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], "engines": { - "node": ">=12" + "node": ">=18" } }, - "node_modules/@esbuild/aix-ppc64": { + "node_modules/@esbuild/win32-arm64": { "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz", - "integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz", + "integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==", "cpu": [ - "ppc64" + "arm64" ], "license": "MIT", "optional": true, "os": [ - "aix" + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz", + "integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz", + "integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" ], "engines": { - "node": ">=18" + "node": ">=18" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "license": "BSD-3-Clause", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@prisma/client": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.7.0.tgz", + "integrity": "sha512-+k61zZn1XHjbZul8q6TdQLpuI/cvyfil87zqK2zpreNIXyXtpUv3+H/oM69hcsFcZXaokHJIzPAt5Z8C8eK2QA==", + "hasInstallScript": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "peerDependencies": { + "prisma": "*", + "typescript": ">=5.1.0" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@prisma/config": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.7.0.tgz", + "integrity": "sha512-di8QDdvSz7DLUi3OOcCHSwxRNeW7jtGRUD2+Z3SdNE3A+pPiNT8WgUJoUyOwJmUr5t+JA2W15P78C/N+8RXrOA==", + "license": "Apache-2.0", + "dependencies": { + "esbuild": ">=0.12 <1", + "esbuild-register": "3.6.0" + } + }, + "node_modules/@prisma/debug": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.7.0.tgz", + "integrity": "sha512-RabHn9emKoYFsv99RLxvfG2GHzWk2ZI1BuVzqYtmMSIcuGboHY5uFt3Q3boOREM9de6z5s3bQoyKeWnq8Fz22w==", + "license": "Apache-2.0" + }, + "node_modules/@prisma/engines": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.7.0.tgz", + "integrity": "sha512-3wDMesnOxPrOsq++e5oKV9LmIiEazFTRFZrlULDQ8fxdub5w4NgRBoxtWbvXmj2nJVCnzuz6eFix3OhIqsZ1jw==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.7.0", + "@prisma/engines-version": "6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed", + "@prisma/fetch-engine": "6.7.0", + "@prisma/get-platform": "6.7.0" + } + }, + "node_modules/@prisma/engines-version": { + "version": "6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed.tgz", + "integrity": "sha512-EvpOFEWf1KkJpDsBCrih0kg3HdHuaCnXmMn7XFPObpFTzagK1N0Q0FMnYPsEhvARfANP5Ok11QyoTIRA2hgJTA==", + "license": "Apache-2.0" + }, + "node_modules/@prisma/fetch-engine": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.7.0.tgz", + "integrity": "sha512-zLlAGnrkmioPKJR4Yf7NfW3hftcvqeNNEHleMZK9yX7RZSkhmxacAYyfGsCcqRt47jiZ7RKdgE0Wh2fWnm7WsQ==", + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.7.0", + "@prisma/engines-version": "6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed", + "@prisma/get-platform": "6.7.0" + } + }, + "node_modules/@prisma/get-platform": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.7.0.tgz", + "integrity": "sha512-i9IH5lO4fQwnMLvQLYNdgVh9TK3PuWBfQd7QLk/YurnAIg+VeADcZDbmhAi4XBBDD+hDif9hrKyASu0hbjwabw==", + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.7.0" + } + }, + "node_modules/@smithy/abort-controller": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", + "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.0.0.tgz", + "integrity": "sha512-+sKqDBQqb036hh4NPaUiEkYFkTUGYzRsn3EuFhyfQfMy6oGHEUJDurLP9Ufb5dasr/XiAmPNMr6wa9afjQB+Gw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader-native": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.0.0.tgz", + "integrity": "sha512-R9wM2yPmfEMsUmlMlIgSzOyICs0x9uu7UTHoccMyt7BWw8shcGM8HqB355+BZCPBcySvbTYMs62EgEQkNxz2ig==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-base64": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.1.4.tgz", + "integrity": "sha512-prmU+rDddxHOH0oNcwemL+SwnzcG65sBF2yXRO7aeXIn/xTlq2pX7JLVbkBnVLowHLg4/OL4+jBmv9hVrVGS+w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.6.0.tgz", + "integrity": "sha512-Pgvfb+TQ4wUNLyHzvgCP4aYZMh16y7GcfF59oirRHcgGgkH1e/s9C0nv/v3WP+Quymyr5je71HeFQCwh+44XLg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-serde": "^4.0.8", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-stream": "^4.2.2", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.6.tgz", + "integrity": "sha512-hKMWcANhUiNbCJouYkZ9V3+/Qf9pteR1dnwgdyzR09R4ODEYx8BbUysHwRSyex4rZ9zapddZhLFTnT4ZijR4pw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-codec": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.0.4.tgz", + "integrity": "sha512-7XoWfZqWb/QoR/rAU4VSi0mWnO2vu9/ltS6JZ5ZSZv0eovLVfDfu0/AX4ub33RsJTOth3TiFWSHS5YdztvFnig==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.3.1", + "@smithy/util-hex-encoding": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-browser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.0.4.tgz", + "integrity": "sha512-3fb/9SYaYqbpy/z/H3yIi0bYKyAa89y6xPmIqwr2vQiUT2St+avRt8UKwsWt9fEdEasc5d/V+QjrviRaX1JRFA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.1.2.tgz", + "integrity": "sha512-JGtambizrWP50xHgbzZI04IWU7LdI0nh/wGbqH3sJesYToMi2j/DcoElqyOcqEIG/D4tNyxgRuaqBXWE3zOFhQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-node": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.0.4.tgz", + "integrity": "sha512-RD6UwNZ5zISpOWPuhVgRz60GkSIp0dy1fuZmj4RYmqLVRtejFqQ16WmfYDdoSoAjlp1LX+FnZo+/hkdmyyGZ1w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-universal": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.0.4.tgz", + "integrity": "sha512-UeJpOmLGhq1SLox79QWw/0n2PFX+oPRE1ZyRMxPIaFEfCqWaqpB7BU9C8kpPOGEhLF7AwEqfFbtwNxGy4ReENA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-codec": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.0.4.tgz", + "integrity": "sha512-AMtBR5pHppYMVD7z7G+OlHHAcgAN7v0kVKEpHuTO4Gb199Gowh0taYi9oDStFeUhetkeP55JLSVlTW1n9rFtUw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.1.2", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.4.tgz", - "integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "node_modules/@smithy/hash-blob-browser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.0.4.tgz", + "integrity": "sha512-WszRiACJiQV3QG6XMV44i5YWlkrlsM5Yxgz4jvsksuu7LDXA6wAtypfPajtNTadzpJy3KyJPoWehYpmZGKUFIQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/chunked-blob-reader": "^5.0.0", + "@smithy/chunked-blob-reader-native": "^4.0.0", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz", - "integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "node_modules/@smithy/hash-node": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.4.tgz", + "integrity": "sha512-qnbTPUhCVnCgBp4z4BUJUhOEkVwxiEi1cyFM+Zj6o+aY8OFGxUQleKWq8ltgp3dujuhXojIvJWdoqpm6dVO3lQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.4.tgz", - "integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "node_modules/@smithy/hash-stream-node": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.0.4.tgz", + "integrity": "sha512-wHo0d8GXyVmpmMh/qOR0R7Y46/G1y6OR8U+bSTB4ppEzRxd1xVAQ9xOE9hOc0bSjhz0ujCPAbfNLkLrpa6cevg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz", - "integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "node_modules/@smithy/invalid-dependency": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.4.tgz", + "integrity": "sha512-bNYMi7WKTJHu0gn26wg8OscncTt1t2b8KcsZxvOv56XA6cyXtOAAAaNP7+m45xfppXfOatXF3Sb1MNsLUgVLTw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz", - "integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "node_modules/@smithy/is-array-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", + "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz", - "integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], + "node_modules/@smithy/md5-js": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.0.4.tgz", + "integrity": "sha512-uGLBVqcOwrLvGh/v/jw423yWHq/ofUGK1W31M2TNspLQbUV1Va0F5kTxtirkoHawODAZcjXTSGi7JwbnPcDPJg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz", - "integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], + "node_modules/@smithy/middleware-content-length": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.4.tgz", + "integrity": "sha512-F7gDyfI2BB1Kc+4M6rpuOLne5LOcEknH1n6UQB69qv+HucXBR1rkzXBnQTB2q46sFy1PM/zuSJOB532yc8bg3w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz", - "integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "node_modules/@smithy/middleware-endpoint": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.13.tgz", + "integrity": "sha512-xg3EHV/Q5ZdAO5b0UiIMj3RIOCobuS40pBBODguUDVdko6YK6QIzCVRrHTogVuEKglBWqWenRnZ71iZnLL3ZAQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.6.0", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-middleware": "^4.0.4", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz", - "integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "node_modules/@smithy/middleware-retry": { + "version": "4.1.14", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.14.tgz", + "integrity": "sha512-eoXaLlDGpKvdmvt+YBfRXE7HmIEtFF+DJCbTPwuLunP0YUnrydl+C4tS+vEM0+nyxXrX3PSUFqC+lP1+EHB1Tw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/service-error-classification": "^4.0.6", + "@smithy/smithy-client": "^4.4.5", + "@smithy/types": "^4.3.1", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz", - "integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "node_modules/@smithy/middleware-serde": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.8.tgz", + "integrity": "sha512-iSSl7HJoJaGyMIoNn2B7czghOVwJ9nD7TMvLhMWeSB5vt0TnEYyRRqPJu/TqW76WScaNvYYB8nRoiBHR9S1Ddw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz", - "integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "node_modules/@smithy/middleware-stack": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.4.tgz", + "integrity": "sha512-kagK5ggDrBUCCzI93ft6DjteNSfY8Ulr83UtySog/h09lTIOAJ/xUSObutanlPT0nhoHAkpmW9V5K8oPyLh+QA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz", - "integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "node_modules/@smithy/node-config-provider": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.1.3.tgz", + "integrity": "sha512-HGHQr2s59qaU1lrVH6MbLlmOBxadtzTsoO4c+bF5asdgVik3I8o7JIOzoeqWc5MjVa+vD36/LWE0iXKpNqooRw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz", - "integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "node_modules/@smithy/node-http-handler": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.6.tgz", + "integrity": "sha512-NqbmSz7AW2rvw4kXhKGrYTiJVDHnMsFnX4i+/FzcZAfbOBauPYs2ekuECkSbtqaxETLLTu9Rl/ex6+I2BKErPA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz", - "integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "node_modules/@smithy/property-provider": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.4.tgz", + "integrity": "sha512-qHJ2sSgu4FqF4U/5UUp4DhXNmdTrgmoAai6oQiM+c5RZ/sbDwJ12qxB1M6FnP+Tn/ggkPZf9ccn4jqKSINaquw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz", - "integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "node_modules/@smithy/protocol-http": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.2.tgz", + "integrity": "sha512-rOG5cNLBXovxIrICSBm95dLqzfvxjEmuZx4KK3hWwPFHGdW3lxY0fZNXfv2zebfRO7sJZ5pKJYHScsqopeIWtQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.4.tgz", + "integrity": "sha512-SwREZcDnEYoh9tLNgMbpop+UTGq44Hl9tdj3rf+yeLcfH7+J8OXEBaMc2kDxtyRHu8BhSg9ADEx0gFHvpJgU8w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "@smithy/util-uri-escape": "^4.0.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz", - "integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "node_modules/@smithy/querystring-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.4.tgz", + "integrity": "sha512-6yZf53i/qB8gRHH/l2ZwUG5xgkPgQF15/KxH0DdXMDHjesA9MeZje/853ifkSY0x4m5S+dfDZ+c4x439PF0M2w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz", - "integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], + "node_modules/@smithy/service-error-classification": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.6.tgz", + "integrity": "sha512-RRoTDL//7xi4tn5FrN2NzH17jbgmnKidUqd4KvquT0954/i6CXXkh1884jBiunq24g9cGtPBEXlU40W6EpNOOg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz", - "integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.4.tgz", + "integrity": "sha512-63X0260LoFBjrHifPDs+nM9tV0VMkOTl4JRMYNuKh/f5PauSjowTfvF3LogfkWdcPoxsA9UjqEOgjeYIbhb7Nw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz", - "integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], + "node_modules/@smithy/signature-v4": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.1.2.tgz", + "integrity": "sha512-d3+U/VpX7a60seHziWnVZOHuEgJlclufjkS6zhXvxcJgkJq4UWdH5eOBLzHRMx6gXjsdT9h6lfpmLzbrdupHgQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-uri-escape": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz", - "integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], + "node_modules/@smithy/smithy-client": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.4.5.tgz", + "integrity": "sha512-+lynZjGuUFJaMdDYSTMnP/uPBBXXukVfrJlP+1U/Dp5SFTEI++w6NMga8DjOENxecOF71V9Z2DllaVDYRnGlkg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.6.0", + "@smithy/middleware-endpoint": "^4.1.13", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-stream": "^4.2.2", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz", - "integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], + "node_modules/@smithy/types": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", + "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz", - "integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], + "node_modules/@smithy/url-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.4.tgz", + "integrity": "sha512-eMkc144MuN7B0TDA4U2fKs+BqczVbk3W+qIvcoCY6D1JY3hnAdCuhCZODC+GAeaxj0p6Jroz4+XMUn3PCxQQeQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz", - "integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], + "node_modules/@smithy/util-base64": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", + "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz", - "integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], + "node_modules/@smithy/util-body-length-browser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz", + "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/util-body-length-node": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.0.0.tgz", + "integrity": "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.0.0" + "node": ">=18.0.0" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true, - "license": "MIT" + "node_modules/@smithy/util-buffer-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/util-config-provider": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz", + "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==", + "license": "Apache-2.0", "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@mapbox/node-pre-gyp": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", - "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", - "license": "BSD-3-Clause", + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.0.21", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.21.tgz", + "integrity": "sha512-wM0jhTytgXu3wzJoIqpbBAG5U6BwiubZ6QKzSbP7/VbmF1v96xlAbX2Am/mz0Zep0NLvLh84JT0tuZnk3wmYQA==", + "license": "Apache-2.0", "dependencies": { - "detect-libc": "^2.0.0", - "https-proxy-agent": "^5.0.0", - "make-dir": "^3.1.0", - "node-fetch": "^2.6.7", - "nopt": "^5.0.0", - "npmlog": "^5.0.1", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.11" + "@smithy/property-provider": "^4.0.4", + "@smithy/smithy-client": "^4.4.5", + "@smithy/types": "^4.3.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" }, - "bin": { - "node-pre-gyp": "bin/node-pre-gyp" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@prisma/client": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.7.0.tgz", - "integrity": "sha512-+k61zZn1XHjbZul8q6TdQLpuI/cvyfil87zqK2zpreNIXyXtpUv3+H/oM69hcsFcZXaokHJIzPAt5Z8C8eK2QA==", - "hasInstallScript": true, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.0.21", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.21.tgz", + "integrity": "sha512-/F34zkoU0GzpUgLJydHY8Rxu9lBn8xQC/s/0M0U9lLBkYbA1htaAFjWYJzpzsbXPuri5D1H8gjp2jBum05qBrA==", "license": "Apache-2.0", + "dependencies": { + "@smithy/config-resolver": "^4.1.4", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/smithy-client": "^4.4.5", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18.18" + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.6.tgz", + "integrity": "sha512-YARl3tFL3WgPuLzljRUnrS2ngLiUtkwhQtj8PAL13XZSyUiNLQxwG3fBBq3QXFqGFUXepIN73pINp3y8c2nBmA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, - "peerDependencies": { - "prisma": "*", - "typescript": ">=5.1.0" + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", + "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" }, - "peerDependenciesMeta": { - "prisma": { - "optional": true - }, - "typescript": { - "optional": true - } + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@prisma/config": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.7.0.tgz", - "integrity": "sha512-di8QDdvSz7DLUi3OOcCHSwxRNeW7jtGRUD2+Z3SdNE3A+pPiNT8WgUJoUyOwJmUr5t+JA2W15P78C/N+8RXrOA==", + "node_modules/@smithy/util-middleware": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.4.tgz", + "integrity": "sha512-9MLKmkBmf4PRb0ONJikCbCwORACcil6gUWojwARCClT7RmLzF04hUR4WdRprIXal7XVyrddadYNfp2eF3nrvtQ==", "license": "Apache-2.0", "dependencies": { - "esbuild": ">=0.12 <1", - "esbuild-register": "3.6.0" + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@prisma/debug": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.7.0.tgz", - "integrity": "sha512-RabHn9emKoYFsv99RLxvfG2GHzWk2ZI1BuVzqYtmMSIcuGboHY5uFt3Q3boOREM9de6z5s3bQoyKeWnq8Fz22w==", - "license": "Apache-2.0" + "node_modules/@smithy/util-retry": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.6.tgz", + "integrity": "sha512-+YekoF2CaSMv6zKrA6iI/N9yva3Gzn4L6n35Luydweu5MMPYpiGZlWqehPHDHyNbnyaYlz/WJyYAZnC+loBDZg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^4.0.6", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@prisma/engines": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.7.0.tgz", - "integrity": "sha512-3wDMesnOxPrOsq++e5oKV9LmIiEazFTRFZrlULDQ8fxdub5w4NgRBoxtWbvXmj2nJVCnzuz6eFix3OhIqsZ1jw==", - "hasInstallScript": true, + "node_modules/@smithy/util-stream": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.2.tgz", + "integrity": "sha512-aI+GLi7MJoVxg24/3J1ipwLoYzgkB4kUfogZfnslcYlynj3xsQ0e7vk4TnTro9hhsS5PvX1mwmkRqqHQjwcU7w==", "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.7.0", - "@prisma/engines-version": "6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed", - "@prisma/fetch-engine": "6.7.0", - "@prisma/get-platform": "6.7.0" + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@prisma/engines-version": { - "version": "6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed.tgz", - "integrity": "sha512-EvpOFEWf1KkJpDsBCrih0kg3HdHuaCnXmMn7XFPObpFTzagK1N0Q0FMnYPsEhvARfANP5Ok11QyoTIRA2hgJTA==", - "license": "Apache-2.0" + "node_modules/@smithy/util-uri-escape": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", + "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@prisma/fetch-engine": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.7.0.tgz", - "integrity": "sha512-zLlAGnrkmioPKJR4Yf7NfW3hftcvqeNNEHleMZK9yX7RZSkhmxacAYyfGsCcqRt47jiZ7RKdgE0Wh2fWnm7WsQ==", + "node_modules/@smithy/util-utf8": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", + "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.7.0", - "@prisma/engines-version": "6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed", - "@prisma/get-platform": "6.7.0" + "@smithy/util-buffer-from": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@prisma/get-platform": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.7.0.tgz", - "integrity": "sha512-i9IH5lO4fQwnMLvQLYNdgVh9TK3PuWBfQd7QLk/YurnAIg+VeADcZDbmhAi4XBBDD+hDif9hrKyASu0hbjwabw==", + "node_modules/@smithy/util-waiter": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.0.6.tgz", + "integrity": "sha512-slcr1wdRbX7NFphXZOxtxRNA7hXAAtJAXJDE/wdoMAos27SIquVCKiSqfB6/28YzQ8FCsB5NKkhdM5gMADbqxg==", "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.7.0" + "@smithy/abort-controller": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@tsconfig/node10": { @@ -775,6 +2412,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", + "license": "MIT" + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -894,6 +2537,26 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/bcrypt": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", @@ -941,6 +2604,12 @@ "node": ">=18" } }, + "node_modules/bowser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -964,6 +2633,16 @@ "node": ">=8" } }, + "node_modules/buffer": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", + "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -1209,6 +2888,21 @@ "dev": true, "license": "MIT" }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", @@ -1260,6 +2954,44 @@ "node": ">=0.3.1" } }, + "node_modules/dotenv": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", + "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-cli": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-8.0.0.tgz", + "integrity": "sha512-aLqYbK7xKOiTMIRf1lDPbI+Y+Ip/wo5k3eyp6ePysVaSqbyxjyK3dK35BTxG+rmd7djf5q2UPs4noPNH+cj0Qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.6", + "dotenv": "^16.3.0", + "dotenv-expand": "^10.0.0", + "minimist": "^1.2.6" + }, + "bin": { + "dotenv": "cli.js" + } + }, + "node_modules/dotenv-expand": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz", + "integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -1411,6 +3143,15 @@ "node": ">= 0.6" } }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/express": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", @@ -1491,6 +3232,37 @@ "node": ">=6.6.0" } }, + "node_modules/fast-xml-parser": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/file-type": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", + "integrity": "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -1736,6 +3508,12 @@ "node": ">= 0.4" } }, + "node_modules/html-comment-regex": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.2.tgz", + "integrity": "sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==", + "license": "MIT" + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -1777,6 +3555,26 @@ "node": ">=0.10.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/ignore-by-default": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", @@ -1893,6 +3691,26 @@ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "license": "MIT" }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/jsonwebtoken": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", @@ -2151,6 +3969,24 @@ "node": ">= 6.0.0" } }, + "node_modules/multer-s3": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/multer-s3/-/multer-s3-3.0.1.tgz", + "integrity": "sha512-BFwSO80a5EW4GJRBdUuSHblz2jhVSAze33ZbnGpcfEicoT0iRolx4kWR+AJV07THFRCQ78g+kelKFdjkCCaXeQ==", + "license": "MIT", + "dependencies": { + "@aws-sdk/lib-storage": "^3.46.0", + "file-type": "^3.3.0", + "html-comment-regex": "^1.1.2", + "run-parallel": "^1.1.6" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-s3": "^3.0.0" + } + }, "node_modules/multer/node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -2368,6 +4204,16 @@ "node": ">=0.10.0" } }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", @@ -2466,6 +4312,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -2570,6 +4436,29 @@ "node": ">= 18" } }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -2657,6 +4546,29 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "license": "ISC" }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", @@ -2778,6 +4690,16 @@ "node": ">= 0.8" } }, + "node_modules/stream-browserify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", + "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", + "license": "MIT", + "dependencies": { + "inherits": "~2.0.4", + "readable-stream": "^3.5.0" + } + }, "node_modules/streamsearch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", @@ -2841,6 +4763,18 @@ "node": ">=0.10.0" } }, + "node_modules/strnum": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", + "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -3038,6 +4972,27 @@ "strip-json-comments": "^2.0.0" } }, + "node_modules/tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/type-is": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", @@ -3100,6 +5055,19 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", @@ -3132,6 +5100,22 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", diff --git a/package.json b/package.json index da70dbdc..e6f9507c 100644 --- a/package.json +++ b/package.json @@ -4,25 +4,29 @@ "description": "", "license": "ISC", "author": "", - "type": "module", "main": "src/index.js", "scripts": { - "dev": "npx prisma generate && nodemon src/app.js", + "dev": "ts-node-dev --respawn --require tsconfig-paths/register src/app.ts", "build": "tsc", "start": "node --enable-source-maps dist/app.js", "migrate": "prisma migrate dev", - "studio": "prisma studio" + "studio": "prisma studio", + "ssh": "dotenv -- bash -c 'ssh -i \"$SSH_KEY_PATH\" -L 5432:$DB_HOST:5432 $EC2_USER@$EC2_HOST'" }, "dependencies": { + "@aws-sdk/client-s3": "^3.842.0", + "@aws-sdk/s3-request-presigner": "^3.842.0", "@prisma/client": "^6.7.0", "bcrypt": "^5.1.1", "cookie": "^1.0.2", "cookie-parser": "^1.4.7", "cors": "^2.8.5", + "dotenv": "^16.5.0", "express": "^5.1.0", "express-jwt": "^8.5.1", "jsonwebtoken": "^9.0.2", "multer": "^1.4.5-lts.2", + "multer-s3": "^3.0.1", "prisma": "^6.7.0" }, "devDependencies": { @@ -32,9 +36,11 @@ "@types/express": "^5.0.3", "@types/jsonwebtoken": "^9.0.9", "@types/multer": "^1.4.13", + "dotenv-cli": "^8.0.0", "nodemon": "^3.1.10", "ts-node": "^10.9.2", "ts-node-dev": "^2.0.0", + "tsconfig-paths": "^4.2.0", "typescript": "^5.8.3" }, "prisma": { diff --git a/src/app.js b/src/app.js deleted file mode 100644 index 6afa9351..00000000 --- a/src/app.js +++ /dev/null @@ -1,50 +0,0 @@ -import express from "express"; -import cookieParser from "cookie-parser"; -import cors from "cors"; -import path from "path"; -import { UnauthorizedError } from "express-jwt"; - -// 컨트롤러 연결 -import userController from "./controllers/userController.js"; -import productController from "./controllers/productController.js"; -import productCommentController from "./controllers/productCommentController.js"; -import favoriteController from "./controllers/favoriteController.js"; -import uploadController from "./controllers/uploadController.js"; - -const app = express(); -const port = process.env.PORT ?? 3000; - -// app 설정 -app.use(cookieParser()); -app.use(express.json()); - -// 프론트에서 요청이 동작하기 위해 cors 적용 -app.use( - cors({ - origin: "http://localhost:3001", - credentials: true, - }) -); - -// 업로드 이미지 정적 경로 -app.use("/uploads", express.static(path.join(process.cwd(), "uploads"))); - -// 컨트롤러 연결 -app.use("/api", uploadController); -app.use("/", userController); -app.use("/products", productController); -app.use("/", productCommentController); -app.use("/", favoriteController); - -// express-jwt 인증 에러 핸들링 -app.use((err, req, res, next) => { - if (err instanceof UnauthorizedError) { - console.error("JWT 인증 오류:", err.message); - return res.status(401).json({ message: "인증이 유효하지 않습니다." }); - } - next(err); -}); - -app.listen(port, () => { - console.log(`Server is running on port ${port}`); -}); diff --git a/src/app.ts b/src/app.ts new file mode 100644 index 00000000..ee3662ef --- /dev/null +++ b/src/app.ts @@ -0,0 +1,63 @@ +import express, { NextFunction, Request, Response } from "express"; +import cookieParser from "cookie-parser"; +import cors from "cors"; +// import path from "path"; +import { UnauthorizedError } from "express-jwt"; + +// 컨트롤러 연결 +import userController from "./controllers/userController"; +import productController from "./controllers/productController"; +import productCommentController from "./controllers/productCommentController"; +import favoriteController from "./controllers/favoriteController"; +import uploadController from "./controllers/uploadController"; +import { AppError } from "./types/errors"; + +const app = express(); +const port = process.env.PORT ?? 3000; + +// app 설정 +app.use(cookieParser()); +app.use(express.json()); + +// 프론트에서 요청이 동작하기 위해 cors 적용 +app.use( + cors({ + origin: "http://localhost:3001", + credentials: true, + }) +); + +// 업로드 이미지 정적 경로 (S3 연결하여 필요 x) +// app.use("/uploads", express.static(path.join(process.cwd(), "uploads"))); + +// 컨트롤러 연결 +app.use("/api", uploadController); +app.use("/", userController); +app.use("/products", productController); +app.use("/", productCommentController); +app.use("/", favoriteController); + +// express-jwt 인증 에러 핸들링 +app.use((err: any, req: Request, res: Response, next: NextFunction): void => { + if (err instanceof UnauthorizedError) { + console.error("JWT 인증 오류:", err.message); + res.status(401).json({ message: "인증이 유효하지 않습니다." }); + return; + } + next(err); +}); + +// AppError 핸들링 +app.use((err: any, req: Request, res: Response, next: NextFunction): void => { + if (err instanceof AppError) { + console.error(`[AppError] ${err.name}:`, err.message); + res.status(err.code || 500).json({ message: err.message, data: err.data }); + return; + } + console.error("[Unknown Error]", err); + res.status(500).json({ message: "서버 내부 오류가 발생했습니다." }); +}); + +app.listen(port, () => { + console.log(`Server is running on port ${port}`); +}); diff --git a/src/config/prisma.js b/src/config/prisma.js deleted file mode 100644 index b5bf6ce8..00000000 --- a/src/config/prisma.js +++ /dev/null @@ -1,5 +0,0 @@ -import { PrismaClient } from "@prisma/client"; - -const prisma = new PrismaClient(); - -export default prisma; diff --git a/src/config/prisma.ts b/src/config/prisma.ts new file mode 100644 index 00000000..dd148123 --- /dev/null +++ b/src/config/prisma.ts @@ -0,0 +1,7 @@ +import { PrismaClient } from "@prisma/client"; + +const prisma = new PrismaClient(); + +export default prisma; + +// DBeaver 연결확인을 위해 나중에 seed.ts 만들어보고 npm run seed 실행 (Express 제대로 배포하기 3교시 영상 참고) diff --git a/src/controllers/favoriteController.js b/src/controllers/favoriteController.js deleted file mode 100644 index 77662ef2..00000000 --- a/src/controllers/favoriteController.js +++ /dev/null @@ -1,52 +0,0 @@ -import express from "express"; -import auth from "../middlewares/auth.js"; -import favoriteService from "../services/favoriteService.js"; - -const favoriteController = express.Router(); - -// 좋아요 등록 api -favoriteController.post( - "/products/:id/favorite", - auth.verifyAccessToken, - async (req, res, next) => { - try { - const userId = req.auth.userId; - const productId = Number(req.params.id); - - const updatedProduct = await favoriteService.toggleFavorite( - userId, - productId - ); - res.json(updatedProduct); - } catch (err) { - next(err); - } - } -); - -// 좋아요 삭제 api -favoriteController.delete( - "/products/:id/favorite", - auth.verifyAccessToken, - async (req, res, next) => { - try { - const userId = req.auth.userId; - const productId = Number(req.params.id); - - const existing = await favoriteService.getFavorite(userId, productId); - if (!existing) { - return res.status(404).json({ message: "좋아요 내역이 없습니다." }); - } - - const updatedProduct = await favoriteService.removeFavorite( - userId, - productId - ); - res.json(updatedProduct); - } catch (err) { - next(err); - } - } -); - -export default favoriteController; diff --git a/src/controllers/favoriteController.ts b/src/controllers/favoriteController.ts new file mode 100644 index 00000000..1800e45c --- /dev/null +++ b/src/controllers/favoriteController.ts @@ -0,0 +1,69 @@ +import express, { NextFunction, Request, Response } from "express"; +import auth from "../middlewares/auth"; +import favoriteService from "../services/favoriteService"; + +const favoriteController = express.Router(); + +// 사용자 인증 객체 확장 +interface AuthenticatedRequest extends Request { + auth?: { + userId: number; + }; +} + +// 좋아요 등록 및 취소 api +favoriteController.post( + "/products/:id/favorite", + auth.verifyAccessToken, + async ( + req: AuthenticatedRequest, + res: Response, + next: NextFunction + ): Promise => { + try { + const userId = req.auth?.userId; + const productId = Number(req.params.id); + + if (!userId) { + res.status(401).json({ message: "인증 정보가 없습니다." }); + return; + } + + const updatedProduct = await favoriteService.toggleFavorite( + userId, + productId + ); + res.json(updatedProduct); + } catch (err) { + next(err); + } + } +); + +export default favoriteController; + +// // 사실 등록에서 토글로 처리해서 프론트에서는 굳이 필요없음. 이는 서버에서 직접 제거용 +// // 좋아요 삭제 api +// favoriteController.delete( +// "/products/:id/favorite", +// auth.verifyAccessToken, +// async (req, res, next) => { +// try { +// const userId = req.auth.userId; +// const productId = Number(req.params.id); + +// const existing = await favoriteService.getFavorite(userId, productId); +// if (!existing) { +// return res.status(404).json({ message: "좋아요 내역이 없습니다." }); +// } + +// const updatedProduct = await favoriteService.removeFavorite( +// userId, +// productId +// ); +// res.json(updatedProduct); +// } catch (err) { +// next(err); +// } +// } +// ); diff --git a/src/controllers/productCommentController.js b/src/controllers/productCommentController.ts similarity index 60% rename from src/controllers/productCommentController.js rename to src/controllers/productCommentController.ts index 320034c4..d60ef84a 100644 --- a/src/controllers/productCommentController.js +++ b/src/controllers/productCommentController.ts @@ -1,16 +1,23 @@ -import express from "express"; -import productCommentService from "../services/productCommentService.js"; -import auth from "../middlewares/auth.js"; +import express, { NextFunction, Request, Response } from "express"; +import productCommentService from "../services/productCommentService"; +import auth from "../middlewares/auth"; +import { AuthenticationError, ValidationError } from "@/types/errors"; const productCommentController = express.Router(); +interface AuthenticatedRequest extends Request { + auth?: { + userId: number; + }; +} + // 상품 댓글 조회 api productCommentController.get( "/products/:id/comments", - async (req, res, next) => { + async (req: Request, res: Response, next: NextFunction) => { try { const productId = Number(req.params.id); - const limit = parseInt(req.query.limit) || 5; + const limit = parseInt(req.query.limit as string) || 5; const cursor = req.query.cursor ? Number(req.query.cursor) : null; const result = await productCommentService.getProductComments(productId, { @@ -29,14 +36,18 @@ productCommentController.get( productCommentController.post( "/products/:id/comments", auth.verifyAccessToken, - async (req, res, next) => { + async (req: AuthenticatedRequest, res: Response, next: NextFunction) => { try { const productId = Number(req.params.id); - const userId = req.auth.userId; + const userId = req.auth?.userId; const { content } = req.body; + if (!userId) { + throw new AuthenticationError("인증 정보가 없습니다."); + } + if (!content || content.trim() === "") { - return res.status(400).json({ message: "댓글 내용을 입력해주세요." }); + throw new ValidationError("댓글 내용을 입력해주세요."); } const newComment = await productCommentService.createProductComment( diff --git a/src/controllers/productController.js b/src/controllers/productController.js deleted file mode 100644 index 348f3271..00000000 --- a/src/controllers/productController.js +++ /dev/null @@ -1,169 +0,0 @@ -import express from "express"; -import auth from "../middlewares/auth.js"; -import productService from "../services/productService.js"; - -const productController = express.Router(); - -// 상품 등록 api -productController.post("/", auth.verifyAccessToken, async (req, res, next) => { - try { - const userId = req.auth.userId; - - // price 검사 - const price = Number(req.body.price); - if (isNaN(price)) { - return res.status(400).json({ message: "가격은 숫자여야 합니다." }); - } - - // tags 검사 - let tags = []; - const rawTags = req.body.tags; - - if (Array.isArray(rawTags)) { - tags = rawTags; - } else if (typeof rawTags === "string") { - try { - tags = JSON.parse(rawTags); - } catch (e) { - console.error("태그 파싱 오류:", rawTags); - return res.status(400).json({ message: "태그 형식 오류" }); - } - } else if (rawTags === undefined) { - tags = []; - } else { - return res.status(400).json({ message: "태그 형식 오류" }); - } - - // images 검사 - let images = []; - const rawImages = req.body.images; - - if (Array.isArray(rawImages)) { - images = rawImages; - } else if (typeof rawImages === "string") { - try { - images = JSON.parse(rawImages); - } catch (e) { - console.error("이미지 파싱 오류:", rawImages); - return res.status(400).json({ message: "이미지 형식 오류" }); - } - } else if (rawImages === undefined) { - images = []; - } else { - return res.status(400).json({ message: "이미지 형식 오류" }); - } - - const productData = { - name: req.body.name, - description: req.body.description, - price, - tags, - images, - }; - - const newProduct = await productService.createProduct(productData, userId); - - res.status(201).json(newProduct); - } catch (err) { - next(err); - } -}); - -// 상품 목록 조회 API -productController.get("/", async (req, res, next) => { - try { - const page = parseInt(req.query.page) || 1; - const pageSize = parseInt(req.query.pageSize) || 10; - const orderBy = req.query.orderBy === "favorite" ? "favorite" : "recent"; - - const { list, totalCount } = await productService.getAllProducts({ - page, - pageSize, - orderBy, - }); - - const formatted = list.map((p) => ({ - ...p, - price: p.price.toLocaleString("ko-KR"), - })); - - res.json({ list: formatted, totalCount }); - } catch (err) { - next(err); - } -}); - -// 상품 상세 조회 api -productController.get( - "/:id", - auth.verifyAccessToken, - async (req, res, next) => { - try { - const product = await productService.getProductById( - Number(req.params.id), - req.auth.userId // 좋아요(isFavorite)에 의해 유저 id 전달 - ); - res.json(product); - } catch (err) { - next(err); - } - } -); - -// 상품 삭제 API -productController.delete( - "/:id", - auth.verifyAccessToken, - async (req, res, next) => { - try { - const deleted = await productService.deleteProduct(Number(req.params.id)); - res.json(deleted); - } catch (err) { - next(err); - } - } -); - -// 상품 수정 API -productController.patch( - "/:id", - auth.verifyAccessToken, - async (req, res, next) => { - try { - const id = Number(req.params.id); - const userId = req.auth.userId; - - // // 1. 새로 업로드된 이미지 경로 - // const newImagePaths = req.files.map( - // (file) => `/uploads/${file.filename}` - // ); - - // 2. 기존 이미지들 (string 또는 array로 올 수 있음) - const existing = req.body.existingImages; - const existingImagePaths = - typeof existing === "string" - ? [existing] // 단일 문자열인 경우 - : Array.isArray(existing) - ? existing - : []; // 없으면 빈 배열 - - // 3. 최종 이미지 배열 - const finalImagePaths = [...existingImagePaths, ...newImagePaths]; - - const updated = await productService.updateProduct(id, { - name: req.body.name, - description: req.body.description, - price: Number(req.body.price), - tags: JSON.parse(req.body.tags), - images: finalImagePaths, // 새 + 기존 이미지 - ownerId: userId, - }); - - res.json(updated); - } catch (err) { - next(err); - } - } -); - -export default productController; diff --git a/src/controllers/productController.ts b/src/controllers/productController.ts new file mode 100644 index 00000000..16461553 --- /dev/null +++ b/src/controllers/productController.ts @@ -0,0 +1,234 @@ +import express, { NextFunction, Request, Response } from "express"; +import auth from "../middlewares/auth"; +import productService from "../services/productService"; +import { Product } from "@prisma/client"; +import { AuthenticationError, ValidationError } from "@/types/errors"; +import multer from "multer"; + +const productController = express.Router(); + +// 상품 등록 api +productController.post( + "/", + auth.verifyAccessToken, + async ( + req: Request< + {}, + {}, + Pick & { + tags?: string[] | string; + images?: string[] | string; + } + > & { auth?: { userId: number } }, + res: Response, + next: NextFunction + ): Promise => { + try { + const userId = req.auth?.userId; + // 인증 정보 유효성 검사 + if (!userId) throw new AuthenticationError("인증 정보가 없습니다."); + + // price 검사 + const price = Number(req.body.price); + if (isNaN(price)) throw new ValidationError("가격은 숫자여야 합니다."); + + // tags 검사 + let tags: string[] = []; + const rawTags = req.body.tags; + + if (Array.isArray(rawTags)) { + tags = rawTags; + } else if (typeof rawTags === "string") { + try { + tags = JSON.parse(rawTags); + } catch (e) { + throw new ValidationError("태그 형식 오류"); + } + } else if (rawTags !== undefined) { + throw new ValidationError("태그 형식 오류"); + } + + // images 검사 + let images: string[] = []; + const rawImages = req.body.images; + + if (Array.isArray(rawImages)) { + images = rawImages; + } else if (typeof rawImages === "string") { + try { + images = JSON.parse(rawImages); + } catch (e) { + throw new ValidationError("이미지 형식 오류"); + } + } else if (rawImages !== undefined) { + throw new ValidationError("이미지 형식 오류"); + } + + const productData: Omit< + Product, + "id" | "createdAt" | "updatedAt" | "ownerId" | "favoriteCount" + > = { + name: req.body.name, + description: req.body.description, + price, + tags, + images, + }; + + const newProduct = await productService.createProduct( + productData, + userId + ); + + res.status(201).json(newProduct); + } catch (err) { + next(err); + } + } +); + +// 상품 목록 조회 API +productController.get( + "/", + async (req: Request, res: Response, next: NextFunction): Promise => { + try { + const page = parseInt(req.query.page as string) || 1; + const pageSize = parseInt(req.query.pageSize as string) || 10; + const orderBy = req.query.orderBy === "favorite" ? "favorite" : "recent"; + + const { list, totalCount } = await productService.getAllProducts({ + page, + pageSize, + orderBy, + }); + + const formatted = list.map((p) => ({ + ...p, + price: p.price.toLocaleString("ko-KR"), + })); + + res.json({ list: formatted, totalCount }); + } catch (err) { + next(err); + } + } +); + +// 상품 상세 조회 api +productController.get( + "/:id", + auth.verifyAccessToken, + async ( + req: Request<{ id: string }> & { auth?: { userId: number } }, + res: Response, + next: NextFunction + ): Promise => { + try { + const userId = req.auth?.userId; + if (userId === undefined) + throw new AuthenticationError("인증 정보가 없습니다."); + + const product = await productService.getProductById( + Number(req.params.id), + userId // 좋아요(isFavorite)에 의해 유저 id 전달 + ); + res.json(product); + } catch (err) { + next(err); + } + } +); + +// 상품 삭제 API +productController.delete( + "/:id", + auth.verifyAccessToken, + async ( + req: Request<{ id: string }> & { auth?: { userId: number } }, + res: Response, + next: NextFunction + ): Promise => { + try { + const userId = req.auth?.userId; + if (!userId) throw new AuthenticationError("인증 정보가 없습니다."); + + const deleted = await productService.deleteProduct(Number(req.params.id)); + res.json(deleted); + } catch (err) { + next(err); + } + } +); + +const upload = multer(); + +// 상품 수정 API +productController.patch( + "/:id", + auth.verifyAccessToken, + upload.none(), + async ( + req: Request<{ id: string }> & { auth?: { userId: number } }, + res: Response, + next: NextFunction + ): Promise => { + try { + const id = Number(req.params.id); + const userId = req.auth?.userId; // undefined 일 수 있으므로 '?' + if (!userId) { + throw new AuthenticationError("인증 정보가 없습니다."); + } + + const body = req.body ?? {}; + + const existing = body.existingImages ?? []; + const existingImagePaths: string[] = Array.isArray(existing) + ? existing + : typeof existing === "string" + ? [existing] + : []; + + const newImages = body.newImagePaths ?? []; + const newImagePaths: string[] = Array.isArray(newImages) + ? newImages + : typeof newImages === "string" + ? [newImages] + : []; + + // 태그 파싱 + let tags: string[] = []; + try { + tags = + typeof body.tags === "string" + ? JSON.parse(body.tags) + : Array.isArray(body.tags) + ? body.tags + : []; + } catch { + throw new ValidationError("태그 형식이 올바르지 않습니다."); + } + + const finalImagePaths = [...existingImagePaths, ...newImagePaths]; + + // price 처리 (빈 문자열 또는 잘못된 값 방지) + const parsedPrice = Number(body.price); + const price = !isNaN(parsedPrice) ? parsedPrice : undefined; + + // 서비스에 넘길 데이터 + const updated = await productService.updateProduct(id, { + name: body.name, + description: body.description, + price, // undefined일 경우 필터링됨 + tags, + images: finalImagePaths, // 새 + 기존 이미지 + ownerId: userId, + }); + + res.json(updated); + } catch (err) { + next(err); + } + } +); + +export default productController; diff --git a/src/controllers/uploadController.js b/src/controllers/uploadController.js deleted file mode 100644 index 9e5f6050..00000000 --- a/src/controllers/uploadController.js +++ /dev/null @@ -1,30 +0,0 @@ -import express from "express"; -import multer from "multer"; -import path from "path"; - -const uploadController = express.Router(); - -// 저장 방식 -const storage = multer.diskStorage({ - destination: (req, file, cb) => { - cb(null, "uploads/"); - }, - filename: (req, file, cb) => { - const ext = path.extname(file.originalname); - cb(null, Date.now() + ext); - }, -}); - -const upload = multer({ storage }); - -// 단일 이미지 업로드 (image 필드 이름으로 전송) -uploadController.post("/upload", upload.single("image"), (req, res) => { - if (!req.file) { - return res.status(400).json({ message: "이미지 파일이 없습니다." }); - } - - const imageUrl = `/uploads/${req.file.filename}`; - res.status(201).json({ url: imageUrl }); -}); - -export default uploadController; diff --git a/src/controllers/uploadController.ts b/src/controllers/uploadController.ts new file mode 100644 index 00000000..05ffa144 --- /dev/null +++ b/src/controllers/uploadController.ts @@ -0,0 +1,96 @@ +import s3 from "@/lib/s3Client"; +import upload from "@/middlewares/s3Uploader"; // S3용 multer 미들웨어 +import { ValidationError } from "@/types/errors"; +import { GetObjectAclCommand } from "@aws-sdk/client-s3"; +import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; +import express, { Request, Response } from "express"; +// import multer, { StorageEngine } from "multer"; +// import path from "path"; + +const uploadController = express.Router(); + +// // 저장 방식 +// const storage: StorageEngine = multer.diskStorage({ +// destination: (req, file, cb) => { +// cb(null, "uploads/"); +// }, +// filename: (req, file, cb) => { +// const ext = path.extname(file.originalname); +// cb(null, Date.now() + ext); +// }, +// }); + +// const upload = multer({ storage }); + +// // 단일 이미지 업로드 (image 필드 이름으로 전송) +// uploadController.post( +// "/upload", +// upload.single("image"), +// (req: Request, res: Response) => { +// if (!req.file) { +// throw new ValidationError("이미지 파일이 없습니다."); +// } + +// const imageUrl = `/uploads/${req.file.filename}`; +// res.status(201).json({ url: imageUrl }); +// } +// ); + +// export default uploadController; + +/** + * 퍼블릭 폴더만 + */ +// // 단일 이미지 업로드 (image 필드 이름으로 전송) +// uploadController.post( +// "/upload", +// upload.single("image"), +// (req: Request, res: Response): void => { +// // multer-s3는 `req.file.location`에 S3 URL을 담음 +// const file = req.file as any; // 타입 선언 +// console.log("S3 업로드 결과:", file); +// if (!file || !file.location) { +// throw new ValidationError("이미지 파일이 없습니다."); +// } + +// res.status(201).json({ url: file.location }); // S3 URL 반환 +// } +// ); + +// export default uploadController; + +/** + * 미리 서명된 URL 포함 + */ +// 단일 이미지 업로드 (image 필드 이름으로 전송) +uploadController.post( + "/upload", + upload.single("image"), + async (req: Request, res: Response): Promise => { + const file = req.file as any; + + if (!file || !file.location || !file.key) { + throw new ValidationError("이미지 업로드에 실패했습니다."); + } + + const isPrivate = req.query.access === "private"; + + let presignedUrl: string | null = null; + if (isPrivate) { + const command = new GetObjectAclCommand({ + Bucket: process.env.AWS_BUCKET_NAME!, + Key: file.key, + }); + + presignedUrl = await getSignedUrl(s3, command, { expiresIn: 60 * 5 }); // 5분 (지나고 응답받은 presignedUrl을 클릭하면 에러 떠야함) + } + + res.status(201).json({ + url: file.location, // S3 전체 URL + key: file.key, // S3 key + presignedUrl, // 미리 서명된 URL (private일 경우에만) + }); + } +); + +export default uploadController; diff --git a/src/controllers/userController.js b/src/controllers/userController.js deleted file mode 100644 index 4babcf7f..00000000 --- a/src/controllers/userController.js +++ /dev/null @@ -1,112 +0,0 @@ -import express from "express"; -import userService from "../services/userService.js"; -import auth from "../middlewares/auth.js"; -import userRepository from "../repositories/userRepository.js"; - -// express 라우터 적용 -const userController = express.Router(); - -/** - * 회원가입 - */ -userController.post("/users", async (req, res, next) => { - try { - const { email, nickName, password } = req.body; - if (!email || !nickName || !password) { - const error = new Error("필수 입력사항입니다."); - error.code = 422; - throw error; - } - const user = await userService.createdUser({ email, nickName, password }); - res.status(201).json(user); - } catch (error) { - next(error); - } -}); - -/** - * 로그인 - */ -userController.post("/login", async (req, res, next) => { - const { email, password } = req.body; - try { - if (!email || !password) { - const error = new Error("email, paswword 가 모두 필요합니다."); - error.code = 422; - throw error; - } - const user = await userService.getUser(email, password); - - /// 토큰 인증 적용 - const accessToken = userService.createToken(user); - const refreshToken = userService.createToken(user, "refresh"); - await userService.updateUser(user.id, { refreshToken }); - - res.cookie("refreshToken", refreshToken, { - httpOnly: true, - sameSite: "none", - secure: true, - path: "/token/refresh", - maxAge: 1000 * 60 * 60 * 24 * 14, // 2주 - }); - - res.json({ - id: user.id, - email: user.email, - nickName: user.nickName, - accessToken, - }); - } catch (error) { - next(error); - } -}); - -/** - * refreshToken 재발급 - */ -userController.post( - "/token/refresh", - auth.verifyRefreshToken, - async (req, res, next) => { - try { - const refreshToken = req.cookies.refreshToken; - const { userId } = req.auth; - const { newAccessToken, newRefreshToken } = - await userService.refreshToken(userId, refreshToken); - res.cookie("refreshToken", newRefreshToken, { - httpOnly: true, - sameSite: "none", - secure: true, - path: "/token/refresh", - maxAge: 1000 * 60 * 60 * 24 * 14, - }); - - res.json({ accessToken: newAccessToken }); - } catch (error) { - next(error); - } - } -); - -/** - * 유저 로그인 유지 - */ -userController.get( - "/users/me", - auth.verifyAccessToken, - async (req, res, next) => { - try { - const user = await userRepository.findById(req.auth.userId); - if (!user) { - return res.status(404).json({ message: "사용자를 찾을 수 없습니다." }); - } - - const { password, refreshToken, ...safeUser } = user; - res.json(safeUser); - } catch (error) { - next(error); - } - } -); - -export default userController; diff --git a/src/controllers/userController.ts b/src/controllers/userController.ts new file mode 100644 index 00000000..bf143f25 --- /dev/null +++ b/src/controllers/userController.ts @@ -0,0 +1,143 @@ +import express, { NextFunction, Request, Response } from "express"; +import userService from "../services/userService"; +import auth from "../middlewares/auth"; +import userRepository from "../repositories/userRepository"; +import { User } from "@prisma/client"; +import { ValidationError } from "@/types/errors"; + +// express 라우터 적용 +const userController = express.Router(); + +/** + * 회원가입 + */ +userController.post( + "/users", + async ( + req: Request<{}, {}, Pick>, + res: Response, + next: NextFunction + ) => { + try { + const { email, nickName, password } = req.body; + if (!email || !nickName || !password) { + throw new ValidationError("필수 입력사항입니다."); + } + const user = await userService.createdUser({ email, nickName, password }); + res.status(201).json(user); + } catch (error) { + next(error); + } + } +); + +/** + * 로그인 + */ +userController.post( + "/login", + async ( + req: Request<{}, {}, Pick>, + res: Response, + next: NextFunction + ) => { + const { email, password } = req.body; + try { + if (!email || !password) { + throw new ValidationError("email, password 가 모두 필요합니다."); + } + const user = await userService.getUser(email, password); + + // JWT 발급 + const accessToken = userService.createToken(user); + const refreshToken = userService.createToken(user, "refresh"); + + // refreshToken DB 저장 + await userService.updateUser(user.id, { refreshToken }); + + // // accessToken도 쿠키에 저장 + // res.cookie("accessToken", accessToken, { + // httpOnly: true, + // secure: true, + // sameSite: "none", + // path: "/", // 모든 요청에서 자동으로 포함되도록 설정 + // maxAge: 1000 * 60 * 15, // 15분 정도로 설정 + // }); + + res.cookie("refreshToken", refreshToken, { + httpOnly: true, + sameSite: "none", + secure: true, + path: "/token/refresh", + maxAge: 1000 * 60 * 60, // 2주 + }); + + res.json({ + id: user.id, + email: user.email, + nickName: user.nickName, + accessToken, + }); + } catch (error) { + next(error); + } + } +); + +/** + * refreshToken 재발급 + */ +userController.post( + "/token/refresh", + auth.verifyRefreshToken, + async (req: Request, res: Response, next: NextFunction) => { + try { + const refreshToken = req.cookies.refreshToken; + const { userId } = (req as any).auth; + const { newAccessToken, newRefreshToken } = + await userService.refreshToken(userId, refreshToken); + res.cookie("refreshToken", newRefreshToken, { + httpOnly: true, + sameSite: "none", + secure: true, + path: "/token/refresh", + maxAge: 1000 * 60 * 60, + }); + + res.json({ accessToken: newAccessToken }); + } catch (error) { + next(error); + } + } +); + +/** + * 유저 로그인 유지 + */ +userController.get( + "/users/me", + auth.verifyAccessToken, + async (req: Request, res: Response, next: NextFunction): Promise => { + try { + const userId = req.auth?.userId; + // const userId = req.auth; // userId가 number가 아니라 { userId: number }, 객체 전체 + if (!userId) { + res.status(401).json({ message: "인증 정보가 없습니다." }); + return; + } + + const user = await userRepository.findById(userId); + if (!user) { + res.status(404).json({ message: "사용자를 찾을 수 없습니다." }); + return; + } + + const { password, refreshToken, ...safeUser } = user; + res.json(safeUser); + } catch (error) { + next(error); + } + } +); + +export default userController; diff --git a/src/lib/s3Client.ts b/src/lib/s3Client.ts new file mode 100644 index 00000000..f6836844 --- /dev/null +++ b/src/lib/s3Client.ts @@ -0,0 +1,13 @@ +import { S3Client } from "@aws-sdk/client-s3"; +import { configDotenv } from "dotenv"; +configDotenv(); + +const s3 = new S3Client({ + region: "ap-northeast-2", + credentials: { + accessKeyId: process.env.AWS_ACCESS_KEY_ID!, + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!, + }, +}); + +export default s3; diff --git a/src/middlewares/auth.js b/src/middlewares/auth.js deleted file mode 100644 index be7e3eb6..00000000 --- a/src/middlewares/auth.js +++ /dev/null @@ -1,35 +0,0 @@ -import { expressjwt } from "express-jwt"; - -// 토큰 인증용 -const verifyAccessToken = expressjwt({ - secret: process.env.JWT_SECRET, - algorithms: ["HS256"], - credentialsRequired: false, // 새로고침 실패 시 처리 완화를 위해 false로 설정 (개발환경만) - // send request용 - getToken: (req) => { - if ( - req.headers.authorization && - req.headers.authorization.startsWith("Bearer ") - ) { - return req.headers.authorization.split(" ")[1]; - } - return req.cookies?.accessToken; // 쿠키도 fallback - }, -}); - -const verifyRefreshToken = expressjwt({ - secret: process.env.JWT_SECRET, - algorithms: ["HS256"], - credentialsRequired: false, // 개발 중 새로고침 시 쿠키 누락 대응 - // 쿠키에 리프레시 토큰을 담기 때문 - getToken: (req) => { - const token = req.cookies?.refreshToken || null; - console.log("refreshToken from cookie:", token); // 디버깅용 - return token; - }, -}); - -export default { - verifyAccessToken, - verifyRefreshToken, -}; diff --git a/src/middlewares/auth.ts b/src/middlewares/auth.ts new file mode 100644 index 00000000..4eadca77 --- /dev/null +++ b/src/middlewares/auth.ts @@ -0,0 +1,44 @@ +import { configDotenv } from "dotenv"; +import { Request } from "express"; +import { expressjwt } from "express-jwt"; +configDotenv(); + +const JWT_SECRET = process.env.JWT_SECRET; + +if (!JWT_SECRET) { + throw new Error("JWT_SECRET is not defined in environment variables"); +} + +// 토큰 인증용 +const verifyAccessToken = expressjwt({ + secret: JWT_SECRET as string, + algorithms: ["HS256"], + credentialsRequired: true, // 새로고침 실패 시 처리 완화를 위해 false로 설정 (개발환경만) + // send request용 + getToken: (req: Request): string | undefined => { + const authHeader = req.headers?.authorization; + if (authHeader?.startsWith("Bearer ")) { + return authHeader.split(" ")[1]; + } + // return req.cookies?.accessToken; // 쿠키도 fallback + return undefined; + }, +}); + +const verifyRefreshToken = expressjwt({ + secret: JWT_SECRET as string, + algorithms: ["HS256"], + credentialsRequired: true, // 개발 중 새로고침 시 쿠키 누락 대응 + // 쿠키에 리프레시 토큰을 담기 때문 + getToken: (req: Request): string | undefined => { + const token = req.cookies?.refreshToken; + return token || undefined; + }, +}); + +const auth = { + verifyAccessToken, + verifyRefreshToken, +}; + +export default auth; diff --git a/src/middlewares/s3Uploader.ts b/src/middlewares/s3Uploader.ts new file mode 100644 index 00000000..49a57651 --- /dev/null +++ b/src/middlewares/s3Uploader.ts @@ -0,0 +1,28 @@ +import multer from "multer"; +import { configDotenv } from "dotenv"; +import s3 from "@/lib/s3Client"; +import { Request } from "express"; +const multerS3 = require("multer-s3"); + +configDotenv(); + +const upload = multer({ + storage: multerS3({ + s3, + bucket: process.env.AWS_BUCKET_NAME!, + // acl: "public-read", + key: ( + req: Request, + file: Express.Multer.File, + cb: (error: Error | null, key?: string) => void + ): void => { + const isPrivate = req.query.access === "private"; + const folder = isPrivate ? "private/" : "panda-market/"; + const uniqueName = `${Date.now()}_${file.originalname}`; + cb(null, `${folder}${uniqueName}`); + }, + }), + limits: { fileSize: 5 * 1024 * 1024 }, // 5MB 제한 +}); + +export default upload; diff --git a/src/repositories/favoriteRepository.js b/src/repositories/favoriteRepository.ts similarity index 57% rename from src/repositories/favoriteRepository.js rename to src/repositories/favoriteRepository.ts index 87a5521d..75875fcf 100644 --- a/src/repositories/favoriteRepository.js +++ b/src/repositories/favoriteRepository.ts @@ -1,7 +1,11 @@ -import prisma from "../config/prisma.js"; +import { Favorite } from "@prisma/client"; +import prisma from "../config/prisma"; // 좋아요 여부 확인 -async function isFavorite(userId, productId) { +async function isFavorite( + userId: Favorite["userId"], + productId: Favorite["productId"] +) { return prisma.favorite.findUnique({ where: { userId_productId: { @@ -13,7 +17,10 @@ async function isFavorite(userId, productId) { } // 좋아요 추가 -async function addFavorite(userId, productId) { +async function addFavorite( + userId: Favorite["userId"], + productId: Favorite["productId"] +) { return prisma.favorite.create({ data: { userId, @@ -23,7 +30,10 @@ async function addFavorite(userId, productId) { } // 좋아요 제거 -async function removeFavorite(userId, productId) { +async function removeFavorite( + userId: Favorite["userId"], + productId: Favorite["productId"] +) { return prisma.favorite.delete({ where: { userId_productId: { diff --git a/src/repositories/productCommentRepository.js b/src/repositories/productCommentRepository.js deleted file mode 100644 index 6a2e8930..00000000 --- a/src/repositories/productCommentRepository.js +++ /dev/null @@ -1,48 +0,0 @@ -import prisma from "../config/prisma.js"; - -// 상품 댓글 조회 (+ 최신순, 커서, 제한) -async function findByProductId({ productId, limit, cursor }) { - const where = { productId }; - - const comments = await prisma.productComment.findMany({ - where, - take: limit, - skip: cursor ? 1 : 0, - cursor: cursor ? { id: cursor } : undefined, - orderBy: { id: "desc" }, - include: { - writer: { - select: { - id: true, - nickName: true, - }, - }, - }, - }); - - return comments; -} - -// 상품 댓글 등록 -async function create({ content, userId, productId }) { - return prisma.productComment.create({ - data: { - content, - userId, - productId, - }, - include: { - writer: { - select: { - id: true, - nickName: true, - }, - }, - }, - }); -} - -export default { - findByProductId, - create, -}; diff --git a/src/repositories/productCommentRepository.ts b/src/repositories/productCommentRepository.ts new file mode 100644 index 00000000..92002c0a --- /dev/null +++ b/src/repositories/productCommentRepository.ts @@ -0,0 +1,73 @@ +import { ProductComment } from "@prisma/client"; +import prisma from "../config/prisma"; + +interface FindByProductIdParams { + productId: number; + limit: number; + cursor?: number; +} + +interface CreateCommentParams { + content: string; + userId: number; + productId: number; +} + +// 상품 댓글 조회 (+ 최신순, 커서, 제한) +async function findByProductId({ + productId, + limit, + cursor, +}: FindByProductIdParams): Promise< + (ProductComment & { + writer: { id: number; nickName: string }; + })[] +> { + return prisma.productComment.findMany({ + where: { productId }, + take: limit, + skip: cursor ? 1 : 0, + cursor: cursor ? { id: cursor } : undefined, + orderBy: { id: "desc" }, + include: { + writer: { + select: { + id: true, + nickName: true, + }, + }, + }, + }); +} + +// 상품 댓글 등록 +async function create({ + content, + userId, + productId, +}: CreateCommentParams): Promise< + ProductComment & { + writer: { id: number; nickName: string }; + } +> { + return prisma.productComment.create({ + data: { + content, + userId, + productId, + }, + include: { + writer: { + select: { + id: true, + nickName: true, + }, + }, + }, + }); +} + +export default { + findByProductId, + create, +}; diff --git a/src/repositories/productRepository.js b/src/repositories/productRepository.ts similarity index 61% rename from src/repositories/productRepository.js rename to src/repositories/productRepository.ts index decd6add..cd19df91 100644 --- a/src/repositories/productRepository.js +++ b/src/repositories/productRepository.ts @@ -1,7 +1,13 @@ -import prisma from "../config/prisma.js"; +import { Product } from "@prisma/client"; +import prisma from "../config/prisma"; // 상품 등록 -async function create(data) { +async function create( + data: Pick< + Product, + "name" | "description" | "price" | "tags" | "images" | "ownerId" + > & { favoriteCount?: number } +) { return prisma.product.create({ data, }); @@ -9,7 +15,15 @@ async function create(data) { // 상품 전체 조회 // 페이징 + 정렬 조건에 따른 상품 조회 -async function findAll({ skip, take, orderBy }) { +async function findAll({ + skip, + take, + orderBy, +}: { + skip: number; + take: number; + orderBy: { [key: string]: "asc" | "desc" }; +}) { return prisma.product.findMany({ skip, take, @@ -23,7 +37,7 @@ async function count() { } // 상품 상세 조회 (좋아요 수 및 내가 누른 좋아요 여부 포함) -async function findById(id, userId) { +async function findById(id: Product["id"], userId?: number) { const product = await prisma.product.findUnique({ where: { id }, include: { @@ -55,7 +69,7 @@ async function findById(id, userId) { } // 좋아요 수 증가 -async function incrementFavoriteCount(productId) { +async function incrementFavoriteCount(productId: Product["id"]) { return prisma.product.update({ where: { id: productId }, data: { @@ -67,7 +81,7 @@ async function incrementFavoriteCount(productId) { } // 좋아요 수 감소 -async function decrementFavoriteCount(productId) { +async function decrementFavoriteCount(productId: Product["id"]) { return prisma.product.update({ where: { id: productId }, data: { @@ -79,17 +93,27 @@ async function decrementFavoriteCount(productId) { } // 상품 삭제 -async function remove(id) { +async function remove(id: Product["id"]) { return prisma.product.delete({ where: { id }, }); } +// undefined를 제거하겠다. +function removeUndefined>(obj: T): Partial { + return Object.fromEntries( + Object.entries(obj).filter(([_, v]) => v !== undefined) + ) as Partial; +} + // 상품 수정 -async function update(id, data) { +async function update( + id: Product["id"], + data: Partial> +) { return prisma.product.update({ where: { id }, - data, + data: removeUndefined(data), }); } diff --git a/src/repositories/userRepository.js b/src/repositories/userRepository.js deleted file mode 100644 index 0ea4a0fa..00000000 --- a/src/repositories/userRepository.js +++ /dev/null @@ -1,52 +0,0 @@ -import prisma from "../config/prisma.js"; - -async function findById(id) { - return prisma.user.findUnique({ - where: { - id, - }, - }); -} - -async function findByEmail(email) { - return await prisma.user.findUnique({ - where: { - email, - }, - }); -} - -async function save(user) { - return prisma.user.create({ - data: { - email: user.email, - nickName: user.nickName, - password: user.password, - }, - }); -} - -async function update(id, data) { - return prisma.user.update({ - where: { - id, - }, - data: data, - }); -} - -async function createOrUpdate(provider, providerId, email, name) { - return prisma.user.upsert({ - where: { provider, providerId }, - update: { email, name }, - create: { provider, providerId, email, name }, - }); -} - -export default { - findById, - findByEmail, - save, - update, - createOrUpdate, -}; diff --git a/src/repositories/userRepository.ts b/src/repositories/userRepository.ts new file mode 100644 index 00000000..96443d1f --- /dev/null +++ b/src/repositories/userRepository.ts @@ -0,0 +1,57 @@ +import { User } from "@prisma/client"; +import prisma from "../config/prisma"; + +async function findById(id: User["id"]): Promise { + return prisma.user.findUnique({ + where: { + id, + }, + }); +} + +async function findByEmail(email: User["email"]): Promise { + return await prisma.user.findUnique({ + where: { + email, + }, + }); +} + +async function save( + user: Pick +): Promise { + return prisma.user.create({ + data: { + email: user.email, + nickName: user.nickName, + password: user.password, + }, + }); +} + +async function update( + id: User["id"], + data: Partial> +): Promise { + return prisma.user.update({ + where: { + id, + }, + data, + }); +} + +// async function createOrUpdate(provider, providerId, email, name) { +// return prisma.user.upsert({ +// where: { provider, providerId }, +// update: { email, name }, +// create: { provider, providerId, email, name }, +// }); +// } + +export default { + findById, + findByEmail, + save, + update, +}; diff --git a/src/services/favoriteService.js b/src/services/favoriteService.ts similarity index 64% rename from src/services/favoriteService.js rename to src/services/favoriteService.ts index 2ce4928b..14599eeb 100644 --- a/src/services/favoriteService.js +++ b/src/services/favoriteService.ts @@ -1,7 +1,22 @@ -import favoriteRepository from "../repositories/favoriteRepository.js"; -import productRepository from "../repositories/productRepository.js"; +import { Favorite } from "@prisma/client"; +import favoriteRepository from "../repositories/favoriteRepository"; +import productRepository from "../repositories/productRepository"; +import { NotFoundError } from "@/types/errors"; -async function toggleFavorite(userId, productId) { +async function toggleFavorite( + userId: Favorite["userId"], + productId: Favorite["productId"] +): Promise<{ + id: number; + name: string; + description: string; + price: number; + tags: string[]; + images: string[]; + favoriteCount: number; + isFavorite: boolean; + ownerNickname: string; +}> { const existing = await favoriteRepository.isFavorite(userId, productId); if (existing) { @@ -14,6 +29,7 @@ async function toggleFavorite(userId, productId) { // 수정된 상품 정보 product에 전달 const product = await productRepository.findById(productId, userId); + if (!product) throw new NotFoundError("상품을 찾을 수 없습니다."); const { owner, favorites, ...rest } = product; return { diff --git a/src/services/productCommentService.js b/src/services/productCommentService.js deleted file mode 100644 index 5b9b11f2..00000000 --- a/src/services/productCommentService.js +++ /dev/null @@ -1,32 +0,0 @@ -import productCommentRepository from "../repositories/productCommentRepository.js"; - -// 상품 댓글 조회 -async function getProductComments(productId, { limit = 5, cursor = null }) { - const comments = await productCommentRepository.findByProductId({ - productId, - limit, - cursor, - }); - - const nextCursor = - comments.length === limit ? comments[comments.length - 1].id : null; - - return { - list: comments, - nextCursor, - }; -} - -// 상품 댓글 등록 -async function createProductComment(productId, userId, content) { - return productCommentRepository.create({ - content, - userId, - productId, - }); -} - -export default { - getProductComments, - createProductComment, -}; diff --git a/src/services/productCommentService.ts b/src/services/productCommentService.ts new file mode 100644 index 00000000..2fa8c08c --- /dev/null +++ b/src/services/productCommentService.ts @@ -0,0 +1,57 @@ +import { ProductComment } from "@prisma/client"; +import productCommentRepository from "../repositories/productCommentRepository"; + +// 댓글 조회 시 사용하는 파라미터 타입 +interface GetProductCommentsOptions { + limit?: number; + cursor?: number | null; +} + +// 댓글 작성 시 반환 타입 +interface CommentWithWriter extends ProductComment { + writer: { + id: number; + nickName: string; + }; +} + +// 상품 댓글 조회 +async function getProductComments( + productId: number, + { limit = 5, cursor = null }: GetProductCommentsOptions +): Promise<{ + list: CommentWithWriter[]; + nextCursor: number | null; +}> { + const comments = await productCommentRepository.findByProductId({ + productId, + limit, + cursor: cursor ?? undefined, + }); + + const nextCursor = + comments.length === limit ? comments[comments.length - 1].id : null; + + return { + list: comments, + nextCursor, + }; +} + +// 상품 댓글 등록 +async function createProductComment( + productId: number, + userId: number, + content: string +): Promise { + return productCommentRepository.create({ + content, + userId, + productId, + }); +} + +export default { + getProductComments, + createProductComment, +}; diff --git a/src/services/productService.js b/src/services/productService.js deleted file mode 100644 index 2327aef7..00000000 --- a/src/services/productService.js +++ /dev/null @@ -1,89 +0,0 @@ -import productRepository from "../repositories/productRepository.js"; - -// 상품 등록 -async function createProduct(productData, userId) { - return productRepository.create({ - ...productData, - ownerId: userId, - favoriteCount: 0, - }); -} - -// 상품 목록 조회 (페이징 + 정렬 포함) -async function getAllProducts({ page = 1, pageSize = 10, orderBy = "recent" }) { - const skip = (page - 1) * pageSize; - const take = pageSize; - - const order = - orderBy === "favorite" ? { favoriteCount: "desc" } : { createdAt: "desc" }; - - const [list, totalCount] = await Promise.all([ - productRepository.findAll({ skip, take, orderBy: order }), - productRepository.count(), - ]); - - return { list, totalCount }; -} - -// 상품 상세 조회 -async function getProductById(id, userId) { - const product = await productRepository.findById(id, userId); - if (!product) { - const error = new Error("상품을 찾을 수 없습니다."); - error.code = 404; - throw error; - } - - const { owner, favorites, ...rest } = product; - - return { - ...rest, - ownerNickname: owner?.nickName || "알 수 없음", // null fallback 처리 - favoriteCount: product.favoriteCount, - isFavorite: product.isFavorite, - }; -} - -// 상품 삭제 -async function deleteProduct(id) { - const product = await productRepository.findById(id); - if (!product) { - const error = new Error("해당 상품을 찾을 수 없습니다."); - error.code = 404; - throw error; - } - - await productRepository.remove(id); - return { id }; -} - -// 상품 수정 -async function updateProduct(id, productData) { - const product = await productRepository.findById(id); - if (!product) { - const error = new Error("수정할 상품을 찾을 수 없습니다."); - error.code = 404; - throw error; - } - - // existingImages 때문에 명시적 필터링 - const cleanData = { - name: productData.name, - description: productData.description, - price: productData.price, - tags: productData.tags, - images: productData.images, - ownerId: productData.ownerId, - }; - - const updated = await productRepository.update(id, cleanData); - return updated; -} - -export default { - createProduct, - getAllProducts, - getProductById, - deleteProduct, - updateProduct, -}; diff --git a/src/services/productService.ts b/src/services/productService.ts new file mode 100644 index 00000000..3da95474 --- /dev/null +++ b/src/services/productService.ts @@ -0,0 +1,121 @@ +import { Product } from "@prisma/client"; +import productRepository from "../repositories/productRepository"; +import { NotFoundError } from "@/types/errors"; + +// 상품 등록 +async function createProduct( + productData: Pick< + Product, + "name" | "description" | "price" | "tags" | "images" + >, + userId: number +): Promise { + return productRepository.create({ + ...productData, + ownerId: userId, + favoriteCount: 0, + }); +} + +// 상품 목록 조회 (페이징 + 정렬 포함) +async function getAllProducts({ + page = 1, + pageSize = 10, + orderBy = "recent", +}: { + page?: number; + pageSize?: number; + orderBy?: "recent" | "favorite"; +}) { + const skip = (page - 1) * pageSize; + const take = pageSize; + + const order: { [key: string]: "desc" | "asc" } = + orderBy === "favorite" ? { favoriteCount: "desc" } : { createdAt: "desc" }; + + // // or + // const order = + // orderBy === "favorite" + // ? ({ favoriteCount: "desc" } as const) + // : ({ createdAt: "desc" } as const); + + const [list, totalCount] = await Promise.all([ + productRepository.findAll({ skip, take, orderBy: order }), + productRepository.count(), + ]); + + return { list, totalCount }; +} + +// 상품 상세 조회 +async function getProductById(id: number, userId?: number) { + const product = await productRepository.findById(id, userId); + if (!product) { + throw new NotFoundError("상품을 찾을 수 없습니다."); + } + + const { owner, favorites, ...rest } = product; + + return { + ...rest, + ownerNickname: owner?.nickName || "알 수 없음", // null fallback 처리 + favoriteCount: product.favoriteCount, + isFavorite: product.isFavorite, + }; +} + +// 상품 삭제 +async function deleteProduct(id: number) { + const product = await productRepository.findById(id); + if (!product) { + throw new NotFoundError("해당 상품을 찾을 수 없습니다."); + } + + await productRepository.remove(id); + return { id }; +} + +function removeUndefined>(obj: T): Partial { + return Object.fromEntries( + Object.entries(obj).filter(([_, v]) => v !== undefined) + ) as Partial; +} + +// for in 문으로 +// function removeUndefined>( +// obj: T +// ): { [K in keyof T]?: T[K] } { +// const result: any = {}; +// for (const key in obj) { +// if (obj[key] !== undefined) { +// result[key] = obj[key]; +// } +// } +// return result; +// } + +// 상품 수정 +async function updateProduct( + id: number, + productData: Partial< + Pick + > & { ownerId: number } +): Promise { + const product = await productRepository.findById(id); + if (!product) { + throw new NotFoundError("수정할 상품을 찾을 수 없습니다."); + } + + const cleanedData = removeUndefined(productData); // undefined 제거 + + const updated = await productRepository.update(id, cleanedData); + return updated; +} + +export default { + createProduct, + getAllProducts, + getProductById, + deleteProduct, + updateProduct, +}; diff --git a/src/services/userService.js b/src/services/userService.js deleted file mode 100644 index 4b2c9c86..00000000 --- a/src/services/userService.js +++ /dev/null @@ -1,118 +0,0 @@ -import userRepository from "../repositories/userRepository.js"; -import bcrypt from "bcrypt"; -import jwt from "jsonwebtoken"; - -// 비번 암호화 함수 -function hashPassword(password) { - return bcrypt.hash(password, 10); -} - -// 비번 제외 필터 함수 -function filterSensitiveUserData(user) { - const { password, refreshToken, ...rest } = user; - return rest; -} - -// 로그인 - 비번 일치 에러 함수 -async function verifyPassword(inputPassword, hashedPassword) { - const isMatch = await bcrypt.compare(inputPassword, hashedPassword); - if (!isMatch) { - const error = new Error("비밀번호가 일치하지 않습니다."); - error.code = 401; - throw error; - } -} - -/** - * 계정 만들기 - 이메일 중복 여부, 비번 해싱 과정 - */ -async function createdUser(user) { - try { - // 이메일을 통한 유저 존재여부 - const existedUser = await userRepository.findByEmail(user.email); - if (existedUser) { - const error = new Error("User already exists"); - error.code = 422; - throw error; - } - - const hashedPassword = await hashPassword(user.password); - const createdUser = await userRepository.save({ - ...user, - password: hashedPassword, - }); - return filterSensitiveUserData(createdUser); - } catch (error) { - if (error.code === 422) throw error; // 위의 중복 체크 에러는 별도로 전달 - - const customError = new Error("올바른 값을 입력하십시오."); - customError.code = 500; - throw customError; - } -} - -/** - * 로그인 - 이메일 존재 여부, 비번 일치 검사 - */ -async function getUser(email, password) { - try { - const user = await userRepository.findByEmail(email); - if (!user) { - const error = new Error("존재하지 않는 이메일입니다."); - error.code = 401; - throw error; - } - await verifyPassword(password, user.password); - return filterSensitiveUserData(user); - } catch (error) { - if (error.code === 401) throw error; - const customError = new Error("올바른 값을 입력하십시오."); - customError.code = 500; - throw customError; - } -} - -// 토큰 생성 함수 -function createToken(user, type = "access") { - const payload = { userId: user.id }; - const expiresIn = type === "refresh" ? "2w" : "1h"; - return jwt.sign(payload, process.env.JWT_SECRET, { expiresIn }); -} - -// 로큰 재발급 함수 -async function refreshToken(userId, incomingRefreshToken) { - const user = await userRepository.findById(userId); - if (!user) { - const error = new Error("Unauthorized - user not found"); - error.code = 401; - throw error; - } - - if (!user.refreshToken || user.refreshToken !== incomingRefreshToken) { - const error = new Error("Unauthorized - invalid refresh token"); - error.code = 401; - throw error; - } - - const newAccessToken = createToken(user); - const newRefreshToken = createToken(user, "refresh"); - - // 새 리프레쉬 토큰 DB에 저장 - await userRepository.update(userId, { refreshToken: newRefreshToken }); - - return { newAccessToken, newRefreshToken }; -} - -// 유저 정보 갱신 -async function updateUser(id, data) { - const updatedUser = await userRepository.update(id, data); - return filterSensitiveUserData(updatedUser); -} - -export default { - createdUser, - getUser, - createToken, - refreshToken, - updateUser, -}; diff --git a/src/services/userService.ts b/src/services/userService.ts new file mode 100644 index 00000000..f16bd3df --- /dev/null +++ b/src/services/userService.ts @@ -0,0 +1,136 @@ +import { User } from "@prisma/client"; +import userRepository from "../repositories/userRepository"; +import bcrypt from "bcrypt"; +import jwt from "jsonwebtoken"; +import { + AuthenticationError, + NotFoundError, + ServerError, + ValidationError, +} from "@/types/errors"; + +// 비번 암호화 함수 +function hashPassword(password: NonNullable) { + return bcrypt.hash(password, 10); +} + +// 비번 제외 필터 함수 +function filterSensitiveUserData( + user: User +): Omit { + const { password, refreshToken, ...rest } = user; + return rest; +} + +// 로그인 - 비번 일치 에러 함수 +async function verifyPassword( + inputPassword: NonNullable, + password: NonNullable +) { + const isMatch = await bcrypt.compare(inputPassword, password); + if (!isMatch) { + throw new AuthenticationError("비밀번호가 일치하지 않습니다."); + } +} + +/** + * 계정 만들기 - 이메일 중복 여부, 비번 해싱 과정 + */ +async function createdUser( + user: Pick +) { + try { + // 이메일을 통한 유저 존재여부 + const existedUser = await userRepository.findByEmail(user.email); + if (existedUser) { + throw new ValidationError("User already exists", { email: user.email }); + } + + const hashedPassword = await hashPassword(user.password); + const createdUser = await userRepository.save({ + ...user, + password: hashedPassword, + }); + return filterSensitiveUserData(createdUser); + } catch (error) { + if (error instanceof ValidationError) throw error; + throw new ServerError("회원가입 중 오류가 발생했습니다"); + } +} + +/** + * 로그인 - 이메일 존재 여부, 비번 일치 검사 + */ +async function getUser( + email: User["email"], + password: NonNullable +) { + try { + const user = await userRepository.findByEmail(email); + if (!user) { + throw new AuthenticationError("존재하지 않는 이메일입니다."); + } + await verifyPassword(password, user.password); + return filterSensitiveUserData(user); + } catch (error) { + if (error instanceof AuthenticationError) throw error; + throw new ServerError("로그인 중 오류가 발생했습니다"); + } +} + +// 토큰 생성 함수 +function createToken( + user: Omit, + type: "access" | "refresh" = "access" +) { + const payload = { userId: user.id }; + const secret = process.env.JWT_SECRET; + if (!secret) + throw new ServerError("JWT_SECRET 환경변수가 설정되지 않았습니다"); + const expiresIn = type === "refresh" ? "1h" : "15m"; + return jwt.sign(payload, secret, { expiresIn }); +} + +// 로큰 재발급 함수 +async function refreshToken( + userId: User["id"], + refreshToken: NonNullable +) { + const user = await userRepository.findById(userId); + if (!user || user.refreshToken !== refreshToken) { + throw new AuthenticationError("Unauthorized"); + } + + const newAccessToken = createToken(user); + const newRefreshToken = createToken(user, "refresh"); + + // 새 리프레쉬 토큰 DB에 저장 + await userRepository.update(userId, { refreshToken: newRefreshToken }); + + return { newAccessToken, newRefreshToken }; +} + +// 유저 정보 갱신 +async function updateUser( + id: User["id"], + data: Partial> +) { + const updatedUser = await userRepository.update(id, data); + return filterSensitiveUserData(updatedUser); +} + +// 사용자 찾기 (실습자료 참고해서 넣음) +async function getUserById(id: User["id"]) { + const user = await userRepository.findById(id); + if (!user) throw new NotFoundError("사용자를 찾을 수 없습니다"); + return filterSensitiveUserData(user); +} + +export default { + createdUser, + getUser, + createToken, + refreshToken, + updateUser, + getUserById, +}; diff --git a/src/types/errors.ts b/src/types/errors.ts new file mode 100644 index 00000000..3cfec6ee --- /dev/null +++ b/src/types/errors.ts @@ -0,0 +1,40 @@ +export class AppError extends Error { + code?: number; // 선택적 속성으로 변경 + data?: any; // 에러핸들러에서 사용하는 data 속성도 추가 + + constructor(message: string, code?: number, data?: any) { + super(message); + this.code = code; + this.data = data; + this.name = "AppError"; + } +} + +// 자주 사용하는 에러들을 위한 편의 클래스들 +export class ValidationError extends AppError { + constructor(message: string, data?: any) { + super(message, 422, data); // 422는 기본값 + this.name = "ValidationError"; + } +} + +export class AuthenticationError extends AppError { + constructor(message: string, data?: any) { + super(message, 401, data); // 401은 기본값 + this.name = "AuthenticationError"; + } +} + +export class ServerError extends AppError { + constructor(message: string, data?: any) { + super(message, 500, data); // 500은 기본값 + this.name = "ServerError"; + } +} + +export class NotFoundError extends AppError { + constructor(message: string, data?: any) { + super(message, 404, data); // 404은 기본값 + this.name = "NotFoundError"; + } +} diff --git a/src/types/express.d.ts b/src/types/express.d.ts new file mode 100644 index 00000000..5f60490a --- /dev/null +++ b/src/types/express.d.ts @@ -0,0 +1,11 @@ +declare global { + namespace Express { + interface Request { + auth?: { + userId: number; + }; + } + } +} + +export {}; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..0485f700 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,122 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "libReplacement": true, /* Enable lib replacement. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "commonjs" /* Specify what module code is generated. */, + "rootDir": "./src" /* Specify the root folder within your source files. */, + "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */, + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + "typeRoots": [ + "./src/types", + "./node_modules/@types" + ] /* Specify multiple folders that act like './node_modules/@types'. */, + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "rewriteRelativeImportExtensions": true, /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "noUncheckedSideEffectImports": true, /* Check side effect imports. */ + "resolveJsonModule": true /* Enable importing .json files. */, + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + "allowJs": true /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */, + // "checkJs": true /* Enable error reporting in type-checked JavaScript files. */, + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + "sourceMap": true /* Create source map files for emitted JavaScript files. */, + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + "outDir": "./dist" /* Specify an output folder for all emitted files. */, + // "removeComments": true, /* Disable emitting comments. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */ + // "erasableSyntaxOnly": true, /* Do not allow runtime constructs that are not part of ECMAScript. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, + + /* Type Checking */ + "strict": true /* Enable all strict type-checking options. */, + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */, + "baseUrl": "./src", + "paths": { + "@/*": ["*"] + } + }, + "include": ["src/**/*"], // 컴파일 대상 폴더 + "exclude": ["node_modules, prisma"] // 컴파일 제외 폴더 +} diff --git a/uploads/1750915544678.png b/uploads/1750915544678.png new file mode 100644 index 00000000..81f05678 Binary files /dev/null and b/uploads/1750915544678.png differ diff --git a/uploads/1751001778867.png b/uploads/1751001778867.png new file mode 100644 index 00000000..77288798 Binary files /dev/null and b/uploads/1751001778867.png differ diff --git a/uploads/1751001840306.png b/uploads/1751001840306.png new file mode 100644 index 00000000..e8859cf4 Binary files /dev/null and b/uploads/1751001840306.png differ diff --git a/uploads/1751001892674.png b/uploads/1751001892674.png new file mode 100644 index 00000000..be65a52c Binary files /dev/null and b/uploads/1751001892674.png differ diff --git "a/\354\212\244\355\201\254\353\246\260\354\203\267 2024-04-18 150644.png" "b/\354\212\244\355\201\254\353\246\260\354\203\267 2024-04-18 150644.png" deleted file mode 100644 index 6911705b..00000000 Binary files "a/\354\212\244\355\201\254\353\246\260\354\203\267 2024-04-18 150644.png" and /dev/null differ