From 871ad61820bc5b03360e8c04bbb83bdf465aa7a8 Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 11 Jun 2025 20:56:50 +0200 Subject: [PATCH 1/5] Adding 'uploadthing' and Configure a File Router --- .env.example | 3 + package.json | 2 + pnpm-lock.yaml | 218 ++++++++++++++++++++++++++++++++ src/app/api/uploadthing/core.ts | 25 ++++ 4 files changed, 248 insertions(+) create mode 100644 src/app/api/uploadthing/core.ts diff --git a/.env.example b/.env.example index d0bbdc0..e9b7b09 100644 --- a/.env.example +++ b/.env.example @@ -19,3 +19,6 @@ SINGLESTORE_DATABASE="database" # Clerk NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=some-publishable-key CLERK_SECRET_KEY=some-secret-key + +# Uploadthing +UPLOADTHING_TOKEN='uploadthing-token' diff --git a/package.json b/package.json index 4a3c7b4..527b754 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@libsql/client": "^0.14.0", "@radix-ui/react-slot": "^1.2.3", "@t3-oss/env-nextjs": "^0.12.0", + "@uploadthing/react": "^7.3.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "drizzle-orm": "^0.41.0", @@ -33,6 +34,7 @@ "react": "^19.0.0", "react-dom": "^19.0.0", "tailwind-merge": "^3.3.0", + "uploadthing": "^7.7.2", "zod": "^3.24.2" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 10d9497..0de454c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,6 +20,9 @@ importers: '@t3-oss/env-nextjs': specifier: ^0.12.0 version: 0.12.0(typescript@5.8.3)(zod@3.24.4) + '@uploadthing/react': + specifier: ^7.3.1 + version: 7.3.1(next@15.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)(uploadthing@7.7.2(express@5.1.0)(next@15.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(tailwindcss@4.1.5)) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -47,6 +50,9 @@ importers: tailwind-merge: specifier: ^3.3.0 version: 3.3.0 + uploadthing: + specifier: ^7.7.2 + version: 7.7.2(express@5.1.0)(next@15.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(tailwindcss@4.1.5) zod: specifier: ^3.24.2 version: 3.24.4 @@ -144,6 +150,11 @@ packages: '@drizzle-team/brocli@0.10.2': resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==} + '@effect/platform@0.81.0': + resolution: {integrity: sha512-RZ0pqpSUET0Ab3CBjOhJ12C2/vWLQsy+SLJbGNxjcOm9xZAwQowggWCs4S3ZXhdnNTR5WJHH02WlAWHJDaMKhA==} + peerDependencies: + effect: ^3.14.21 + '@emnapi/core@1.4.3': resolution: {integrity: sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==} @@ -654,6 +665,36 @@ packages: resolution: {integrity: sha512-k/1pb70eD638anoi0e8wUGAlbMJXyvdV4p62Ko+EZ7eBe1xMx8Uhak1R5DgfoofsK5IBBnRwsYGTaLZl+6/+RQ==} engines: {node: '>=18'} + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': + resolution: {integrity: sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==} + cpu: [arm64] + os: [darwin] + + '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3': + resolution: {integrity: sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==} + cpu: [x64] + os: [darwin] + + '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3': + resolution: {integrity: sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==} + cpu: [arm64] + os: [linux] + + '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3': + resolution: {integrity: sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==} + cpu: [arm] + os: [linux] + + '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3': + resolution: {integrity: sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==} + cpu: [x64] + os: [linux] + + '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3': + resolution: {integrity: sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==} + cpu: [x64] + os: [win32] + '@napi-rs/wasm-runtime@0.2.9': resolution: {integrity: sha512-OKRBiajrrxB9ATokgEQoG87Z25c67pCpYcCwmXYX8PBftC9pBfN18gnm/fh1wurSLEKIAt+QRFLFCQISrb66Jg==} @@ -757,6 +798,12 @@ packages: '@rushstack/eslint-patch@1.11.0': resolution: {integrity: sha512-zxnHvoMQVqewTJr/W4pKjF0bMGiKJv1WX7bSrkl46Hg0QjESbzBROWK0Wg4RphzSOS5Jiy7eFimmM3UgMrMZbQ==} + '@standard-schema/spec@1.0.0': + resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} + + '@standard-schema/spec@1.0.0-beta.4': + resolution: {integrity: sha512-d3IxtzLo7P1oZ8s8YNvxzBUXRXojSut8pbPrTYtzsc5sn4+53jVqbk66pQerSZbZSJZQux6LkclB/+8IDordHg==} + '@swc/counter@0.1.3': resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} @@ -1037,6 +1084,22 @@ packages: cpu: [x64] os: [win32] + '@uploadthing/mime-types@0.3.5': + resolution: {integrity: sha512-iYOmod80XXOSe4NVvaUG9FsS91YGPUaJMTBj52Nwu0G2aTzEN6Xcl0mG1rWqXJ4NUH8MzjVqg+tQND5TPkJWhg==} + + '@uploadthing/react@7.3.1': + resolution: {integrity: sha512-yIAFw46ZO/NPb74zpomwn6Hf2ZX/Ws+vNlR4oKNLJ7YtJ+/bqERclzC3xnRVi/pT47ctISlqXQFGiXUn85wg5Q==} + peerDependencies: + next: '*' + react: ^17.0.2 || ^18.0.0 || ^19.0.0 + uploadthing: ^7.2.0 + peerDependenciesMeta: + next: + optional: true + + '@uploadthing/shared@7.1.8': + resolution: {integrity: sha512-OA9ZrTfILOCt1G93wOD7dZmS653z99Nr3isZpIxzBO3y4B2geKFmPjJUZClig2RrAWLKr2VUYToXKfd9D/wP9w==} + accepts@2.0.0: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} @@ -1403,6 +1466,9 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + effect@3.14.21: + resolution: {integrity: sha512-TKR7zfWcuZgEdWd+oIGA8LdREj/c+1Q0wz4pWqQtYT7VHnkW/QQEYCXgrDI5dT6lJgRTgyQAC1bAnpAf6MdjIA==} + emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} @@ -1615,6 +1681,10 @@ packages: resolution: {integrity: sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==} engines: {node: '>= 18'} + fast-check@3.23.2: + resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==} + engines: {node: '>=8.0.0'} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1651,6 +1721,10 @@ packages: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} + file-selector@0.6.0: + resolution: {integrity: sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==} + engines: {node: '>= 12'} + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -1659,6 +1733,9 @@ packages: resolution: {integrity: sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==} engines: {node: '>= 0.8'} + find-my-way-ts@0.1.5: + resolution: {integrity: sha512-4GOTMrpGQVzsCH2ruUn2vmwzV/02zF4q+ybhCIrw/Rkt3L8KWcycdC6aJMctJzwN4fXD4SD5F/4B9Sksh5rE0A==} + find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} @@ -2120,6 +2197,16 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + msgpackr-extract@3.0.3: + resolution: {integrity: sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==} + hasBin: true + + msgpackr@1.11.4: + resolution: {integrity: sha512-uaff7RG9VIC4jacFW9xzL3jc0iM32DNHe4jYVycBcjUePT/Klnfj7pqtWJt9khvDFizmjN2TlYniYmSS2LIaZg==} + + multipasta@0.2.5: + resolution: {integrity: sha512-c8eMDb1WwZcE02WVjHoOmUVk7fnKU/RmUcosHACglrWAuPQsEJv+E8430sXj6jNc1jHw0zrS16aCjQh4BcEb4A==} + mysql2@3.14.1: resolution: {integrity: sha512-7ytuPQJjQB8TNAYX/H2yhL+iQOnIBjAMam361R7UAL0lOVXWjtdrmoL9HYKqKoLp/8UUTRcvo1QPvK9KL7wA8w==} engines: {node: '>= 8.0'} @@ -2178,6 +2265,10 @@ packages: resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + node-gyp-build-optional-packages@5.2.2: + resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==} + hasBin: true + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -2361,6 +2452,9 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + pure-rand@6.1.0: + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + qs@6.14.0: resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} engines: {node: '>=0.6'} @@ -2535,6 +2629,9 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + sqids@0.3.0: + resolution: {integrity: sha512-lOQK1ucVg+W6n3FhRwwSeUijxe93b51Bfz5PMRMihVf1iVkl82ePQG7V5vwrhzB11v0NtsR25PSZRGiSomJaJw==} + sqlstring@2.3.3: resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==} engines: {node: '>= 0.6'} @@ -2701,6 +2798,27 @@ packages: unrs-resolver@1.7.2: resolution: {integrity: sha512-BBKpaylOW8KbHsu378Zky/dGh4ckT/4NW/0SHRABdqRLcQJ2dAOjDo9g97p04sWflm0kqPqpUatxReNV/dqI5A==} + uploadthing@7.7.2: + resolution: {integrity: sha512-Q8rp40vnh8g4+OHvocmf+YVoSi+6RWTKmemo5BgW7R8iFU2O2EYH/n+CIxPzxhYyH/QjhRF/pHoIMcUL/8OtSg==} + engines: {node: '>=18.13.0'} + peerDependencies: + express: '*' + fastify: '*' + h3: '*' + next: '*' + tailwindcss: ^3.0.0 || ^4.0.0-beta.0 + peerDependenciesMeta: + express: + optional: true + fastify: + optional: true + h3: + optional: true + next: + optional: true + tailwindcss: + optional: true + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -2827,6 +2945,13 @@ snapshots: '@drizzle-team/brocli@0.10.2': {} + '@effect/platform@0.81.0(effect@3.14.21)': + dependencies: + effect: 3.14.21 + find-my-way-ts: 0.1.5 + msgpackr: 1.11.4 + multipasta: 0.2.5 + '@emnapi/core@1.4.3': dependencies: '@emnapi/wasi-threads': 1.0.2 @@ -3194,6 +3319,24 @@ snapshots: transitivePeerDependencies: - supports-color + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3': + optional: true + '@napi-rs/wasm-runtime@0.2.9': dependencies: '@emnapi/core': 1.4.3 @@ -3266,6 +3409,10 @@ snapshots: '@rushstack/eslint-patch@1.11.0': {} + '@standard-schema/spec@1.0.0': {} + + '@standard-schema/spec@1.0.0-beta.4': {} + '@swc/counter@0.1.3': {} '@swc/helpers@0.5.15': @@ -3507,6 +3654,23 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.7.2': optional: true + '@uploadthing/mime-types@0.3.5': {} + + '@uploadthing/react@7.3.1(next@15.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)(uploadthing@7.7.2(express@5.1.0)(next@15.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(tailwindcss@4.1.5))': + dependencies: + '@uploadthing/shared': 7.1.8 + file-selector: 0.6.0 + react: 19.1.0 + uploadthing: 7.7.2(express@5.1.0)(next@15.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(tailwindcss@4.1.5) + optionalDependencies: + next: 15.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + + '@uploadthing/shared@7.1.8': + dependencies: + '@uploadthing/mime-types': 0.3.5 + effect: 3.14.21 + sqids: 0.3.0 + accepts@2.0.0: dependencies: mime-types: 3.0.1 @@ -3815,6 +3979,11 @@ snapshots: ee-first@1.1.1: {} + effect@3.14.21: + dependencies: + '@standard-schema/spec': 1.0.0 + fast-check: 3.23.2 + emoji-regex@9.2.2: {} encodeurl@2.0.0: {} @@ -4233,6 +4402,10 @@ snapshots: transitivePeerDependencies: - supports-color + fast-check@3.23.2: + dependencies: + pure-rand: 6.1.0 + fast-deep-equal@3.1.3: {} fast-glob@3.3.1: @@ -4272,6 +4445,10 @@ snapshots: dependencies: flat-cache: 4.0.1 + file-selector@0.6.0: + dependencies: + tslib: 2.8.1 + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -4287,6 +4464,8 @@ snapshots: transitivePeerDependencies: - supports-color + find-my-way-ts@0.1.5: {} + find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -4733,6 +4912,24 @@ snapshots: ms@2.1.3: {} + msgpackr-extract@3.0.3: + dependencies: + node-gyp-build-optional-packages: 5.2.2 + optionalDependencies: + '@msgpackr-extract/msgpackr-extract-darwin-arm64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-darwin-x64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-linux-arm': 3.0.3 + '@msgpackr-extract/msgpackr-extract-linux-arm64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-linux-x64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.3 + optional: true + + msgpackr@1.11.4: + optionalDependencies: + msgpackr-extract: 3.0.3 + + multipasta@0.2.5: {} + mysql2@3.14.1: dependencies: aws-ssl-profiles: 1.1.2 @@ -4795,6 +4992,11 @@ snapshots: fetch-blob: 3.2.0 formdata-polyfill: 4.0.10 + node-gyp-build-optional-packages@5.2.2: + dependencies: + detect-libc: 2.0.4 + optional: true + object-assign@4.1.1: {} object-inspect@1.13.4: {} @@ -4927,6 +5129,8 @@ snapshots: punycode@2.3.1: {} + pure-rand@6.1.0: {} + qs@6.14.0: dependencies: side-channel: 1.1.0 @@ -5174,6 +5378,8 @@ snapshots: source-map@0.6.1: {} + sqids@0.3.0: {} + sqlstring@2.3.3: {} stable-hash@0.0.5: {} @@ -5377,6 +5583,18 @@ snapshots: '@unrs/resolver-binding-win32-ia32-msvc': 1.7.2 '@unrs/resolver-binding-win32-x64-msvc': 1.7.2 + uploadthing@7.7.2(express@5.1.0)(next@15.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(tailwindcss@4.1.5): + dependencies: + '@effect/platform': 0.81.0(effect@3.14.21) + '@standard-schema/spec': 1.0.0-beta.4 + '@uploadthing/mime-types': 0.3.5 + '@uploadthing/shared': 7.1.8 + effect: 3.14.21 + optionalDependencies: + express: 5.1.0 + next: 15.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + tailwindcss: 4.1.5 + uri-js@4.4.1: dependencies: punycode: 2.3.1 diff --git a/src/app/api/uploadthing/core.ts b/src/app/api/uploadthing/core.ts new file mode 100644 index 0000000..ca24b18 --- /dev/null +++ b/src/app/api/uploadthing/core.ts @@ -0,0 +1,25 @@ +import { auth } from "@clerk/nextjs/server"; +import { createUploadthing, type FileRouter } from "uploadthing/next"; +import { UploadThingError } from "uploadthing/server"; + +const f = createUploadthing(); + +export const ourFileRouter = { + imageUploader: f({ image: { maxFileSize: "4MB", maxFileCount: 1 } }) + .middleware(async () => { + const user = await auth(); + + // eslint-disable-next-line @typescript-eslint/only-throw-error + if (!user.userId) throw new UploadThingError("Unauthorized"); + + return { userId: user.userId }; + }) + .onUploadComplete(async ({ metadata, file }) => { + console.log("Upload complete for userId:", metadata.userId); + console.log("file url", file.ufsUrl); + + return { uploadedBy: metadata.userId }; + }), +} satisfies FileRouter; + +export type OurFileRouter = typeof ourFileRouter; From 70644c76f167d6fb3a848fed6c4dc1ff62a607bc Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 11 Jun 2025 20:59:36 +0200 Subject: [PATCH 2/5] Add Database Mutation to Create a File --- src/app/api/uploadthing/core.ts | 5 +++++ src/server/db/mutations.ts | 6 ++++++ 2 files changed, 11 insertions(+) create mode 100644 src/server/db/mutations.ts diff --git a/src/app/api/uploadthing/core.ts b/src/app/api/uploadthing/core.ts index ca24b18..61a82aa 100644 --- a/src/app/api/uploadthing/core.ts +++ b/src/app/api/uploadthing/core.ts @@ -2,6 +2,8 @@ import { auth } from "@clerk/nextjs/server"; import { createUploadthing, type FileRouter } from "uploadthing/next"; import { UploadThingError } from "uploadthing/server"; +import * as mutations from "~/server/db/mutations"; + const f = createUploadthing(); export const ourFileRouter = { @@ -18,6 +20,9 @@ export const ourFileRouter = { console.log("Upload complete for userId:", metadata.userId); console.log("file url", file.ufsUrl); + const uploadedFile = { ...file, parent: 0 }; + await mutations.createFile(uploadedFile, metadata.userId); + return { uploadedBy: metadata.userId }; }), } satisfies FileRouter; diff --git a/src/server/db/mutations.ts b/src/server/db/mutations.ts new file mode 100644 index 0000000..f83722d --- /dev/null +++ b/src/server/db/mutations.ts @@ -0,0 +1,6 @@ +import { db } from "~/server/db"; +import { type File, files_table as filesSchema } from "~/server/db/schema"; + +export function createFile(file: Omit, _userId: string) { + return db.insert(filesSchema).values(file); +} From 130b08fbdfdd91a8ff2799a37d292ab161f823de Mon Sep 17 00:00:00 2001 From: Thomas Date: Fri, 4 Jul 2025 07:46:41 +0200 Subject: [PATCH 3/5] Display an Upload Button --- src/app/api/uploadthing/route.ts | 8 ++++++++ src/app/drive-contents.tsx | 3 +++ src/components/uploadthing.ts | 5 +++++ src/styles/globals.css | 6 +++++- 4 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 src/app/api/uploadthing/route.ts create mode 100644 src/components/uploadthing.ts diff --git a/src/app/api/uploadthing/route.ts b/src/app/api/uploadthing/route.ts new file mode 100644 index 0000000..2c4640a --- /dev/null +++ b/src/app/api/uploadthing/route.ts @@ -0,0 +1,8 @@ +import { createRouteHandler } from "uploadthing/next"; + +import { ourFileRouter } from "./core"; + +// Export routes for Next App Router +export const { GET, POST } = createRouteHandler({ + router: ourFileRouter, +}); diff --git a/src/app/drive-contents.tsx b/src/app/drive-contents.tsx index 24e75ea..6330fac 100644 --- a/src/app/drive-contents.tsx +++ b/src/app/drive-contents.tsx @@ -4,8 +4,10 @@ import { SignedIn, SignedOut, SignInButton, UserButton } from "@clerk/nextjs"; import { ChevronRight } from "lucide-react"; import Link from "next/link"; +import { UploadButton } from "~/components/uploadthing"; import type { File, Folder } from "~/server/db/schema"; import { FileRow, FolderRow } from "./file-row"; +import { UploadButton } from "~/components/uploadthing"; export default function DriveContents(props: { files: File[]; @@ -59,6 +61,7 @@ export default function DriveContents(props: { ))} + ); diff --git a/src/components/uploadthing.ts b/src/components/uploadthing.ts new file mode 100644 index 0000000..f1c1fca --- /dev/null +++ b/src/components/uploadthing.ts @@ -0,0 +1,5 @@ +import { generateUploadButton } from "@uploadthing/react"; + +import type { OurFileRouter } from "~/app/api/uploadthing/core"; + +export const UploadButton = generateUploadButton(); diff --git a/src/styles/globals.css b/src/styles/globals.css index 48b3fb5..cfb177f 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -1,10 +1,14 @@ @import "tailwindcss"; @import "tw-animate-css"; +@import "uploadthing/tw/v4"; +@source "../../node_modules/@uploadthing/react/dist"; /** <-- depends on project structure */ + @custom-variant dark (&:is(.dark *)); @theme { - --font-sans: var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif, + --font-sans: + var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; } From a8dc37da2db907e332c2a4a86636b6426e8e8b13 Mon Sep 17 00:00:00 2001 From: Thomas Date: Fri, 4 Jul 2025 07:54:02 +0200 Subject: [PATCH 4/5] Fix Upload Bug and Refresh Page --- src/app/drive-contents.tsx | 11 +++++++++-- src/server/db/mutations.ts | 4 ++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/app/drive-contents.tsx b/src/app/drive-contents.tsx index 6330fac..f5a8149 100644 --- a/src/app/drive-contents.tsx +++ b/src/app/drive-contents.tsx @@ -3,17 +3,19 @@ import { SignedIn, SignedOut, SignInButton, UserButton } from "@clerk/nextjs"; import { ChevronRight } from "lucide-react"; import Link from "next/link"; +import { useRouter } from "next/navigation"; import { UploadButton } from "~/components/uploadthing"; import type { File, Folder } from "~/server/db/schema"; import { FileRow, FolderRow } from "./file-row"; -import { UploadButton } from "~/components/uploadthing"; export default function DriveContents(props: { files: File[]; folders: Folder[]; parents: Folder[]; }) { + const navigate = useRouter(); + return (
@@ -61,7 +63,12 @@ export default function DriveContents(props: { ))}
- + { + navigate.refresh(); + }} + />
); diff --git a/src/server/db/mutations.ts b/src/server/db/mutations.ts index f83722d..d28a28e 100644 --- a/src/server/db/mutations.ts +++ b/src/server/db/mutations.ts @@ -1,6 +1,6 @@ import { db } from "~/server/db"; import { type File, files_table as filesSchema } from "~/server/db/schema"; -export function createFile(file: Omit, _userId: string) { - return db.insert(filesSchema).values(file); +export function createFile(file: Omit, _userId: string) { + return db.insert(filesSchema).values({ ...file, parent: 1 }); } From be55fc83c56f703b10cb2596b3d39c834f535eb8 Mon Sep 17 00:00:00 2001 From: Thomas Date: Fri, 4 Jul 2025 08:06:51 +0200 Subject: [PATCH 5/5] Update README.md and Metadata --- README.md | 11 ++++++++++- src/app/layout.tsx | 4 ++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e773036..da6e2d9 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ Tracking progress on key features and tasks for the project. - [x] 🛢️ Set up the database and define data models - [x] 🔗 Sync folder open state with the URL - [x] 🔐 Implement user authentication -- [ ] 📁 Enable file upload functionality +- [x] 📁 Enable file upload functionality - [ ] 📊 Add analytics tracking ### 📝 Note from 5-28-2025 @@ -106,3 +106,12 @@ The database and UI are now connected, some improvements to make: - [x] Change folders to link components, remove all client state - [x] Clean up the database and data fetching patterns - [ ] Real homepage + +### 📝 Note from 7-4-2025 + +Uploading a file to '[uploadthing](https://uploadthing.com/)' works, things that +can be approved: + +- [ ] Upload files to the right folder +- [ ] Delete file button +- [ ] Allow files that are not images to be uploaded diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 8d54fde..d442c0e 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -5,8 +5,8 @@ import { type Metadata } from "next"; import { Geist } from "next/font/google"; export const metadata: Metadata = { - title: "Create T3 App", - description: "Generated by create-t3-app", + title: "Drive Tutorial", + description: "It's like Google Drive, but worse!", icons: [{ rel: "icon", url: "/favicon.ico" }], };