From fd0c9f92ef34ddfba401dd1c051c2476263e054b Mon Sep 17 00:00:00 2001 From: EGOIST Date: Sun, 15 May 2022 00:43:49 +0800 Subject: [PATCH] use cloudflare r2 --- README.md | 2 +- package.json | 2 +- pnpm-lock.yaml | 623 ++++++++++++++++- pnpm-workspace.yaml | 3 +- r2/README.md | 15 + r2/package.json | 16 + r2/pnpm-lock.yaml | 655 ++++++++++++++++++ r2/src/index.ts | 104 +++ r2/tsconfig.json | 105 +++ r2/wrangler.toml | 19 + web/.env.example | 9 +- web/package.json | 3 +- web/src/components/dashboard/AvatarForm.tsx | 34 +- web/src/components/ui/Avatar.tsx | 3 +- web/src/hooks/useSignedJwt.ts | 8 + web/src/hooks/useUploadFile.ts | 38 + web/src/lib/env.server.ts | 6 +- web/src/lib/env.ts | 2 +- web/src/lib/upload.client.ts | 15 - web/src/lib/upload.server.ts | 64 -- web/src/lib/user-contents.ts | 10 +- web/src/pages/_middleware.ts | 10 - web/src/pages/api/upload-image.ts | 33 - .../pages/dashboard/[subdomain]/editor.tsx | 43 +- web/src/router/site.ts | 2 +- web/src/router/user.ts | 35 +- web/types.d.ts | 8 +- 27 files changed, 1654 insertions(+), 213 deletions(-) create mode 100644 r2/README.md create mode 100644 r2/package.json create mode 100644 r2/pnpm-lock.yaml create mode 100644 r2/src/index.ts create mode 100644 r2/tsconfig.json create mode 100644 r2/wrangler.toml create mode 100644 web/src/hooks/useSignedJwt.ts create mode 100644 web/src/hooks/useUploadFile.ts delete mode 100644 web/src/lib/upload.client.ts delete mode 100644 web/src/lib/upload.server.ts delete mode 100644 web/src/pages/api/upload-image.ts diff --git a/README.md b/README.md index 2d17b4c8..33d07bcc 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Follow the steps to run it locally. -1. Copy `.env.example` to `.env` +1. Copy `web/.env.example` to `web/.env` 2. Run `postgres` locally, the easiest way on mac is `brew install postgresql`, you can also use docker 3. Install dependencies: `pnpm i` 4. Initialize the database by running `pnpm prisma db push` diff --git a/package.json b/package.json index 952e0f10..f69a28da 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "private": true, "name": "proselog-workspace", "scripts": { - "dev": "pnpm --filter web dev", + "dev": "pnpm -r dev", "db-generate": "pnpm --filter web db-generate" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 357c440f..26fdeea3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,6 +5,18 @@ importers: .: specifiers: {} + r2: + specifiers: + '@cloudflare/workers-types': ^3.10.0 + '@proselog/jwt': ^0.1.1 + typescript: ^4.6.4 + wrangler: 2.0.5 + devDependencies: + '@cloudflare/workers-types': 3.10.0 + '@proselog/jwt': 0.1.1 + typescript: 4.6.4 + wrangler: 2.0.5 + web: specifiers: '@aws-sdk/client-s3': ^3.81.0 @@ -16,6 +28,7 @@ importers: '@hapi/iron': ^6.0.0 '@headlessui/react': ^1.6.0 '@prisma/client': ^3.14.0 + '@proselog/jwt': ^0.1.1 '@trpc/client': ^9.23.2 '@trpc/next': ^9.23.2 '@trpc/react': ^9.23.2 @@ -76,6 +89,7 @@ importers: '@hapi/iron': 6.0.0 '@headlessui/react': 1.6.1_ef5jwxihqo6n7gxfmzogljlgcm '@prisma/client': 3.14.0_prisma@3.14.0 + '@proselog/jwt': 0.1.1 '@trpc/client': 9.23.2_@trpc+server@9.23.2 '@trpc/next': 9.23.2_ekhln2aut74ytaacni4ejzffly '@trpc/react': 9.23.2_ztktndqynogm35aelp247cafku @@ -1166,6 +1180,10 @@ packages: to-fast-properties: 2.0.0 dev: false + /@cloudflare/workers-types/3.10.0: + resolution: {integrity: sha512-gn+H5Ps9yV1VYz8FJ08bMfXLW8ubNblRF9Z+m+0ctNMyl3CdoZmUfH1pMTxYE8SMUJbLxuNvpdlZhwLDoGFXaw==} + dev: true + /@codemirror/autocomplete/0.20.0: resolution: {integrity: sha512-F6VOM8lImn5ApqxJcaWgdl7hhlw8B5yAfZJGlsQifcpNotkZOMND61mBFZ84OmSLWxtT8/smkSeLvJupKbjP9w==} dependencies: @@ -1257,6 +1275,24 @@ packages: w3c-keyname: 2.2.4 dev: false + /@esbuild-plugins/node-globals-polyfill/0.1.1_esbuild@0.14.34: + resolution: {integrity: sha512-MR0oAA+mlnJWrt1RQVQ+4VYuRJW/P2YmRTv1AsplObyvuBMnPHiizUF95HHYiSsMGLhyGtWufaq2XQg6+iurBg==} + peerDependencies: + esbuild: '*' + dependencies: + esbuild: 0.14.34 + dev: true + + /@esbuild-plugins/node-modules-polyfill/0.1.4_esbuild@0.14.34: + resolution: {integrity: sha512-uZbcXi0zbmKC/050p3gJnne5Qdzw8vkXIv+c2BW0Lsc1ji1SkrxbKPUy5Efr0blbTu1SL8w4eyfpnSdPg3G0Qg==} + peerDependencies: + esbuild: '*' + dependencies: + esbuild: 0.14.34 + escape-string-regexp: 4.0.0 + rollup-plugin-node-polyfills: 0.2.1 + dev: true + /@eslint/eslintrc/1.2.3: resolution: {integrity: sha512-uGo44hIwoLGNyduRpjdEpovcbMdd+Nv7amtmJxnKmI8xj6yd5LncmSwDa5NgX/41lIFJtkjD6YdVfgEzPfJ5UA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -1337,6 +1373,10 @@ packages: resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} dev: true + /@iarna/toml/2.2.5: + resolution: {integrity: sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==} + dev: true + /@jridgewell/gen-mapping/0.1.1: resolution: {integrity: sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==} engines: {node: '>=6.0.0'} @@ -1398,6 +1438,150 @@ packages: '@lezer/highlight': 0.16.0 dev: false + /@miniflare/cache/2.4.0: + resolution: {integrity: sha512-tMDXlUVlThgFubJmlxZoKmLK8kBxDmuMbVMt7csHpXegzkuo2TmIsDqBE/C3CRiJ5xeCQgpD6iZtKBu5Zn5fRA==} + engines: {node: '>=16.7'} + dependencies: + '@miniflare/core': 2.4.0 + '@miniflare/shared': 2.4.0 + http-cache-semantics: 4.1.0 + undici: 4.13.0 + dev: true + + /@miniflare/cli-parser/2.4.0: + resolution: {integrity: sha512-Xr5lO8f+oIr9r/b2dfo0on1p0MNN+pkwRHWoY5ACSnp9FaGnwm/g71DM7AajIQPIk0TpRsSVNDx8Ygj1LPT+sQ==} + engines: {node: '>=16.7'} + dependencies: + '@miniflare/shared': 2.4.0 + kleur: 4.1.4 + dev: true + + /@miniflare/core/2.4.0: + resolution: {integrity: sha512-vYl8xaWTFzxtkbzx3IkT4Py0OAFdfmFnVo627O1HKHWVGlkjVr8UKtxBpIR+f5pq/HCMzzqA1HM9FXO0dQfy3A==} + engines: {node: '>=16.7'} + dependencies: + '@iarna/toml': 2.2.5 + '@miniflare/shared': 2.4.0 + '@miniflare/watcher': 2.4.0 + busboy: 0.3.1 + dotenv: 10.0.0 + kleur: 4.1.4 + set-cookie-parser: 2.4.8 + undici: 4.13.0 + dev: true + + /@miniflare/durable-objects/2.4.0: + resolution: {integrity: sha512-VVLaUXXcAQcYE/3YmDLTacZf5OzR8bib6q1T9NqVb0uK5sLMQqyHvQdsG5rMqs7iyxfJxyZ0bL2OW9XGALOkoQ==} + engines: {node: '>=16.7'} + dependencies: + '@miniflare/core': 2.4.0 + '@miniflare/shared': 2.4.0 + '@miniflare/storage-memory': 2.4.0 + undici: 4.13.0 + dev: true + + /@miniflare/html-rewriter/2.4.0: + resolution: {integrity: sha512-ZG8819N7LelDD+8+Ss5FZpVyQQq/V2igod0qE68JK4he/w4/yn57Rk6Efb49y15HoHAXl2RpCCsnCyIow/Xjug==} + engines: {node: '>=16.7'} + dependencies: + '@miniflare/core': 2.4.0 + '@miniflare/shared': 2.4.0 + html-rewriter-wasm: 0.4.1 + undici: 4.13.0 + dev: true + + /@miniflare/http-server/2.4.0: + resolution: {integrity: sha512-r6Z/nqxE0oa1z63L95yvnG0PUeLRxZOeGS7ADxZMFKan4WD5lvYtSKDuDEm0lkbQshCOHQ3uXFr0cotOm8JoMQ==} + engines: {node: '>=16.7'} + dependencies: + '@miniflare/core': 2.4.0 + '@miniflare/shared': 2.4.0 + '@miniflare/web-sockets': 2.4.0 + kleur: 4.1.4 + selfsigned: 2.0.1 + undici: 4.13.0 + ws: 8.6.0 + youch: 2.2.2 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: true + + /@miniflare/kv/2.4.0: + resolution: {integrity: sha512-1UW7f1386xR6EDEXNZOR1TpFwQfRRSxUPqD6m/U0WprlsbM0cIYGz+AUeaVbkFf8lfE2MeXCUrjbWsLOvsnw3g==} + engines: {node: '>=16.7'} + dependencies: + '@miniflare/shared': 2.4.0 + dev: true + + /@miniflare/runner-vm/2.4.0: + resolution: {integrity: sha512-7sdwBYzXQTwYeR3tTvQ+vJfzc7BXwqR8AUPK9l5gvCtg+Geq9sMslr5SikIJpgcvbYqKDjvC9DQEPJ3sqr9cSQ==} + engines: {node: '>=16.7'} + dependencies: + '@miniflare/shared': 2.4.0 + dev: true + + /@miniflare/scheduler/2.4.0: + resolution: {integrity: sha512-dfMCXoAS8Y+3xABNxYju62I2xIBS54Op7ohCHoatvAM5RvualJUPICEMPZzX6/z29q5xPIeSLhLDhl/asAQ19w==} + engines: {node: '>=16.7'} + dependencies: + '@miniflare/core': 2.4.0 + '@miniflare/shared': 2.4.0 + cron-schedule: 3.0.6 + dev: true + + /@miniflare/shared/2.4.0: + resolution: {integrity: sha512-lPQFzBUVGNQ93gQ/dliToWnO0OqAgsD3/902Pd/IixVSRwRj3BTnYv2dHMUKZcODBPrhnbqZeqcPWdBLzEx8uw==} + engines: {node: '>=16.7'} + dependencies: + ignore: 5.2.0 + kleur: 4.1.4 + dev: true + + /@miniflare/sites/2.4.0: + resolution: {integrity: sha512-YZy/TujnR1lkBvCncDDQ8tsWsXRE4JJ4x9a0bKN/XnZh7r6OhDM0sw4BFcBQhT6Ukdtttam1O3FlJxnMitrDGg==} + engines: {node: '>=16.7'} + dependencies: + '@miniflare/kv': 2.4.0 + '@miniflare/shared': 2.4.0 + '@miniflare/storage-file': 2.4.0 + dev: true + + /@miniflare/storage-file/2.4.0: + resolution: {integrity: sha512-f1AUMz8xps/4VhNJMb8JeCevZFeU4pg2lIWmC11gG4xeq2nibTPBi6Qtx594Le7ZKij/tF6rRsoNfGau2Q5gdw==} + engines: {node: '>=16.7'} + dependencies: + '@miniflare/shared': 2.4.0 + '@miniflare/storage-memory': 2.4.0 + dev: true + + /@miniflare/storage-memory/2.4.0: + resolution: {integrity: sha512-mhWwgHhDNtEa7y1bYbdVucV0lqUmzagYXUSppAdSGS5JPyJMyw3HseqRNTk6gC/vKlvEYlFf3ugWcREGCedr9A==} + engines: {node: '>=16.7'} + dependencies: + '@miniflare/shared': 2.4.0 + dev: true + + /@miniflare/watcher/2.4.0: + resolution: {integrity: sha512-gDQRUxwOjmctvowyd4Hcdy3fjxz3ERKzirp6TvA3AWUohKZk3IhwGlaA8aCwbdP+ELYQlG5wK44AfLSGi956fg==} + engines: {node: '>=16.7'} + dependencies: + '@miniflare/shared': 2.4.0 + dev: true + + /@miniflare/web-sockets/2.4.0: + resolution: {integrity: sha512-cz/cN0GoQOXRLh80UmlcEJODPIw2ijKBK3PLRzvfTzqQ5avK6wp2M8Fj8C/5JIT6g7siwvBANyKXD3U3RpKGHQ==} + engines: {node: '>=16.7'} + dependencies: + '@miniflare/core': 2.4.0 + '@miniflare/shared': 2.4.0 + undici: 4.13.0 + ws: 8.6.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: true + /@next/env/12.1.6: resolution: {integrity: sha512-Te/OBDXFSodPU6jlXYPAXpmZr/AkG6DCATAxttQxqOWaq6eDFX25Db3dK0120GZrSZmv4QCe9KsZmJKDbWs4OA==} dev: false @@ -1537,6 +1721,9 @@ packages: fastq: 1.13.0 dev: true + /@panva/hkdf/1.0.2: + resolution: {integrity: sha512-MSAs9t3Go7GUkMhpKC44T58DJ5KGk2vBo+h1cqQeqlMfdGkxaVB78ZWpv9gYi/g2fa4sopag9gJsNvS8XGgWJA==} + /@prisma/client/3.14.0_prisma@3.14.0: resolution: {integrity: sha512-atb41UpgTR1MCst0VIbiHTMw8lmXnwUvE1KyUCAkq08+wJyjRE78Due+nSf+7uwqQn+fBFYVmoojtinhlLOSaA==} engines: {node: '>=12.6'} @@ -1560,6 +1747,13 @@ packages: requiresBuild: true dev: true + /@proselog/jwt/0.1.1: + resolution: {integrity: sha512-bmAHf70G5b29FDF8G14twg109DgN3Eu3jbd5+6FSdFEeWK8afs+X3zCNiePeNolPUfJuMiepxfTgWaFlBs1L1Q==} + dependencies: + '@panva/hkdf': 1.0.2 + jose: 4.8.1 + nanoid: 3.3.4 + /@rushstack/eslint-patch/1.1.3: resolution: {integrity: sha512-WiBSI6JBIhC6LRIsB2Kwh8DsGTlbBU+mLRxJmAe3LjHTdkDpwIbEOZgoXBbZilk/vlfjK8i6nKRAvIRn1XaIMw==} dev: true @@ -1701,6 +1895,10 @@ packages: resolution: {integrity: sha512-C3064MH72iEfeGCYEGCt7FCxXoAXaMPG0QPnstcxvPmbl54erpISu06d++FY37Smja64iWy5L8wOyHHBghWbJQ==} dev: true + /@types/stack-trace/0.0.29: + resolution: {integrity: sha512-TgfOX+mGY/NyNxJLIbDWrO9DjGoVSW9+aB8H2yy1fy32jsvxijhmyJI9fDFgvz3YP4lvJaq9DzdR/M1bOgVc9g==} + dev: true + /@types/ua-parser-js/0.7.36: resolution: {integrity: sha512-N1rW+njavs70y2cApeIw1vLMYXRwfBy+7trgavGuuTfOd7j1Yh7QTRc/yqsPl6ncokt72ZXuxEU0PiCp9bSwNQ==} dev: true @@ -1988,6 +2186,10 @@ packages: engines: {node: '>=8'} dev: true + /blake3-wasm/2.1.5: + resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==} + dev: true + /bluebird/3.7.2: resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} dev: false @@ -2034,12 +2236,15 @@ packages: picocolors: 1.0.0 dev: false + /buffer-from/1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + dev: true + /busboy/0.3.1: resolution: {integrity: sha512-y7tTxhGKXcyBxRKAni+awqx8uqaJKrSFSNFSeRG5CsWNdmy2BIK+6VGWEW7TZnIO/533mtMEA4rOevQV815YJw==} engines: {node: '>=4.5.0'} dependencies: dicer: 0.3.0 - dev: false /busboy/1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} @@ -2149,6 +2354,11 @@ packages: /concat-map/0.0.1: resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} + /cookie/0.4.2: + resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==} + engines: {node: '>= 0.6'} + dev: true + /cookie/0.5.0: resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} engines: {node: '>= 0.6'} @@ -2170,6 +2380,10 @@ packages: resolution: {integrity: sha512-+BO9wPPi+DWTDcNYhr/W90myha8ptzftZT+LwcmUbbok0rcP/fequmFYCw8NMoH7pkAZQzU78b3kYrlua5a9eA==} dev: false + /cron-schedule/3.0.6: + resolution: {integrity: sha512-izfGgKyzzIyLaeb1EtZ3KbglkS6AKp9cv7LxmiyoOu+fXfol1tQDC0Cof0enVZGNtudTHW+3lfuW9ZkLQss4Wg==} + dev: true + /cross-spawn/7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -2274,7 +2488,6 @@ packages: engines: {node: '>=4.5.0'} dependencies: streamsearch: 0.1.2 - dev: false /didyoumean/1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} @@ -2305,6 +2518,11 @@ packages: esutils: 2.0.3 dev: true + /dotenv/10.0.0: + resolution: {integrity: sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==} + engines: {node: '>=10'} + dev: true + /dotenv/16.0.0: resolution: {integrity: sha512-qD9WU0MPM4SWLPJy/r2Be+2WgQj8plChsyrCNQzW/0WjvcJQiKQJ9mH3ZgB3fxbUUxgc/11ZJ0Fi5KiimWGz2Q==} engines: {node: '>=12'} @@ -2369,6 +2587,214 @@ packages: is-date-object: 1.0.5 is-symbol: 1.0.4 + /esbuild-android-64/0.14.34: + resolution: {integrity: sha512-XfxcfJqmMYsT/LXqrptzFxmaR3GWzXHDLdFNIhm6S00zPaQF1TBBWm+9t0RZ6LRR7iwH57DPjaOeW20vMqI4Yw==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /esbuild-android-arm64/0.14.34: + resolution: {integrity: sha512-T02+NXTmSRL1Mc6puz+R9CB54rSPICkXKq6+tw8B6vxZFnCPzbJxgwIX4kcluz9p8nYBjF3+lSilTGWb7+Xgew==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /esbuild-darwin-64/0.14.34: + resolution: {integrity: sha512-pLRip2Bh4Ng7Bf6AMgCrSp3pPe/qZyf11h5Qo2mOfJqLWzSVjxrXW+CFRJfrOVP7TCnh/gmZSM2AFdCPB72vtw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /esbuild-darwin-arm64/0.14.34: + resolution: {integrity: sha512-vpidSJEBxx6lf1NWgXC+DCmGqesJuZ5Y8aQVVsaoO4i8tRXbXb0whChRvop/zd3nfNM4dIl5EXAky0knRX5I6w==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /esbuild-freebsd-64/0.14.34: + resolution: {integrity: sha512-m0HBjePhe0hAQJgtMRMNV9kMgIyV4/qSnzPx42kRMQBcPhgjAq1JRu4Il26czC+9FgpMbFkUktb07f/Lwnc6CA==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /esbuild-freebsd-arm64/0.14.34: + resolution: {integrity: sha512-cpRc2B94L1KvMPPYB4D6G39jLqpKlD3noAMY4/e86iXXXkhUYJJEtTuyNFTa9JRpWM0xCAp4mxjHjoIiLuoCLA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-32/0.14.34: + resolution: {integrity: sha512-8nQaEaoW7MH/K/RlozJa+lE1ejHIr8fuPIHhc513UebRav7HtXgQvxHQ6VZRUkWtep23M6dd7UqhwO1tMOfzQQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-64/0.14.34: + resolution: {integrity: sha512-Y3of4qQoLLlAgf042MlrY1P+7PnN9zWj8nVtw9XQG5hcLOZLz7IKpU35oeu7n4wvyaZHwvQqDJ93gRLqdJekcQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-arm/0.14.34: + resolution: {integrity: sha512-9lpq1NcJqssAF7alCO6zL3gvBVVt/lKw4oetUM7OgNnRX0OWpB+ZIO9FwCrSj/dMdmgDhPLf+119zB8QxSMmAg==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-arm64/0.14.34: + resolution: {integrity: sha512-IlWaGtj9ir7+Nrume1DGcyzBDlK8GcnJq0ANKwcI9pVw8tqr+6GD0eqyF9SF1mR8UmAp+odrx1H5NdR2cHdFHA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-mips64le/0.14.34: + resolution: {integrity: sha512-k3or+01Rska1AjUyNjA4buEwB51eyN/xPQAoOx1CjzAQC3l8rpjUDw55kXyL63O/1MUi4ISvtNtl8gLwdyEcxw==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-ppc64le/0.14.34: + resolution: {integrity: sha512-+qxb8M9FfM2CJaVU7GgYpJOHM1ngQOx+/VrtBjb4C8oVqaPcESCeg2anjl+HRZy8VpYc71q/iBYausPPbJ+Keg==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-riscv64/0.14.34: + resolution: {integrity: sha512-Y717ltBdQ5j5sZIHdy1DV9kieo0wMip0dCmVSTceowCPYSn1Cg33Kd6981+F/3b9FDMzNWldZFOBRILViENZSA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-s390x/0.14.34: + resolution: {integrity: sha512-bDDgYO4LhL4+zPs+WcBkXph+AQoPcQRTv18FzZS0WhjfH8TZx2QqlVPGhmhZ6WidrY+jKthUqO6UhGyIb4MpmA==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-netbsd-64/0.14.34: + resolution: {integrity: sha512-cfaFGXdRt0+vHsjNPyF0POM4BVSHPSbhLPe8mppDc7GDDxjIl08mV1Zou14oDWMp/XZMjYN1kWYRSfftiD0vvQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /esbuild-openbsd-64/0.14.34: + resolution: {integrity: sha512-vmy9DxXVnRiI14s8GKuYBtess+EVcDALkbpTqd5jw4XITutIzyB7n4x0Tj5utAkKsgZJB22lLWGekr0ABnSLow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /esbuild-sunos-64/0.14.34: + resolution: {integrity: sha512-eNPVatNET1F7tRMhii7goL/eptfxc0ALRjrj9SPFNqp0zmxrehBFD6BaP3R4LjMn6DbMO0jOAnTLFKr8NqcJAA==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /esbuild-windows-32/0.14.34: + resolution: {integrity: sha512-EFhpXyHEcnqWYe2rAHFd8dRw8wkrd9U+9oqcyoEL84GbanAYjiiIjBZsnR8kl0sCQ5w6bLpk7vCEIA2VS32Vcg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /esbuild-windows-64/0.14.34: + resolution: {integrity: sha512-a8fbl8Ky7PxNEjf1aJmtxdDZj32/hC7S1OcA2ckEpCJRTjiKslI9vAdPpSjrKIWhws4Galpaawy0nB7fjHYf5Q==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /esbuild-windows-arm64/0.14.34: + resolution: {integrity: sha512-EYvmKbSa2B3sPnpC28UEu9jBK5atGV4BaVRE7CYGUci2Hlz4AvtV/LML+TcDMT6gBgibnN2gcltWclab3UutMg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /esbuild/0.14.34: + resolution: {integrity: sha512-QIWdPT/gFF6hCaf4m7kP0cJ+JIuFkdHibI7vVFvu3eJS1HpVmYHWDulyN5WXwbRA0SX/7ZDaJ/1DH8SdY9xOJg==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + esbuild-android-64: 0.14.34 + esbuild-android-arm64: 0.14.34 + esbuild-darwin-64: 0.14.34 + esbuild-darwin-arm64: 0.14.34 + esbuild-freebsd-64: 0.14.34 + esbuild-freebsd-arm64: 0.14.34 + esbuild-linux-32: 0.14.34 + esbuild-linux-64: 0.14.34 + esbuild-linux-arm: 0.14.34 + esbuild-linux-arm64: 0.14.34 + esbuild-linux-mips64le: 0.14.34 + esbuild-linux-ppc64le: 0.14.34 + esbuild-linux-riscv64: 0.14.34 + esbuild-linux-s390x: 0.14.34 + esbuild-netbsd-64: 0.14.34 + esbuild-openbsd-64: 0.14.34 + esbuild-sunos-64: 0.14.34 + esbuild-windows-32: 0.14.34 + esbuild-windows-64: 0.14.34 + esbuild-windows-arm64: 0.14.34 + dev: true + /escalade/3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} engines: {node: '>=6'} @@ -2649,6 +3075,10 @@ packages: engines: {node: '>=4.0'} dev: true + /estree-walker/0.6.1: + resolution: {integrity: sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==} + dev: true + /esutils/2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} @@ -2895,6 +3325,14 @@ packages: dependencies: function-bind: 1.1.1 + /html-rewriter-wasm/0.4.1: + resolution: {integrity: sha512-lNovG8CMCCmcVB1Q7xggMSf7tqPCijZXaH4gL6iE8BFghdQCbaY5Met9i1x2Ex8m/cZHDUtXK9H6/znKamRP8Q==} + dev: true + + /http-cache-semantics/4.1.0: + resolution: {integrity: sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==} + dev: true + /ignore/5.2.0: resolution: {integrity: sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==} engines: {node: '>= 4'} @@ -3062,6 +3500,9 @@ packages: engines: {node: '>=0.10.0'} dev: false + /jose/4.8.1: + resolution: {integrity: sha512-+/hpTbRcCw9YC0TOfN1W47pej4a9lRmltdOVdRLz5FP5UvUq3CenhXjQK7u/8NdMIIShMXYAh9VLPhc7TjhvFw==} + /js-sha3/0.8.0: resolution: {integrity: sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==} dev: false @@ -3110,6 +3551,11 @@ packages: engines: {node: '>=0.10.0'} dev: false + /kleur/4.1.4: + resolution: {integrity: sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==} + engines: {node: '>=6'} + dev: true + /ky-universal/0.8.2_pkdcfzdjosae4cy3uef4ugd56u: resolution: {integrity: sha512-xe0JaOH9QeYxdyGLnzUOVGK4Z6FGvDVzcXFTdrYA1f33MZdEa45sUDaMBy98xQMcsd2XIBrTXRrRYnegcSdgVQ==} engines: {node: '>=10.17'} @@ -3191,6 +3637,12 @@ packages: yallist: 4.0.0 dev: true + /magic-string/0.25.9: + resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} + dependencies: + sourcemap-codec: 1.4.8 + dev: true + /mailgun.js/5.2.2: resolution: {integrity: sha512-stnq7qJSwxpbHZFHoxw+IBRMm1A+pEwxCGdRZlyujdjIy7wENX+6XHbA8syfuSaWg/gklSGUBa3IMtfzxQ8ZwQ==} dependencies: @@ -3256,6 +3708,45 @@ packages: mime-db: 1.52.0 dev: false + /miniflare/2.4.0: + resolution: {integrity: sha512-xOBL/dQsUL95rxIYO+KrrVPPpm2TAf6TKl4AvhcjSfUeVkzDWd/y9f7hXCt8x6srDoK0Z2LpYouatvSfSUzGOw==} + engines: {node: '>=16.7'} + hasBin: true + peerDependencies: + '@miniflare/storage-redis': 2.4.0 + cron-schedule: ^3.0.4 + ioredis: ^4.27.9 + peerDependenciesMeta: + '@miniflare/storage-redis': + optional: true + cron-schedule: + optional: true + ioredis: + optional: true + dependencies: + '@miniflare/cache': 2.4.0 + '@miniflare/cli-parser': 2.4.0 + '@miniflare/core': 2.4.0 + '@miniflare/durable-objects': 2.4.0 + '@miniflare/html-rewriter': 2.4.0 + '@miniflare/http-server': 2.4.0 + '@miniflare/kv': 2.4.0 + '@miniflare/runner-vm': 2.4.0 + '@miniflare/scheduler': 2.4.0 + '@miniflare/shared': 2.4.0 + '@miniflare/sites': 2.4.0 + '@miniflare/storage-file': 2.4.0 + '@miniflare/storage-memory': 2.4.0 + '@miniflare/web-sockets': 2.4.0 + kleur: 4.1.4 + semiver: 1.1.0 + source-map-support: 0.5.21 + undici: 4.13.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: true + /minimatch/3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} dependencies: @@ -3279,6 +3770,11 @@ packages: object-assign: 4.1.1 dev: false + /mustache/4.2.0: + resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} + hasBin: true + dev: true + /nano-time/1.0.0: resolution: {integrity: sha1-sFVPaa2J4i0JB/ehKwmTpdlhN+8=} dependencies: @@ -3346,6 +3842,11 @@ packages: - domexception dev: false + /node-forge/1.3.1: + resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==} + engines: {node: '>= 6.13.0'} + dev: true + /node-releases/2.0.4: resolution: {integrity: sha512-gbMzqQtTtDz/00jQzZ21PQzdI9PyLYqUSvD0p3naOhX4odFji0ZxYdnVwPTxmSwkmxhcFImpozceidSG+AgoPQ==} dev: false @@ -3478,6 +3979,10 @@ packages: /path-parse/1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + /path-to-regexp/6.2.1: + resolution: {integrity: sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==} + dev: true + /path-type/4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -3789,6 +4294,27 @@ packages: dependencies: glob: 7.2.0 + /rollup-plugin-inject/3.0.2: + resolution: {integrity: sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w==} + deprecated: This package has been deprecated and is no longer maintained. Please use @rollup/plugin-inject. + dependencies: + estree-walker: 0.6.1 + magic-string: 0.25.9 + rollup-pluginutils: 2.8.2 + dev: true + + /rollup-plugin-node-polyfills/0.2.1: + resolution: {integrity: sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA==} + dependencies: + rollup-plugin-inject: 3.0.2 + dev: true + + /rollup-pluginutils/2.8.2: + resolution: {integrity: sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==} + dependencies: + estree-walker: 0.6.1 + dev: true + /run-parallel/1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} dependencies: @@ -3805,6 +4331,18 @@ packages: loose-envify: 1.4.0 dev: false + /selfsigned/2.0.1: + resolution: {integrity: sha512-LmME957M1zOsUhG+67rAjKfiWFox3SBxE/yymatMZsAx+oMrJ0YQ8AToOnyCm7xbeg2ep37IHLxdu0o2MavQOQ==} + engines: {node: '>=10'} + dependencies: + node-forge: 1.3.1 + dev: true + + /semiver/1.1.0: + resolution: {integrity: sha512-QNI2ChmuioGC1/xjyYwyZYADILWyW6AmS1UH6gDj/SFUUUS4MBAWs/7mxnkRPc/F4iHezDP+O8t0dO8WHiEOdg==} + engines: {node: '>=6'} + dev: true + /semver/6.3.0: resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} hasBin: true @@ -3822,6 +4360,10 @@ packages: lru-cache: 6.0.0 dev: true + /set-cookie-parser/2.4.8: + resolution: {integrity: sha512-edRH8mBKEWNVIVMKejNnuJxleqYE/ZSdcT8/Nem9/mmosx12pctd80s2Oy00KNZzrogMZS5mauK2/ymL1bvlvg==} + dev: true + /shallow-clone/3.0.1: resolution: {integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==} engines: {node: '>=8'} @@ -3861,10 +4403,29 @@ packages: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'} + /source-map-support/0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + dev: true + + /source-map/0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + dev: true + + /sourcemap-codec/1.4.8: + resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} + dev: true + + /stack-trace/0.0.10: + resolution: {integrity: sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=} + dev: true + /streamsearch/0.1.2: resolution: {integrity: sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=} engines: {node: '>=0.8.0'} - dev: false /streamsearch/1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} @@ -4063,6 +4624,11 @@ packages: has-symbols: 1.0.3 which-boxed-primitive: 1.0.2 + /undici/4.13.0: + resolution: {integrity: sha512-8lk8S/f2V0VUNGf2scU2b+KI2JSzEQLdCyRNRF3XmHu+5jectlSDaPSBCXAHFaUlt1rzngzOBVDgJS9/Gue/KA==} + engines: {node: '>=12.18'} + dev: true + /unload/2.2.0: resolution: {integrity: sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==} dependencies: @@ -4179,14 +4745,56 @@ packages: engines: {node: '>=0.10.0'} dev: true + /wrangler/2.0.5: + resolution: {integrity: sha512-0BVKQDGm9whPrzLPACGJ9CGdk/taKYYIEvnjLSVOBOxa8her6R9VcmGP6U0wXQnvMasQqlmj+NnjNWgfRw8nIQ==} + engines: {node: '>=16.7.0'} + hasBin: true + dependencies: + '@esbuild-plugins/node-globals-polyfill': 0.1.1_esbuild@0.14.34 + '@esbuild-plugins/node-modules-polyfill': 0.1.4_esbuild@0.14.34 + blake3-wasm: 2.1.5 + esbuild: 0.14.34 + miniflare: 2.4.0 + nanoid: 3.3.4 + path-to-regexp: 6.2.1 + selfsigned: 2.0.1 + semiver: 1.1.0 + xxhash-wasm: 1.0.1 + optionalDependencies: + fsevents: 2.3.2 + transitivePeerDependencies: + - '@miniflare/storage-redis' + - bufferutil + - cron-schedule + - ioredis + - utf-8-validate + dev: true + /wrappy/1.0.2: resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=} + /ws/8.6.0: + resolution: {integrity: sha512-AzmM3aH3gk0aX7/rZLYvjdvZooofDu3fFOzGqcSnQ1tOcTWwhM/o+q++E8mAyVVIyUdajrkzWUGftaVSDLn1bw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dev: true + /xtend/4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} dev: true + /xxhash-wasm/1.0.1: + resolution: {integrity: sha512-Lc9CTvDrH2vRoiaUzz25q7lRaviMhz90pkx6YxR9EPYtF99yOJnv2cB+CQ0hp/TLoqrUsk8z/W2EN31T568Azw==} + dev: true + /yallist/4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} dev: true @@ -4196,6 +4804,15 @@ packages: engines: {node: '>= 6'} dev: true + /youch/2.2.2: + resolution: {integrity: sha512-/FaCeG3GkuJwaMR34GHVg0l8jCbafZLHiFowSjqLlqhC6OMyf2tPJBu8UirF7/NI9X/R5ai4QfEKUCOxMAGxZQ==} + dependencies: + '@types/stack-trace': 0.0.29 + cookie: 0.4.2 + mustache: 4.2.0 + stack-trace: 0.0.10 + dev: true + /zod/3.15.1: resolution: {integrity: sha512-WAdjcoOxa4S9oc/u7fTbC3CC7uVqptLLU0LKqS8RDBOrCXp2t5avM8BUfgNVZJymGWAx6SEUYxWPPoYuQ5rgwQ==} dev: false diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 379220ea..6c9455c1 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,2 +1,3 @@ packages: - - web \ No newline at end of file + - web + - r2 \ No newline at end of file diff --git a/r2/README.md b/r2/README.md new file mode 100644 index 00000000..c00c3f6e --- /dev/null +++ b/r2/README.md @@ -0,0 +1,15 @@ +A cloudflare worker based REST API for your R2 bucket. + +Usage: + +- Change the `bucket_name` and `preview_bucket_name` in `wrangler.toml` if you want. +- Set `API_TOKEN` in the worker secrets using Wrangler CLI. + +Endpoints: + +- GET `/:key`: Public access +- POST `/`: Require `authorization: Bearer TOKEN` header, where `TOKEN` should match `API_TOKEN` from your worker secrets. The request content type should be `multipart/form-data` with following fields: + - `prefix`: optional prefix for the key, see blow + - `file`: `File` file to upload + +The object key is generated from `prefix` + `uuid()` + `file.extension` diff --git a/r2/package.json b/r2/package.json new file mode 100644 index 00000000..04b64acc --- /dev/null +++ b/r2/package.json @@ -0,0 +1,16 @@ +{ + "name": "r2", + "version": "0.0.0", + "devDependencies": { + "@cloudflare/workers-types": "^3.10.0", + "typescript": "^4.6.4", + "wrangler": "2.0.5", + "@proselog/jwt": "^0.1.1" + }, + "private": true, + "scripts": { + "dev": "wrangler dev --env development", + "deploy": "wrangler publish" + }, + "license": "MIT" +} diff --git a/r2/pnpm-lock.yaml b/r2/pnpm-lock.yaml new file mode 100644 index 00000000..1e562d59 --- /dev/null +++ b/r2/pnpm-lock.yaml @@ -0,0 +1,655 @@ +lockfileVersion: 5.4 + +specifiers: + '@cloudflare/workers-types': ^3.10.0 + typescript: ^4.6.4 + wrangler: 2.0.5 + +devDependencies: + '@cloudflare/workers-types': 3.10.0 + typescript: 4.6.4 + wrangler: 2.0.5 + +packages: + + /@cloudflare/workers-types/3.10.0: + resolution: {integrity: sha512-gn+H5Ps9yV1VYz8FJ08bMfXLW8ubNblRF9Z+m+0ctNMyl3CdoZmUfH1pMTxYE8SMUJbLxuNvpdlZhwLDoGFXaw==} + dev: true + + /@esbuild-plugins/node-globals-polyfill/0.1.1_esbuild@0.14.34: + resolution: {integrity: sha512-MR0oAA+mlnJWrt1RQVQ+4VYuRJW/P2YmRTv1AsplObyvuBMnPHiizUF95HHYiSsMGLhyGtWufaq2XQg6+iurBg==} + peerDependencies: + esbuild: '*' + dependencies: + esbuild: 0.14.34 + dev: true + + /@esbuild-plugins/node-modules-polyfill/0.1.4_esbuild@0.14.34: + resolution: {integrity: sha512-uZbcXi0zbmKC/050p3gJnne5Qdzw8vkXIv+c2BW0Lsc1ji1SkrxbKPUy5Efr0blbTu1SL8w4eyfpnSdPg3G0Qg==} + peerDependencies: + esbuild: '*' + dependencies: + esbuild: 0.14.34 + escape-string-regexp: 4.0.0 + rollup-plugin-node-polyfills: 0.2.1 + dev: true + + /@iarna/toml/2.2.5: + resolution: {integrity: sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==} + dev: true + + /@miniflare/cache/2.4.0: + resolution: {integrity: sha512-tMDXlUVlThgFubJmlxZoKmLK8kBxDmuMbVMt7csHpXegzkuo2TmIsDqBE/C3CRiJ5xeCQgpD6iZtKBu5Zn5fRA==} + engines: {node: '>=16.7'} + dependencies: + '@miniflare/core': 2.4.0 + '@miniflare/shared': 2.4.0 + http-cache-semantics: 4.1.0 + undici: 4.13.0 + dev: true + + /@miniflare/cli-parser/2.4.0: + resolution: {integrity: sha512-Xr5lO8f+oIr9r/b2dfo0on1p0MNN+pkwRHWoY5ACSnp9FaGnwm/g71DM7AajIQPIk0TpRsSVNDx8Ygj1LPT+sQ==} + engines: {node: '>=16.7'} + dependencies: + '@miniflare/shared': 2.4.0 + kleur: 4.1.4 + dev: true + + /@miniflare/core/2.4.0: + resolution: {integrity: sha512-vYl8xaWTFzxtkbzx3IkT4Py0OAFdfmFnVo627O1HKHWVGlkjVr8UKtxBpIR+f5pq/HCMzzqA1HM9FXO0dQfy3A==} + engines: {node: '>=16.7'} + dependencies: + '@iarna/toml': 2.2.5 + '@miniflare/shared': 2.4.0 + '@miniflare/watcher': 2.4.0 + busboy: 0.3.1 + dotenv: 10.0.0 + kleur: 4.1.4 + set-cookie-parser: 2.4.8 + undici: 4.13.0 + dev: true + + /@miniflare/durable-objects/2.4.0: + resolution: {integrity: sha512-VVLaUXXcAQcYE/3YmDLTacZf5OzR8bib6q1T9NqVb0uK5sLMQqyHvQdsG5rMqs7iyxfJxyZ0bL2OW9XGALOkoQ==} + engines: {node: '>=16.7'} + dependencies: + '@miniflare/core': 2.4.0 + '@miniflare/shared': 2.4.0 + '@miniflare/storage-memory': 2.4.0 + undici: 4.13.0 + dev: true + + /@miniflare/html-rewriter/2.4.0: + resolution: {integrity: sha512-ZG8819N7LelDD+8+Ss5FZpVyQQq/V2igod0qE68JK4he/w4/yn57Rk6Efb49y15HoHAXl2RpCCsnCyIow/Xjug==} + engines: {node: '>=16.7'} + dependencies: + '@miniflare/core': 2.4.0 + '@miniflare/shared': 2.4.0 + html-rewriter-wasm: 0.4.1 + undici: 4.13.0 + dev: true + + /@miniflare/http-server/2.4.0: + resolution: {integrity: sha512-r6Z/nqxE0oa1z63L95yvnG0PUeLRxZOeGS7ADxZMFKan4WD5lvYtSKDuDEm0lkbQshCOHQ3uXFr0cotOm8JoMQ==} + engines: {node: '>=16.7'} + dependencies: + '@miniflare/core': 2.4.0 + '@miniflare/shared': 2.4.0 + '@miniflare/web-sockets': 2.4.0 + kleur: 4.1.4 + selfsigned: 2.0.1 + undici: 4.13.0 + ws: 8.6.0 + youch: 2.2.2 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: true + + /@miniflare/kv/2.4.0: + resolution: {integrity: sha512-1UW7f1386xR6EDEXNZOR1TpFwQfRRSxUPqD6m/U0WprlsbM0cIYGz+AUeaVbkFf8lfE2MeXCUrjbWsLOvsnw3g==} + engines: {node: '>=16.7'} + dependencies: + '@miniflare/shared': 2.4.0 + dev: true + + /@miniflare/runner-vm/2.4.0: + resolution: {integrity: sha512-7sdwBYzXQTwYeR3tTvQ+vJfzc7BXwqR8AUPK9l5gvCtg+Geq9sMslr5SikIJpgcvbYqKDjvC9DQEPJ3sqr9cSQ==} + engines: {node: '>=16.7'} + dependencies: + '@miniflare/shared': 2.4.0 + dev: true + + /@miniflare/scheduler/2.4.0: + resolution: {integrity: sha512-dfMCXoAS8Y+3xABNxYju62I2xIBS54Op7ohCHoatvAM5RvualJUPICEMPZzX6/z29q5xPIeSLhLDhl/asAQ19w==} + engines: {node: '>=16.7'} + dependencies: + '@miniflare/core': 2.4.0 + '@miniflare/shared': 2.4.0 + cron-schedule: 3.0.6 + dev: true + + /@miniflare/shared/2.4.0: + resolution: {integrity: sha512-lPQFzBUVGNQ93gQ/dliToWnO0OqAgsD3/902Pd/IixVSRwRj3BTnYv2dHMUKZcODBPrhnbqZeqcPWdBLzEx8uw==} + engines: {node: '>=16.7'} + dependencies: + ignore: 5.2.0 + kleur: 4.1.4 + dev: true + + /@miniflare/sites/2.4.0: + resolution: {integrity: sha512-YZy/TujnR1lkBvCncDDQ8tsWsXRE4JJ4x9a0bKN/XnZh7r6OhDM0sw4BFcBQhT6Ukdtttam1O3FlJxnMitrDGg==} + engines: {node: '>=16.7'} + dependencies: + '@miniflare/kv': 2.4.0 + '@miniflare/shared': 2.4.0 + '@miniflare/storage-file': 2.4.0 + dev: true + + /@miniflare/storage-file/2.4.0: + resolution: {integrity: sha512-f1AUMz8xps/4VhNJMb8JeCevZFeU4pg2lIWmC11gG4xeq2nibTPBi6Qtx594Le7ZKij/tF6rRsoNfGau2Q5gdw==} + engines: {node: '>=16.7'} + dependencies: + '@miniflare/shared': 2.4.0 + '@miniflare/storage-memory': 2.4.0 + dev: true + + /@miniflare/storage-memory/2.4.0: + resolution: {integrity: sha512-mhWwgHhDNtEa7y1bYbdVucV0lqUmzagYXUSppAdSGS5JPyJMyw3HseqRNTk6gC/vKlvEYlFf3ugWcREGCedr9A==} + engines: {node: '>=16.7'} + dependencies: + '@miniflare/shared': 2.4.0 + dev: true + + /@miniflare/watcher/2.4.0: + resolution: {integrity: sha512-gDQRUxwOjmctvowyd4Hcdy3fjxz3ERKzirp6TvA3AWUohKZk3IhwGlaA8aCwbdP+ELYQlG5wK44AfLSGi956fg==} + engines: {node: '>=16.7'} + dependencies: + '@miniflare/shared': 2.4.0 + dev: true + + /@miniflare/web-sockets/2.4.0: + resolution: {integrity: sha512-cz/cN0GoQOXRLh80UmlcEJODPIw2ijKBK3PLRzvfTzqQ5avK6wp2M8Fj8C/5JIT6g7siwvBANyKXD3U3RpKGHQ==} + engines: {node: '>=16.7'} + dependencies: + '@miniflare/core': 2.4.0 + '@miniflare/shared': 2.4.0 + undici: 4.13.0 + ws: 8.6.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: true + + /@types/stack-trace/0.0.29: + resolution: {integrity: sha512-TgfOX+mGY/NyNxJLIbDWrO9DjGoVSW9+aB8H2yy1fy32jsvxijhmyJI9fDFgvz3YP4lvJaq9DzdR/M1bOgVc9g==} + dev: true + + /blake3-wasm/2.1.5: + resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==} + dev: true + + /buffer-from/1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + dev: true + + /busboy/0.3.1: + resolution: {integrity: sha512-y7tTxhGKXcyBxRKAni+awqx8uqaJKrSFSNFSeRG5CsWNdmy2BIK+6VGWEW7TZnIO/533mtMEA4rOevQV815YJw==} + engines: {node: '>=4.5.0'} + dependencies: + dicer: 0.3.0 + dev: true + + /cookie/0.4.2: + resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==} + engines: {node: '>= 0.6'} + dev: true + + /cron-schedule/3.0.6: + resolution: {integrity: sha512-izfGgKyzzIyLaeb1EtZ3KbglkS6AKp9cv7LxmiyoOu+fXfol1tQDC0Cof0enVZGNtudTHW+3lfuW9ZkLQss4Wg==} + dev: true + + /dicer/0.3.0: + resolution: {integrity: sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==} + engines: {node: '>=4.5.0'} + dependencies: + streamsearch: 0.1.2 + dev: true + + /dotenv/10.0.0: + resolution: {integrity: sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==} + engines: {node: '>=10'} + dev: true + + /esbuild-android-64/0.14.34: + resolution: {integrity: sha512-XfxcfJqmMYsT/LXqrptzFxmaR3GWzXHDLdFNIhm6S00zPaQF1TBBWm+9t0RZ6LRR7iwH57DPjaOeW20vMqI4Yw==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /esbuild-android-arm64/0.14.34: + resolution: {integrity: sha512-T02+NXTmSRL1Mc6puz+R9CB54rSPICkXKq6+tw8B6vxZFnCPzbJxgwIX4kcluz9p8nYBjF3+lSilTGWb7+Xgew==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /esbuild-darwin-64/0.14.34: + resolution: {integrity: sha512-pLRip2Bh4Ng7Bf6AMgCrSp3pPe/qZyf11h5Qo2mOfJqLWzSVjxrXW+CFRJfrOVP7TCnh/gmZSM2AFdCPB72vtw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /esbuild-darwin-arm64/0.14.34: + resolution: {integrity: sha512-vpidSJEBxx6lf1NWgXC+DCmGqesJuZ5Y8aQVVsaoO4i8tRXbXb0whChRvop/zd3nfNM4dIl5EXAky0knRX5I6w==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /esbuild-freebsd-64/0.14.34: + resolution: {integrity: sha512-m0HBjePhe0hAQJgtMRMNV9kMgIyV4/qSnzPx42kRMQBcPhgjAq1JRu4Il26czC+9FgpMbFkUktb07f/Lwnc6CA==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /esbuild-freebsd-arm64/0.14.34: + resolution: {integrity: sha512-cpRc2B94L1KvMPPYB4D6G39jLqpKlD3noAMY4/e86iXXXkhUYJJEtTuyNFTa9JRpWM0xCAp4mxjHjoIiLuoCLA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-32/0.14.34: + resolution: {integrity: sha512-8nQaEaoW7MH/K/RlozJa+lE1ejHIr8fuPIHhc513UebRav7HtXgQvxHQ6VZRUkWtep23M6dd7UqhwO1tMOfzQQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-64/0.14.34: + resolution: {integrity: sha512-Y3of4qQoLLlAgf042MlrY1P+7PnN9zWj8nVtw9XQG5hcLOZLz7IKpU35oeu7n4wvyaZHwvQqDJ93gRLqdJekcQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-arm/0.14.34: + resolution: {integrity: sha512-9lpq1NcJqssAF7alCO6zL3gvBVVt/lKw4oetUM7OgNnRX0OWpB+ZIO9FwCrSj/dMdmgDhPLf+119zB8QxSMmAg==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-arm64/0.14.34: + resolution: {integrity: sha512-IlWaGtj9ir7+Nrume1DGcyzBDlK8GcnJq0ANKwcI9pVw8tqr+6GD0eqyF9SF1mR8UmAp+odrx1H5NdR2cHdFHA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-mips64le/0.14.34: + resolution: {integrity: sha512-k3or+01Rska1AjUyNjA4buEwB51eyN/xPQAoOx1CjzAQC3l8rpjUDw55kXyL63O/1MUi4ISvtNtl8gLwdyEcxw==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-ppc64le/0.14.34: + resolution: {integrity: sha512-+qxb8M9FfM2CJaVU7GgYpJOHM1ngQOx+/VrtBjb4C8oVqaPcESCeg2anjl+HRZy8VpYc71q/iBYausPPbJ+Keg==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-riscv64/0.14.34: + resolution: {integrity: sha512-Y717ltBdQ5j5sZIHdy1DV9kieo0wMip0dCmVSTceowCPYSn1Cg33Kd6981+F/3b9FDMzNWldZFOBRILViENZSA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-s390x/0.14.34: + resolution: {integrity: sha512-bDDgYO4LhL4+zPs+WcBkXph+AQoPcQRTv18FzZS0WhjfH8TZx2QqlVPGhmhZ6WidrY+jKthUqO6UhGyIb4MpmA==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-netbsd-64/0.14.34: + resolution: {integrity: sha512-cfaFGXdRt0+vHsjNPyF0POM4BVSHPSbhLPe8mppDc7GDDxjIl08mV1Zou14oDWMp/XZMjYN1kWYRSfftiD0vvQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /esbuild-openbsd-64/0.14.34: + resolution: {integrity: sha512-vmy9DxXVnRiI14s8GKuYBtess+EVcDALkbpTqd5jw4XITutIzyB7n4x0Tj5utAkKsgZJB22lLWGekr0ABnSLow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /esbuild-sunos-64/0.14.34: + resolution: {integrity: sha512-eNPVatNET1F7tRMhii7goL/eptfxc0ALRjrj9SPFNqp0zmxrehBFD6BaP3R4LjMn6DbMO0jOAnTLFKr8NqcJAA==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /esbuild-windows-32/0.14.34: + resolution: {integrity: sha512-EFhpXyHEcnqWYe2rAHFd8dRw8wkrd9U+9oqcyoEL84GbanAYjiiIjBZsnR8kl0sCQ5w6bLpk7vCEIA2VS32Vcg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /esbuild-windows-64/0.14.34: + resolution: {integrity: sha512-a8fbl8Ky7PxNEjf1aJmtxdDZj32/hC7S1OcA2ckEpCJRTjiKslI9vAdPpSjrKIWhws4Galpaawy0nB7fjHYf5Q==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /esbuild-windows-arm64/0.14.34: + resolution: {integrity: sha512-EYvmKbSa2B3sPnpC28UEu9jBK5atGV4BaVRE7CYGUci2Hlz4AvtV/LML+TcDMT6gBgibnN2gcltWclab3UutMg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /esbuild/0.14.34: + resolution: {integrity: sha512-QIWdPT/gFF6hCaf4m7kP0cJ+JIuFkdHibI7vVFvu3eJS1HpVmYHWDulyN5WXwbRA0SX/7ZDaJ/1DH8SdY9xOJg==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + esbuild-android-64: 0.14.34 + esbuild-android-arm64: 0.14.34 + esbuild-darwin-64: 0.14.34 + esbuild-darwin-arm64: 0.14.34 + esbuild-freebsd-64: 0.14.34 + esbuild-freebsd-arm64: 0.14.34 + esbuild-linux-32: 0.14.34 + esbuild-linux-64: 0.14.34 + esbuild-linux-arm: 0.14.34 + esbuild-linux-arm64: 0.14.34 + esbuild-linux-mips64le: 0.14.34 + esbuild-linux-ppc64le: 0.14.34 + esbuild-linux-riscv64: 0.14.34 + esbuild-linux-s390x: 0.14.34 + esbuild-netbsd-64: 0.14.34 + esbuild-openbsd-64: 0.14.34 + esbuild-sunos-64: 0.14.34 + esbuild-windows-32: 0.14.34 + esbuild-windows-64: 0.14.34 + esbuild-windows-arm64: 0.14.34 + dev: true + + /escape-string-regexp/4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + dev: true + + /estree-walker/0.6.1: + resolution: {integrity: sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==} + dev: true + + /fsevents/2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /html-rewriter-wasm/0.4.1: + resolution: {integrity: sha512-lNovG8CMCCmcVB1Q7xggMSf7tqPCijZXaH4gL6iE8BFghdQCbaY5Met9i1x2Ex8m/cZHDUtXK9H6/znKamRP8Q==} + dev: true + + /http-cache-semantics/4.1.0: + resolution: {integrity: sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==} + dev: true + + /ignore/5.2.0: + resolution: {integrity: sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==} + engines: {node: '>= 4'} + dev: true + + /kleur/4.1.4: + resolution: {integrity: sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==} + engines: {node: '>=6'} + dev: true + + /magic-string/0.25.9: + resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} + dependencies: + sourcemap-codec: 1.4.8 + dev: true + + /miniflare/2.4.0: + resolution: {integrity: sha512-xOBL/dQsUL95rxIYO+KrrVPPpm2TAf6TKl4AvhcjSfUeVkzDWd/y9f7hXCt8x6srDoK0Z2LpYouatvSfSUzGOw==} + engines: {node: '>=16.7'} + hasBin: true + peerDependencies: + '@miniflare/storage-redis': 2.4.0 + cron-schedule: ^3.0.4 + ioredis: ^4.27.9 + peerDependenciesMeta: + '@miniflare/storage-redis': + optional: true + cron-schedule: + optional: true + ioredis: + optional: true + dependencies: + '@miniflare/cache': 2.4.0 + '@miniflare/cli-parser': 2.4.0 + '@miniflare/core': 2.4.0 + '@miniflare/durable-objects': 2.4.0 + '@miniflare/html-rewriter': 2.4.0 + '@miniflare/http-server': 2.4.0 + '@miniflare/kv': 2.4.0 + '@miniflare/runner-vm': 2.4.0 + '@miniflare/scheduler': 2.4.0 + '@miniflare/shared': 2.4.0 + '@miniflare/sites': 2.4.0 + '@miniflare/storage-file': 2.4.0 + '@miniflare/storage-memory': 2.4.0 + '@miniflare/web-sockets': 2.4.0 + kleur: 4.1.4 + semiver: 1.1.0 + source-map-support: 0.5.21 + undici: 4.13.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: true + + /mustache/4.2.0: + resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} + hasBin: true + dev: true + + /nanoid/3.3.4: + resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: true + + /node-forge/1.3.1: + resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==} + engines: {node: '>= 6.13.0'} + dev: true + + /path-to-regexp/6.2.1: + resolution: {integrity: sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==} + dev: true + + /rollup-plugin-inject/3.0.2: + resolution: {integrity: sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w==} + deprecated: This package has been deprecated and is no longer maintained. Please use @rollup/plugin-inject. + dependencies: + estree-walker: 0.6.1 + magic-string: 0.25.9 + rollup-pluginutils: 2.8.2 + dev: true + + /rollup-plugin-node-polyfills/0.2.1: + resolution: {integrity: sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA==} + dependencies: + rollup-plugin-inject: 3.0.2 + dev: true + + /rollup-pluginutils/2.8.2: + resolution: {integrity: sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==} + dependencies: + estree-walker: 0.6.1 + dev: true + + /selfsigned/2.0.1: + resolution: {integrity: sha512-LmME957M1zOsUhG+67rAjKfiWFox3SBxE/yymatMZsAx+oMrJ0YQ8AToOnyCm7xbeg2ep37IHLxdu0o2MavQOQ==} + engines: {node: '>=10'} + dependencies: + node-forge: 1.3.1 + dev: true + + /semiver/1.1.0: + resolution: {integrity: sha512-QNI2ChmuioGC1/xjyYwyZYADILWyW6AmS1UH6gDj/SFUUUS4MBAWs/7mxnkRPc/F4iHezDP+O8t0dO8WHiEOdg==} + engines: {node: '>=6'} + dev: true + + /set-cookie-parser/2.4.8: + resolution: {integrity: sha512-edRH8mBKEWNVIVMKejNnuJxleqYE/ZSdcT8/Nem9/mmosx12pctd80s2Oy00KNZzrogMZS5mauK2/ymL1bvlvg==} + dev: true + + /source-map-support/0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + dev: true + + /source-map/0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + dev: true + + /sourcemap-codec/1.4.8: + resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} + dev: true + + /stack-trace/0.0.10: + resolution: {integrity: sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=} + dev: true + + /streamsearch/0.1.2: + resolution: {integrity: sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=} + engines: {node: '>=0.8.0'} + dev: true + + /typescript/4.6.4: + resolution: {integrity: sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==} + engines: {node: '>=4.2.0'} + hasBin: true + dev: true + + /undici/4.13.0: + resolution: {integrity: sha512-8lk8S/f2V0VUNGf2scU2b+KI2JSzEQLdCyRNRF3XmHu+5jectlSDaPSBCXAHFaUlt1rzngzOBVDgJS9/Gue/KA==} + engines: {node: '>=12.18'} + dev: true + + /wrangler/2.0.5: + resolution: {integrity: sha512-0BVKQDGm9whPrzLPACGJ9CGdk/taKYYIEvnjLSVOBOxa8her6R9VcmGP6U0wXQnvMasQqlmj+NnjNWgfRw8nIQ==} + engines: {node: '>=16.7.0'} + hasBin: true + dependencies: + '@esbuild-plugins/node-globals-polyfill': 0.1.1_esbuild@0.14.34 + '@esbuild-plugins/node-modules-polyfill': 0.1.4_esbuild@0.14.34 + blake3-wasm: 2.1.5 + esbuild: 0.14.34 + miniflare: 2.4.0 + nanoid: 3.3.4 + path-to-regexp: 6.2.1 + selfsigned: 2.0.1 + semiver: 1.1.0 + xxhash-wasm: 1.0.1 + optionalDependencies: + fsevents: 2.3.2 + transitivePeerDependencies: + - '@miniflare/storage-redis' + - bufferutil + - cron-schedule + - ioredis + - utf-8-validate + dev: true + + /ws/8.6.0: + resolution: {integrity: sha512-AzmM3aH3gk0aX7/rZLYvjdvZooofDu3fFOzGqcSnQ1tOcTWwhM/o+q++E8mAyVVIyUdajrkzWUGftaVSDLn1bw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dev: true + + /xxhash-wasm/1.0.1: + resolution: {integrity: sha512-Lc9CTvDrH2vRoiaUzz25q7lRaviMhz90pkx6YxR9EPYtF99yOJnv2cB+CQ0hp/TLoqrUsk8z/W2EN31T568Azw==} + dev: true + + /youch/2.2.2: + resolution: {integrity: sha512-/FaCeG3GkuJwaMR34GHVg0l8jCbafZLHiFowSjqLlqhC6OMyf2tPJBu8UirF7/NI9X/R5ai4QfEKUCOxMAGxZQ==} + dependencies: + '@types/stack-trace': 0.0.29 + cookie: 0.4.2 + mustache: 4.2.0 + stack-trace: 0.0.10 + dev: true diff --git a/r2/src/index.ts b/r2/src/index.ts new file mode 100644 index 00000000..fe6cb35a --- /dev/null +++ b/r2/src/index.ts @@ -0,0 +1,104 @@ +import { decrypt, getDerivedKey } from "@proselog/jwt" + +const randomId = () => crypto.randomUUID() + +const getExtension = (path: string) => { + const lastPart = path.split(".").pop() + return lastPart ? `.${lastPart}` : "" +} + +const corsHeaders = { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Headers": "*", + "Access-Control-Allow-Methods": "*", +} + +const send = (data: string | Record, init?: ResponseInit) => { + const isJSON = data && typeof data === "object" + return new Response(isJSON ? JSON.stringify(data) : data, { + ...init, + headers: { + ...init?.headers, + ...corsHeaders, + "content-type": isJSON ? "application/json" : "text/plain", + }, + }) +} + +const handler: ExportedHandler<{ BUCKET: R2Bucket; ENCRYPT_SECRET: string }> = { + async fetch(request, env): Promise { + try { + // Files are publicly accessible + if (request.method === "GET") { + const url = new URL(request.url) + + if (url.pathname === "/") { + return send("hello world") + } + + const key = url.pathname.substring(1) + const object = await env.BUCKET.get(key) + if (!object) { + return send("object not found", { status: 404 }) + } + return new Response(object.body) + } + + if (request.method === "OPTIONS") { + return send("") + } + + if (request.method === "POST") { + const token = request.headers + .get("authorization") + ?.replace("Bearer ", "") + + if (!token) { + throw new Error("missing auth token") + } + + let uid: string | undefined + if (env.ENCRYPT_SECRET && token) { + const key = await getDerivedKey(env.ENCRYPT_SECRET) + try { + const payload = await decrypt(token, key) + uid = payload.uid as string + } catch (error) { + console.error({ error }) + return send("invalid token", { + status: 401, + }) + } + } + + if (!uid) { + throw new Error("failed to extract uid") + } + + const data = await request.formData() + const id = randomId() + const file = data.get("file") as File + + if (!file || !(file instanceof File)) { + throw new Error("missing file or invalid file") + } + + const key = `${uid}/${id}${getExtension(file.name)}` + + await env.BUCKET.put(key, await file.arrayBuffer(), { + httpMetadata: { + contentType: file.type, + }, + }) + return send({ key }) + } + } catch (error: any) { + console.error({ error }) + return send(error.message, { status: 500 }) + } + + return send("Hello World!") + }, +} + +export default handler diff --git a/r2/tsconfig.json b/r2/tsconfig.json new file mode 100644 index 00000000..2591fb14 --- /dev/null +++ b/r2/tsconfig.json @@ -0,0 +1,105 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Projects */ + // "incremental": true, /* Enable incremental compilation */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2021" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, + "lib": [ + "es2021" + ] /* Specify a set of bundled library declaration files that describe the target runtime environment. */, + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */ + // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + + /* Modules */ + "module": "es2022" /* Specify what module code is generated. */, + // "rootDir": "./", /* Specify the root folder within your source files. */ + "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */, + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ + "types": [ + "@cloudflare/workers-types" + ] /* Specify type package names to be included without being referenced in a source file. */, + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + "resolveJsonModule": true /* Enable importing .json files */, + // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + "allowJs": true /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */, + "checkJs": true /* Enable error reporting in type-checked JavaScript files. */, + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */ + // "outDir": "./", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + "noEmit": true /* Disable emitting files from a compilation. */, + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + "isolatedModules": true /* Ensure that each file can be safely transpiled without relying on other imports. */, + "allowSyntheticDefaultImports": true /* Allow 'import x from y' when a module doesn't have a default export. */, + // "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */, + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, + + /* Type Checking */ + "strict": true /* Enable all strict type-checking options. */, + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ + // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ + // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +} diff --git a/r2/wrangler.toml b/r2/wrangler.toml new file mode 100644 index 00000000..ef63a407 --- /dev/null +++ b/r2/wrangler.toml @@ -0,0 +1,19 @@ +name = "r2" +main = "src/index.ts" +compatibility_date = "2022-05-14" + +[[r2_buckets]] +binding = 'BUCKET' +bucket_name = 'proselog' +preview_bucket_name ='proselog-dev' + +# secrets +# API_TOKEN +[env.development.vars] +ENCRYPT_SECRET="uIeqyCW1E5PbK2ztsc+UXcPwsZ0L/Z4k08ArOSaKpHHFViRbdnFJcQ==" + +# Why do I need to set it again? Cloudflare? +[[env.development.r2_buckets]] +binding = 'BUCKET' +bucket_name = 'proselog' +preview_bucket_name ='proselog-dev' \ No newline at end of file diff --git a/web/.env.example b/web/.env.example index 64690ca6..b21aab6d 100644 --- a/web/.env.example +++ b/web/.env.example @@ -16,13 +16,8 @@ ENCRYPT_SECRET=uIeqyCW1E5PbK2ztsc+UXcPwsZ0L/Z4k08ArOSaKpHHFViRbdnFJcQ== OUR_DOMAIN=localhost:3000 NEXT_PUBLIC_OUR_DOMAIN=$OUR_DOMAIN -S3_BUCKET_NAME=xxx -S3_REGION=us-west-004 -S3_ACCESS_KEY_ID=xxx -S3_SECRET_ACCESS_KEY=xxx -S3_ENDPOINT=xxx -S3_CDN_PREFIX=https://user-contents.proselog.com/ -NEXT_PUBLIC_S3_CDN_PREFIX=$S3_CDN_PREFIX +NEXT_PUBLIC_R2_URL=http://localhost:8787 +R2_API_TOKEN=xxx MAILGUN_APIKEY= MAILGUN_DOMAIN= diff --git a/web/package.json b/web/package.json index ea6a64d6..4edb0656 100644 --- a/web/package.json +++ b/web/package.json @@ -1,5 +1,5 @@ { - "name": "web", + "name": "proselog", "private": true, "description": "A publishing platform", "license": "unlicensed", @@ -19,6 +19,7 @@ "@hapi/iron": "^6.0.0", "@headlessui/react": "^1.6.0", "@prisma/client": "^3.14.0", + "@proselog/jwt": "^0.1.1", "@trpc/client": "^9.23.2", "@trpc/next": "^9.23.2", "@trpc/react": "^9.23.2", diff --git a/web/src/components/dashboard/AvatarForm.tsx b/web/src/components/dashboard/AvatarForm.tsx index fd74989b..1aff7102 100644 --- a/web/src/components/dashboard/AvatarForm.tsx +++ b/web/src/components/dashboard/AvatarForm.tsx @@ -7,22 +7,28 @@ import { Button } from "~/components/ui/Button" import toast from "react-hot-toast" import createPica from "pica" import { trpc } from "~/lib/trpc" +import { UploadFile, useUploadFile } from "~/hooks/useUploadFile" const AvatarEditorModal: React.FC<{ isOpen: boolean image?: File | null setIsOpen: (open: boolean) => void site?: string -}> = ({ isOpen, setIsOpen, image, site }) => { + uploadFile: UploadFile +}> = ({ isOpen, setIsOpen, image, site, uploadFile }) => { const editorRef = useRef(null) const [isSaving, setIsSaving] = useState(false) const ctx = trpc.useContext() + const updateProfile = trpc.useMutation("user.updateProfile") + const updateSite = trpc.useMutation("site.updateSite") const cropAndSave = async () => { if (!editorRef.current) return try { setIsSaving(true) + + // Get cropped image const fromCanvas = editorRef.current.getImage() const toCanvas = document.createElement("canvas") toCanvas.width = 460 @@ -30,23 +36,17 @@ const AvatarEditorModal: React.FC<{ const pica = createPica() const result = await pica.resize(fromCanvas, toCanvas) const blob = await pica.toBlob(result, "image/jpeg", 0.9) - const form = new FormData() + + // Upload image to R2 + const { key } = await uploadFile(blob, image!.name) + + // Save the image to profile / site if (site) { - form.append("site", site) - } - form.append("file", blob) - const res = await fetch("/api/save-avatar", { - body: form, - method: "post", - credentials: "same-origin", - }) - if (!res.ok) { - throw new Error(res.statusText) - } - const data = await res.json() - if (data.error) { - throw new Error(data.error) + await updateSite.mutateAsync({ site, icon: key }) + } else { + await updateProfile.mutateAsync({ avatar: key }) } + setIsOpen(false) toast.success("Updated!") ctx.invalidateQueries() @@ -102,6 +102,7 @@ export const AvatarForm: React.FC<{ const [isOpen, setIsOpen] = useState(false) const inputEl = useRef(null) const [image, setImage] = useState(null) + const uploadFile = useUploadFile() const onClick = () => { inputEl.current?.click() @@ -122,6 +123,7 @@ export const AvatarForm: React.FC<{ setIsOpen={setIsOpen} image={image} site={site} + uploadFile={uploadFile} /> - { + const jwt = trpc.useQuery(["user.getSignedJwt"], { + refetchInterval: 60_000, + }) + return jwt.data +} diff --git a/web/src/hooks/useUploadFile.ts b/web/src/hooks/useUploadFile.ts new file mode 100644 index 00000000..18a43859 --- /dev/null +++ b/web/src/hooks/useUploadFile.ts @@ -0,0 +1,38 @@ +import { useCallback } from "react" +import { R2_URL } from "~/lib/env" +import { useSignedJwt } from "./useSignedJwt" + +export const useUploadFile = () => { + const signedJwt = useSignedJwt() + + const uploadFile = useCallback( + async (blob, filename) => { + if (!signedJwt) { + throw new Error("failed to retrieve signed jwt, please try again") + } + + const form = new FormData() + form.append("file", blob, filename) + const res = await fetch(R2_URL, { + body: form, + method: "post", + headers: { + authorization: `Bearer ${signedJwt}`, + }, + }) + if (!res.ok) { + throw new Error(await res.text()) + } + const data = await res.json() + return data + }, + [signedJwt] + ) + + return uploadFile +} + +export type UploadFile = ( + blob: Blob, + filename: string +) => Promise<{ key: string }> diff --git a/web/src/lib/env.server.ts b/web/src/lib/env.server.ts index 198a2209..c0850703 100644 --- a/web/src/lib/env.server.ts +++ b/web/src/lib/env.server.ts @@ -1,8 +1,4 @@ -export const S3_REGION = process.env.S3_REGION -export const S3_ACCESS_KEY_ID = process.env.S3_ACCESS_KEY_ID -export const S3_SECRET_ACCESS_KEY = process.env.S3_SECRET_ACCESS_KEY -export const S3_BUCKET_NAME = process.env.S3_BUCKET_NAME -export const S3_ENDPOINT = process.env.S3_ENDPOINT +export const R2_API_TOKEN = process.env.R2_API_TOKEN export const MAILGUN_APIKEY = process.env.MAILGUN_APIKEY export const MAILGUN_DOMAIN = process.env.MAILGUN_DOMAIN export const ENCRYPT_SECRET = process.env.ENCRYPT_SECRET diff --git a/web/src/lib/env.ts b/web/src/lib/env.ts index 3f83ed37..255f03d4 100644 --- a/web/src/lib/env.ts +++ b/web/src/lib/env.ts @@ -2,5 +2,5 @@ import { IS_PROD } from "./constants" export const APP_NAME = process.env.NEXT_PUBLIC_APP_NAME export const OUR_DOMAIN = process.env.NEXT_PUBLIC_OUR_DOMAIN -export const S3_CDN_PREFIX = process.env.NEXT_PUBLIC_S3_CDN_PREFIX export const SITE_URL = `${IS_PROD ? "https" : "http"}://${OUR_DOMAIN}` +export const R2_URL = process.env.NEXT_PUBLIC_R2_URL diff --git a/web/src/lib/upload.client.ts b/web/src/lib/upload.client.ts deleted file mode 100644 index 96b2c3c2..00000000 --- a/web/src/lib/upload.client.ts +++ /dev/null @@ -1,15 +0,0 @@ -export const uploadFileInBrowser = async (blob: Blob) => { - const form = new FormData() - form.append("file", blob) - form.append("type", "image") - const res = await fetch(`/api/upload`, { - credentials: "same-origin", - body: form, - method: "POST", - }) - if (!res.ok) { - throw new Error(`Upload failed: ${res.statusText}`) - } - const json = await res.json() - return json as { file: string } -} diff --git a/web/src/lib/upload.server.ts b/web/src/lib/upload.server.ts deleted file mode 100644 index c40c194d..00000000 --- a/web/src/lib/upload.server.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { nanoid } from "nanoid" -import path from "path" -import busboy, { FileInfo } from "busboy" -import { s3uploadFile } from "./s3.server" -import { IncomingMessage } from "http" - -type File = { name: string; buffer: Buffer; info: FileInfo } - -export const uploadImage = async ( - req: IncomingMessage, - { userId }: { userId: string } -) => { - const filePromises: Promise[] = [] - const { files, fields } = await new Promise<{ - files: File[] - fields: Record - }>((resolve, reject) => { - const bb = busboy({ headers: req.headers, highWaterMark: 2 * 1024 * 1024 }) - - const fields: Record = {} - const files: File[] = [] - - bb.on("file", async (name, file, info) => { - const fn = async () => { - const chunks = [] - for await (let chunk of file) { - chunks.push(chunk) - } - const buffer = Buffer.concat(chunks) - files.push({ name, buffer, info }) - } - filePromises.push(fn()) - }) - - bb.on("field", (name, val) => { - fields[name] = val - }) - - const onEnd = (err?: Error) => { - if (err) return reject(err) - resolve({ files, fields }) - } - - bb.on("close", onEnd) - - bb.on("error", (error) => reject(error)) - - req.pipe(bb) - }) - - await Promise.all(filePromises) - - await Promise.all( - files.map(async (file) => { - const saveAs = `${userId}/${nanoid()}${ - path.extname(file.info.filename) || ".jpg" - }` - await s3uploadFile(saveAs, file.buffer, file.info.mimeType) - fields[file.name] = saveAs - }) - ) - - return fields -} diff --git a/web/src/lib/user-contents.ts b/web/src/lib/user-contents.ts index 80196587..26b208d8 100644 --- a/web/src/lib/user-contents.ts +++ b/web/src/lib/user-contents.ts @@ -1,5 +1,4 @@ -import { IS_PROD } from "./constants" -import { S3_CDN_PREFIX } from "./env" +import { R2_URL } from "./env" export function getUserContentsUrl(filename: string): string export function getUserContentsUrl(filename: undefined | null): undefined @@ -8,10 +7,5 @@ export function getUserContentsUrl( ): T export function getUserContentsUrl(filename: string | undefined | null) { if (!filename) return undefined - if (IS_PROD) { - return `${S3_CDN_PREFIX}/${filename}` - } - return `/dev-s3-proxy?${new URLSearchParams({ - filename: filename, - }).toString()}` + return `${R2_URL}/${filename}` } diff --git a/web/src/pages/_middleware.ts b/web/src/pages/_middleware.ts index 5c85e347..373aec43 100644 --- a/web/src/pages/_middleware.ts +++ b/web/src/pages/_middleware.ts @@ -1,19 +1,9 @@ import { NextRequest, NextResponse } from "next/server" -import { IS_PROD } from "~/lib/constants" -import { S3_BUCKET_NAME, S3_ENDPOINT } from "~/lib/env.server" import { getTenant } from "~/lib/tenant.server" export default function middleware(req: NextRequest) { const { pathname } = req.nextUrl - if (!IS_PROD && pathname.startsWith("/dev-s3-proxy")) { - const filename = req.nextUrl.searchParams.get("filename") - console.log(`https://${S3_BUCKET_NAME}.${S3_ENDPOINT}/${filename}`) - return NextResponse.rewrite( - `https://${S3_BUCKET_NAME}.${S3_ENDPOINT}/${filename}` - ) - } - if (pathname.startsWith("/api/") || pathname.startsWith("/dashboard")) { return NextResponse.next() } diff --git a/web/src/pages/api/upload-image.ts b/web/src/pages/api/upload-image.ts deleted file mode 100644 index 7f617719..00000000 --- a/web/src/pages/api/upload-image.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { NextApiHandler } from "next" -import { z } from "zod" -import { getAuthUser } from "~/lib/auth.server" -import { uploadImage } from "~/lib/upload.server" - -export const config = { - api: { - bodyParser: false, - }, -} - -const handler: NextApiHandler = async (req, res) => { - if (req.method !== "POST") return res.end("only POST") - try { - const user = await getAuthUser(req, true) - const data = await uploadImage(req, { userId: user.id }) - - const values = z - .object({ - file: z.string(), - }) - .parse(data) - - res.send({ file: values.file }) - } catch (error: any) { - console.error(error) - res.send({ - error: error.message, - }) - } -} - -export default handler diff --git a/web/src/pages/dashboard/[subdomain]/editor.tsx b/web/src/pages/dashboard/[subdomain]/editor.tsx index a5955393..ce59905f 100644 --- a/web/src/pages/dashboard/[subdomain]/editor.tsx +++ b/web/src/pages/dashboard/[subdomain]/editor.tsx @@ -15,6 +15,7 @@ import { trpc } from "~/lib/trpc" import { useRouter } from "next/router" import { DashboardLayout } from "~/components/dashboard/DashboardLayout" import { UniLink } from "~/components/ui/UniLink" +import { useUploadFile } from "~/hooks/useUploadFile" const getInputDatetimeValue = (date: Date | string) => { const str = dayjs(date).format() @@ -44,6 +45,7 @@ export default function SubdomainEditor() { publishedAt: page?.publishedAt || null, }) const createOrUpdatePage = trpc.useMutation("site.createOrUpdatePage") + const uploadFile = useUploadFile() const [values, setValues] = useState({ title: "", @@ -101,42 +103,29 @@ export default function SubdomainEditor() { [] ) - const handleDropFile = useCallback(async (file: File, view: EditorView) => { - if (!file.type.startsWith("image/")) { - toast.error("You can only upload images") - return - } + const handleDropFile = useCallback( + async (file: File, view: EditorView) => { + const toastId = toast.loading("Uploading...") + try { + if (!file.type.startsWith("image/")) { + throw new Error("You can only upload images") + } - const toastId = toast.loading("Uploading...") - - const form = new FormData() - form.append("file", file) - const res = await fetch("/api/upload-image", { - body: form, - headers: {}, - credentials: "same-origin", - method: "POST", - }) - if (!res.ok) { - toast.error("Upload failed", { - id: toastId, - }) - } else { - const data = await res.json() - if (data.error) { - toast.error(data.error, { id: toastId }) - } else { + const { key } = await uploadFile(file, file.name) toast.success("Uploaded!", { id: toastId, }) view.dispatch( view.state.replaceSelection( - `\n\n![${file.name.replace(/\.\w+$/, "")}](${data.file})\n\n` + `\n\n![${file.name.replace(/\.\w+$/, "")}](${key})\n\n` ) ) + } catch (error: any) { + toast.error(error.mesage, { id: toastId }) } - } - }, []) + }, + [uploadFile] + ) const { editorRef } = useEditor({ value: values.content, diff --git a/web/src/router/site.ts b/web/src/router/site.ts index 44eff261..67835c81 100644 --- a/web/src/router/site.ts +++ b/web/src/router/site.ts @@ -118,7 +118,7 @@ export const siteRouter = createRouter() site: z.string(), name: z.string().optional(), description: z.string().optional(), - icon: z.string().nullish().optional(), + icon: z.string().nullish(), subdomain: z.string().optional(), navigation: z .array( diff --git a/web/src/router/user.ts b/web/src/router/user.ts index 4624c702..e411aca7 100644 --- a/web/src/router/user.ts +++ b/web/src/router/user.ts @@ -1,15 +1,28 @@ +import { encrypt, getDerivedKey } from "@proselog/jwt" import { z } from "zod" +import { ENCRYPT_SECRET } from "~/lib/env.server" import { createRouter } from "~/lib/trpc.server" import { userModel } from "~/models/user.model" -export const userRouter = createRouter().mutation("updateProfile", { - input: z.object({ - username: z.string().optional(), - name: z.string().optional(), - email: z.string().optional(), - bio: z.string().optional(), - }), - async resolve({ ctx, input }) { - await userModel.updateProfile(ctx.gate, input) - }, -}) +export const userRouter = createRouter() + .query("getSignedJwt", { + output: z.string(), + async resolve({ ctx }) { + const user = ctx.gate.getUser(true) + const key = await getDerivedKey(ENCRYPT_SECRET) + const token = await encrypt({ uid: user.id }, key, { expiresIn: "1h" }) + return token + }, + }) + .mutation("updateProfile", { + input: z.object({ + username: z.string().optional(), + name: z.string().optional(), + email: z.string().optional(), + bio: z.string().optional(), + avatar: z.string().optional(), + }), + async resolve({ ctx, input }) { + await userModel.updateProfile(ctx.gate, input) + }, + }) diff --git a/web/types.d.ts b/web/types.d.ts index e5cfbe7d..109726b1 100644 --- a/web/types.d.ts +++ b/web/types.d.ts @@ -4,17 +4,13 @@ declare interface ServerEnv { DATABASE_URL: string AUTH_COOKIE_NAME: string OUR_DOMAIN: string - S3_REGION: string - S3_ACCESS_KEY_ID: string - S3_SECRET_ACCESS_KEY: string - S3_BUCKET_NAME: string - S3_ENDPOINT?: string MAILGUN_APIKEY: string MAILGUN_DOMAIN: string - S3_CDN_PREFIX: string FLY_REGION?: string PRIMARY_REGION?: string ENCRYPT_SECRET: string + NEXT_PUBLIC_R2_URL: string + R2_API_TOKEN: string } declare interface BrowserEnv {