diff --git a/app/node/package-lock.json b/app/node/package-lock.json index 6ff0485..c34e5aa 100644 --- a/app/node/package-lock.json +++ b/app/node/package-lock.json @@ -7,7 +7,1278 @@ "": { "name": "fds-aws-coding-exercise", "version": "1.0.0", - "license": "ISC" + "license": "ISC", + "dependencies": { + "@aws-sdk/client-dynamodb": "3.913.0", + "@aws-sdk/lib-dynamodb": "3.913.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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-dynamodb": { + "version": "3.913.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.913.0.tgz", + "integrity": "sha512-J9qRCKknNwdUyGJWZUFHhWbYaZZ3r5auY8VxaVtiRc08PhQXTCZd6yFg0dr6uNp3HtHt2inQu5/NQnrJekXp1w==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.911.0", + "@aws-sdk/credential-provider-node": "3.913.0", + "@aws-sdk/middleware-endpoint-discovery": "3.910.0", + "@aws-sdk/middleware-host-header": "3.910.0", + "@aws-sdk/middleware-logger": "3.910.0", + "@aws-sdk/middleware-recursion-detection": "3.910.0", + "@aws-sdk/middleware-user-agent": "3.911.0", + "@aws-sdk/region-config-resolver": "3.910.0", + "@aws-sdk/types": "3.910.0", + "@aws-sdk/util-endpoints": "3.910.0", + "@aws-sdk/util-user-agent-browser": "3.910.0", + "@aws-sdk/util-user-agent-node": "3.911.0", + "@smithy/config-resolver": "^4.3.2", + "@smithy/core": "^3.16.1", + "@smithy/fetch-http-handler": "^5.3.3", + "@smithy/hash-node": "^4.2.2", + "@smithy/invalid-dependency": "^4.2.2", + "@smithy/middleware-content-length": "^4.2.2", + "@smithy/middleware-endpoint": "^4.3.3", + "@smithy/middleware-retry": "^4.4.3", + "@smithy/middleware-serde": "^4.2.2", + "@smithy/middleware-stack": "^4.2.2", + "@smithy/node-config-provider": "^4.3.2", + "@smithy/node-http-handler": "^4.4.1", + "@smithy/protocol-http": "^5.3.2", + "@smithy/smithy-client": "^4.8.1", + "@smithy/types": "^4.7.1", + "@smithy/url-parser": "^4.2.2", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.2", + "@smithy/util-defaults-mode-node": "^4.2.3", + "@smithy/util-endpoints": "^3.2.2", + "@smithy/util-middleware": "^4.2.2", + "@smithy/util-retry": "^4.2.2", + "@smithy/util-utf8": "^4.2.0", + "@smithy/util-waiter": "^4.2.2", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.911.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.911.0.tgz", + "integrity": "sha512-N9QAeMvN3D1ZyKXkQp4aUgC4wUMuA5E1HuVCkajc0bq1pnH4PIke36YlrDGGREqPlyLFrXCkws2gbL5p23vtlg==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.911.0", + "@aws-sdk/middleware-host-header": "3.910.0", + "@aws-sdk/middleware-logger": "3.910.0", + "@aws-sdk/middleware-recursion-detection": "3.910.0", + "@aws-sdk/middleware-user-agent": "3.911.0", + "@aws-sdk/region-config-resolver": "3.910.0", + "@aws-sdk/types": "3.910.0", + "@aws-sdk/util-endpoints": "3.910.0", + "@aws-sdk/util-user-agent-browser": "3.910.0", + "@aws-sdk/util-user-agent-node": "3.911.0", + "@smithy/config-resolver": "^4.3.2", + "@smithy/core": "^3.16.1", + "@smithy/fetch-http-handler": "^5.3.3", + "@smithy/hash-node": "^4.2.2", + "@smithy/invalid-dependency": "^4.2.2", + "@smithy/middleware-content-length": "^4.2.2", + "@smithy/middleware-endpoint": "^4.3.3", + "@smithy/middleware-retry": "^4.4.3", + "@smithy/middleware-serde": "^4.2.2", + "@smithy/middleware-stack": "^4.2.2", + "@smithy/node-config-provider": "^4.3.2", + "@smithy/node-http-handler": "^4.4.1", + "@smithy/protocol-http": "^5.3.2", + "@smithy/smithy-client": "^4.8.1", + "@smithy/types": "^4.7.1", + "@smithy/url-parser": "^4.2.2", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.2", + "@smithy/util-defaults-mode-node": "^4.2.3", + "@smithy/util-endpoints": "^3.2.2", + "@smithy/util-middleware": "^4.2.2", + "@smithy/util-retry": "^4.2.2", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.911.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.911.0.tgz", + "integrity": "sha512-k4QG9A+UCq/qlDJFmjozo6R0eXXfe++/KnCDMmajehIE9kh+b/5DqlGvAmbl9w4e92LOtrY6/DN3mIX1xs4sXw==", + "dependencies": { + "@aws-sdk/types": "3.910.0", + "@aws-sdk/xml-builder": "3.911.0", + "@smithy/core": "^3.16.1", + "@smithy/node-config-provider": "^4.3.2", + "@smithy/property-provider": "^4.2.2", + "@smithy/protocol-http": "^5.3.2", + "@smithy/signature-v4": "^5.3.2", + "@smithy/smithy-client": "^4.8.1", + "@smithy/types": "^4.7.1", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.2", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.911.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.911.0.tgz", + "integrity": "sha512-6FWRwWn3LUZzLhqBXB+TPMW2ijCWUqGICSw8bVakEdODrvbiv1RT/MVUayzFwz/ek6e6NKZn6DbSWzx07N9Hjw==", + "dependencies": { + "@aws-sdk/core": "3.911.0", + "@aws-sdk/types": "3.910.0", + "@smithy/property-provider": "^4.2.2", + "@smithy/types": "^4.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.911.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.911.0.tgz", + "integrity": "sha512-xUlwKmIUW2fWP/eM3nF5u4CyLtOtyohlhGJ5jdsJokr3MrQ7w0tDITO43C9IhCn+28D5UbaiWnKw5ntkw7aVfA==", + "dependencies": { + "@aws-sdk/core": "3.911.0", + "@aws-sdk/types": "3.910.0", + "@smithy/fetch-http-handler": "^5.3.3", + "@smithy/node-http-handler": "^4.4.1", + "@smithy/property-provider": "^4.2.2", + "@smithy/protocol-http": "^5.3.2", + "@smithy/smithy-client": "^4.8.1", + "@smithy/types": "^4.7.1", + "@smithy/util-stream": "^4.5.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.913.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.913.0.tgz", + "integrity": "sha512-iR4c4NQ1OSRKQi0SxzpwD+wP1fCy+QNKtEyCajuVlD0pvmoIHdrm5THK9e+2/7/SsQDRhOXHJfLGxHapD74WJw==", + "dependencies": { + "@aws-sdk/core": "3.911.0", + "@aws-sdk/credential-provider-env": "3.911.0", + "@aws-sdk/credential-provider-http": "3.911.0", + "@aws-sdk/credential-provider-process": "3.911.0", + "@aws-sdk/credential-provider-sso": "3.911.0", + "@aws-sdk/credential-provider-web-identity": "3.911.0", + "@aws-sdk/nested-clients": "3.911.0", + "@aws-sdk/types": "3.910.0", + "@smithy/credential-provider-imds": "^4.2.2", + "@smithy/property-provider": "^4.2.2", + "@smithy/shared-ini-file-loader": "^4.3.2", + "@smithy/types": "^4.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.913.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.913.0.tgz", + "integrity": "sha512-HQPLkKDxS83Q/nZKqg9bq4igWzYQeOMqhpx5LYs4u1GwsKeCsYrrfz12Iu4IHNWPp9EnGLcmdfbfYuqZGrsaSQ==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.911.0", + "@aws-sdk/credential-provider-http": "3.911.0", + "@aws-sdk/credential-provider-ini": "3.913.0", + "@aws-sdk/credential-provider-process": "3.911.0", + "@aws-sdk/credential-provider-sso": "3.911.0", + "@aws-sdk/credential-provider-web-identity": "3.911.0", + "@aws-sdk/types": "3.910.0", + "@smithy/credential-provider-imds": "^4.2.2", + "@smithy/property-provider": "^4.2.2", + "@smithy/shared-ini-file-loader": "^4.3.2", + "@smithy/types": "^4.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.911.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.911.0.tgz", + "integrity": "sha512-mKshhV5jRQffZjbK9x7bs+uC2IsYKfpzYaBamFsEov3xtARCpOiKaIlM8gYKFEbHT2M+1R3rYYlhhl9ndVWS2g==", + "dependencies": { + "@aws-sdk/core": "3.911.0", + "@aws-sdk/types": "3.910.0", + "@smithy/property-provider": "^4.2.2", + "@smithy/shared-ini-file-loader": "^4.3.2", + "@smithy/types": "^4.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.911.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.911.0.tgz", + "integrity": "sha512-JAxd4uWe0Zc9tk6+N0cVxe9XtJVcOx6Ms0k933ZU9QbuRMH6xti/wnZxp/IvGIWIDzf5fhqiGyw5MSyDeI5b1w==", + "dependencies": { + "@aws-sdk/client-sso": "3.911.0", + "@aws-sdk/core": "3.911.0", + "@aws-sdk/token-providers": "3.911.0", + "@aws-sdk/types": "3.910.0", + "@smithy/property-provider": "^4.2.2", + "@smithy/shared-ini-file-loader": "^4.3.2", + "@smithy/types": "^4.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.911.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.911.0.tgz", + "integrity": "sha512-urIbXWWG+cm54RwwTFQuRwPH0WPsMFSDF2/H9qO2J2fKoHRURuyblFCyYG3aVKZGvFBhOizJYexf5+5w3CJKBw==", + "dependencies": { + "@aws-sdk/core": "3.911.0", + "@aws-sdk/nested-clients": "3.911.0", + "@aws-sdk/types": "3.910.0", + "@smithy/property-provider": "^4.2.2", + "@smithy/shared-ini-file-loader": "^4.3.2", + "@smithy/types": "^4.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/endpoint-cache": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/endpoint-cache/-/endpoint-cache-3.893.0.tgz", + "integrity": "sha512-KSwTfyLZyNLszz5f/yoLC+LC+CRKpeJii/+zVAy7JUOQsKhSykiRUPYUx7o2Sdc4oJfqqUl26A/jSttKYnYtAA==", + "dependencies": { + "mnemonist": "0.38.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/lib-dynamodb": { + "version": "3.913.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-dynamodb/-/lib-dynamodb-3.913.0.tgz", + "integrity": "sha512-tygUmkPzTFxegery4Q9Y1G4UMscx7zbGMIijn2FTa2CisSLo6WzOFMPUBwDDI515vDY2Eutc9NJAzwv3cPYs9g==", + "dependencies": { + "@aws-sdk/core": "3.911.0", + "@aws-sdk/util-dynamodb": "3.913.0", + "@smithy/core": "^3.16.1", + "@smithy/smithy-client": "^4.8.1", + "@smithy/types": "^4.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-dynamodb": "^3.913.0" + } + }, + "node_modules/@aws-sdk/middleware-endpoint-discovery": { + "version": "3.910.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint-discovery/-/middleware-endpoint-discovery-3.910.0.tgz", + "integrity": "sha512-KZvTt8lUUhkQptu00iSSdf5+6h6NP3L5tP251/4FRh9XDXMdpIoAAGsmamhVySkUSODDaALMHjXPSK5SJq/RYw==", + "dependencies": { + "@aws-sdk/endpoint-cache": "3.893.0", + "@aws-sdk/types": "3.910.0", + "@smithy/node-config-provider": "^4.3.2", + "@smithy/protocol-http": "^5.3.2", + "@smithy/types": "^4.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.910.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.910.0.tgz", + "integrity": "sha512-F9Lqeu80/aTM6S/izZ8RtwSmjfhWjIuxX61LX+/9mxJyEkgaECRxv0chsLQsLHJumkGnXRy/eIyMLBhcTPF5vg==", + "dependencies": { + "@aws-sdk/types": "3.910.0", + "@smithy/protocol-http": "^5.3.2", + "@smithy/types": "^4.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.910.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.910.0.tgz", + "integrity": "sha512-3LJyyfs1USvRuRDla1pGlzGRtXJBXD1zC9F+eE9Iz/V5nkmhyv52A017CvKWmYoR0DM9dzjLyPOI0BSSppEaTw==", + "dependencies": { + "@aws-sdk/types": "3.910.0", + "@smithy/types": "^4.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.910.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.910.0.tgz", + "integrity": "sha512-m/oLz0EoCy+WoIVBnXRXJ4AtGpdl0kPE7U+VH9TsuUzHgxY1Re/176Q1HWLBRVlz4gr++lNsgsMWEC+VnAwMpw==", + "dependencies": { + "@aws-sdk/types": "3.910.0", + "@aws/lambda-invoke-store": "^0.0.1", + "@smithy/protocol-http": "^5.3.2", + "@smithy/types": "^4.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.911.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.911.0.tgz", + "integrity": "sha512-rY3LvGvgY/UI0nmt5f4DRzjEh8135A2TeHcva1bgOmVfOI4vkkGfA20sNRqerOkSO6hPbkxJapO50UJHFzmmyA==", + "dependencies": { + "@aws-sdk/core": "3.911.0", + "@aws-sdk/types": "3.910.0", + "@aws-sdk/util-endpoints": "3.910.0", + "@smithy/core": "^3.16.1", + "@smithy/protocol-http": "^5.3.2", + "@smithy/types": "^4.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.911.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.911.0.tgz", + "integrity": "sha512-lp/sXbdX/S0EYaMYPVKga0omjIUbNNdFi9IJITgKZkLC6CzspihIoHd5GIdl4esMJevtTQQfkVncXTFkf/a4YA==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.911.0", + "@aws-sdk/middleware-host-header": "3.910.0", + "@aws-sdk/middleware-logger": "3.910.0", + "@aws-sdk/middleware-recursion-detection": "3.910.0", + "@aws-sdk/middleware-user-agent": "3.911.0", + "@aws-sdk/region-config-resolver": "3.910.0", + "@aws-sdk/types": "3.910.0", + "@aws-sdk/util-endpoints": "3.910.0", + "@aws-sdk/util-user-agent-browser": "3.910.0", + "@aws-sdk/util-user-agent-node": "3.911.0", + "@smithy/config-resolver": "^4.3.2", + "@smithy/core": "^3.16.1", + "@smithy/fetch-http-handler": "^5.3.3", + "@smithy/hash-node": "^4.2.2", + "@smithy/invalid-dependency": "^4.2.2", + "@smithy/middleware-content-length": "^4.2.2", + "@smithy/middleware-endpoint": "^4.3.3", + "@smithy/middleware-retry": "^4.4.3", + "@smithy/middleware-serde": "^4.2.2", + "@smithy/middleware-stack": "^4.2.2", + "@smithy/node-config-provider": "^4.3.2", + "@smithy/node-http-handler": "^4.4.1", + "@smithy/protocol-http": "^5.3.2", + "@smithy/smithy-client": "^4.8.1", + "@smithy/types": "^4.7.1", + "@smithy/url-parser": "^4.2.2", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.2", + "@smithy/util-defaults-mode-node": "^4.2.3", + "@smithy/util-endpoints": "^3.2.2", + "@smithy/util-middleware": "^4.2.2", + "@smithy/util-retry": "^4.2.2", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.910.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.910.0.tgz", + "integrity": "sha512-gzQAkuHI3xyG6toYnH/pju+kc190XmvnB7X84vtN57GjgdQJICt9So/BD0U6h+eSfk9VBnafkVrAzBzWMEFZVw==", + "dependencies": { + "@aws-sdk/types": "3.910.0", + "@smithy/node-config-provider": "^4.3.2", + "@smithy/types": "^4.7.1", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-middleware": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.911.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.911.0.tgz", + "integrity": "sha512-O1c5F1pbEImgEe3Vr8j1gpWu69UXWj3nN3vvLGh77hcrG5dZ8I27tSP5RN4Labm8Dnji/6ia+vqSYpN8w6KN5A==", + "dependencies": { + "@aws-sdk/core": "3.911.0", + "@aws-sdk/nested-clients": "3.911.0", + "@aws-sdk/types": "3.910.0", + "@smithy/property-provider": "^4.2.2", + "@smithy/shared-ini-file-loader": "^4.3.2", + "@smithy/types": "^4.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.910.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.910.0.tgz", + "integrity": "sha512-o67gL3vjf4nhfmuSUNNkit0d62QJEwwHLxucwVJkR/rw9mfUtAWsgBs8Tp16cdUbMgsyQtCQilL8RAJDoGtadQ==", + "dependencies": { + "@smithy/types": "^4.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-dynamodb": { + "version": "3.913.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-dynamodb/-/util-dynamodb-3.913.0.tgz", + "integrity": "sha512-VI+QoyKbAj5sdR89zxb1jn6pMj5c1hrZDrL02NvZSLYEKo6rQ/bJyKmGTcLgo0tXFdtKG5QynPCwJ7oxDur/XA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-dynamodb": "^3.913.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.910.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.910.0.tgz", + "integrity": "sha512-6XgdNe42ibP8zCQgNGDWoOF53RfEKzpU/S7Z29FTTJ7hcZv0SytC0ZNQQZSx4rfBl036YWYwJRoJMlT4AA7q9A==", + "dependencies": { + "@aws-sdk/types": "3.910.0", + "@smithy/types": "^4.7.1", + "@smithy/url-parser": "^4.2.2", + "@smithy/util-endpoints": "^3.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.893.0.tgz", + "integrity": "sha512-T89pFfgat6c8nMmpI8eKjBcDcgJq36+m9oiXbcUzeU55MP9ZuGgBomGjGnHaEyF36jenW9gmg3NfZDm0AO2XPg==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.910.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.910.0.tgz", + "integrity": "sha512-iOdrRdLZHrlINk9pezNZ82P/VxO/UmtmpaOAObUN+xplCUJu31WNM2EE/HccC8PQw6XlAudpdA6HDTGiW6yVGg==", + "dependencies": { + "@aws-sdk/types": "3.910.0", + "@smithy/types": "^4.7.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.911.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.911.0.tgz", + "integrity": "sha512-3l+f6ooLF6Z6Lz0zGi7vSKSUYn/EePPizv88eZQpEAFunBHv+CSVNPtxhxHfkm7X9tTsV4QGZRIqo3taMLolmA==", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.911.0", + "@aws-sdk/types": "3.910.0", + "@smithy/node-config-provider": "^4.3.2", + "@smithy/types": "^4.7.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.911.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.911.0.tgz", + "integrity": "sha512-/yh3oe26bZfCVGrIMRM9Z4hvvGJD+qx5tOLlydOkuBkm72aXON7D9+MucjJXTAcI8tF2Yq+JHa0478eHQOhnLg==", + "dependencies": { + "@smithy/types": "^4.7.1", + "fast-xml-parser": "5.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.0.1.tgz", + "integrity": "sha512-ORHRQ2tmvnBXc8t/X9Z8IcSbBA4xTLKuN873FopzklHMeqBst7YG0d+AX97inkvDX+NChYtSr+qGfcqGFaI8Zw==", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/abort-controller": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.3.tgz", + "integrity": "sha512-xWL9Mf8b7tIFuAlpjKtRPnHrR8XVrwTj5NPYO/QwZPtc0SDLsPxb56V5tzi5yspSMytISHybifez+4jlrx0vkQ==", + "dependencies": { + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.0.tgz", + "integrity": "sha512-Kkmz3Mup2PGp/HNJxhCWkLNdlajJORLSjwkcfrj0E7nu6STAEdcMR1ir5P9/xOmncx8xXfru0fbUYLlZog/cFg==", + "dependencies": { + "@smithy/node-config-provider": "^4.3.3", + "@smithy/types": "^4.8.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-endpoints": "^3.2.3", + "@smithy/util-middleware": "^4.2.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.17.0.tgz", + "integrity": "sha512-Tir3DbfoTO97fEGUZjzGeoXgcQAUBRDTmuH9A8lxuP8ATrgezrAJ6cLuRvwdKN4ZbYNlHgKlBX69Hyu3THYhtg==", + "dependencies": { + "@smithy/middleware-serde": "^4.2.3", + "@smithy/protocol-http": "^5.3.3", + "@smithy/types": "^4.8.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-middleware": "^4.2.3", + "@smithy/util-stream": "^4.5.3", + "@smithy/util-utf8": "^4.2.0", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.3.tgz", + "integrity": "sha512-hA1MQ/WAHly4SYltJKitEsIDVsNmXcQfYBRv2e+q04fnqtAX5qXaybxy/fhUeAMCnQIdAjaGDb04fMHQefWRhw==", + "dependencies": { + "@smithy/node-config-provider": "^4.3.3", + "@smithy/property-provider": "^4.2.3", + "@smithy/types": "^4.8.0", + "@smithy/url-parser": "^4.2.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.4.tgz", + "integrity": "sha512-bwigPylvivpRLCm+YK9I5wRIYjFESSVwl8JQ1vVx/XhCw0PtCi558NwTnT2DaVCl5pYlImGuQTSwMsZ+pIavRw==", + "dependencies": { + "@smithy/protocol-http": "^5.3.3", + "@smithy/querystring-builder": "^4.2.3", + "@smithy/types": "^4.8.0", + "@smithy/util-base64": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.3.tgz", + "integrity": "sha512-6+NOdZDbfuU6s1ISp3UOk5Rg953RJ2aBLNLLBEcamLjHAg1Po9Ha7QIB5ZWhdRUVuOUrT8BVFR+O2KIPmw027g==", + "dependencies": { + "@smithy/types": "^4.8.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.3.tgz", + "integrity": "sha512-Cc9W5DwDuebXEDMpOpl4iERo8I0KFjTnomK2RMdhhR87GwrSmUmwMxS4P5JdRf+LsjOdIqumcerwRgYMr/tZ9Q==", + "dependencies": { + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", + "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.3.tgz", + "integrity": "sha512-/atXLsT88GwKtfp5Jr0Ks1CSa4+lB+IgRnkNrrYP0h1wL4swHNb0YONEvTceNKNdZGJsye+W2HH8W7olbcPUeA==", + "dependencies": { + "@smithy/protocol-http": "^5.3.3", + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.3.4.tgz", + "integrity": "sha512-/RJhpYkMOaUZoJEkddamGPPIYeKICKXOu/ojhn85dKDM0n5iDIhjvYAQLP3K5FPhgB203O3GpWzoK2OehEoIUw==", + "dependencies": { + "@smithy/core": "^3.17.0", + "@smithy/middleware-serde": "^4.2.3", + "@smithy/node-config-provider": "^4.3.3", + "@smithy/shared-ini-file-loader": "^4.3.3", + "@smithy/types": "^4.8.0", + "@smithy/url-parser": "^4.2.3", + "@smithy/util-middleware": "^4.2.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.4.tgz", + "integrity": "sha512-vSgABQAkuUHRO03AhR2rWxVQ1un284lkBn+NFawzdahmzksAoOeVMnXXsuPViL4GlhRHXqFaMlc8Mj04OfQk1w==", + "dependencies": { + "@smithy/node-config-provider": "^4.3.3", + "@smithy/protocol-http": "^5.3.3", + "@smithy/service-error-classification": "^4.2.3", + "@smithy/smithy-client": "^4.9.0", + "@smithy/types": "^4.8.0", + "@smithy/util-middleware": "^4.2.3", + "@smithy/util-retry": "^4.2.3", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.3.tgz", + "integrity": "sha512-8g4NuUINpYccxiCXM5s1/V+uLtts8NcX4+sPEbvYQDZk4XoJfDpq5y2FQxfmUL89syoldpzNzA0R9nhzdtdKnQ==", + "dependencies": { + "@smithy/protocol-http": "^5.3.3", + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.3.tgz", + "integrity": "sha512-iGuOJkH71faPNgOj/gWuEGS6xvQashpLwWB1HjHq1lNNiVfbiJLpZVbhddPuDbx9l4Cgl0vPLq5ltRfSaHfspA==", + "dependencies": { + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.3.tgz", + "integrity": "sha512-NzI1eBpBSViOav8NVy1fqOlSfkLgkUjUTlohUSgAEhHaFWA3XJiLditvavIP7OpvTjDp5u2LhtlBhkBlEisMwA==", + "dependencies": { + "@smithy/property-provider": "^4.2.3", + "@smithy/shared-ini-file-loader": "^4.3.3", + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.2.tgz", + "integrity": "sha512-MHFvTjts24cjGo1byXqhXrbqm7uznFD/ESFx8npHMWTFQVdBZjrT1hKottmp69LBTRm/JQzP/sn1vPt0/r6AYQ==", + "dependencies": { + "@smithy/abort-controller": "^4.2.3", + "@smithy/protocol-http": "^5.3.3", + "@smithy/querystring-builder": "^4.2.3", + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.3.tgz", + "integrity": "sha512-+1EZ+Y+njiefCohjlhyOcy1UNYjT+1PwGFHCxA/gYctjg3DQWAU19WigOXAco/Ql8hZokNehpzLd0/+3uCreqQ==", + "dependencies": { + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.3.tgz", + "integrity": "sha512-Mn7f/1aN2/jecywDcRDvWWWJF4uwg/A0XjFMJtj72DsgHTByfjRltSqcT9NyE9RTdBSN6X1RSXrhn/YWQl8xlw==", + "dependencies": { + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.3.tgz", + "integrity": "sha512-LOVCGCmwMahYUM/P0YnU/AlDQFjcu+gWbFJooC417QRB/lDJlWSn8qmPSDp+s4YVAHOgtgbNG4sR+SxF/VOcJQ==", + "dependencies": { + "@smithy/types": "^4.8.0", + "@smithy/util-uri-escape": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.3.tgz", + "integrity": "sha512-cYlSNHcTAX/wc1rpblli3aUlLMGgKZ/Oqn8hhjFASXMCXjIqeuQBei0cnq2JR8t4RtU9FpG6uyl6PxyArTiwKA==", + "dependencies": { + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.3.tgz", + "integrity": "sha512-NkxsAxFWwsPsQiwFG2MzJ/T7uIR6AQNh1SzcxSUnmmIqIQMlLRQDKhc17M7IYjiuBXhrQRjQTo3CxX+DobS93g==", + "dependencies": { + "@smithy/types": "^4.8.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.3.3.tgz", + "integrity": "sha512-9f9Ixej0hFhroOK2TxZfUUDR13WVa8tQzhSzPDgXe5jGL3KmaM9s8XN7RQwqtEypI82q9KHnKS71CJ+q/1xLtQ==", + "dependencies": { + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.3.tgz", + "integrity": "sha512-CmSlUy+eEYbIEYN5N3vvQTRfqt0lJlQkaQUIf+oizu7BbDut0pozfDjBGecfcfWf7c62Yis4JIEgqQ/TCfodaA==", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "@smithy/protocol-http": "^5.3.3", + "@smithy/types": "^4.8.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-middleware": "^4.2.3", + "@smithy/util-uri-escape": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.9.0.tgz", + "integrity": "sha512-qz7RTd15GGdwJ3ZCeBKLDQuUQ88m+skh2hJwcpPm1VqLeKzgZvXf6SrNbxvx7uOqvvkjCMXqx3YB5PDJyk00ww==", + "dependencies": { + "@smithy/core": "^3.17.0", + "@smithy/middleware-endpoint": "^4.3.4", + "@smithy/middleware-stack": "^4.2.3", + "@smithy/protocol-http": "^5.3.3", + "@smithy/types": "^4.8.0", + "@smithy/util-stream": "^4.5.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.8.0.tgz", + "integrity": "sha512-QpELEHLO8SsQVtqP+MkEgCYTFW0pleGozfs3cZ183ZBj9z3VC1CX1/wtFMK64p+5bhtZo41SeLK1rBRtd25nHQ==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.3.tgz", + "integrity": "sha512-I066AigYvY3d9VlU3zG9XzZg1yT10aNqvCaBTw9EPgu5GrsEl1aUkcMvhkIXascYH1A8W0LQo3B1Kr1cJNcQEw==", + "dependencies": { + "@smithy/querystring-parser": "^4.2.3", + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-base64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", + "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", + "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz", + "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", + "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-config-provider": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz", + "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.3.tgz", + "integrity": "sha512-vqHoybAuZXbFXZqgzquiUXtdY+UT/aU33sxa4GBPkiYklmR20LlCn+d3Wc3yA5ZM13gQ92SZe/D8xh6hkjx+IQ==", + "dependencies": { + "@smithy/property-provider": "^4.2.3", + "@smithy/smithy-client": "^4.9.0", + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.5.tgz", + "integrity": "sha512-YQ9GQEC3knSa8oGSNdl5U6TlLynoOlLMIszrehgJxNh80v+ZCBnlXLtjyz0ffOxuM7j9cgviJuvuNkAzUseq6w==", + "dependencies": { + "@smithy/config-resolver": "^4.4.0", + "@smithy/credential-provider-imds": "^4.2.3", + "@smithy/node-config-provider": "^4.3.3", + "@smithy/property-provider": "^4.2.3", + "@smithy/smithy-client": "^4.9.0", + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.3.tgz", + "integrity": "sha512-aCfxUOVv0CzBIkU10TubdgKSx5uRvzH064kaiPEWfNIvKOtNpu642P4FP1hgOFkjQIkDObrfIDnKMKkeyrejvQ==", + "dependencies": { + "@smithy/node-config-provider": "^4.3.3", + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", + "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-middleware": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.3.tgz", + "integrity": "sha512-v5ObKlSe8PWUHCqEiX2fy1gNv6goiw6E5I/PN2aXg3Fb/hse0xeaAnSpXDiWl7x6LamVKq7senB+m5LOYHUAHw==", + "dependencies": { + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-retry": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.3.tgz", + "integrity": "sha512-lLPWnakjC0q9z+OtiXk+9RPQiYPNAovt2IXD3CP4LkOnd9NpUsxOjMx1SnoUVB7Orb7fZp67cQMtTBKMFDvOGg==", + "dependencies": { + "@smithy/service-error-classification": "^4.2.3", + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-stream": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.3.tgz", + "integrity": "sha512-oZvn8a5bwwQBNYHT2eNo0EU8Kkby3jeIg1P2Lu9EQtqDxki1LIjGRJM6dJ5CZUig8QmLxWxqOKWvg3mVoOBs5A==", + "dependencies": { + "@smithy/fetch-http-handler": "^5.3.4", + "@smithy/node-http-handler": "^4.4.2", + "@smithy/types": "^4.8.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-uri-escape": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", + "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", + "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-waiter": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.3.tgz", + "integrity": "sha512-5+nU///E5sAdD7t3hs4uwvCTWQtTR8JwKwOCSJtBRx0bY1isDo1QwH87vRK86vlFLBTISqoDA2V6xvP6nF1isQ==", + "dependencies": { + "@smithy/abort-controller": "^4.2.3", + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/uuid": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.0.tgz", + "integrity": "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/bowser": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.12.1.tgz", + "integrity": "sha512-z4rE2Gxh7tvshQ4hluIT7XcFrgLIQaw9X3A+kTTRdovCz5PMukm/0QC/BKSYPj3omF5Qfypn9O/c5kgpmvYUCw==" + }, + "node_modules/fast-xml-parser": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", + "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "dependencies": { + "strnum": "^2.1.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/mnemonist": { + "version": "0.38.3", + "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.38.3.tgz", + "integrity": "sha512-2K9QYubXx/NAjv4VLq1d1Ly8pWNC5L3BrixtdkyTegXWJIqY+zLNDhhX/A+ZwWt70tB1S8H4BE8FLYEFyNoOBw==", + "dependencies": { + "obliterator": "^1.6.1" + } + }, + "node_modules/obliterator": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-1.6.1.tgz", + "integrity": "sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig==" + }, + "node_modules/strnum": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", + "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ] + }, + "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==" } } } diff --git a/app/node/package.json b/app/node/package.json index d591f40..fd5c93b 100644 --- a/app/node/package.json +++ b/app/node/package.json @@ -4,6 +4,10 @@ "description": "Node.js ", "license": "ISC", "author": "", - "type": "commonjs", - "main": "index.js" + "type": "module", + "main": "index.js", + "dependencies": { + "@aws-sdk/client-dynamodb": "3.913.0", + "@aws-sdk/lib-dynamodb": "3.913.0" + } } diff --git a/app/node/src/error.js b/app/node/src/error.js new file mode 100644 index 0000000..3656896 --- /dev/null +++ b/app/node/src/error.js @@ -0,0 +1,6 @@ +export class AppError extends Error { + constructor(status = 500, message = 'Internal server error'){ + super(message) + this.status = status + } +} \ No newline at end of file diff --git a/app/node/src/getHandler.js b/app/node/src/getHandler.js new file mode 100644 index 0000000..8d11bdc --- /dev/null +++ b/app/node/src/getHandler.js @@ -0,0 +1,70 @@ + +import { QueryCommand } from '@aws-sdk/lib-dynamodb' +import { ddb, TABLE_NAME } from './utils.js' +import { AppError } from './error.js' + +export const getUserSubscription = async(userId, subscriptionId = null, includeInactive = false)=>{ + const subscriptionQueryParams = { + TableName: TABLE_NAME, + KeyConditionExpression: "pk = :pk AND begins_with(sk, :skPrefix)", + ExpressionAttributeValues:{ + ":pk": `user_${userId}`, + ":skPrefix": `sub_` + } + } + + if(!includeInactive){ + subscriptionQueryParams['FilterExpression'] = "#st = :active" + subscriptionQueryParams['ExpressionAttributeNames'] = {"#st": "status"} + subscriptionQueryParams.ExpressionAttributeValues[":active"] = 'ACTIVE' + } + + const subscriptionResponse = await ddb.send(new QueryCommand(subscriptionQueryParams)) + if(!subscriptionResponse.Items.length){ + return {} + } + + const subscription = subscriptionResponse.Items[0] + if(subscriptionId & subscriptionId !== subscription.pk){ + throw new AppError(400, 'User subscription mismatch') + } + + const plan = await getPlan(subscription.planSku) + const response = { + userId, + subscriptionId: subscription.pk, + plan:{ + sku: subscription.planSku, + name: plan.name, + price: plan.price, + currency: plan.currency, + billingCycle: plan.billingCycle, + features: plan.features + }, + startDate: subscription.startDate, + expiresAt: subscription.expiresAt, + canceledAt: subscription.canceledAt, + status: subscription.status, + attributes: subscription.attributes + } + return response +} + +export const getPlan = async (planSku)=>{ + const planQueryParams = { + TableName: TABLE_NAME, + KeyConditionExpression: "pk = :pk", + FilterExpression: "#st = :status", + ExpressionAttributeNames: {"#st": "status"}, + ExpressionAttributeValues:{ + ":pk": `${planSku}`, + ":status": 'ACTIVE' + } + } + const planResponse = await ddb.send(new QueryCommand(planQueryParams)) + if(!planResponse.Items.length){ + return {} + } + const plan = planResponse.Items[0] + return plan +} \ No newline at end of file diff --git a/app/node/src/httpWrapper.js b/app/node/src/httpWrapper.js new file mode 100644 index 0000000..76af20a --- /dev/null +++ b/app/node/src/httpWrapper.js @@ -0,0 +1,17 @@ +import { AppError } from "./error.js"; +import { createResponse } from "./utils.js"; +export const httpWrapper = (handlerFunction)=>{ + return async (event)=>{ + try{ + const result = await handlerFunction(event) + return createResponse(200, result) + }catch(error){ + console.log(error) + if(error instanceof(AppError)){ + return createResponse(error.status, {message: error.message}, ) + }else{ + return createResponse(500, {message: error.message}) + } + } + } +} \ No newline at end of file diff --git a/app/node/src/index.js b/app/node/src/index.js index 0997147..ea0b9e0 100755 --- a/app/node/src/index.js +++ b/app/node/src/index.js @@ -1,7 +1,38 @@ -exports.handler = async (event, context) => { - const response = { - statusCode: 200, - body: JSON.stringify('Hello from Node.js Lambda!'), - }; - return response; -}; \ No newline at end of file + +import { AppError } from './error.js'; +import { getUserSubscription } from './getHandler.js' +import { httpWrapper } from './httpWrapper.js'; +import { postHandler } from './postHandler.js' +const router = async (event) => { + + const method = (event.httpMethod || event.requestContext?.http?.method || "").toUpperCase(); + //routes + switch(method){ + case 'GET':{ + const userId = event?.pathParameters?.userId + if(!userId){ + throw new AppError(400, 'Missing UserId!') + } + //controller + const response = await getUserSubscription(userId) + return response + } + case 'POST':{ + const raw = event?.body ?? ''; + const isB64 = !!event?.isBase64Encoded; + const text = isB64 ? Buffer.from(raw, 'base64').toString('utf8') : raw; + + const parsedBody = JSON.parse(text || '{}') + if(!parsedBody){ + throw new AppError(400, 'Missing body!') + } + //controller + const result = await postHandler({eventType: parsedBody.eventType, parsedBody}) + return result + } + default: + throw new AppError(405, 'Method not allowed!') + } +}; + +export const handler = httpWrapper(router) diff --git a/app/node/src/postHandler.js b/app/node/src/postHandler.js new file mode 100644 index 0000000..a1c4f1b --- /dev/null +++ b/app/node/src/postHandler.js @@ -0,0 +1,149 @@ + +import { PutCommand, UpdateCommand } from '@aws-sdk/lib-dynamodb' +import { ddb, TABLE_NAME, createResponse, billingCycles } from './utils.js' +import { getPlan, getUserSubscription } from './getHandler.js' +import { AppError } from './error.js' + +const SubEvents = Object.freeze({ + CREATED: 'subscription.created', + RENEWED: 'subscription.renewed', + CANCELED: 'subscription.cancelled' +}) + +const createSubscription = async (body)=>{ + const { userId, subscriptionId, expiresAt, metadata, timestamp } = body + if( !userId || !subscriptionId || !expiresAt || !metadata || !timestamp ){ + throw new AppError(400, `Missing required fields`) + } + if( !metadata.planSku ){ + throw new AppError(400, `Missing metadata fields`) + } + + const { planSku } = metadata + + //validate user has no subscription + const subscription = await getUserSubscription(userId) + + if(Object.keys(subscription).length > 0){ + throw new AppError(400, 'User has an active subscription') + } + + //validate requested plan is not inactive + const plan = await getPlan(planSku) + if(Object.keys(plan).length === 0){ + throw new AppError(400, `Requested plan don't exist or is inactive`) + } + + //create item + const newSubscription = { + pk: `user_${userId}`, + sk: `${subscriptionId}`, + type: 'sub', + planSku, + startDate: timestamp, + expiresAt, + canceledAt: '', + lastModifiedAt: new Date().toISOString(), + attributes: metadata, + status: 'ACTIVE' + } + await ddb.send(new PutCommand({ + TableName: TABLE_NAME, + Item: newSubscription + })) + return newSubscription +} + +const cancelSubscription = async(body)=>{ + const { userId, subscriptionId, expiresAt } = body + + if(!userId || !subscriptionId){ + throw new AppError(400, 'Missing required fields') + } + + //validate subscription exists + const subscription = await getUserSubscription(userId, subscriptionId) + if(Object.keys(subscription).length === 0){ + throw new AppError(400, 'User has no active subscription') + } + + //update subscription status + const now = new Date() + const expireDate = new Date(expiresAt) + const updateParams = { + TableName: TABLE_NAME, + Key: { pk: `user_${userId}`, sk: subscriptionId }, + UpdateExpression: ` + SET canceledAt = :canceledAt, + lastModifiedAt = :lastModifiedAt, + #st = :status + `, + ExpressionAttributeNames: {"#st" : "status"}, + ExpressionAttributeValues: { + ":canceledAt": now.toISOString(), + ":lastModifiedAt": now.toISOString(), + ":status": now < expireDate ? 'PENDING' : 'CANCELED' + }, + ConditionExpression: "attribute_exists(pk) AND attribute_exists(sk)", + ReturnValues: "ALL_NEW" + } + const response = await ddb.send(new UpdateCommand(updateParams)) + return response +} + +const renewSubscription = async(body) =>{ + const { userId, subscriptionId, expiresAt } = body + + //validate subscription exists + if(!userId || !subscriptionId){ + return createResponse(400, {message: 'Missing required fields'}) + } + + const subscription = await getUserSubscription(userId, subscriptionId, true) + + if(Object.keys(subscription).length === 0){ + throw new AppError(400, 'Subscription not found') + } + + //update subscription status + const now = new Date().toISOString() + + const updateParams = { + TableName: TABLE_NAME, + Key: { pk: `user_${userId}`, sk: subscriptionId }, + UpdateExpression: ` + SET lastModifiedAt = :lastModifiedAt, + expiresAt = :expiresAt, + canceledAt = :canceledAt, + #st = :status + `, + ExpressionAttributeNames: {"#st" : "status"}, + ExpressionAttributeValues: { + ":lastModifiedAt": now, + ":expiresAt": expiresAt, + ":status": 'ACTIVE', + ":canceledAt": '' + }, + ConditionExpression: "attribute_exists(pk) AND attribute_exists(sk)", + ReturnValues: "ALL_NEW" + } + + const response = await ddb.send(new UpdateCommand(updateParams)) + return response + +} +export const postHandler = async({ eventType, parsedBody })=>{ + switch(eventType){ + case SubEvents.CREATED: + const createdResponse = await createSubscription(parsedBody) + return createdResponse + case SubEvents.CANCELED: + const canceledResponse = await cancelSubscription(parsedBody) + return canceledResponse.Attributes + case SubEvents.RENEWED: + const renewedResponse = await renewSubscription(parsedBody) + return renewedResponse.Attributes + default: + throw new AppError(400, 'Invalid post event') + } +} diff --git a/app/node/src/utils.js b/app/node/src/utils.js new file mode 100644 index 0000000..fe723d9 --- /dev/null +++ b/app/node/src/utils.js @@ -0,0 +1,22 @@ + +import { DynamoDBClient } from '@aws-sdk/client-dynamodb' +import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb' + +const config = { + region: process.env.REGION, +} +const client = new DynamoDBClient(config) +export const createResponse = (statusCode, body) => { + return { + statusCode, body: JSON.stringify(body), headers: { "Content-Type": "application/json" }, + } +} + +export const ddb = DynamoDBDocumentClient.from(client, {marshallOptions: { convertClassInstanceToMap: true, removeUndefinedValues: true}}) + +export const TABLE_NAME = process.env.TABLE_NAME + +export const billingCycles = Object.freeze({ + MONTHLY: 'MONTHLY', + YEARLY: 'YEARLY' +}) \ No newline at end of file diff --git a/postman/Fender.postman_collection.json b/postman/Fender.postman_collection.json new file mode 100644 index 0000000..c69c71f --- /dev/null +++ b/postman/Fender.postman_collection.json @@ -0,0 +1,514 @@ +{ + "info": { + "_postman_id": "1244362f-fd87-4055-8705-a4f79bc03b8d", + "name": "Fender", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "7708664" + }, + "item": [ + { + "name": "E2E Tests", + "item": [ + { + "name": "POST - Create subscription - Test", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"POST created -> 200\", () => pm.response.to.have.status(200));\r", + "const jsonBody = pm.response.json();\r", + "pm.test(\"POST Body\",()=> {\r", + " pm.expect(jsonBody.status).to.eql(\"ACTIVE\")\r", + " pm.expect(jsonBody.planSku).to.eql(\"PREMIUM_MONTHLY\")\r", + "})" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"eventId\": \"evt_123456789\",\r\n \"eventType\": \"subscription.created\",\r\n \"timestamp\": \"2024-03-20T10:00:00Z\",\r\n \"provider\": \"STRIPE\",\r\n \"subscriptionId\": \"sub_456789\",\r\n \"paymentId\": \"pm_123456\",\r\n \"userId\": \"123\",\r\n \"customerId\": \"cus_789012\",\r\n \"expiresAt\": \"2024-04-20T10:00:00Z\",\r\n \"metadata\": {\r\n \"planSku\": \"PREMIUM_MONTHLY\",\r\n \"autoRenew\": true,\r\n \"paymentMethod\": \"CREDIT_CARD\"\r\n }\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "https://{{apiId}}.execute-api.{{region}}.amazonaws.com/{{stage}}/api/v1/webhooks/subscriptions", + "protocol": "https", + "host": [ + "{{apiId}}", + "execute-api", + "{{region}}", + "amazonaws", + "com" + ], + "path": [ + "{{stage}}", + "api", + "v1", + "webhooks", + "subscriptions" + ] + } + }, + "response": [] + }, + { + "name": "GET - User subscriptions - Verify create", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"GET -> 200\", () => pm.response.to.have.status(200));\r", + "const jsonBody = pm.response.json();\r", + "pm.test(\"POST Body\",()=> {\r", + " pm.expect(jsonBody.status).to.eql(\"ACTIVE\")\r", + " pm.expect(jsonBody.plan.sku).to.eql(\"PREMIUM_MONTHLY\")\r", + "})" + ], + "type": "text/javascript" + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": {} + }, + "request": { + "auth": { + "type": "noauth" + }, + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json", + "type": "text", + "disabled": true + } + ], + "url": { + "raw": "https://{{apiId}}.execute-api.{{region}}.amazonaws.com/{{stage}}/api/v1/subscriptions/{{userId}}", + "protocol": "https", + "host": [ + "{{apiId}}", + "execute-api", + "{{region}}", + "amazonaws", + "com" + ], + "path": [ + "{{stage}}", + "api", + "v1", + "subscriptions", + "{{userId}}" + ] + } + }, + "response": [] + }, + { + "name": "POST - Cancel subscription Copy", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"POST Cancel -> 200\", () => pm.response.to.have.status(200));\r", + "const jsonBody = pm.response.json();\r", + "pm.test(\"POST Body\",()=> {\r", + " pm.expect(jsonBody.status).to.eql(\"CANCELED\")\r", + " pm.expect(jsonBody.planSku).to.eql(\"PREMIUM_MONTHLY\")\r", + "})" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"eventId\": \"evt_456789123\",\r\n \"eventType\": \"subscription.cancelled\",\r\n \"timestamp\": \"2024-05-20T10:00:00Z\",\r\n \"provider\": \"STRIPE\",\r\n \"subscriptionId\": \"sub_456789\",\r\n \"paymentId\": null,\r\n \"userId\": \"123\",\r\n \"customerId\": \"cus_789012\",\r\n \"expiresAt\": \"2024-05-20T10:00:00Z\",\r\n \"cancelledAt\": \"2024-05-20T10:00:00Z\",\r\n \"metadata\": {\r\n \"planSku\": \"PREMIUM_MONTHLY\",\r\n \"autoRenew\": false,\r\n \"paymentMethod\": \"CREDIT_CARD\",\r\n \"cancelReason\": \"USER_REQUESTED\"\r\n }\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "https://{{apiId}}.execute-api.{{region}}.amazonaws.com/{{stage}}/api/v1/webhooks/subscriptions", + "protocol": "https", + "host": [ + "{{apiId}}", + "execute-api", + "{{region}}", + "amazonaws", + "com" + ], + "path": [ + "{{stage}}", + "api", + "v1", + "webhooks", + "subscriptions" + ] + } + }, + "response": [] + }, + { + "name": "GET - User subscriptions - Verify cancel", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"GET -> 200\", () => pm.response.to.have.status(200));\r", + "const jsonBody = pm.response.json();\r", + "pm.test(\"POST Body\",()=> {\r", + " pm.expect(Object.keys(jsonBody).length).to.eql(0)\r", + "})" + ], + "type": "text/javascript" + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": {} + }, + "request": { + "auth": { + "type": "noauth" + }, + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json", + "type": "text", + "disabled": true + } + ], + "url": { + "raw": "https://{{apiId}}.execute-api.{{region}}.amazonaws.com/{{stage}}/api/v1/subscriptions/{{userId}}", + "protocol": "https", + "host": [ + "{{apiId}}", + "execute-api", + "{{region}}", + "amazonaws", + "com" + ], + "path": [ + "{{stage}}", + "api", + "v1", + "subscriptions", + "{{userId}}" + ] + } + }, + "response": [] + }, + { + "name": "POST - Renew subscription", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"POST Cancel -> 200\", () => pm.response.to.have.status(200));\r", + "const jsonBody = pm.response.json();\r", + "pm.test(\"POST Body\",()=> {\r", + " pm.expect(jsonBody.status).to.eql(\"ACTIVE\")\r", + " pm.expect(jsonBody.planSku).to.eql(\"PREMIUM_MONTHLY\")\r", + "})" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"eventId\": \"evt_987654321\",\r\n \"eventType\": \"subscription.renewed\",\r\n \"timestamp\": \"2024-04-20T10:00:00Z\",\r\n \"provider\": \"STRIPE\",\r\n \"subscriptionId\": \"sub_456789\",\r\n \"paymentId\": \"pm_654321\",\r\n \"userId\": \"123\",\r\n \"customerId\": \"cus_789012\",\r\n \"expiresAt\": \"2024-05-20T10:00:00Z\",\r\n \"metadata\": {\r\n \"planSku\": \"PREMIUM_MONTHLY\",\r\n \"autoRenew\": true,\r\n \"paymentMethod\": \"CREDIT_CARD\"\r\n }\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "https://{{apiId}}.execute-api.{{region}}.amazonaws.com/{{stage}}/api/v1/webhooks/subscriptions", + "protocol": "https", + "host": [ + "{{apiId}}", + "execute-api", + "{{region}}", + "amazonaws", + "com" + ], + "path": [ + "{{stage}}", + "api", + "v1", + "webhooks", + "subscriptions" + ] + } + }, + "response": [] + }, + { + "name": "GET - User subscriptions - Verify renew", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"GET -> 200\", () => pm.response.to.have.status(200));\r", + "const jsonBody = pm.response.json();\r", + "pm.test(\"POST Body\",()=> {\r", + " pm.expect(jsonBody.status).to.eql(\"ACTIVE\")\r", + " pm.expect(jsonBody.plan.sku).to.eql(\"PREMIUM_MONTHLY\")\r", + "})" + ], + "type": "text/javascript" + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": {} + }, + "request": { + "auth": { + "type": "noauth" + }, + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json", + "type": "text", + "disabled": true + } + ], + "url": { + "raw": "https://{{apiId}}.execute-api.{{region}}.amazonaws.com/{{stage}}/api/v1/subscriptions/{{userId}}", + "protocol": "https", + "host": [ + "{{apiId}}", + "execute-api", + "{{region}}", + "amazonaws", + "com" + ], + "path": [ + "{{stage}}", + "api", + "v1", + "subscriptions", + "{{userId}}" + ] + } + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ] + }, + { + "name": "POST - Create subscription", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"eventId\": \"evt_123456789\",\r\n \"eventType\": \"subscription.created\",\r\n \"timestamp\": \"2024-03-20T10:00:00Z\",\r\n \"provider\": \"STRIPE\",\r\n \"subscriptionId\": \"sub_456789\",\r\n \"paymentId\": \"pm_123456\",\r\n \"userId\": \"123\",\r\n \"customerId\": \"cus_789012\",\r\n \"expiresAt\": \"2024-04-20T10:00:00Z\",\r\n \"metadata\": {\r\n \"planSku\": \"PREMIUM_MONTHLY\",\r\n \"autoRenew\": true,\r\n \"paymentMethod\": \"CREDIT_CARD\"\r\n }\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "https://{{apiId}}.execute-api.{{region}}.amazonaws.com/{{stage}}/api/v1/webhooks/subscriptions", + "protocol": "https", + "host": [ + "{{apiId}}", + "execute-api", + "{{region}}", + "amazonaws", + "com" + ], + "path": [ + "{{stage}}", + "api", + "v1", + "webhooks", + "subscriptions" + ] + } + }, + "response": [] + }, + { + "name": "POST - Renew subscription", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"eventId\": \"evt_987654321\",\r\n \"eventType\": \"subscription.renewed\",\r\n \"timestamp\": \"2024-04-20T10:00:00Z\",\r\n \"provider\": \"STRIPE\",\r\n \"subscriptionId\": \"sub_456789\",\r\n \"paymentId\": \"pm_654321\",\r\n \"userId\": \"123\",\r\n \"customerId\": \"cus_789012\",\r\n \"expiresAt\": \"2024-05-20T10:00:00Z\",\r\n \"metadata\": {\r\n \"planSku\": \"PREMIUM_MONTHLY\",\r\n \"autoRenew\": true,\r\n \"paymentMethod\": \"CREDIT_CARD\"\r\n }\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "https://{{apiId}}.execute-api.{{region}}.amazonaws.com/{{stage}}/api/v1/webhooks/subscriptions", + "protocol": "https", + "host": [ + "{{apiId}}", + "execute-api", + "{{region}}", + "amazonaws", + "com" + ], + "path": [ + "{{stage}}", + "api", + "v1", + "webhooks", + "subscriptions" + ] + } + }, + "response": [] + }, + { + "name": "POST - Cancel subscription", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"eventId\": \"evt_456789123\",\r\n \"eventType\": \"subscription.cancelled\",\r\n \"timestamp\": \"2024-05-20T10:00:00Z\",\r\n \"provider\": \"STRIPE\",\r\n \"subscriptionId\": \"sub_456789\",\r\n \"paymentId\": null,\r\n \"userId\": \"123\",\r\n \"customerId\": \"cus_789012\",\r\n \"expiresAt\": \"2024-05-20T10:00:00Z\",\r\n \"cancelledAt\": \"2024-05-20T10:00:00Z\",\r\n \"metadata\": {\r\n \"planSku\": \"PREMIUM_MONTHLY\",\r\n \"autoRenew\": false,\r\n \"paymentMethod\": \"CREDIT_CARD\",\r\n \"cancelReason\": \"USER_REQUESTED\"\r\n }\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "https://{{apiId}}.execute-api.{{region}}.amazonaws.com/{{stage}}/api/v1/webhooks/subscriptions", + "protocol": "https", + "host": [ + "{{apiId}}", + "execute-api", + "{{region}}", + "amazonaws", + "com" + ], + "path": [ + "{{stage}}", + "api", + "v1", + "webhooks", + "subscriptions" + ] + } + }, + "response": [] + }, + { + "name": "GET - User subscriptions", + "protocolProfileBehavior": { + "disabledSystemHeaders": {} + }, + "request": { + "auth": { + "type": "noauth" + }, + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json", + "type": "text", + "disabled": true + } + ], + "url": { + "raw": "https://{{apiId}}.execute-api.{{region}}.amazonaws.com/{{stage}}/api/v1/subscriptions/{{userId}}", + "protocol": "https", + "host": [ + "{{apiId}}", + "execute-api", + "{{region}}", + "amazonaws", + "com" + ], + "path": [ + "{{stage}}", + "api", + "v1", + "subscriptions", + "{{userId}}" + ] + } + }, + "response": [] + } + ], + "variable": [ + { + "key": "userId", + "value": "" + }, + { + "key": "stage", + "value": "" + }, + { + "key": "apiId", + "value": "" + }, + { + "key": "region", + "value": "" + } + ] +} \ No newline at end of file diff --git a/scripts/deploy-node.sh b/scripts/deploy-node.sh index a4a8f9d..f399a0b 100755 --- a/scripts/deploy-node.sh +++ b/scripts/deploy-node.sh @@ -9,6 +9,7 @@ cd ../.. cp -r app/node/node_modules .temp/package/ cp -r app/node/src/. .temp/package/ +cp -r app/node/package.json .temp/package/ cd .temp/package zip -r ../package.zip .