diff --git a/README.md b/README.md index c4dfeed7..474bfb8c 100644 --- a/README.md +++ b/README.md @@ -72,13 +72,14 @@ Installation #### AWS S3 -1. [Create bucket](https://s3.console.aws.amazon.com/s3) with "Block all public access" turned off +1. [Create bucket](https://s3.console.aws.amazon.com/s3) with "ACLs enabled," and "Block all public access" turned off - Setup CORS: ``` [{ - "AllowedHeaders": [], + "AllowedHeaders": ["*"], "AllowedMethods": [ - "GET" + "GET", + "PUT" ], "AllowedOrigins": [ "http://localhost:*", @@ -92,10 +93,10 @@ Installation - `NEXT_PUBLIC_S3_BUCKET` - `NEXT_PUBLIC_S3_REGION` 2. [Create IAM policy](https://console.aws.amazon.com/iam/home#/policies) for client uploads (JSON editor recommended) - - Action: `s3:PutObject` - - Resource: `arn:aws:s3:::{BUCKET_NAME}/uploads/*` + - Action: `s3:PutObject`, `s3:PutObjectACL` + - Resource: `arn:aws:s3:::{BUCKET_NAME}/upload-*` 3. [Create IAM policy](https://console.aws.amazon.com/iam/home#/policies) for admin actions (JSON editor recommended) - - Action: `s3:PutObject`, `s3:GetObject`, `s3:ListBucket`, `s3:DeleteObject` + - Action: `s3:PutObject`, `s3:PutObjectACL`, `s3:GetObject`, `s3:ListBucket`, `s3:DeleteObject` - Resource: `arn:aws:s3:::{BUCKET_NAME}`, `arn:aws:s3:::{BUCKET_NAME}/*` 4. [Create IAM user](https://console.aws.amazon.com/iam/home#/users) for upload policy (by choosing "Attach policies directly"), create access key under "Security credentials," choose "Application running outside AWS," and store credentials - `NEXT_PUBLIC_S3_UPLOAD_ACCESS_KEY` diff --git a/package.json b/package.json index c82e9c27..20e38310 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "analyze": "ANALYZE=true next build" }, "dependencies": { + "@aws-sdk/client-s3": "^3.456.0", "@next/bundle-analyzer": "14.0.3", "@tailwindcss/forms": "^0.5.7", "@testing-library/jest-dom": "^6.1.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cc0f5c93..d1438535 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,6 +5,9 @@ settings: excludeLinksFromLockfile: false dependencies: + '@aws-sdk/client-s3': + specifier: ^3.456.0 + version: 3.456.0 '@next/bundle-analyzer': specifier: 14.0.3 version: 14.0.3 @@ -145,6 +148,588 @@ packages: preact-render-to-string: 5.2.3(preact@10.11.3) dev: false + /@aws-crypto/crc32@3.0.0: + resolution: {integrity: sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==} + dependencies: + '@aws-crypto/util': 3.0.0 + '@aws-sdk/types': 3.451.0 + tslib: 1.14.1 + dev: false + + /@aws-crypto/crc32c@3.0.0: + resolution: {integrity: sha512-ENNPPManmnVJ4BTXlOjAgD7URidbAznURqD0KvfREyc4o20DPYdEldU1f5cQ7Jbj0CJJSPaMIk/9ZshdB3210w==} + dependencies: + '@aws-crypto/util': 3.0.0 + '@aws-sdk/types': 3.451.0 + tslib: 1.14.1 + dev: false + + /@aws-crypto/ie11-detection@3.0.0: + resolution: {integrity: sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==} + dependencies: + tslib: 1.14.1 + dev: false + + /@aws-crypto/sha1-browser@3.0.0: + resolution: {integrity: sha512-NJth5c997GLHs6nOYTzFKTbYdMNA6/1XlKVgnZoaZcQ7z7UJlOgj2JdbHE8tiYLS3fzXNCguct77SPGat2raSw==} + dependencies: + '@aws-crypto/ie11-detection': 3.0.0 + '@aws-crypto/supports-web-crypto': 3.0.0 + '@aws-crypto/util': 3.0.0 + '@aws-sdk/types': 3.451.0 + '@aws-sdk/util-locate-window': 3.310.0 + '@aws-sdk/util-utf8-browser': 3.259.0 + tslib: 1.14.1 + dev: false + + /@aws-crypto/sha256-browser@3.0.0: + resolution: {integrity: sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==} + dependencies: + '@aws-crypto/ie11-detection': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-crypto/supports-web-crypto': 3.0.0 + '@aws-crypto/util': 3.0.0 + '@aws-sdk/types': 3.451.0 + '@aws-sdk/util-locate-window': 3.310.0 + '@aws-sdk/util-utf8-browser': 3.259.0 + tslib: 1.14.1 + dev: false + + /@aws-crypto/sha256-js@3.0.0: + resolution: {integrity: sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==} + dependencies: + '@aws-crypto/util': 3.0.0 + '@aws-sdk/types': 3.451.0 + tslib: 1.14.1 + dev: false + + /@aws-crypto/supports-web-crypto@3.0.0: + resolution: {integrity: sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==} + dependencies: + tslib: 1.14.1 + dev: false + + /@aws-crypto/util@3.0.0: + resolution: {integrity: sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==} + dependencies: + '@aws-sdk/types': 3.451.0 + '@aws-sdk/util-utf8-browser': 3.259.0 + tslib: 1.14.1 + dev: false + + /@aws-sdk/client-s3@3.456.0: + resolution: {integrity: sha512-987Mls+9w+mpdq4Vpc/OEQ93afkM12H7l97lIejcidZySuLVo5tdOM9ErekmgjAuotFzBgu2ExL83XtYIMgA0g==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-crypto/sha1-browser': 3.0.0 + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/client-sts': 3.454.0 + '@aws-sdk/core': 3.451.0 + '@aws-sdk/credential-provider-node': 3.451.0 + '@aws-sdk/middleware-bucket-endpoint': 3.451.0 + '@aws-sdk/middleware-expect-continue': 3.451.0 + '@aws-sdk/middleware-flexible-checksums': 3.451.0 + '@aws-sdk/middleware-host-header': 3.451.0 + '@aws-sdk/middleware-location-constraint': 3.451.0 + '@aws-sdk/middleware-logger': 3.451.0 + '@aws-sdk/middleware-recursion-detection': 3.451.0 + '@aws-sdk/middleware-sdk-s3': 3.451.0 + '@aws-sdk/middleware-signing': 3.451.0 + '@aws-sdk/middleware-ssec': 3.451.0 + '@aws-sdk/middleware-user-agent': 3.451.0 + '@aws-sdk/region-config-resolver': 3.451.0 + '@aws-sdk/signature-v4-multi-region': 3.451.0 + '@aws-sdk/types': 3.451.0 + '@aws-sdk/util-endpoints': 3.451.0 + '@aws-sdk/util-user-agent-browser': 3.451.0 + '@aws-sdk/util-user-agent-node': 3.451.0 + '@aws-sdk/xml-builder': 3.310.0 + '@smithy/config-resolver': 2.0.19 + '@smithy/eventstream-serde-browser': 2.0.14 + '@smithy/eventstream-serde-config-resolver': 2.0.14 + '@smithy/eventstream-serde-node': 2.0.14 + '@smithy/fetch-http-handler': 2.2.7 + '@smithy/hash-blob-browser': 2.0.15 + '@smithy/hash-node': 2.0.16 + '@smithy/hash-stream-node': 2.0.16 + '@smithy/invalid-dependency': 2.0.14 + '@smithy/md5-js': 2.0.16 + '@smithy/middleware-content-length': 2.0.16 + '@smithy/middleware-endpoint': 2.2.1 + '@smithy/middleware-retry': 2.0.21 + '@smithy/middleware-serde': 2.0.14 + '@smithy/middleware-stack': 2.0.8 + '@smithy/node-config-provider': 2.1.6 + '@smithy/node-http-handler': 2.1.10 + '@smithy/protocol-http': 3.0.10 + '@smithy/smithy-client': 2.1.16 + '@smithy/types': 2.6.0 + '@smithy/url-parser': 2.0.14 + '@smithy/util-base64': 2.0.1 + '@smithy/util-body-length-browser': 2.0.0 + '@smithy/util-body-length-node': 2.1.0 + '@smithy/util-defaults-mode-browser': 2.0.20 + '@smithy/util-defaults-mode-node': 2.0.26 + '@smithy/util-endpoints': 1.0.5 + '@smithy/util-retry': 2.0.7 + '@smithy/util-stream': 2.0.21 + '@smithy/util-utf8': 2.0.2 + '@smithy/util-waiter': 2.0.14 + fast-xml-parser: 4.2.5 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/client-sso@3.451.0: + resolution: {integrity: sha512-KkYSke3Pdv3MfVH/5fT528+MKjMyPKlcLcd4zQb0x6/7Bl7EHrPh1JZYjzPLHelb+UY5X0qN8+cb8iSu1eiwIQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/core': 3.451.0 + '@aws-sdk/middleware-host-header': 3.451.0 + '@aws-sdk/middleware-logger': 3.451.0 + '@aws-sdk/middleware-recursion-detection': 3.451.0 + '@aws-sdk/middleware-user-agent': 3.451.0 + '@aws-sdk/region-config-resolver': 3.451.0 + '@aws-sdk/types': 3.451.0 + '@aws-sdk/util-endpoints': 3.451.0 + '@aws-sdk/util-user-agent-browser': 3.451.0 + '@aws-sdk/util-user-agent-node': 3.451.0 + '@smithy/config-resolver': 2.0.19 + '@smithy/fetch-http-handler': 2.2.7 + '@smithy/hash-node': 2.0.16 + '@smithy/invalid-dependency': 2.0.14 + '@smithy/middleware-content-length': 2.0.16 + '@smithy/middleware-endpoint': 2.2.1 + '@smithy/middleware-retry': 2.0.21 + '@smithy/middleware-serde': 2.0.14 + '@smithy/middleware-stack': 2.0.8 + '@smithy/node-config-provider': 2.1.6 + '@smithy/node-http-handler': 2.1.10 + '@smithy/protocol-http': 3.0.10 + '@smithy/smithy-client': 2.1.16 + '@smithy/types': 2.6.0 + '@smithy/url-parser': 2.0.14 + '@smithy/util-base64': 2.0.1 + '@smithy/util-body-length-browser': 2.0.0 + '@smithy/util-body-length-node': 2.1.0 + '@smithy/util-defaults-mode-browser': 2.0.20 + '@smithy/util-defaults-mode-node': 2.0.26 + '@smithy/util-endpoints': 1.0.5 + '@smithy/util-retry': 2.0.7 + '@smithy/util-utf8': 2.0.2 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/client-sts@3.454.0: + resolution: {integrity: sha512-0fDvr8WeB6IYO8BUCzcivWmahgGl/zDbaYfakzGnt4mrl5ztYaXE875WI6b7+oFcKMRvN+KLvwu5TtyFuNY+GQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/core': 3.451.0 + '@aws-sdk/credential-provider-node': 3.451.0 + '@aws-sdk/middleware-host-header': 3.451.0 + '@aws-sdk/middleware-logger': 3.451.0 + '@aws-sdk/middleware-recursion-detection': 3.451.0 + '@aws-sdk/middleware-sdk-sts': 3.451.0 + '@aws-sdk/middleware-signing': 3.451.0 + '@aws-sdk/middleware-user-agent': 3.451.0 + '@aws-sdk/region-config-resolver': 3.451.0 + '@aws-sdk/types': 3.451.0 + '@aws-sdk/util-endpoints': 3.451.0 + '@aws-sdk/util-user-agent-browser': 3.451.0 + '@aws-sdk/util-user-agent-node': 3.451.0 + '@smithy/config-resolver': 2.0.19 + '@smithy/fetch-http-handler': 2.2.7 + '@smithy/hash-node': 2.0.16 + '@smithy/invalid-dependency': 2.0.14 + '@smithy/middleware-content-length': 2.0.16 + '@smithy/middleware-endpoint': 2.2.1 + '@smithy/middleware-retry': 2.0.21 + '@smithy/middleware-serde': 2.0.14 + '@smithy/middleware-stack': 2.0.8 + '@smithy/node-config-provider': 2.1.6 + '@smithy/node-http-handler': 2.1.10 + '@smithy/protocol-http': 3.0.10 + '@smithy/smithy-client': 2.1.16 + '@smithy/types': 2.6.0 + '@smithy/url-parser': 2.0.14 + '@smithy/util-base64': 2.0.1 + '@smithy/util-body-length-browser': 2.0.0 + '@smithy/util-body-length-node': 2.1.0 + '@smithy/util-defaults-mode-browser': 2.0.20 + '@smithy/util-defaults-mode-node': 2.0.26 + '@smithy/util-endpoints': 1.0.5 + '@smithy/util-retry': 2.0.7 + '@smithy/util-utf8': 2.0.2 + fast-xml-parser: 4.2.5 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/core@3.451.0: + resolution: {integrity: sha512-SamWW2zHEf1ZKe3j1w0Piauryl8BQIlej0TBS18A4ACzhjhWXhCs13bO1S88LvPR5mBFXok3XOT6zPOnKDFktw==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/smithy-client': 2.1.16 + tslib: 2.6.2 + dev: false + + /@aws-sdk/credential-provider-env@3.451.0: + resolution: {integrity: sha512-9dAav7DcRgaF7xCJEQR5ER9ErXxnu/tdnVJ+UPmb1NPeIZdESv1A3lxFDEq1Fs8c4/lzAj9BpshGyJVIZwZDKg==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.451.0 + '@smithy/property-provider': 2.0.15 + '@smithy/types': 2.6.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/credential-provider-ini@3.451.0: + resolution: {integrity: sha512-TySt64Ci5/ZbqFw1F9Z0FIGvYx5JSC9e6gqDnizIYd8eMnn8wFRUscRrD7pIHKfrhvVKN5h0GdYovmMO/FMCBw==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/credential-provider-env': 3.451.0 + '@aws-sdk/credential-provider-process': 3.451.0 + '@aws-sdk/credential-provider-sso': 3.451.0 + '@aws-sdk/credential-provider-web-identity': 3.451.0 + '@aws-sdk/types': 3.451.0 + '@smithy/credential-provider-imds': 2.1.2 + '@smithy/property-provider': 2.0.15 + '@smithy/shared-ini-file-loader': 2.2.5 + '@smithy/types': 2.6.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/credential-provider-node@3.451.0: + resolution: {integrity: sha512-AEwM1WPyxUdKrKyUsKyFqqRFGU70e4qlDyrtBxJnSU9NRLZI8tfEZ67bN7fHSxBUBODgDXpMSlSvJiBLh5/3pw==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/credential-provider-env': 3.451.0 + '@aws-sdk/credential-provider-ini': 3.451.0 + '@aws-sdk/credential-provider-process': 3.451.0 + '@aws-sdk/credential-provider-sso': 3.451.0 + '@aws-sdk/credential-provider-web-identity': 3.451.0 + '@aws-sdk/types': 3.451.0 + '@smithy/credential-provider-imds': 2.1.2 + '@smithy/property-provider': 2.0.15 + '@smithy/shared-ini-file-loader': 2.2.5 + '@smithy/types': 2.6.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/credential-provider-process@3.451.0: + resolution: {integrity: sha512-HQywSdKeD5PErcLLnZfSyCJO+6T+ZyzF+Lm/QgscSC+CbSUSIPi//s15qhBRVely/3KBV6AywxwNH+5eYgt4lQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.451.0 + '@smithy/property-provider': 2.0.15 + '@smithy/shared-ini-file-loader': 2.2.5 + '@smithy/types': 2.6.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/credential-provider-sso@3.451.0: + resolution: {integrity: sha512-Usm/N51+unOt8ID4HnQzxIjUJDrkAQ1vyTOC0gSEEJ7h64NSSPGD5yhN7il5WcErtRd3EEtT1a8/GTC5TdBctg==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/client-sso': 3.451.0 + '@aws-sdk/token-providers': 3.451.0 + '@aws-sdk/types': 3.451.0 + '@smithy/property-provider': 2.0.15 + '@smithy/shared-ini-file-loader': 2.2.5 + '@smithy/types': 2.6.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/credential-provider-web-identity@3.451.0: + resolution: {integrity: sha512-Xtg3Qw65EfDjWNG7o2xD6sEmumPfsy3WDGjk2phEzVg8s7hcZGxf5wYwe6UY7RJvlEKrU0rFA+AMn6Hfj5oOzg==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.451.0 + '@smithy/property-provider': 2.0.15 + '@smithy/types': 2.6.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-bucket-endpoint@3.451.0: + resolution: {integrity: sha512-KWyZ1JGnYz2QbHuJtYTP1BVnMOfVopR8rP8dTinVb/JR5HfAYz4imICJlJUbOYRjN7wpA3PrRI8dNRjrSBjWJg==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.451.0 + '@aws-sdk/util-arn-parser': 3.310.0 + '@smithy/node-config-provider': 2.1.6 + '@smithy/protocol-http': 3.0.10 + '@smithy/types': 2.6.0 + '@smithy/util-config-provider': 2.0.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-expect-continue@3.451.0: + resolution: {integrity: sha512-vwG8o2Uk6biLDlOZnqXemsO4dS2HvrprUdxyouwu6hlzLFskg8nL122butn19JqXJKgcVLuSSLzT+xwqBWy2Rg==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.451.0 + '@smithy/protocol-http': 3.0.10 + '@smithy/types': 2.6.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-flexible-checksums@3.451.0: + resolution: {integrity: sha512-eOkpcC2zgAvqs1w7Yp5nsk9LBIj6qLU5kaZuZEBOiFbNKIrTnPo6dQuhgvDcKHD6Y5W/cUjSBiFMs/ROb5aoug==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-crypto/crc32': 3.0.0 + '@aws-crypto/crc32c': 3.0.0 + '@aws-sdk/types': 3.451.0 + '@smithy/is-array-buffer': 2.0.0 + '@smithy/protocol-http': 3.0.10 + '@smithy/types': 2.6.0 + '@smithy/util-utf8': 2.0.2 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-host-header@3.451.0: + resolution: {integrity: sha512-j8a5jAfhWmsK99i2k8oR8zzQgXrsJtgrLxc3js6U+525mcZytoiDndkWTmD5fjJ1byU1U2E5TaPq+QJeDip05Q==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.451.0 + '@smithy/protocol-http': 3.0.10 + '@smithy/types': 2.6.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-location-constraint@3.451.0: + resolution: {integrity: sha512-R4U2G7mybP0BMiQBJWTcB47g49F4PSXTiCsvMDp5WOEhpWvGQuO1ZIhTxCl5s5lgTSne063Os8W6KSdK2yG2TQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.451.0 + '@smithy/types': 2.6.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-logger@3.451.0: + resolution: {integrity: sha512-0kHrYEyVeB2QBfP6TfbI240aRtatLZtcErJbhpiNUb+CQPgEL3crIjgVE8yYiJumZ7f0jyjo8HLPkwD1/2APaw==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.451.0 + '@smithy/types': 2.6.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-recursion-detection@3.451.0: + resolution: {integrity: sha512-J6jL6gJ7orjHGM70KDRcCP7so/J2SnkN4vZ9YRLTeeZY6zvBuHDjX8GCIgSqPn/nXFXckZO8XSnA7u6+3TAT0w==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.451.0 + '@smithy/protocol-http': 3.0.10 + '@smithy/types': 2.6.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-sdk-s3@3.451.0: + resolution: {integrity: sha512-XF4Cw8HrYUwGLKOqKtWs6ss1WXoxvQUcgGLACGSqn9a0p51446NiS5671x7qJUsfBuygdKlIKcOc8pPr9a+5Ow==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.451.0 + '@aws-sdk/util-arn-parser': 3.310.0 + '@smithy/protocol-http': 3.0.10 + '@smithy/smithy-client': 2.1.16 + '@smithy/types': 2.6.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-sdk-sts@3.451.0: + resolution: {integrity: sha512-UJ6UfVUEgp0KIztxpAeelPXI5MLj9wUtUCqYeIMP7C1ZhoEMNm3G39VLkGN43dNhBf1LqjsV9jkKMZbVfYXuwg==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/middleware-signing': 3.451.0 + '@aws-sdk/types': 3.451.0 + '@smithy/types': 2.6.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-signing@3.451.0: + resolution: {integrity: sha512-s5ZlcIoLNg1Huj4Qp06iKniE8nJt/Pj1B/fjhWc6cCPCM7XJYUCejCnRh6C5ZJoBEYodjuwZBejPc1Wh3j+znA==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.451.0 + '@smithy/property-provider': 2.0.15 + '@smithy/protocol-http': 3.0.10 + '@smithy/signature-v4': 2.0.16 + '@smithy/types': 2.6.0 + '@smithy/util-middleware': 2.0.7 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-ssec@3.451.0: + resolution: {integrity: sha512-hDkeBUiRsvuDbvsPha0/uJHE680WDzjAOoE6ZnLBoWsw7ry+Bw1ULMj0sCmpBVrQ7Gpivi/6zbezhClVmt3ITw==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.451.0 + '@smithy/types': 2.6.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-user-agent@3.451.0: + resolution: {integrity: sha512-8NM/0JiKLNvT9wtAQVl1DFW0cEO7OvZyLSUBLNLTHqyvOZxKaZ8YFk7d8PL6l76LeUKRxq4NMxfZQlUIRe0eSA==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.451.0 + '@aws-sdk/util-endpoints': 3.451.0 + '@smithy/protocol-http': 3.0.10 + '@smithy/types': 2.6.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/region-config-resolver@3.451.0: + resolution: {integrity: sha512-3iMf4OwzrFb4tAAmoROXaiORUk2FvSejnHIw/XHvf/jjR4EqGGF95NZP/n/MeFZMizJWVssrwS412GmoEyoqhg==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/node-config-provider': 2.1.6 + '@smithy/types': 2.6.0 + '@smithy/util-config-provider': 2.0.0 + '@smithy/util-middleware': 2.0.7 + tslib: 2.6.2 + dev: false + + /@aws-sdk/signature-v4-multi-region@3.451.0: + resolution: {integrity: sha512-qQKY7/txeNUTLyRL3WxUWEwaZ5sf76EIZgu9kLaR96cAYSxwQi/qQB3ijbfD6u7sJIA8aROMxeYK0VmRsQg0CA==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.451.0 + '@smithy/protocol-http': 3.0.10 + '@smithy/signature-v4': 2.0.16 + '@smithy/types': 2.6.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/token-providers@3.451.0: + resolution: {integrity: sha512-ij1L5iUbn6CwxVOT1PG4NFjsrsKN9c4N1YEM0lkl6DwmaNOscjLKGSNyj9M118vSWsOs1ZDbTwtj++h0O/BWrQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/middleware-host-header': 3.451.0 + '@aws-sdk/middleware-logger': 3.451.0 + '@aws-sdk/middleware-recursion-detection': 3.451.0 + '@aws-sdk/middleware-user-agent': 3.451.0 + '@aws-sdk/region-config-resolver': 3.451.0 + '@aws-sdk/types': 3.451.0 + '@aws-sdk/util-endpoints': 3.451.0 + '@aws-sdk/util-user-agent-browser': 3.451.0 + '@aws-sdk/util-user-agent-node': 3.451.0 + '@smithy/config-resolver': 2.0.19 + '@smithy/fetch-http-handler': 2.2.7 + '@smithy/hash-node': 2.0.16 + '@smithy/invalid-dependency': 2.0.14 + '@smithy/middleware-content-length': 2.0.16 + '@smithy/middleware-endpoint': 2.2.1 + '@smithy/middleware-retry': 2.0.21 + '@smithy/middleware-serde': 2.0.14 + '@smithy/middleware-stack': 2.0.8 + '@smithy/node-config-provider': 2.1.6 + '@smithy/node-http-handler': 2.1.10 + '@smithy/property-provider': 2.0.15 + '@smithy/protocol-http': 3.0.10 + '@smithy/shared-ini-file-loader': 2.2.5 + '@smithy/smithy-client': 2.1.16 + '@smithy/types': 2.6.0 + '@smithy/url-parser': 2.0.14 + '@smithy/util-base64': 2.0.1 + '@smithy/util-body-length-browser': 2.0.0 + '@smithy/util-body-length-node': 2.1.0 + '@smithy/util-defaults-mode-browser': 2.0.20 + '@smithy/util-defaults-mode-node': 2.0.26 + '@smithy/util-endpoints': 1.0.5 + '@smithy/util-retry': 2.0.7 + '@smithy/util-utf8': 2.0.2 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/types@3.451.0: + resolution: {integrity: sha512-rhK+qeYwCIs+laJfWCcrYEjay2FR/9VABZJ2NRM89jV/fKqGVQR52E5DQqrI+oEIL5JHMhhnr4N4fyECMS35lw==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 2.6.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-arn-parser@3.310.0: + resolution: {integrity: sha512-jL8509owp/xB9+Or0pvn3Fe+b94qfklc2yPowZZIFAkFcCSIdkIglz18cPDWnYAcy9JGewpMS1COXKIUhZkJsA==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-endpoints@3.451.0: + resolution: {integrity: sha512-giqLGBTnRIcKkDqwU7+GQhKbtJ5Ku35cjGQIfMyOga6pwTBUbaK0xW1Sdd8sBQ1GhApscnChzI9o/R9x0368vw==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.451.0 + '@smithy/util-endpoints': 1.0.5 + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-locate-window@3.310.0: + resolution: {integrity: sha512-qo2t/vBTnoXpjKxlsC2e1gBrRm80M3bId27r0BRB2VniSSe7bL1mmzM+/HFtujm0iAxtPM+aLEflLJlJeDPg0w==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-user-agent-browser@3.451.0: + resolution: {integrity: sha512-Ws5mG3J0TQifH7OTcMrCTexo7HeSAc3cBgjfhS/ofzPUzVCtsyg0G7I6T7wl7vJJETix2Kst2cpOsxygPgPD9w==} + dependencies: + '@aws-sdk/types': 3.451.0 + '@smithy/types': 2.6.0 + bowser: 2.11.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-user-agent-node@3.451.0: + resolution: {integrity: sha512-TBzm6P+ql4mkGFAjPlO1CI+w3yUT+NulaiALjl/jNX/nnUp6HsJsVxJf4nVFQTG5KRV0iqMypcs7I3KIhH+LmA==} + engines: {node: '>=14.0.0'} + peerDependencies: + aws-crt: '>=1.0.0' + peerDependenciesMeta: + aws-crt: + optional: true + dependencies: + '@aws-sdk/types': 3.451.0 + '@smithy/node-config-provider': 2.1.6 + '@smithy/types': 2.6.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-utf8-browser@3.259.0: + resolution: {integrity: sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==} + dependencies: + tslib: 2.6.2 + dev: false + + /@aws-sdk/xml-builder@3.310.0: + resolution: {integrity: sha512-TqELu4mOuSIKQCqj63fGVs86Yh+vBx5nHRpWKNUNhB2nPTpfbziTs5c1X358be3peVWA4wPxW7Nt53KIg1tnNw==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.2 + dev: false + /@babel/code-frame@7.22.13: resolution: {integrity: sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==} engines: {node: '>=6.9.0'} @@ -972,6 +1557,444 @@ packages: '@sinonjs/commons': 3.0.0 dev: false + /@smithy/abort-controller@2.0.14: + resolution: {integrity: sha512-zXtteuYLWbSXnzI3O6xq3FYvigYZFW8mdytGibfarLL2lxHto9L3ILtGVnVGmFZa7SDh62l39EnU5hesLN87Fw==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 2.6.0 + tslib: 2.6.2 + dev: false + + /@smithy/chunked-blob-reader-native@2.0.1: + resolution: {integrity: sha512-N2oCZRglhWKm7iMBu7S6wDzXirjAofi7tAd26cxmgibRYOBS4D3hGfmkwCpHdASZzwZDD8rluh0Rcqw1JeZDRw==} + dependencies: + '@smithy/util-base64': 2.0.1 + tslib: 2.6.2 + dev: false + + /@smithy/chunked-blob-reader@2.0.0: + resolution: {integrity: sha512-k+J4GHJsMSAIQPChGBrjEmGS+WbPonCXesoqP9fynIqjn7rdOThdH8FAeCmokP9mxTYKQAKoHCLPzNlm6gh7Wg==} + dependencies: + tslib: 2.6.2 + dev: false + + /@smithy/config-resolver@2.0.19: + resolution: {integrity: sha512-JsghnQ5zjWmjEVY8TFOulLdEOCj09SjRLugrHlkPZTIBBm7PQitCFVLThbsKPZQOP7N3ME1DU1nKUc1UaVnBog==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/node-config-provider': 2.1.6 + '@smithy/types': 2.6.0 + '@smithy/util-config-provider': 2.0.0 + '@smithy/util-middleware': 2.0.7 + tslib: 2.6.2 + dev: false + + /@smithy/credential-provider-imds@2.1.2: + resolution: {integrity: sha512-Y62jBWdoLPSYjr9fFvJf+KwTa1EunjVr6NryTEWCnwIY93OJxwV4t0qxjwdPl/XMsUkq79ppNJSEQN6Ohnhxjw==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/node-config-provider': 2.1.6 + '@smithy/property-provider': 2.0.15 + '@smithy/types': 2.6.0 + '@smithy/url-parser': 2.0.14 + tslib: 2.6.2 + dev: false + + /@smithy/eventstream-codec@2.0.14: + resolution: {integrity: sha512-g/OU/MeWGfHDygoXgMWfG/Xb0QqDnAGcM9t2FRrVAhleXYRddGOEnfanR5cmHgB9ue52MJsyorqFjckzXsylaA==} + dependencies: + '@aws-crypto/crc32': 3.0.0 + '@smithy/types': 2.6.0 + '@smithy/util-hex-encoding': 2.0.0 + tslib: 2.6.2 + dev: false + + /@smithy/eventstream-serde-browser@2.0.14: + resolution: {integrity: sha512-41wmYE9smDGJi1ZXp+LogH6BR7MkSsQD91wneIFISF/mupKULvoOJUkv/Nf0NMRxWlM3Bf1Vvi9FlR2oV4KU8Q==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/eventstream-serde-universal': 2.0.14 + '@smithy/types': 2.6.0 + tslib: 2.6.2 + dev: false + + /@smithy/eventstream-serde-config-resolver@2.0.14: + resolution: {integrity: sha512-43IyRIzQ82s+5X+t/3Ood00CcWtAXQdmUIUKMed2Qg9REPk8SVIHhpm3rwewLwg+3G2Nh8NOxXlEQu6DsPUcMw==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 2.6.0 + tslib: 2.6.2 + dev: false + + /@smithy/eventstream-serde-node@2.0.14: + resolution: {integrity: sha512-jVh9E2qAr6DxH5tWfCAl9HV6tI0pEQ3JVmu85JknDvYTC66djcjDdhctPV2EHuKWf2kjRiFJcMIn0eercW4THA==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/eventstream-serde-universal': 2.0.14 + '@smithy/types': 2.6.0 + tslib: 2.6.2 + dev: false + + /@smithy/eventstream-serde-universal@2.0.14: + resolution: {integrity: sha512-Ie35+AISNn1NmEjn5b2SchIE49pvKp4Q74bE9ME5RULWI1MgXyGkQUajWd5E6OBSr/sqGcs+rD3IjPErXnCm9g==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/eventstream-codec': 2.0.14 + '@smithy/types': 2.6.0 + tslib: 2.6.2 + dev: false + + /@smithy/fetch-http-handler@2.2.7: + resolution: {integrity: sha512-iSDBjxuH9TgrtMYAr7j5evjvkvgwLY3y+9D547uep+JNkZ1ZT+BaeU20j6I/bO/i26ilCWFImrlXTPsfQtZdIQ==} + dependencies: + '@smithy/protocol-http': 3.0.10 + '@smithy/querystring-builder': 2.0.14 + '@smithy/types': 2.6.0 + '@smithy/util-base64': 2.0.1 + tslib: 2.6.2 + dev: false + + /@smithy/hash-blob-browser@2.0.15: + resolution: {integrity: sha512-HX/7GIyPUT/HDWVYe2HYQu0iRnSYpF4uZVNhAhZsObPRawk5Mv0PbyluBgIFI2DDCCKgL/tloCYYwycff1GtQg==} + dependencies: + '@smithy/chunked-blob-reader': 2.0.0 + '@smithy/chunked-blob-reader-native': 2.0.1 + '@smithy/types': 2.6.0 + tslib: 2.6.2 + dev: false + + /@smithy/hash-node@2.0.16: + resolution: {integrity: sha512-Wbi9A0PacMYUOwjAulQP90Wl3mQ6NDwnyrZQzFjDz+UzjXOSyQMgBrTkUBz+pVoYVlX3DUu24gWMZBcit+wOGg==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 2.6.0 + '@smithy/util-buffer-from': 2.0.0 + '@smithy/util-utf8': 2.0.2 + tslib: 2.6.2 + dev: false + + /@smithy/hash-stream-node@2.0.16: + resolution: {integrity: sha512-4x24GFdeWos1Z49MC5sYdM1j+z32zcUr6oWM9Ggm3WudFAcRIcbG9uDQ1XgJ0Kl+ZTjpqLKniG0iuWvQb2Ud1A==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 2.6.0 + '@smithy/util-utf8': 2.0.2 + tslib: 2.6.2 + dev: false + + /@smithy/invalid-dependency@2.0.14: + resolution: {integrity: sha512-d8ohpwZo9RzTpGlAfsWtfm1SHBSU7+N4iuZ6MzR10xDTujJJWtmXYHK1uzcr7rggbpUTaWyHpPFgnf91q0EFqQ==} + dependencies: + '@smithy/types': 2.6.0 + tslib: 2.6.2 + dev: false + + /@smithy/is-array-buffer@2.0.0: + resolution: {integrity: sha512-z3PjFjMyZNI98JFRJi/U0nGoLWMSJlDjAW4QUX2WNZLas5C0CmVV6LJ01JI0k90l7FvpmixjWxPFmENSClQ7ug==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.2 + dev: false + + /@smithy/md5-js@2.0.16: + resolution: {integrity: sha512-YhWt9aKl+EMSNXyUTUo7I01WHf3HcCkPu/Hl2QmTNwrHT49eWaY7hptAMaERZuHFH0V5xHgPKgKZo2I93DFtgQ==} + dependencies: + '@smithy/types': 2.6.0 + '@smithy/util-utf8': 2.0.2 + tslib: 2.6.2 + dev: false + + /@smithy/middleware-content-length@2.0.16: + resolution: {integrity: sha512-9ddDia3pp1d3XzLXKcm7QebGxLq9iwKf+J1LapvlSOhpF8EM9SjMeSrMOOFgG+2TfW5K3+qz4IAJYYm7INYCng==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/protocol-http': 3.0.10 + '@smithy/types': 2.6.0 + tslib: 2.6.2 + dev: false + + /@smithy/middleware-endpoint@2.2.1: + resolution: {integrity: sha512-dVDS7HNJl/wb0lpByXor6whqDbb1YlLoaoWYoelyYzLHioXOE7y/0iDwJWtDcN36/tVCw9EPBFZ3aans84jLpg==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/middleware-serde': 2.0.14 + '@smithy/node-config-provider': 2.1.6 + '@smithy/shared-ini-file-loader': 2.2.5 + '@smithy/types': 2.6.0 + '@smithy/url-parser': 2.0.14 + '@smithy/util-middleware': 2.0.7 + tslib: 2.6.2 + dev: false + + /@smithy/middleware-retry@2.0.21: + resolution: {integrity: sha512-EZS1EXv1k6IJX6hyu/0yNQuPcPaXwG8SWljQHYueyRbOxmqYgoWMWPtfZj0xRRQ4YtLawQSpBgAeiJltq8/MPw==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/node-config-provider': 2.1.6 + '@smithy/protocol-http': 3.0.10 + '@smithy/service-error-classification': 2.0.7 + '@smithy/types': 2.6.0 + '@smithy/util-middleware': 2.0.7 + '@smithy/util-retry': 2.0.7 + tslib: 2.6.2 + uuid: 8.3.2 + dev: false + + /@smithy/middleware-serde@2.0.14: + resolution: {integrity: sha512-hFi3FqoYWDntCYA2IGY6gJ6FKjq2gye+1tfxF2HnIJB5uW8y2DhpRNBSUMoqP+qvYzRqZ6ntv4kgbG+o3pX57g==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 2.6.0 + tslib: 2.6.2 + dev: false + + /@smithy/middleware-stack@2.0.8: + resolution: {integrity: sha512-7/N59j0zWqVEKExJcA14MrLDZ/IeN+d6nbkN8ucs+eURyaDUXWYlZrQmMOd/TyptcQv0+RDlgag/zSTTV62y/Q==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 2.6.0 + tslib: 2.6.2 + dev: false + + /@smithy/node-config-provider@2.1.6: + resolution: {integrity: sha512-HLqTs6O78m3M3z1cPLFxddxhEPv5MkVatfPuxoVO3A+cHZanNd/H5I6btcdHy6N2CB1MJ/lihJC92h30SESsBA==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/property-provider': 2.0.15 + '@smithy/shared-ini-file-loader': 2.2.5 + '@smithy/types': 2.6.0 + tslib: 2.6.2 + dev: false + + /@smithy/node-http-handler@2.1.10: + resolution: {integrity: sha512-lkALAwtN6odygIM4nB8aHDahINM6WXXjNrZmWQAh0RSossySRT2qa31cFv0ZBuAYVWeprskRk13AFvvLmf1WLw==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/abort-controller': 2.0.14 + '@smithy/protocol-http': 3.0.10 + '@smithy/querystring-builder': 2.0.14 + '@smithy/types': 2.6.0 + tslib: 2.6.2 + dev: false + + /@smithy/property-provider@2.0.15: + resolution: {integrity: sha512-YbRFBn8oiiC3o1Kn3a4KjGa6k47rCM9++5W9cWqYn9WnkyH+hBWgfJAckuxpyA2Hq6Ys4eFrWzXq6fqHEw7iew==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 2.6.0 + tslib: 2.6.2 + dev: false + + /@smithy/protocol-http@3.0.10: + resolution: {integrity: sha512-6+tjNk7rXW7YTeGo9qwxXj/2BFpJTe37kTj3EnZCoX/nH+NP/WLA7O83fz8XhkGqsaAhLUPo/bB12vvd47nsmg==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 2.6.0 + tslib: 2.6.2 + dev: false + + /@smithy/querystring-builder@2.0.14: + resolution: {integrity: sha512-lQ4pm9vTv9nIhl5jt6uVMPludr6syE2FyJmHsIJJuOD7QPIJnrf9HhUGf1iHh9KJ4CUv21tpOU3X6s0rB6uJ0g==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 2.6.0 + '@smithy/util-uri-escape': 2.0.0 + tslib: 2.6.2 + dev: false + + /@smithy/querystring-parser@2.0.14: + resolution: {integrity: sha512-+cbtXWI9tNtQjlgQg3CA+pvL3zKTAxPnG3Pj6MP89CR3vi3QMmD0SOWoq84tqZDnJCxlsusbgIXk1ngMReXo+A==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 2.6.0 + tslib: 2.6.2 + dev: false + + /@smithy/service-error-classification@2.0.7: + resolution: {integrity: sha512-LLxgW12qGz8doYto15kZ4x1rHjtXl0BnCG6T6Wb8z2DI4PT9cJfOSvzbuLzy7+5I24PAepKgFeWHRd9GYy3Z9w==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 2.6.0 + dev: false + + /@smithy/shared-ini-file-loader@2.2.5: + resolution: {integrity: sha512-LHA68Iu7SmNwfAVe8egmjDCy648/7iJR/fK1UnVw+iAOUJoEYhX2DLgVd5pWllqdDiRbQQzgaHLcRokM+UFR1w==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 2.6.0 + tslib: 2.6.2 + dev: false + + /@smithy/signature-v4@2.0.16: + resolution: {integrity: sha512-ilLY85xS2kZZzTb83diQKYLIYALvart0KnBaKnIRnMBHAGEio5aHSlANQoxVn0VsonwmQ3CnWhnCT0sERD8uTg==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/eventstream-codec': 2.0.14 + '@smithy/is-array-buffer': 2.0.0 + '@smithy/types': 2.6.0 + '@smithy/util-hex-encoding': 2.0.0 + '@smithy/util-middleware': 2.0.7 + '@smithy/util-uri-escape': 2.0.0 + '@smithy/util-utf8': 2.0.2 + tslib: 2.6.2 + dev: false + + /@smithy/smithy-client@2.1.16: + resolution: {integrity: sha512-Lw67+yQSpLl4YkDLUzI2KgS8TXclXmbzSeOJUmRFS4ueT56B4pw3RZRF/SRzvgyxM/HxgkUan8oSHXCujPDafQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/middleware-stack': 2.0.8 + '@smithy/types': 2.6.0 + '@smithy/util-stream': 2.0.21 + tslib: 2.6.2 + dev: false + + /@smithy/types@2.6.0: + resolution: {integrity: sha512-PgqxJq2IcdMF9iAasxcqZqqoOXBHufEfmbEUdN1pmJrJltT42b0Sc8UiYSWWzKkciIp9/mZDpzYi4qYG1qqg6g==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.2 + dev: false + + /@smithy/url-parser@2.0.14: + resolution: {integrity: sha512-kbu17Y1AFXi5lNlySdDj7ZzmvupyWKCX/0jNZ8ffquRyGdbDZb+eBh0QnWqsSmnZa/ctyWaTf7n4l/pXLExrnw==} + dependencies: + '@smithy/querystring-parser': 2.0.14 + '@smithy/types': 2.6.0 + tslib: 2.6.2 + dev: false + + /@smithy/util-base64@2.0.1: + resolution: {integrity: sha512-DlI6XFYDMsIVN+GH9JtcRp3j02JEVuWIn/QOZisVzpIAprdsxGveFed0bjbMRCqmIFe8uetn5rxzNrBtIGrPIQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/util-buffer-from': 2.0.0 + tslib: 2.6.2 + dev: false + + /@smithy/util-body-length-browser@2.0.0: + resolution: {integrity: sha512-JdDuS4ircJt+FDnaQj88TzZY3+njZ6O+D3uakS32f2VNnDo3vyEuNdBOh/oFd8Df1zSZOuH1HEChk2AOYDezZg==} + dependencies: + tslib: 2.6.2 + dev: false + + /@smithy/util-body-length-node@2.1.0: + resolution: {integrity: sha512-/li0/kj/y3fQ3vyzn36NTLGmUwAICb7Jbe/CsWCktW363gh1MOcpEcSO3mJ344Gv2dqz8YJCLQpb6hju/0qOWw==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.2 + dev: false + + /@smithy/util-buffer-from@2.0.0: + resolution: {integrity: sha512-/YNnLoHsR+4W4Vf2wL5lGv0ksg8Bmk3GEGxn2vEQt52AQaPSCuaO5PM5VM7lP1K9qHRKHwrPGktqVoAHKWHxzw==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/is-array-buffer': 2.0.0 + tslib: 2.6.2 + dev: false + + /@smithy/util-config-provider@2.0.0: + resolution: {integrity: sha512-xCQ6UapcIWKxXHEU4Mcs2s7LcFQRiU3XEluM2WcCjjBtQkUN71Tb+ydGmJFPxMUrW/GWMgQEEGipLym4XG0jZg==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.2 + dev: false + + /@smithy/util-defaults-mode-browser@2.0.20: + resolution: {integrity: sha512-QJtnbTIl0/BbEASkx1MUFf6EaoWqWW1/IM90N++8NNscePvPf77GheYfpoPis6CBQawUWq8QepTP2QUSAdrVkw==} + engines: {node: '>= 10.0.0'} + dependencies: + '@smithy/property-provider': 2.0.15 + '@smithy/smithy-client': 2.1.16 + '@smithy/types': 2.6.0 + bowser: 2.11.0 + tslib: 2.6.2 + dev: false + + /@smithy/util-defaults-mode-node@2.0.26: + resolution: {integrity: sha512-lGFPOFCHv1ql019oegYqa54BZH7HREw6EBqjDLbAr0wquMX0BDi2sg8TJ6Eq+JGLijkZbJB73m4+aK8OFAapMg==} + engines: {node: '>= 10.0.0'} + dependencies: + '@smithy/config-resolver': 2.0.19 + '@smithy/credential-provider-imds': 2.1.2 + '@smithy/node-config-provider': 2.1.6 + '@smithy/property-provider': 2.0.15 + '@smithy/smithy-client': 2.1.16 + '@smithy/types': 2.6.0 + tslib: 2.6.2 + dev: false + + /@smithy/util-endpoints@1.0.5: + resolution: {integrity: sha512-K7qNuCOD5K/90MjHvHm9kJldrfm40UxWYQxNEShMFxV/lCCCRIg8R4uu1PFAxRvPxNpIdcrh1uK6I1ISjDXZJw==} + engines: {node: '>= 14.0.0'} + dependencies: + '@smithy/node-config-provider': 2.1.6 + '@smithy/types': 2.6.0 + tslib: 2.6.2 + dev: false + + /@smithy/util-hex-encoding@2.0.0: + resolution: {integrity: sha512-c5xY+NUnFqG6d7HFh1IFfrm3mGl29lC+vF+geHv4ToiuJCBmIfzx6IeHLg+OgRdPFKDXIw6pvi+p3CsscaMcMA==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.2 + dev: false + + /@smithy/util-middleware@2.0.7: + resolution: {integrity: sha512-tRINOTlf1G9B0ECarFQAtTgMhpnrMPSa+5j4ZEwEawCLfTFTavk6757sxhE4RY5RMlD/I3x+DCS8ZUiR8ho9Pw==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 2.6.0 + tslib: 2.6.2 + dev: false + + /@smithy/util-retry@2.0.7: + resolution: {integrity: sha512-fIe5yARaF0+xVT1XKcrdnHKTJ1Vc4+3e3tLDjCuIcE9b6fkBzzGFY7AFiX4M+vj6yM98DrwkuZeHf7/hmtVp0Q==} + engines: {node: '>= 14.0.0'} + dependencies: + '@smithy/service-error-classification': 2.0.7 + '@smithy/types': 2.6.0 + tslib: 2.6.2 + dev: false + + /@smithy/util-stream@2.0.21: + resolution: {integrity: sha512-0BUE16d7n1x7pi1YluXJdB33jOTyBChT0j/BlOkFa9uxfg6YqXieHxjHNuCdJRARa7AZEj32LLLEPJ1fSa4inA==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/fetch-http-handler': 2.2.7 + '@smithy/node-http-handler': 2.1.10 + '@smithy/types': 2.6.0 + '@smithy/util-base64': 2.0.1 + '@smithy/util-buffer-from': 2.0.0 + '@smithy/util-hex-encoding': 2.0.0 + '@smithy/util-utf8': 2.0.2 + tslib: 2.6.2 + dev: false + + /@smithy/util-uri-escape@2.0.0: + resolution: {integrity: sha512-ebkxsqinSdEooQduuk9CbKcI+wheijxEb3utGXkCoYQkJnwTnLbH1JXGimJtUkQwNQbsbuYwG2+aFVyZf5TLaw==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.2 + dev: false + + /@smithy/util-utf8@2.0.2: + resolution: {integrity: sha512-qOiVORSPm6Ce4/Yu6hbSgNHABLP2VMv8QOC3tTDNHHlWY19pPyc++fBTbZPtx6egPXi4HQxKDnMxVxpbtX2GoA==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/util-buffer-from': 2.0.0 + tslib: 2.6.2 + dev: false + + /@smithy/util-waiter@2.0.14: + resolution: {integrity: sha512-Q6gSz4GUNjNGhrfNg+2Mjy+7K4pEI3r82x1b/+3dSc03MQqobMiUrRVN/YK/4nHVagvBELCoXsiHAFQJNQ5BeA==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/abort-controller': 2.0.14 + '@smithy/types': 2.6.0 + tslib: 2.6.2 + dev: false + /@swc/helpers@0.5.2: resolution: {integrity: sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==} dependencies: @@ -1671,6 +2694,10 @@ packages: engines: {node: '>=8'} dev: false + /bowser@2.11.0: + resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==} + dev: false + /brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} dependencies: @@ -2628,6 +3655,13 @@ packages: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} dev: false + /fast-xml-parser@4.2.5: + resolution: {integrity: sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==} + hasBin: true + dependencies: + strnum: 1.0.5 + dev: false + /fastq@1.15.0: resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} dependencies: @@ -4977,6 +6011,10 @@ packages: engines: {node: '>=8'} dev: false + /strnum@1.0.5: + resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} + dev: false + /styled-jsx@5.1.1(@babel/core@7.23.3)(react@18.2.0): resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==} engines: {node: '>= 12.0.0'} @@ -5167,6 +6205,10 @@ packages: strip-bom: 3.0.0 dev: false + /tslib@1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + dev: false + /tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} dev: false @@ -5303,6 +6345,11 @@ packages: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} dev: false + /uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + dev: false + /v8-to-istanbul@9.1.3: resolution: {integrity: sha512-9lDD+EVI2fjFsMWXc6dy5JJzBsVTcQ2fVkfBvncZ6xJWG9wtBhOldG+mHkSL0+V1K/xgZz0JDO5UT5hFwHUghg==} engines: {node: '>=10.12.0'} diff --git a/src/cache/index.ts b/src/cache/index.ts index 9776b78e..5dfd5922 100644 --- a/src/cache/index.ts +++ b/src/cache/index.ts @@ -20,7 +20,7 @@ import { getUniqueFilmSimulations, getPhotosFilmSimulationDateRange, getPhotosFilmSimulationCount, -} from '@/services/postgres'; +} from '@/services/vercel-postgres'; import { parseCachedPhotoDates, parseCachedPhotosDates } from '@/photo'; import { getBlobPhotoUrls, getBlobUploadUrls } from '@/services/blob'; import type { Session } from 'next-auth'; diff --git a/src/photo/PhotoUpload.tsx b/src/photo/PhotoUpload.tsx index 83db79f4..b4afc705 100644 --- a/src/photo/PhotoUpload.tsx +++ b/src/photo/PhotoUpload.tsx @@ -56,7 +56,7 @@ export default function PhotoUpload({ blob, extension, ) - .then(({ url }) => { + .then(url => { if (isLastBlob) { // Refresh page to update upload list, // relevant to upload count in nav diff --git a/src/photo/actions.ts b/src/photo/actions.ts index 6cf42747..0097482f 100644 --- a/src/photo/actions.ts +++ b/src/photo/actions.ts @@ -7,7 +7,7 @@ import { sqlUpdatePhoto, sqlRenamePhotoTagGlobally, getPhoto, -} from '@/services/postgres'; +} from '@/services/vercel-postgres'; import { PhotoFormData, convertFormDataToPhotoDbInsert, @@ -16,7 +16,7 @@ import { import { redirect } from 'next/navigation'; import { convertUploadToPhoto, - deleteBlobPhoto, + deleteBlobUrl, } from '@/services/blob'; import { revalidateAdminPaths, @@ -52,7 +52,7 @@ export async function updatePhotoAction(formData: FormData) { export async function deletePhotoAction(formData: FormData) { await Promise.all([ - deleteBlobPhoto(formData.get('url') as string), + deleteBlobUrl(formData.get('url') as string), sqlDeletePhoto(formData.get('id') as string), ]); @@ -80,7 +80,7 @@ export async function renamePhotoTagGloballyAction(formData: FormData) { } export async function deleteBlobPhotoAction(formData: FormData) { - await deleteBlobPhoto(formData.get('url') as string); + await deleteBlobUrl(formData.get('url') as string); revalidateAdminPaths(); diff --git a/src/photo/server.ts b/src/photo/server.ts index 62d08f33..7971c917 100644 --- a/src/photo/server.ts +++ b/src/photo/server.ts @@ -1,4 +1,7 @@ -import { getExtensionFromBlobUrl, getIdFromBlobUrl } from '@/services/blob'; +import { + getExtensionFromBlobUrl, + getIdFromBlobUrl, +} from '@/services/blob'; import { convertExifToFormData } from '@/photo/form'; import { getFujifilmSimulationFromMakerNote, diff --git a/src/services/blob.ts b/src/services/blob.ts deleted file mode 100644 index 1e53f12e..00000000 --- a/src/services/blob.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { PATH_ADMIN_UPLOAD_BLOB } from '@/site/paths'; -import { copy, del, list } from '@vercel/blob'; -import { upload } from '@vercel/blob/client'; - -const STORE_ID = process.env.BLOB_READ_WRITE_TOKEN?.match( - /^vercel_blob_rw_([a-z0-9]+)_[a-z0-9]+$/i, -)?.[1].toLowerCase(); - -export const BLOB_BASE_URL = - `https://${STORE_ID}.public.blob.vercel-storage.com`; - -const PREFIX_UPLOAD = 'upload'; -const PREFIX_PHOTO = 'photo'; - -const REGEX_UPLOAD_PATH = new RegExp( - `(?:${PREFIX_UPLOAD})\.[a-z]{1,4}`, - 'i', -); - -const REGEX_UPLOAD_ID = new RegExp( - `.${PREFIX_UPLOAD}-([a-z0-9]+)\.[a-z]{1,4}$`, - 'i', -); - -export const pathForBlobUrl = (url: string) => - url.replace(`${BLOB_BASE_URL}/`, ''); - -export const getExtensionFromBlobUrl = (url: string) => - url.match(/.([a-z]{1,4})$/i)?.[1]; - -export const getIdFromBlobUrl = (url: string) => - url.match(REGEX_UPLOAD_ID)?.[1]; - -export const isUploadPathnameValid = (pathname?: string) => - pathname?.match(REGEX_UPLOAD_PATH); - -export const uploadPhotoFromClient = async ( - file: File | Blob, - extension = 'jpg', -) => - upload( - `${PREFIX_UPLOAD}.${extension}`, - file, - { - access: 'public', - handleUploadUrl: PATH_ADMIN_UPLOAD_BLOB, - }, - ); - -export const convertUploadToPhoto = async ( - uploadUrl: string, - photoId?: string, -) => { - const fileName = photoId ? `${PREFIX_PHOTO}-${photoId}` : `${PREFIX_PHOTO}`; - const fileExtension = getExtensionFromBlobUrl(uploadUrl) ?? 'jpg'; - const photoUrl = `${fileName}.${fileExtension ?? 'jpg'}`; - - const { url } = await copy( - uploadUrl, - photoUrl, - { - access: 'public', - ...photoId && { addRandomSuffix: false }, - } - ); - - if (url) { - await del(uploadUrl); - } - - return url; -}; - -export const deleteBlobPhoto = (url: string) => del(url); - -export const getBlobUploadUrls = () => - list({ prefix: `${PREFIX_UPLOAD}-` }) - .then(({ blobs }) => blobs.map(({ url }) => url)); - -export const getBlobPhotoUrls = () => - list({ prefix: `${PREFIX_PHOTO}-` }) - .then(({ blobs }) => blobs.map(({ url }) => url)); diff --git a/src/services/blob/aws-s3.ts b/src/services/blob/aws-s3.ts new file mode 100644 index 00000000..875a613e --- /dev/null +++ b/src/services/blob/aws-s3.ts @@ -0,0 +1,93 @@ +import { generateNanoid } from '@/utility/nanoid'; +import { + S3Client, + CopyObjectCommand, + DeleteObjectCommand, + ListObjectsCommand, + PutObjectCommand, +} from '@aws-sdk/client-s3'; + +const S3_BUCKET = process.env.NEXT_PUBLIC_S3_BUCKET ?? ''; +const S3_REGION = process.env.NEXT_PUBLIC_S3_REGION ?? ''; +const S3_UPLOAD_ACCESS_KEY = + process.env.NEXT_PUBLIC_S3_UPLOAD_ACCESS_KEY ?? ''; +const S3_UPLOAD_SECRET_ACCESS_KEY = + process.env.NEXT_PUBLIC_S3_UPLOAD_SECRET_ACCESS_KEY ?? ''; +const S3_ADMIN_ACCESS_KEY = + process.env.S3_ADMIN_ACCESS_KEY; +const S3_ADMIN_SECRET_ACCESS_KEY = + process.env.S3_ADMIN_SECRET_ACCESS_KEY; + +export const HAS_AWS_S3_STORAGE = + S3_BUCKET.length > 0 && + S3_REGION.length > 0 && + S3_UPLOAD_ACCESS_KEY.length > 0 && + S3_UPLOAD_SECRET_ACCESS_KEY.length > 0; + +const client = new S3Client({ + region: S3_REGION, + credentials: { + // Fallback on upload credentials if admin credentials are not available + accessKeyId: S3_ADMIN_ACCESS_KEY ?? S3_UPLOAD_ACCESS_KEY, + secretAccessKey: S3_ADMIN_SECRET_ACCESS_KEY ?? S3_UPLOAD_SECRET_ACCESS_KEY, + }, +}); + +export const AWS_S3_BASE_URL = + `https://${S3_BUCKET}.s3.${S3_REGION}.amazonaws.com`; + +export const isUrlFromAwsS3 = (url: string) => + url.startsWith(AWS_S3_BASE_URL); + +const urlForKey = (key?: string) => `${AWS_S3_BASE_URL}/${key}`; + +export const awsS3UploadFromClient = async ( + file: File | Blob, + fileName: string, + extension: string, + addRandomSuffix?: boolean, +) => { + const Key = addRandomSuffix + ? `${fileName}-${generateNanoid()}.${extension}` + : `${fileName}.${extension}`; + return client.send(new PutObjectCommand({ + Bucket: S3_BUCKET, + Key, + Body: file, + ACL: 'public-read', + })) + .then(() => urlForKey(Key)); +}; + +export const awsS3Copy = async ( + fileNameSource: string, + fileNameDestination: string, + addRandomSuffix?: boolean, +) => { + const name = fileNameSource.split('.')[0]; + const extension = fileNameSource.split('.')[1]; + const Key = addRandomSuffix + ? `${name}-${generateNanoid()}.${extension}` + : fileNameDestination; + return client.send(new CopyObjectCommand({ + Bucket: S3_BUCKET, + CopySource: fileNameSource, + Key, + ACL: 'public-read', + })) + .then(() => urlForKey(fileNameDestination)); +}; + +export const awsS3Delete = async (Key: string) => { + client.send(new DeleteObjectCommand({ + Bucket: S3_BUCKET, + Key, + })); +}; + +export const awsS3List = async (Prefix: string) => + client.send(new ListObjectsCommand({ + Bucket: S3_BUCKET, + Prefix, + })) + .then((data) => data.Contents?.map(({ Key }) => urlForKey(Key)) ?? []); diff --git a/src/services/blob/index.ts b/src/services/blob/index.ts new file mode 100644 index 00000000..f6849731 --- /dev/null +++ b/src/services/blob/index.ts @@ -0,0 +1,86 @@ +import { + VERCEL_BLOB_BASE_URL, + vercelBlobCopy, + vercelBlobDelete, + vercelBlobList, + vercelBlobUploadFromClient, +} from './vercel-blob'; +import { + AWS_S3_BASE_URL, + HAS_AWS_S3_STORAGE, + awsS3Copy, + awsS3Delete, + awsS3List, + awsS3UploadFromClient, + isUrlFromAwsS3, +} from './aws-s3'; + +const PREFIX_UPLOAD = 'upload'; +const PREFIX_PHOTO = 'photo'; +const BLOB_BASE_URL = AWS_S3_BASE_URL ?? VERCEL_BLOB_BASE_URL; + +const REGEX_UPLOAD_PATH = new RegExp( + `(?:${PREFIX_UPLOAD})\.[a-z]{1,4}`, + 'i', +); + +const REGEX_UPLOAD_ID = new RegExp( + `.${PREFIX_UPLOAD}-([a-z0-9]+)\.[a-z]{1,4}$`, + 'i', +); + +export const pathForBlobUrl = (url: string) => + url.replace(`${BLOB_BASE_URL}/`, ''); + +export const getExtensionFromBlobUrl = (url: string) => + url.match(/.([a-z]{1,4})$/i)?.[1]; + +export const getIdFromBlobUrl = (url: string) => + url.match(REGEX_UPLOAD_ID)?.[1]; + +export const isUploadPathnameValid = (pathname?: string) => + pathname?.match(REGEX_UPLOAD_PATH); + +const getFileNameFromBlobUrl = (url: string) => + (new URL(url).pathname.match(/\/(.+)$/)?.[1]) ?? ''; + +export const uploadPhotoFromClient = async ( + file: File | Blob, + extension = 'jpg', +) => HAS_AWS_S3_STORAGE + ? awsS3UploadFromClient(file, PREFIX_UPLOAD, extension, true) + : vercelBlobUploadFromClient(file, `${PREFIX_UPLOAD}.${extension}`); + +export const convertUploadToPhoto = async ( + uploadUrl: string, + photoId?: string, +): Promise => { + const fileName = photoId ? `${PREFIX_PHOTO}-${photoId}` : `${PREFIX_PHOTO}`; + const fileExtension = getExtensionFromBlobUrl(uploadUrl); + const photoUrl = `${fileName}.${fileExtension ?? 'jpg'}`; + + const url = await (HAS_AWS_S3_STORAGE + ? awsS3Copy(uploadUrl, photoUrl, photoId === undefined) + : vercelBlobCopy(uploadUrl, photoUrl, photoId === undefined)); + + if (url) { + await (HAS_AWS_S3_STORAGE + ? awsS3Delete(getFileNameFromBlobUrl(uploadUrl)) + : vercelBlobDelete(uploadUrl)); + } + + return url; +}; + +export const deleteBlobUrl = (url: string) => + HAS_AWS_S3_STORAGE && isUrlFromAwsS3(url) + ? awsS3Delete(getFileNameFromBlobUrl(url)) + : vercelBlobDelete(url); + +export const getBlobUploadUrls = (): Promise => HAS_AWS_S3_STORAGE + ? awsS3List(`${PREFIX_UPLOAD}-`) + : vercelBlobList(`${PREFIX_UPLOAD}-`); + +export const getBlobPhotoUrls = (): Promise => HAS_AWS_S3_STORAGE + ? awsS3List(`${PREFIX_PHOTO}-`) + : vercelBlobList(`${PREFIX_PHOTO}-`); diff --git a/src/services/blob/vercel-blob.ts b/src/services/blob/vercel-blob.ts new file mode 100644 index 00000000..f14b2041 --- /dev/null +++ b/src/services/blob/vercel-blob.ts @@ -0,0 +1,44 @@ +import { PATH_ADMIN_UPLOAD_BLOB } from '@/site/paths'; +import { copy, del, list } from '@vercel/blob'; +import { upload } from '@vercel/blob/client'; + +const VERCEL_BLOB_STORE_ID = process.env.BLOB_READ_WRITE_TOKEN?.match( + /^vercel_blob_rw_([a-z0-9]+)_[a-z0-9]+$/i, +)?.[1].toLowerCase(); + +export const VERCEL_BLOB_BASE_URL = + `https://${VERCEL_BLOB_STORE_ID}.public.blob.vercel-storage.com`; + +export const vercelBlobUploadFromClient = async ( + file: File | Blob, + fileName: string, +) => + upload( + fileName, + file, + { + access: 'public', + handleUploadUrl: PATH_ADMIN_UPLOAD_BLOB, + }, + ) + .then(({ url }) => url); + +export const vercelBlobCopy = ( + fileNameSource: string, + fileNameDestination: string, + addRandomSuffix?: boolean, +): Promise => + copy( + fileNameSource, + fileNameDestination, + { + access: 'public', + addRandomSuffix, + }, + ) + .then(({ url }) => url); + +export const vercelBlobDelete = (fileName: string) => del(fileName); + +export const vercelBlobList = (prefix: string) => list({ prefix }) + .then(({ blobs }) => blobs.map(({ url }) => url)); diff --git a/src/services/postgres.ts b/src/services/vercel-postgres.ts similarity index 100% rename from src/services/postgres.ts rename to src/services/vercel-postgres.ts diff --git a/src/site/config.ts b/src/site/config.ts index 1a48c90d..cb3c36fa 100644 --- a/src/site/config.ts +++ b/src/site/config.ts @@ -36,10 +36,10 @@ const hasVercelBlob = (process.env.BLOB_READ_WRITE_TOKEN ?? '').length > 0; const hasAwsS3Storage = (process.env.NEXT_PUBLIC_S3_BUCKET ?? '').length > 0 && (process.env.NEXT_PUBLIC_S3_REGION ?? '').length > 0 && - (process.env.NEXT_PUBLIC_S3_UPLOAD_ACCESS_ID ?? '').length > 0 && - (process.env.NEXT_PUBLIC_S3_UPLOAD_SECRET ?? '').length > 0 && - (process.env.S3_ADMIN_ACCESS_ID ?? '').length > 0 && - (process.env.S3_ADMIN_ACCESS_SECRET ?? '').length > 0; + (process.env.NEXT_PUBLIC_S3_UPLOAD_ACCESS_KEY ?? '').length > 0 && + (process.env.NEXT_PUBLIC_S3_UPLOAD_SECRET_ACCESS_KEY ?? '').length > 0 && + (process.env.S3_ADMIN_ACCESS_KEY ?? '').length > 0 && + (process.env.S3_ADMIN_SECRET_ACCESS_KEY ?? '').length > 0; // SETTINGS