From f3eee90be0ce8f9bff0edec0a1178c4f301a7492 Mon Sep 17 00:00:00 2001 From: 0xGeel Date: Wed, 3 May 2023 21:00:25 +0200 Subject: [PATCH 01/18] Uploadthing test --- next.config.js | 8 ++++ package-lock.json | 55 ++++++++++++++++++++++++++- package.json | 3 ++ src/pages/api/uploadthing.ts | 8 ++++ src/pages/upload/index.tsx | 56 ++++++++++++++++++++++++++++ src/utils/uploadthing/index.ts | 3 ++ src/utils/uploadthing/uploadthing.ts | 29 ++++++++++++++ tsconfig.json | 8 +++- 8 files changed, 167 insertions(+), 3 deletions(-) create mode 100644 next.config.js create mode 100644 src/pages/api/uploadthing.ts create mode 100644 src/pages/upload/index.tsx create mode 100644 src/utils/uploadthing/index.ts create mode 100644 src/utils/uploadthing/uploadthing.ts diff --git a/next.config.js b/next.config.js new file mode 100644 index 000000000..9eb8f3122 --- /dev/null +++ b/next.config.js @@ -0,0 +1,8 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + experimental: { + esmExternals: false, + }, +}; + +module.exports = nextConfig; diff --git a/package-lock.json b/package-lock.json index e87219332..8c31fad95 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "@radix-ui/react-tabs": "^1.0.2", "@radix-ui/react-tooltip": "^1.0.3", "@supabase/supabase-js": "^2.11.0", + "@uploadthing/react": "^2.0.1", "axios": "^1.2.3", "axios-rate-limit": "^1.3.0", "class-variance-authority": "^0.4.0", @@ -38,6 +39,7 @@ "pino-logflare": "^0.3.12", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-dropzone": "^14.2.3", "react-fast-marquee": "^1.3.5", "react-hot-toast": "^2.4.0", "react-jazzicon": "^1.0.4", @@ -45,6 +47,7 @@ "siwe": "^2.1.3", "tailwind-merge": "^1.9.0", "tailwindcss-animate": "^1.0.5", + "uploadthing": "^2.0.1", "uuid": "^9.0.0", "wagmi": "~0.10.0" }, @@ -4254,6 +4257,16 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@uploadthing/react": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@uploadthing/react/-/react-2.0.1.tgz", + "integrity": "sha512-wWqQBdFwZkIqlfwUledhzosT0bPrKuC/dS3Vx8iHRS3XsTugEroBEe5wU4bptXNkLk8C9yic/6hu+7lihDgWYg==", + "peerDependencies": { + "react": "^17.0.2 || ^18.0.0", + "react-dropzone": "^14.2.3", + "uploadthing": "2.0.1" + } + }, "node_modules/@wagmi/chains": { "version": "0.1.11", "resolved": "https://registry.npmjs.org/@wagmi/chains/-/chains-0.1.11.tgz", @@ -5894,6 +5907,14 @@ "node": ">=8.0.0" } }, + "node_modules/attr-accept": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz", + "integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==", + "engines": { + "node": ">=4" + } + }, "node_modules/autoprefixer": { "version": "10.4.13", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.13.tgz", @@ -8670,6 +8691,17 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/file-selector": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.6.0.tgz", + "integrity": "sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==", + "dependencies": { + "tslib": "^2.4.0" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -12060,7 +12092,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -12881,7 +12912,6 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -13067,6 +13097,22 @@ "react": "^18.2.0" } }, + "node_modules/react-dropzone": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.2.3.tgz", + "integrity": "sha512-O3om8I+PkFKbxCukfIR3QAGftYXDZfOE2N1mr/7qebQJHs7U+/RSL/9xomJNpRg9kM5h9soQSdf0Gc7OHF5Fug==", + "dependencies": { + "attr-accept": "^2.2.2", + "file-selector": "^0.6.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">= 10.13" + }, + "peerDependencies": { + "react": ">= 16.8 || 18.0.0" + } + }, "node_modules/react-fast-marquee": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/react-fast-marquee/-/react-fast-marquee-1.3.5.tgz", @@ -14591,6 +14637,11 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/uploadthing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/uploadthing/-/uploadthing-2.0.1.tgz", + "integrity": "sha512-KjOfJv8yBndB91UI1n0y2VoBQ3OveplAIB0poyx7lo1CSX6cIOD5y9PFtsD0in+g5WWqakdOj8YKJwUOfvtd9w==" + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", diff --git a/package.json b/package.json index 4da848196..f185c25ed 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "@radix-ui/react-tabs": "^1.0.2", "@radix-ui/react-tooltip": "^1.0.3", "@supabase/supabase-js": "^2.11.0", + "@uploadthing/react": "^2.0.1", "axios": "^1.2.3", "axios-rate-limit": "^1.3.0", "class-variance-authority": "^0.4.0", @@ -46,6 +47,7 @@ "pino-logflare": "^0.3.12", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-dropzone": "^14.2.3", "react-fast-marquee": "^1.3.5", "react-hot-toast": "^2.4.0", "react-jazzicon": "^1.0.4", @@ -53,6 +55,7 @@ "siwe": "^2.1.3", "tailwind-merge": "^1.9.0", "tailwindcss-animate": "^1.0.5", + "uploadthing": "^2.0.1", "uuid": "^9.0.0", "wagmi": "~0.10.0" }, diff --git a/src/pages/api/uploadthing.ts b/src/pages/api/uploadthing.ts new file mode 100644 index 000000000..089447cfe --- /dev/null +++ b/src/pages/api/uploadthing.ts @@ -0,0 +1,8 @@ +import { createNextPageApiHandler } from "uploadthing/server"; +import fileRouter from "@/src/utils/uploadthing"; + +const handler = createNextPageApiHandler({ + router: fileRouter, +}); + +export default handler; diff --git a/src/pages/upload/index.tsx b/src/pages/upload/index.tsx new file mode 100644 index 000000000..7060ea575 --- /dev/null +++ b/src/pages/upload/index.tsx @@ -0,0 +1,56 @@ +import { generateReactHelpers } from "@uploadthing/react"; +import type { OurFileRouter } from "@/src/utils/uploadthing/uploadthing"; +import { PlusIcon } from "@heroicons/react/20/solid"; +import { useRef } from "react"; + +const { useUploadThing } = generateReactHelpers(); + +const MultiUploader = () => { + const { getRootProps, getInputProps, isDragActive, files, startUpload } = + useUploadThing("imageUploader"); + + const inputRef = useRef(null); + + const handleClick = () => { + inputRef.current?.click(); + }; + + return ( +
+
+

Content

+ +
+
+ + + + )} + +
+
+ {files.length === 0 ? ( + <> + ) : ( + + )} +
+
+ ); +}; + +export default MultiUploader; diff --git a/src/utils/uploadthing/index.ts b/src/utils/uploadthing/index.ts new file mode 100644 index 000000000..4edc6bd2b --- /dev/null +++ b/src/utils/uploadthing/index.ts @@ -0,0 +1,3 @@ +import { ourFileRouter } from "./uploadthing"; + +export default ourFileRouter; diff --git a/src/utils/uploadthing/uploadthing.ts b/src/utils/uploadthing/uploadthing.ts new file mode 100644 index 000000000..64a54ea40 --- /dev/null +++ b/src/utils/uploadthing/uploadthing.ts @@ -0,0 +1,29 @@ +import { createFilething, type FileRouter } from "uploadthing/server"; +const f = createFilething(); + +const auth = (req: Request) => ({ id: "fakeId" }); // Fake auth function + +// FileRouter for your app, can contain multiple FileRoutes +export const ourFileRouter = { + // Define as many FileRoutes as you like, each with a unique routeSlug + imageUploader: f + // Set permissions and file types for this FileRoute + .fileTypes(["image", "video"]) + .maxSize("1GB") + .middleware(async (req) => { + // This code runs on your server before upload + const user = await auth(req); + + // If you throw, the user will not be able to upload + if (!user) throw new Error("Unauthorized"); + + // Whatever is returned here is accessible in onUploadComplete as `metadata` + return { userId: user.id }; + }) + .onUploadComplete(async ({ metadata }) => { + // This code RUNS ON YOUR SERVER after upload + console.log("Upload complete for userId:", metadata.userId); + }), +} satisfies FileRouter; + +export type OurFileRouter = typeof ourFileRouter; diff --git a/tsconfig.json b/tsconfig.json index 0da6c3f2a..54edf4f0f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,6 +19,12 @@ "@/*": ["./*"] } }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "jest.config.js"], + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + "jest.config.js", + "next.config.js" + ], "exclude": ["node_modules"] } From c4751c0cf4b4d2554d544da2b72755191c36ba0c Mon Sep 17 00:00:00 2001 From: 0xGeel Date: Wed, 17 May 2023 20:54:41 +0200 Subject: [PATCH 02/18] =?UTF-8?q?Add=20(working)=20LoopgateNode=20backend?= =?UTF-8?q?=20prototype=20=F0=9F=8E=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- env.d.ts | 1 + package-lock.json | 277 ++++++++++++++------------- package.json | 3 +- src/pages/_app.tsx | 1 - src/pages/api/submarine.ts | 74 +++++++ src/pages/api/uploadthing.ts | 8 - src/pages/upload/form.tsx | 54 ++++++ src/pages/upload/index.tsx | 218 +++++++++++++++++---- src/utils/uploadthing/index.ts | 3 - src/utils/uploadthing/uploadthing.ts | 29 --- 10 files changed, 453 insertions(+), 215 deletions(-) create mode 100644 src/pages/api/submarine.ts delete mode 100644 src/pages/api/uploadthing.ts create mode 100644 src/pages/upload/form.tsx delete mode 100644 src/utils/uploadthing/index.ts delete mode 100644 src/utils/uploadthing/uploadthing.ts diff --git a/env.d.ts b/env.d.ts index 98b75d54d..65aa82b65 100644 --- a/env.d.ts +++ b/env.d.ts @@ -8,5 +8,6 @@ namespace NodeJS { NEXT_PUBLIC_SUPABASE_URL: string; NEXT_PUBLIC_LOGFLARE_API_KEY: string; NEXT_PUBLIC_LOGFLARE_SOURCE_TOKEN: string; + LOOPGATE_API_KEY: string; } } diff --git a/package-lock.json b/package-lock.json index 8c31fad95..4ea6a55c8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,6 @@ "@radix-ui/react-tabs": "^1.0.2", "@radix-ui/react-tooltip": "^1.0.3", "@supabase/supabase-js": "^2.11.0", - "@uploadthing/react": "^2.0.1", "axios": "^1.2.3", "axios-rate-limit": "^1.3.0", "class-variance-authority": "^0.4.0", @@ -33,6 +32,7 @@ "date-fns": "^2.29.3", "ethers": "^5.7.2", "iron-session": "^6.3.1", + "jszip": "^3.10.1", "next": "^13.0.0", "pinata-submarine": "^0.1.6", "pino": "^8.11.0", @@ -47,7 +47,6 @@ "siwe": "^2.1.3", "tailwind-merge": "^1.9.0", "tailwindcss-animate": "^1.0.5", - "uploadthing": "^2.0.1", "uuid": "^9.0.0", "wagmi": "~0.10.0" }, @@ -2194,9 +2193,9 @@ } }, "node_modules/@next/env": { - "version": "13.1.4", - "resolved": "https://registry.npmjs.org/@next/env/-/env-13.1.4.tgz", - "integrity": "sha512-x7ydhMpi9/xX7yVK+Fw33OuwwQWVZUFRxenK3z89fmPzQZyUk35Ynb+b7JkrhfRhDIFFvvqpzVSXeseSlBAw7A==" + "version": "13.3.4", + "resolved": "https://registry.npmjs.org/@next/env/-/env-13.3.4.tgz", + "integrity": "sha512-oTK/wRV2qga86m/4VdrR1+/56UA6U1Qv3sIgowB+bZjahniZLEG5BmmQjfoGv7ZuLXBZ8Eec6hkL9BqJcrEL2g==" }, "node_modules/@next/eslint-plugin-next": { "version": "12.3.4", @@ -2212,40 +2211,10 @@ "resolved": "https://registry.npmjs.org/@next/font/-/font-13.1.6.tgz", "integrity": "sha512-AITjmeb1RgX1HKMCiA39ztx2mxeAyxl4ljv2UoSBUGAbFFMg8MO7YAvjHCgFhD39hL7YTbFjol04e/BPBH5RzQ==" }, - "node_modules/@next/swc-android-arm-eabi": { - "version": "13.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-13.1.4.tgz", - "integrity": "sha512-5PAchzFst3In6Ml+9APvBj89H29lcPXcUqEYBVv09fWK/V4IuViKc2qOqM9pyPyw7KsqaZPmuqaG595E6jdZLA==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-android-arm64": { - "version": "13.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-13.1.4.tgz", - "integrity": "sha512-LCLjjRhsQ5fR9ExzR2fqxuyJe/D4Ct/YkdonVfJfqOfkEpFwUTQDOVo5GrQec4LZDk3zY+o6vZYjXbB0nD9VLA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, "node_modules/@next/swc-darwin-arm64": { - "version": "13.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.1.4.tgz", - "integrity": "sha512-LSc/tF1FQ1y1SwKiCdGg8IIl7+Csk6nuLcLIyQXs24UNYjXg5+7vUQXqE8y66v/Dq8qFDC9rM61QhpM9ZDftbg==", + "version": "13.3.4", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.3.4.tgz", + "integrity": "sha512-vux7RWfzxy1lD21CMwZsy9Ej+0+LZdIIj1gEhVmzOQqQZ5N56h8JamrjIVCfDL+Lpj8KwOmFZbPHE8qaYnL2pg==", "cpu": [ "arm64" ], @@ -2258,9 +2227,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "13.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.1.4.tgz", - "integrity": "sha512-WoApDo8xfafrNc9+Mz5MwGFKUwbDHsGqLleTGZ8upegwVqDyHsYzqJQudf+loqhV58oGTOqP1eWaHn2J7dijXA==", + "version": "13.3.4", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.3.4.tgz", + "integrity": "sha512-1tb+6JT98+t7UIhVQpKL7zegKnCs9RKU6cKNyj+DYKuC/NVl49/JaIlmwCwK8Ibl+RXxJrK7uSXSIO71feXsgw==", "cpu": [ "x64" ], @@ -2272,40 +2241,10 @@ "node": ">= 10" } }, - "node_modules/@next/swc-freebsd-x64": { - "version": "13.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-freebsd-x64/-/swc-freebsd-x64-13.1.4.tgz", - "integrity": "sha512-fqNyeT8G4guN8AHPIoBRhGY2GJg89FyWpuwX4o0Y3vUy/84IGZpNst3paCzaYkQSqQE/AuCpkB7hKxkN7ittXw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm-gnueabihf": { - "version": "13.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-13.1.4.tgz", - "integrity": "sha512-MEfm8OC1YR9/tYHUzlQsxcSmiuf8XdO7bqh5VtG4pilScjc5I5t+tQgIDgoDGePfh5W99W23hb3s6oCFrt99rw==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "13.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.1.4.tgz", - "integrity": "sha512-2wgth/KsuODzW/E7jsRoWdhKmE5oZzXcBPvf9RW+ZpBNvYQkEDlzfLA7n8DtxTU8I4oMas0mdEPdCWXrSNnVZw==", + "version": "13.3.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.3.4.tgz", + "integrity": "sha512-UqcKkYTKslf5YAJNtZ5XV1D5MQJIkVtDHL8OehDZERHzqOe7jvy41HFto33IDPPU8gJiP5eJb3V9U26uifqHjw==", "cpu": [ "arm64" ], @@ -2318,9 +2257,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "13.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.1.4.tgz", - "integrity": "sha512-GdWhCRljsT7rNEElEsdu4RRppd+XaQOX1IJslsh/+HU6LsJGUE8tXpa68yJjCsHZHifkbdZNeCr5SYdsN6CbAA==", + "version": "13.3.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.3.4.tgz", + "integrity": "sha512-HE/FmE8VvstAfehyo/XsrhGgz97cEr7uf9IfkgJ/unqSXE0CDshDn/4as6rRid74eDR8/exi7c2tdo49Tuqxrw==", "cpu": [ "arm64" ], @@ -2333,9 +2272,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "13.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.1.4.tgz", - "integrity": "sha512-Rsk/ojwYqMskN2eo5hUSVe7UuMV/aSjmrmJ0BCFGFPfBY9sPgmYj/oXlDDN0y5lJD9acPuiBjknLWgnOnx5JIA==", + "version": "13.3.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.3.4.tgz", + "integrity": "sha512-xU+ugaupGA4SL5aK1ZYEqVHrW3TPOhxVcpaJLfpANm2443J4GfxCmOacu9XcSgy5c51Mq7C9uZ1LODKHfZosRQ==", "cpu": [ "x64" ], @@ -2348,9 +2287,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "13.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.1.4.tgz", - "integrity": "sha512-gKSVPozedA2gpA+vggYnAqpDuzWFed2oxFeXxHw0aW2ALdAZswAinn1ZwXEQ5fHnVguxjZhH0+2nBxpMdF8p5Q==", + "version": "13.3.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.3.4.tgz", + "integrity": "sha512-cZvmf5KcYeTfIK6bCypfmxGUjme53Ep7hx94JJtGrYgCA1VwEuYdh+KouubJaQCH3aqnNE7+zGnVEupEKfoaaA==", "cpu": [ "x64" ], @@ -2363,9 +2302,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "13.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.1.4.tgz", - "integrity": "sha512-+kAXIIVb7Q4LCKmi7dn9qVlG1XUf3Chgj5Rwl0rAP4WBV2TnJIgsOEC24G1Mm3jjif+qXm7SJS9YZ9Yg3Y8sSQ==", + "version": "13.3.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.3.4.tgz", + "integrity": "sha512-7dL+CAUAjmgnVbjXPIpdj7/AQKFqEUL3bKtaOIE1JzJ5UMHHAXCPwzQtibrsvQpf9MwcAmiv8aburD3xH1xf8w==", "cpu": [ "arm64" ], @@ -2378,9 +2317,9 @@ } }, "node_modules/@next/swc-win32-ia32-msvc": { - "version": "13.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.1.4.tgz", - "integrity": "sha512-EsfzAFBVaw1zg1FzlLMgRaTX/DKY+EnAvJ6mCIJMGeSOPIj4Oy6xF2yEQ3VaRkwFpAafHJH6JNB/CGrdKFCMXw==", + "version": "13.3.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.3.4.tgz", + "integrity": "sha512-qplTyzEl1vPkS+/DRK3pKSL0HeXrPHkYsV7U6gboHYpfqoHY+bcLUj3gwVUa9PEHRIoq4vXvPzx/WtzE6q52ng==", "cpu": [ "ia32" ], @@ -2393,9 +2332,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "13.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.1.4.tgz", - "integrity": "sha512-bygNjmnq+F9NqJXh7OfhJgqu6LGU29GNKQYVyZkxY/h5K0WWUvAE/VL+TdyMwbvQr9KByx5XLwORwetLxXCo4g==", + "version": "13.3.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.3.4.tgz", + "integrity": "sha512-usdvZT7JHrTuXC+4OKN5mCzUkviFkCyJJTkEz8jhBpucg+T7s83e7owm3oNFzmj5iKfvxU2St6VkcnSgpFvEYA==", "cpu": [ "x64" ], @@ -3558,9 +3497,9 @@ } }, "node_modules/@swc/helpers": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.14.tgz", - "integrity": "sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.1.tgz", + "integrity": "sha512-sJ902EfIzn1Fa+qYmjdQqh8tPsoxyBz+8yBKC2HKUxyezKJFwPGOn7pv4WY6QuQW//ySQi5lJjA/ZT9sNWWNTg==", "dependencies": { "tslib": "^2.4.0" } @@ -4257,16 +4196,6 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@uploadthing/react": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@uploadthing/react/-/react-2.0.1.tgz", - "integrity": "sha512-wWqQBdFwZkIqlfwUledhzosT0bPrKuC/dS3Vx8iHRS3XsTugEroBEe5wU4bptXNkLk8C9yic/6hu+7lihDgWYg==", - "peerDependencies": { - "react": "^17.0.2 || ^18.0.0", - "react-dropzone": "^14.2.3", - "uploadthing": "2.0.1" - } - }, "node_modules/@wagmi/chains": { "version": "0.1.11", "resolved": "https://registry.npmjs.org/@wagmi/chains/-/chains-0.1.11.tgz", @@ -6467,6 +6396,17 @@ "node": ">=6.14.2" } }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, "node_modules/cacheable-lookup": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", @@ -6893,6 +6833,11 @@ "url": "https://opencollective.com/core-js" } }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, "node_modules/create-hash": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", @@ -9416,6 +9361,11 @@ "node": ">= 4" } }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -11002,6 +10952,49 @@ "node": ">=4.0" } }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jszip/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/keccak": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.3.tgz", @@ -11075,6 +11068,14 @@ "node": ">= 0.8.0" } }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/lilconfig": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz", @@ -11891,12 +11892,13 @@ "dev": true }, "node_modules/next": { - "version": "13.1.4", - "resolved": "https://registry.npmjs.org/next/-/next-13.1.4.tgz", - "integrity": "sha512-g0oBUU+tcOPKbXTVdsDO2adc6wd/ggqauHHysPQJxuIKqZ+fwICGJht0C5D5V0A/77eQDF5EFwNdAHkFvBDsog==", + "version": "13.3.4", + "resolved": "https://registry.npmjs.org/next/-/next-13.3.4.tgz", + "integrity": "sha512-sod7HeokBSvH5QV0KB+pXeLfcXUlLrGnVUXxHpmhilQ+nQYT3Im2O8DswD5e4uqbR8Pvdu9pcWgb1CbXZQZlmQ==", "dependencies": { - "@next/env": "13.1.4", - "@swc/helpers": "0.4.14", + "@next/env": "13.3.4", + "@swc/helpers": "0.5.1", + "busboy": "1.6.0", "caniuse-lite": "^1.0.30001406", "postcss": "8.4.14", "styled-jsx": "5.1.1" @@ -11905,24 +11907,21 @@ "next": "dist/bin/next" }, "engines": { - "node": ">=14.6.0" + "node": ">=16.8.0" }, "optionalDependencies": { - "@next/swc-android-arm-eabi": "13.1.4", - "@next/swc-android-arm64": "13.1.4", - "@next/swc-darwin-arm64": "13.1.4", - "@next/swc-darwin-x64": "13.1.4", - "@next/swc-freebsd-x64": "13.1.4", - "@next/swc-linux-arm-gnueabihf": "13.1.4", - "@next/swc-linux-arm64-gnu": "13.1.4", - "@next/swc-linux-arm64-musl": "13.1.4", - "@next/swc-linux-x64-gnu": "13.1.4", - "@next/swc-linux-x64-musl": "13.1.4", - "@next/swc-win32-arm64-msvc": "13.1.4", - "@next/swc-win32-ia32-msvc": "13.1.4", - "@next/swc-win32-x64-msvc": "13.1.4" - }, - "peerDependencies": { + "@next/swc-darwin-arm64": "13.3.4", + "@next/swc-darwin-x64": "13.3.4", + "@next/swc-linux-arm64-gnu": "13.3.4", + "@next/swc-linux-arm64-musl": "13.3.4", + "@next/swc-linux-x64-gnu": "13.3.4", + "@next/swc-linux-x64-musl": "13.3.4", + "@next/swc-win32-arm64-msvc": "13.3.4", + "@next/swc-win32-ia32-msvc": "13.3.4", + "@next/swc-win32-x64-msvc": "13.3.4" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", "fibers": ">= 3.1.0", "node-sass": "^6.0.0 || ^7.0.0", "react": "^18.2.0", @@ -11930,6 +11929,9 @@ "sass": "^1.3.0" }, "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, "fibers": { "optional": true }, @@ -12306,6 +12308,11 @@ "node": ">=6" } }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -12890,6 +12897,11 @@ "node": ">= 0.6.0" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, "node_modules/process-warning": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-2.2.0.tgz", @@ -13929,6 +13941,14 @@ "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/strict-uri-encode": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", @@ -14637,11 +14657,6 @@ "browserslist": ">= 4.21.0" } }, - "node_modules/uploadthing": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/uploadthing/-/uploadthing-2.0.1.tgz", - "integrity": "sha512-KjOfJv8yBndB91UI1n0y2VoBQ3OveplAIB0poyx7lo1CSX6cIOD5y9PFtsD0in+g5WWqakdOj8YKJwUOfvtd9w==" - }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", diff --git a/package.json b/package.json index f185c25ed..40a2cb572 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,6 @@ "@radix-ui/react-tabs": "^1.0.2", "@radix-ui/react-tooltip": "^1.0.3", "@supabase/supabase-js": "^2.11.0", - "@uploadthing/react": "^2.0.1", "axios": "^1.2.3", "axios-rate-limit": "^1.3.0", "class-variance-authority": "^0.4.0", @@ -41,6 +40,7 @@ "date-fns": "^2.29.3", "ethers": "^5.7.2", "iron-session": "^6.3.1", + "jszip": "^3.10.1", "next": "^13.0.0", "pinata-submarine": "^0.1.6", "pino": "^8.11.0", @@ -55,7 +55,6 @@ "siwe": "^2.1.3", "tailwind-merge": "^1.9.0", "tailwindcss-animate": "^1.0.5", - "uploadthing": "^2.0.1", "uuid": "^9.0.0", "wagmi": "~0.10.0" }, diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index f46dd2188..9e2151b75 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -37,7 +37,6 @@ const App = ({ Component, pageProps }: AppProps) => { fontSize: "14px", border: "1px solid rgba(255,255,255,.2)", maxWidth: "420px", - width: "100%", lineHeight: 1.5, }, }} diff --git a/src/pages/api/submarine.ts b/src/pages/api/submarine.ts new file mode 100644 index 000000000..44c5075bc --- /dev/null +++ b/src/pages/api/submarine.ts @@ -0,0 +1,74 @@ +import type { NextApiRequest, NextApiResponse } from "next"; +import { withSessionRoute } from "@/src/utils/iron-session/withSession"; +import { siwe } from "@/src/utils/siwe"; +import logger from "@/src/utils/logger"; +import axios from "axios"; + +const LOOPGATE_NODE_URL = { + LOCAL: "http://localhost:1337", + PROD: "https://loopgate.up.railway.app", +}; + +const error = { + methodNotAllowed: "Method not allowed, please use a POST request.", + unauthorized: + "You are not authorized to access this resource. Sign In With Ethereum, and try again.", + noBody: + "Your request does not contain a file body. Please add one, and try again.", + unableToUpload: "Unable to upload the file.", + generic: "Internal Server Error", +}; + +type HttpErrorCode = 400 | 401 | 403 | 404 | 405 | 500; + +const handleError = ( + res: NextApiResponse, + httpErrorCode: HttpErrorCode, + message: string +) => { + logger.error(message); + return res.status(httpErrorCode).end(message); +}; + +const handler = async (req: NextApiRequest, res: NextApiResponse) => { + if (req.method !== "POST") { + handleError(res, 405, error.methodNotAllowed); + } + + const siweSesh = await siwe.getSession(req, res); + + if (!siweSesh.address) { + handleError(res, 401, error.unauthorized); + } + + try { + } catch (err) { + handleError(res, 500, error.generic); + } + + // Call LoopGate Node Backend to handle FileUpload + const result = await axios.post( + `${LOOPGATE_NODE_URL.LOCAL}/api/pinata/submarine/upload`, + req, + { + headers: { + "Content-Type": req.headers["content-type"], + "x-api-key": process.env.LOOPGATE_API_KEY, + }, + } + ); + + if (!result) { + handleError(res, 500, error.unableToUpload); + } + + return res.status(200).json(result.data.items); +}; + +export default withSessionRoute(handler); + +export const config = { + api: { + bodyParser: false, + }, +}; diff --git a/src/pages/api/uploadthing.ts b/src/pages/api/uploadthing.ts deleted file mode 100644 index 089447cfe..000000000 --- a/src/pages/api/uploadthing.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { createNextPageApiHandler } from "uploadthing/server"; -import fileRouter from "@/src/utils/uploadthing"; - -const handler = createNextPageApiHandler({ - router: fileRouter, -}); - -export default handler; diff --git a/src/pages/upload/form.tsx b/src/pages/upload/form.tsx new file mode 100644 index 000000000..fe9c377b7 --- /dev/null +++ b/src/pages/upload/form.tsx @@ -0,0 +1,54 @@ +import React, { useState, ChangeEvent, FormEvent } from "react"; +import axios from "axios"; +import { cn } from "@/src/utils/generic"; +import Spinner from "@/src/components/Spinner"; + +// Important: POST header: `Content-Type: multipart/form-data`. + +interface FileUploadState { + files: FileList | null; +} + +export default function FileUploadForm() { + const [file, setFile] = useState(); + const [isLoading, setIsLoading] = useState(false); + + const handleFileChange = (event: ChangeEvent) => { + const files = event.target.files; + setFile({ files }); + }; + + const handleSubmit = async (event: FormEvent) => { + event.preventDefault(); + setIsLoading(true); + + try { + if (file?.files) { + const formData = new FormData(); + formData.append("file", file.files[0]); + + const { data } = await axios.post("/api/submarine", formData); + } + } catch (error) { + console.error(error); + } finally { + setIsLoading(false); + } + }; + + return ( +
+ + + {isLoading && } + + ); +} diff --git a/src/pages/upload/index.tsx b/src/pages/upload/index.tsx index 7060ea575..fea9d3f28 100644 --- a/src/pages/upload/index.tsx +++ b/src/pages/upload/index.tsx @@ -1,56 +1,192 @@ -import { generateReactHelpers } from "@uploadthing/react"; -import type { OurFileRouter } from "@/src/utils/uploadthing/uploadthing"; +import { useState, useCallback, useEffect } from "react"; +import { useDropzone } from "react-dropzone"; +import Spinner from "@/src/components/Spinner"; import { PlusIcon } from "@heroicons/react/20/solid"; -import { useRef } from "react"; +import { ArrowUpTrayIcon, CheckCircleIcon } from "@heroicons/react/24/outline"; +import { cn } from "@/src/utils/generic"; +import { toast } from "react-hot-toast"; +import JSZip from "jszip"; +import { ArrowDownOnSquareIcon } from "@heroicons/react/24/outline"; +import { NoSymbolIcon } from "@heroicons/react/24/solid"; +import axios from "axios"; -const { useUploadThing } = generateReactHelpers(); +// 1. Upload file to Zupload bucket +// 2. Get bucket download link from Zupload +// 3. Pass bucket download link to Pinata Submarine API +// 4. onSuccess => delete file from Zupload bucket -const MultiUploader = () => { - const { getRootProps, getInputProps, isDragActive, files, startUpload } = - useUploadThing("imageUploader"); +const UploadContainer = ({ + children, + className, +}: { + children: React.ReactElement | React.ReactElement[]; + className?: string; +}) => ( +
+ {children} +
+); - const inputRef = useRef(null); +const processFiles = async (files: File[]) => { + if (files.length === 1) { + return files[0]; + } else { + const zip = new JSZip(); - const handleClick = () => { - inputRef.current?.click(); + for (const file of files) { + const fileData = await file.arrayBuffer(); + zip.file(file.name, fileData); + } + + const timestamp = new Date().toISOString().slice(0, 10); + const blob = await zip.generateAsync({ type: "blob" }); + const file = new File([blob], `loopgate-unlockable@${timestamp}.zip`, { + type: "application/zip", + }); + + return file; + } +}; + +const UploadPage = () => { + const [isLoading, setIsLoading] = useState(false); + const [fileUrl, setFileUrl] = useState(); + + const onDrop = async (acceptedFiles: File[]) => { + console.log("onDrop"); + setIsLoading(true); + try { + const file = acceptedFiles[0]; + const response = await axios.post("/api/submarine", file); + if (response) { + console.log("SUCCESS"); + } + console.log(response.data); + setIsLoading(false); + } catch (error) { + console.error(error); + setIsLoading(false); + } }; + // const onDrop = useCallback( + // async (acceptedFiles: File[]) => { + // setIsLoading(true); + // setFileUrl(undefined); + + // if (acceptedFiles.length === 0) { + // setIsLoading(false); + // toast.error("The file or files you tried to upload are not supported."); + // return; + // } + + // const processedFile = await processFiles(acceptedFiles); + + // await upload({ file: processedFile, key: processedFile.name }); + + // const { error: downloadError, url: downloadUrl } = await download( + // processedFile.name + // ); + + // if (!downloadUrl || downloadError) { + // setIsLoading(false); + // console.error(downloadError); + // toast.error("Unable to retrieve bucket download link"); + // return; + // } + + // if (downloadUrl) { + // setFileUrl(downloadUrl); + // setIsLoading(false); + // toast.success("Upload succeeded!"); + // } + // }, + // [download, upload] + // ); + + const { + getRootProps, + getInputProps, + isDragActive, + isDragAccept, + isDragReject, + acceptedFiles, + } = useDropzone({ + onDrop, + accept: { + "image/png": [".jpg", ".jpeg", ".png"], + "text/html": [".html", ".txt"], + }, + }); + return ( -
-
-

Content

- -
-
- - - - )} - -
+
+
+

Content

+
+ {isLoading ? ( + + +

Uploading your file(s)...

+ {/*

+ Submarining your file(s)... (2/2) +

*/} +
+ ) : ( +
+ + <> + {isDragAccept && ( + +

+ Drop it like it' hot +

+ +
+ )} + {isDragReject && ( + + +

+ File format not supported +

+
+ )} + {!isDragActive && ( + + +

+ Click or drag to upload a file +

+
+ )} + +
+ )} + {/*
+
+ +

File(s) hosted on IPFS.

+
+

+ bafybeiehgpaip4f7jafzf7imgannx3nnv3ubaiwp6ph56mlyzijpqxi45m +

+
*/}
- {files.length === 0 ? ( - <> - ) : ( - +
+
+ {fileUrl && ( + + Public Bucket Download + + )}
); }; -export default MultiUploader; +export default UploadPage; diff --git a/src/utils/uploadthing/index.ts b/src/utils/uploadthing/index.ts deleted file mode 100644 index 4edc6bd2b..000000000 --- a/src/utils/uploadthing/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { ourFileRouter } from "./uploadthing"; - -export default ourFileRouter; diff --git a/src/utils/uploadthing/uploadthing.ts b/src/utils/uploadthing/uploadthing.ts deleted file mode 100644 index 64a54ea40..000000000 --- a/src/utils/uploadthing/uploadthing.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { createFilething, type FileRouter } from "uploadthing/server"; -const f = createFilething(); - -const auth = (req: Request) => ({ id: "fakeId" }); // Fake auth function - -// FileRouter for your app, can contain multiple FileRoutes -export const ourFileRouter = { - // Define as many FileRoutes as you like, each with a unique routeSlug - imageUploader: f - // Set permissions and file types for this FileRoute - .fileTypes(["image", "video"]) - .maxSize("1GB") - .middleware(async (req) => { - // This code runs on your server before upload - const user = await auth(req); - - // If you throw, the user will not be able to upload - if (!user) throw new Error("Unauthorized"); - - // Whatever is returned here is accessible in onUploadComplete as `metadata` - return { userId: user.id }; - }) - .onUploadComplete(async ({ metadata }) => { - // This code RUNS ON YOUR SERVER after upload - console.log("Upload complete for userId:", metadata.userId); - }), -} satisfies FileRouter; - -export type OurFileRouter = typeof ourFileRouter; From d173ea3341eced5b904805e5c5a3e60025225a9a Mon Sep 17 00:00:00 2001 From: 0xGeel Date: Wed, 17 May 2023 21:01:08 +0200 Subject: [PATCH 03/18] Cleanup --- next.config.js | 8 -------- src/pages/upload/index.tsx | 5 +++++ tsconfig.json | 8 +------- 3 files changed, 6 insertions(+), 15 deletions(-) delete mode 100644 next.config.js diff --git a/next.config.js b/next.config.js deleted file mode 100644 index 9eb8f3122..000000000 --- a/next.config.js +++ /dev/null @@ -1,8 +0,0 @@ -/** @type {import('next').NextConfig} */ -const nextConfig = { - experimental: { - esmExternals: false, - }, -}; - -module.exports = nextConfig; diff --git a/src/pages/upload/index.tsx b/src/pages/upload/index.tsx index fea9d3f28..f314c550b 100644 --- a/src/pages/upload/index.tsx +++ b/src/pages/upload/index.tsx @@ -1,3 +1,8 @@ +/** + * Note: This is a file left from a bucket exploration (which unfortunately did not work.) + * There are some parts in this code that I'd like to reuse. Beware, it is messy though. + */ + import { useState, useCallback, useEffect } from "react"; import { useDropzone } from "react-dropzone"; import Spinner from "@/src/components/Spinner"; diff --git a/tsconfig.json b/tsconfig.json index 54edf4f0f..0da6c3f2a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,12 +19,6 @@ "@/*": ["./*"] } }, - "include": [ - "next-env.d.ts", - "**/*.ts", - "**/*.tsx", - "jest.config.js", - "next.config.js" - ], + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "jest.config.js"], "exclude": ["node_modules"] } From 050a5e9e3203a4071e866301e1a709af51818922 Mon Sep 17 00:00:00 2001 From: 0xGeel Date: Sun, 21 May 2023 17:46:34 +0200 Subject: [PATCH 04/18] =?UTF-8?q?Create=20unlock=C2=A0overviews=20by=20'ow?= =?UTF-8?q?ner'=20&=20minor=20code=20improvements?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Button/Button.tsx | 6 +- src/components/Footer/FooterLink.tsx | 2 +- src/pages/unlocks/browse.tsx | 30 ++++++++-- src/utils/generic/fetchAllUnlockables.ts | 20 ++++--- src/utils/generic/fetchUnlockableByUuid.ts | 12 ++-- src/utils/generic/findAllUnlockables.ts | 5 ++ src/utils/loopring/_types.ts | 69 ++++++++++++++++++++++ src/utils/loopring/extractNfts.ts | 6 +- src/utils/supabase/index.ts | 4 +- src/utils/supabase/supabase.ts | 4 +- 10 files changed, 126 insertions(+), 32 deletions(-) create mode 100644 src/utils/loopring/_types.ts diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx index 81b3a237a..964c1e456 100644 --- a/src/components/Button/Button.tsx +++ b/src/components/Button/Button.tsx @@ -1,9 +1,9 @@ -interface IButton { - onClick: any; +interface Props { + onClick: () => void; children: JSX.Element | JSX.Element[]; } -const Button = ({ onClick, children }: IButton): React.ReactElement => { +const Button = ({ onClick, children }: Props): React.ReactElement => { return ( + + + - {isLoading && } - + ); -} +}; + +export default FormPage; diff --git a/src/utils/generic/fetchAllUnlockables.ts b/src/utils/generic/fetchAllUnlockables.ts index 273fc5e86..542dfe026 100644 --- a/src/utils/generic/fetchAllUnlockables.ts +++ b/src/utils/generic/fetchAllUnlockables.ts @@ -2,11 +2,11 @@ import Supabase from "../supabase"; import { parseSqlUnlockable } from "../supabase/helpers"; const fetchAllUnlockables = async (owner?: string) => { - let { data: unlockables, error } = await Supabase.from( + const { data: unlockables, error } = await Supabase.from( "unlockables_with_criteria" ) .select(`*`) - .eq("owner", owner ? owner : "*"); + .eq(owner ? "owner" : "", owner); if (error) { throw error; diff --git a/src/utils/siwe/configureSIWE.tsx b/src/utils/siwe/configureSIWE.tsx index b00eda211..d102d92a0 100644 --- a/src/utils/siwe/configureSIWE.tsx +++ b/src/utils/siwe/configureSIWE.tsx @@ -121,6 +121,9 @@ const verifyRoute = async ( if (fields.data.nonce !== session.nonce) { return res.status(422).end("Invalid nonce."); } + + // @GEEL TODO: See if siweMessage.verify({sig, provider}) can be extended + // to persist the user's Loop API key for API requests on their behalf session.address = fields.data.address; session.chainId = fields.data.chainId; await session.save(); diff --git a/src/utils/supabase/supabase.ts b/src/utils/supabase/supabase.ts index 9b438f9fd..91aac13be 100644 --- a/src/utils/supabase/supabase.ts +++ b/src/utils/supabase/supabase.ts @@ -6,4 +6,11 @@ const Supabase = createClient( process.env.NEXT_PUBLIC_SUPABASE_ANON || "Undefined supabase Anon" ); +export const getServiceSupabase = () => { + createClient( + process.env.NEXT_PUBLIC_SUPABASE_ANON || "", + process.env.SUPABASE_SERVICE_ROLE + ); +}; + export default Supabase; diff --git a/src/utils/supabase/types.ts b/src/utils/supabase/types.ts index c423afe70..f34db9f96 100644 --- a/src/utils/supabase/types.ts +++ b/src/utils/supabase/types.ts @@ -4,131 +4,154 @@ export type Json = | boolean | null | { [key: string]: Json } - | Json[] + | Json[]; export interface Database { public: { Tables: { + "calcium-crew-holders": { + Row: { + amount: number; + cc_ids: string[]; + created_at: string; + eth_address: string; + id: number; + }; + Insert: { + amount: number; + cc_ids: string[]; + created_at?: string; + eth_address: string; + id?: number; + }; + Update: { + amount?: number; + cc_ids?: string[]; + created_at?: string; + eth_address?: string; + id?: number; + }; + }; content_types: { Row: { - content_id: number - content_name: string - } + content_id: number; + content_name: string; + }; Insert: { - content_id?: number - content_name: string - } + content_id?: number; + content_name: string; + }; Update: { - content_id?: number - content_name?: string - } - } + content_id?: number; + content_name?: string; + }; + }; roles: { Row: { - role_id: number - role_name: string - } + role_id: number; + role_name: string; + }; Insert: { - role_id?: number - role_name: string - } + role_id?: number; + role_name: string; + }; Update: { - role_id?: number - role_name?: string - } - } + role_id?: number; + role_name?: string; + }; + }; unlock_criteria: { Row: { - id: number - nft_id: string - owner: string - unlockable_id: string - updated_at: string | null - } + id: number; + nft_id: string; + owner: string; + unlockable_id: string; + updated_at: string | null; + }; Insert: { - id?: number - nft_id: string - owner: string - unlockable_id: string - updated_at?: string | null - } + id?: number; + nft_id: string; + owner: string; + unlockable_id: string; + updated_at?: string | null; + }; Update: { - id?: number - nft_id?: string - owner?: string - unlockable_id?: string - updated_at?: string | null - } - } + id?: number; + nft_id?: string; + owner?: string; + unlockable_id?: string; + updated_at?: string | null; + }; + }; unlockables: { Row: { - content_type_id: number - content_url: string - criteria_unlock_amount: number - description: string | null - id: string - name: string | null - owner: string - updated_at: string | null - } + content_type_id: number; + content_url: string; + criteria_unlock_amount: number; + description: string | null; + id: string; + name: string | null; + owner: string; + updated_at: string | null; + }; Insert: { - content_type_id: number - content_url: string - criteria_unlock_amount: number - description?: string | null - id?: string - name?: string | null - owner: string - updated_at?: string | null - } + content_type_id: number; + content_url: string; + criteria_unlock_amount: number; + description?: string | null; + id?: string; + name?: string | null; + owner: string; + updated_at?: string | null; + }; Update: { - content_type_id?: number - content_url?: string - criteria_unlock_amount?: number - description?: string | null - id?: string - name?: string | null - owner?: string - updated_at?: string | null - } - } + content_type_id?: number; + content_url?: string; + criteria_unlock_amount?: number; + description?: string | null; + id?: string; + name?: string | null; + owner?: string; + updated_at?: string | null; + }; + }; users: { Row: { - eth_address: string - role_id: number - } + eth_address: string; + role_id: number; + }; Insert: { - eth_address: string - role_id: number - } + eth_address: string; + role_id: number; + }; Update: { - eth_address?: string - role_id?: number - } - } - } + eth_address?: string; + role_id?: number; + }; + }; + }; Views: { unlockables_with_criteria: { Row: { - content_url: string | null - criteria_unlock_amount: number | null - description: string | null - id: string | null - name: string | null - nft_ids: string | null - owner: string | null - updated_at: string | null - } - } - } + content_url: string | null; + criteria_unlock_amount: number | null; + description: string | null; + id: string | null; + name: string | null; + nft_ids: string | null; + owner: string | null; + updated_at: string | null; + }; + }; + }; Functions: { - [_ in never]: never - } + [_ in never]: never; + }; Enums: { - [_ in never]: never - } + [_ in never]: never; + }; CompositeTypes: { - [_ in never]: never - } - } + [_ in never]: never; + }; + }; } From b412cd520f5747cf0e6b6ab7922b5a6681b29117 Mon Sep 17 00:00:00 2001 From: 0xGeel Date: Tue, 23 May 2023 23:28:17 +0200 Subject: [PATCH 06/18] Update CC types --- .github/workflows/main.yml | 4 +- .github/workflows/pr.yml | 4 +- src/middleware/checkAuthentication.ts | 2 +- src/utils/supabase/types.ts | 222 +++++++++++--------------- 4 files changed, 102 insertions(+), 130 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4fbecf03d..0a621c03f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,8 +8,8 @@ jobs: main: runs-on: ubuntu-latest env: - SUPABASE_URL: ${{ secrets.SUPABASE_URL }} - SUPABASE_ANON: ${{ secrets.SUPABASE_ANON }} + NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.NEXT_PUBLIC_SUPABASE_URL }} + NEXT_PUBLIC_SUPABASE_ANON: ${{ secrets.NEXT_PUBLIC_SUPABASE_ANON }} steps: - name: 🏗 Setup repo uses: actions/checkout@v3 diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 105c23125..48c01776c 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -6,8 +6,8 @@ jobs: main: runs-on: ubuntu-latest env: - SUPABASE_URL: ${{ secrets.SUPABASE_URL }} - SUPABASE_ANON: ${{ secrets.SUPABASE_ANON }} + NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.NEXT_PUBLIC_SUPABASE_URL }} + NEXT_PUBLIC_SUPABASE_ANON: ${{ secrets.NEXT_PUBLIC_SUPABASE_ANON }} steps: - name: 🏗 Setup repo uses: actions/checkout@v3 diff --git a/src/middleware/checkAuthentication.ts b/src/middleware/checkAuthentication.ts index af19c1d26..b196ac238 100644 --- a/src/middleware/checkAuthentication.ts +++ b/src/middleware/checkAuthentication.ts @@ -41,7 +41,7 @@ export async function checkAuthentication( const siweSesh = await getSiweSesh(); try { - const { data } = await Supabase.from("calcium-crew-holders") + const { data } = await Supabase.from("calcium_crew_holders") .select("*") .eq("eth_address", siweSesh.address?.toLowerCase()) .single(); diff --git a/src/utils/supabase/types.ts b/src/utils/supabase/types.ts index f34db9f96..2a730ce89 100644 --- a/src/utils/supabase/types.ts +++ b/src/utils/supabase/types.ts @@ -4,154 +4,126 @@ export type Json = | boolean | null | { [key: string]: Json } - | Json[]; + | Json[] export interface Database { public: { Tables: { - "calcium-crew-holders": { + calcium_crew_holders: { Row: { - amount: number; - cc_ids: string[]; - created_at: string; - eth_address: string; - id: number; - }; + amount: number + cc_ids: string[] + created_at: string + eth_address: string + id: number + } Insert: { - amount: number; - cc_ids: string[]; - created_at?: string; - eth_address: string; - id?: number; - }; + amount: number + cc_ids: string[] + created_at?: string + eth_address: string + id?: number + } Update: { - amount?: number; - cc_ids?: string[]; - created_at?: string; - eth_address?: string; - id?: number; - }; - }; + amount?: number + cc_ids?: string[] + created_at?: string + eth_address?: string + id?: number + } + } content_types: { Row: { - content_id: number; - content_name: string; - }; + content_id: number + content_name: string + } Insert: { - content_id?: number; - content_name: string; - }; + content_id?: number + content_name: string + } Update: { - content_id?: number; - content_name?: string; - }; - }; - roles: { - Row: { - role_id: number; - role_name: string; - }; - Insert: { - role_id?: number; - role_name: string; - }; - Update: { - role_id?: number; - role_name?: string; - }; - }; + content_id?: number + content_name?: string + } + } unlock_criteria: { Row: { - id: number; - nft_id: string; - owner: string; - unlockable_id: string; - updated_at: string | null; - }; + id: number + nft_id: string + owner: string + unlockable_id: string + updated_at: string | null + } Insert: { - id?: number; - nft_id: string; - owner: string; - unlockable_id: string; - updated_at?: string | null; - }; + id?: number + nft_id: string + owner: string + unlockable_id: string + updated_at?: string | null + } Update: { - id?: number; - nft_id?: string; - owner?: string; - unlockable_id?: string; - updated_at?: string | null; - }; - }; + id?: number + nft_id?: string + owner?: string + unlockable_id?: string + updated_at?: string | null + } + } unlockables: { Row: { - content_type_id: number; - content_url: string; - criteria_unlock_amount: number; - description: string | null; - id: string; - name: string | null; - owner: string; - updated_at: string | null; - }; - Insert: { - content_type_id: number; - content_url: string; - criteria_unlock_amount: number; - description?: string | null; - id?: string; - name?: string | null; - owner: string; - updated_at?: string | null; - }; - Update: { - content_type_id?: number; - content_url?: string; - criteria_unlock_amount?: number; - description?: string | null; - id?: string; - name?: string | null; - owner?: string; - updated_at?: string | null; - }; - }; - users: { - Row: { - eth_address: string; - role_id: number; - }; + content_type_id: number + content_url: string + criteria_unlock_amount: number + description: string | null + id: string + name: string | null + owner: string + updated_at: string | null + } Insert: { - eth_address: string; - role_id: number; - }; + content_type_id: number + content_url: string + criteria_unlock_amount: number + description?: string | null + id?: string + name?: string | null + owner: string + updated_at?: string | null + } Update: { - eth_address?: string; - role_id?: number; - }; - }; - }; + content_type_id?: number + content_url?: string + criteria_unlock_amount?: number + description?: string | null + id?: string + name?: string | null + owner?: string + updated_at?: string | null + } + } + } Views: { unlockables_with_criteria: { Row: { - content_url: string | null; - criteria_unlock_amount: number | null; - description: string | null; - id: string | null; - name: string | null; - nft_ids: string | null; - owner: string | null; - updated_at: string | null; - }; - }; - }; + content_url: string | null + criteria_unlock_amount: number | null + description: string | null + id: string | null + name: string | null + nft_ids: string | null + owner: string | null + updated_at: string | null + } + } + } Functions: { - [_ in never]: never; - }; + [_ in never]: never + } Enums: { - [_ in never]: never; - }; + [_ in never]: never + } CompositeTypes: { - [_ in never]: never; - }; - }; + [_ in never]: never + } + } } From 777aa5b8ed12a7ca0c9775df3e34cb6864df4578 Mon Sep 17 00:00:00 2001 From: 0xGeel Date: Fri, 26 May 2023 15:58:54 +0200 Subject: [PATCH 07/18] Cleanup: Simplify existing API routes --- docs/setup/4-RUNNING.md | 2 +- docs/setup/5-NETLIFY.md | 2 +- .../UnlockCard/UnlockSection.tsx | 6 +- src/middleware/checkAuthentication.ts | 2 + src/middleware/handleError.ts | 2 +- src/middleware/loopgateError.ts | 30 ++++++ src/pages/api/checkUnlockable.ts | 88 ----------------- src/pages/api/getHoldersForNftId.ts | 55 ----------- src/pages/api/getUserUnlocks.ts | 70 -------------- .../check-env-status.ts} | 2 +- .../{generateUuid.ts => generate-uuid.ts} | 2 +- ...mpleUnlockable.ts => sample-unlockable.ts} | 4 +- src/pages/api/nft/get-holders-nft-id.ts | 43 +++++++++ .../get-holders-nftdata.ts} | 14 +-- .../{getNftData.ts => nft/get-nft-data.ts} | 16 +--- .../{getUserNfts.ts => nft/get-user-nfts.ts} | 16 +--- src/pages/api/upload/createUnlockable.ts | 50 ---------- src/pages/api/upload/updateUnlockable.ts | 94 ------------------- src/pages/unlocks/[uuid].tsx | 2 +- src/pages/upload/form.tsx | 11 +-- src/utils/pinata/generateAccessLink.ts | 6 +- src/utils/pinata/index.ts | 4 +- src/utils/pinata/listFolderContent.ts | 4 +- src/utils/pinata/{sub.ts => submarine.ts} | 6 +- .../supabase/helpers/parseNftIdString.ts | 9 +- src/utils/supabase/supabase.ts | 13 +-- 26 files changed, 126 insertions(+), 427 deletions(-) delete mode 100644 src/pages/api/checkUnlockable.ts delete mode 100644 src/pages/api/getHoldersForNftId.ts delete mode 100644 src/pages/api/getUserUnlocks.ts rename src/pages/api/{env-status.ts => helpers/check-env-status.ts} (93%) rename src/pages/api/helpers/{generateUuid.ts => generate-uuid.ts} (90%) rename src/pages/api/helpers/{sampleUnlockable.ts => sample-unlockable.ts} (94%) create mode 100644 src/pages/api/nft/get-holders-nft-id.ts rename src/pages/api/{getNftHolders.ts => nft/get-holders-nftdata.ts} (50%) rename src/pages/api/{getNftData.ts => nft/get-nft-data.ts} (57%) rename src/pages/api/{getUserNfts.ts => nft/get-user-nfts.ts} (50%) delete mode 100644 src/pages/api/upload/createUnlockable.ts delete mode 100644 src/pages/api/upload/updateUnlockable.ts rename src/utils/pinata/{sub.ts => submarine.ts} (55%) diff --git a/docs/setup/4-RUNNING.md b/docs/setup/4-RUNNING.md index 1842daa3f..0e88ca210 100644 --- a/docs/setup/4-RUNNING.md +++ b/docs/setup/4-RUNNING.md @@ -21,7 +21,7 @@ You can access it by opening a browser and going to the following URL: \ Now your LoopGate is running on your local machine, it's time to check the `.env` file. -- Go to the following url: [http://localhost:3000/api/helpers/checkEnvStatus](http://localhost:3000/api/helpers/checkEnvStatus). +- Go to the following url: [http://localhost:3000/api/helpers/check-env-status](http://localhost:3000/api/helpers/checkEnvStatus). This checks the secrets in your `.env` file: if these are misconfigured, LoopGate will not work. diff --git a/docs/setup/5-NETLIFY.md b/docs/setup/5-NETLIFY.md index 6a204768e..0ae73e86a 100644 --- a/docs/setup/5-NETLIFY.md +++ b/docs/setup/5-NETLIFY.md @@ -30,7 +30,7 @@ However, there is still one important step: adding the environment secrets to Ne 1. Go to your project's the 'env' settings of your project's Netlify page: [https://app.netlify.com/sites/YOUR_PROJECT_NAME/settings/env](https://app.netlify.com/sites/YOUR_PROJECT_NAME/settings/env) 2. Click on 'Add a variable', then 'import from a .env file'. 3. Copy the contents of your `.env` file, and paste them in the input field. Click on 'Import variables'. -4. Once more, check the `/api/helpers/checkEnvStatus` endpoint to see if all secrets are defined. Your site should be live soon at [https://YOUR_PROJECT_NAME.netlify.app/api/helpers/checkEnvStatus](https://YOUR_PROJECT_NAME.netlify.app/api/helpers/checkEnvStatus) +4. Once more, check the `/api/helpers/checkEnvStatus` endpoint to see if all secrets are defined. Your site should be live soon at [https://YOUR_PROJECT_NAME.netlify.app/api/helpers/checkEnvStatus](https://YOUR_PROJECT_NAME.netlify.app/api/helpers/check-env-status) {% hint style="info" %} Your Netlify website will most likely have an auto-generated name like '[https://adjective-noun-12345.netlify.app](https://adjective-noun-12345.netlify.app)'. You can easily change the domain name in Netlify! diff --git a/src/components/UnlockablePage/UnlockCard/UnlockSection.tsx b/src/components/UnlockablePage/UnlockCard/UnlockSection.tsx index 148394be1..f9bb454c3 100644 --- a/src/components/UnlockablePage/UnlockCard/UnlockSection.tsx +++ b/src/components/UnlockablePage/UnlockCard/UnlockSection.tsx @@ -24,7 +24,7 @@ const UnlockSection = ({ unlockable }: Props) => { ) => { axios .get( - `/api/checkUnlockable?address=${address}&unlockableId=${unlockable.id}` + `/api/unlockable/verify-access?address=${address}&unlockableId=${unlockable.id}` ) .then((data) => { setUnlockedContent(data.data.unlock); @@ -32,8 +32,8 @@ const UnlockSection = ({ unlockable }: Props) => { }) .catch((error) => { setIsLoading(false); - logger.error(error.request.response); - toast.error(error.request.response); + logger.error(error.response.data.message); + toast.error(error.response.data.message); }); }; diff --git a/src/middleware/checkAuthentication.ts b/src/middleware/checkAuthentication.ts index b196ac238..632580566 100644 --- a/src/middleware/checkAuthentication.ts +++ b/src/middleware/checkAuthentication.ts @@ -46,6 +46,8 @@ export async function checkAuthentication( .eq("eth_address", siweSesh.address?.toLowerCase()) .single(); + console.log(data); + if ( !data?.amount || data.amount < parseInt(process.env.LOOPGATE_CC_THRESHOLD, 10) diff --git a/src/middleware/handleError.ts b/src/middleware/handleError.ts index 87f877e19..a3539f7f0 100644 --- a/src/middleware/handleError.ts +++ b/src/middleware/handleError.ts @@ -4,5 +4,5 @@ import { Error } from "./loopgateError"; export const handleError = (res: NextApiResponse, error: Error) => { logger.error(error[1]); - return res.status(error[0]).send(error[1]); + return res.status(error[0]).send({ message: error[1] }); }; diff --git a/src/middleware/loopgateError.ts b/src/middleware/loopgateError.ts index 6daf83de6..608193e47 100644 --- a/src/middleware/loopgateError.ts +++ b/src/middleware/loopgateError.ts @@ -14,4 +14,34 @@ export const LoopgateError: { [key: string]: Error } = { "Bad request. Check the parameters or body before trying again.", ], server: [500, "Internal server error"], + noAddressProvided: [ + 400, + "Invalid Request: 0x address not provided. Please provide a valid 0x address and try again.", + ], + noLoopringAccount: [ + 400, + "No Loopring Account could be found for the connected 0x address. Is your L2 account activated?", + ], + noUnlockableFound: [ + 404, + "Unable to find the Unlockable for the specified UUID.", + ], + noNftsFound: [404, "Unable to find any NFTs in your wallet."], + noPinataContentFound: [ + 404, + "Unable to find the submarined content on Pinata. It may be deleted.", + ], + unlockReqNotMet: [ + 405, + "Your connected wallet does not meet the unlock requirements.", + ], + noTheGraphData: [ + 404, + "Unable to retrieve data from TheGraph with this NFT ID. Please provide a valid Loopring NFT ID, and try again.", + ], + noLoopringDataFound: [ + 400, + "Unable to retrieve data from the Loopring API with this NFT ID. Please provide a valid Loopring NFT ID, and try again.", + ], + noHoldersFound: [404, "Unable to find holders for this NFT ID."], }; diff --git a/src/pages/api/checkUnlockable.ts b/src/pages/api/checkUnlockable.ts deleted file mode 100644 index 0cd918050..000000000 --- a/src/pages/api/checkUnlockable.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { NextApiRequest, NextApiResponse } from "next"; -import { siwe } from "@/src/utils/siwe"; -import { getUserAddress, getAllUserNftIds } from "@/src/utils/loopring"; -import { findUnlockableByUuid } from "@/src/utils/generic"; -import { getPinataIndexLink } from "@/src/utils/pinata"; -import { withSessionRoute } from "@/src/utils/iron-session/withSession"; -import logger from "@/src/utils/logger"; - -const handler = async (req: NextApiRequest, res: NextApiResponse) => { - const query = req.query; - const { address, unlockableId } = query; - const siweSesh = await siwe.getSession(req, res); - - // Check validity of request - if ( - !address || - Array.isArray(address) || - !unlockableId || - Array.isArray(unlockableId) - ) { - const errorMsg = - "Invalid Request: 0x address not provided. Please provide a valid 0x address and try again."; - logger.error(errorMsg); - return res.status(400).send(errorMsg); - } - - // Check if there is a session. Only connected users may call this endpoint. - if (!siweSesh.address || siweSesh.address !== address) { - const errorMsg = - "You are not authorized to access this resource. Sign In With Ethereum, and try again."; - logger.error(errorMsg); - return res.status(401).send(errorMsg); - } - - // 1️⃣ Call the Loopring API to find the User's Loopring Account ID - const accountId = await getUserAddress(address); - - if (!accountId) { - const errorMsg = - "No Loopring Account could be found for the connected 0x address. Is your L2 account activated?"; - logger.error(errorMsg); - return res.status(400).send(errorMsg); - } - - const unlockable = findUnlockableByUuid(unlockableId); - if (!unlockable) { - const errorMsg = "Unable to find the Unlockable for the specified UUID."; - logger.error(errorMsg); - return res.status(404).send(errorMsg); - } - - // 2️⃣ Call the Loopring API to find the NFTs held by the user - const userNftIds = await getAllUserNftIds(accountId); - - if (!userNftIds) { - const errorMsg = "Unable to find any NFTs for the specified 0x address."; - logger.error(errorMsg); - return res.status(400).send(errorMsg); - } - - // 3️⃣ Check if the user meets the unlock criteria - const intersection = unlockable.unlockCriteria.nftId.filter((value) => - userNftIds.includes(value) - ); - - if (intersection.length < unlockable.unlockCriteria.unlockAmount) { - const errorMsg = - "Your connected wallet does not meet the unlock requirements."; - logger.error(errorMsg); - return res.status(405).send(errorMsg); - } - - // 4️⃣ Get access link for the unlockable - const unlock = await getPinataIndexLink(unlockable.content.url); - - if (!unlock) { - const errorMsg = - "Unable to find the submarined content on Pinata. It may be deleted."; - logger.error(errorMsg); - return res.status(404).send(errorMsg); - } - - return res.status(200).json({ - unlock: unlock, - }); -}; - -export default withSessionRoute(handler); diff --git a/src/pages/api/getHoldersForNftId.ts b/src/pages/api/getHoldersForNftId.ts deleted file mode 100644 index 17a5b6f4d..000000000 --- a/src/pages/api/getHoldersForNftId.ts +++ /dev/null @@ -1,55 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from "next"; -import { - getMinterAndToken, - getNftData, - getNftHolders, -} from "@/src/utils/loopring"; -import logger from "@/src/utils/logger"; - -// Request holders for a NFT held on Loopring -const handler = async (req: NextApiRequest, res: NextApiResponse) => { - const query = req.query; - const { nftId } = query; - - // Check if multiple or no Account IDs are specified. If so: early return. - if (!nftId || Array.isArray(nftId)) { - const errorMsg = "Unable to find data for the NFT ID you supplied."; - logger.error(errorMsg); - return res.status(400).send(errorMsg); - } - - // Call TheGraph API to find NFT Datas for a NFT ID - const theGraphRes = await getMinterAndToken(nftId); - - if (!theGraphRes) { - const errorMsg = - "Unable to retrieve data from TheGraph with this NFT ID. Please provide a valid Loopring NFT ID, and try again."; - logger.error(errorMsg); - return res.status(404).send(errorMsg); - } - - const nftDataRes = await getNftData( - theGraphRes.minter, - theGraphRes.tokenAddress, - nftId - ); - - if (!nftDataRes) { - const errorMsg = - "Unable to retrieve data from the Loopring API with this NFT ID. Please provide a valid Loopring NFT ID, and try again."; - logger.error(errorMsg); - return res.status(400).send(errorMsg); - } - - const holders = await getNftHolders(nftDataRes.nftData); - - if (!holders) { - const errorMsg = "Unable to find holders for this NFT ID."; - logger.error(errorMsg); - return res.status(404).send(errorMsg); - } - - return res.status(200).json(holders); -}; - -export default handler; diff --git a/src/pages/api/getUserUnlocks.ts b/src/pages/api/getUserUnlocks.ts deleted file mode 100644 index 23e6e7aa1..000000000 --- a/src/pages/api/getUserUnlocks.ts +++ /dev/null @@ -1,70 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from "next"; -import { findUnlockedCids } from "../../utils/generic"; -import { getAllUserNftIds, getUserAddress } from "../../utils/loopring"; -import { getPinataIndexLink } from "../../utils/pinata"; -import { withSessionRoute } from "@/src/utils/iron-session/withSession"; -import { siwe } from "@/src/utils/siwe"; -import logger from "@/src/utils/logger"; - -// Summary of what happens: -// 1️⃣ Call the Loopring API to find the User's Loopring Account ID -// 2️⃣ Call the Loopring API to find the NFTs held by the user -// 3️⃣ Check the user's NFT IDs against the config.ts to determine unlocks -// 4️⃣ Call the Pinata API for each CID the user should have access to -const handler = async (req: NextApiRequest, res: NextApiResponse) => { - const query = req.query; - const { address } = query; - const siweSesh = await siwe.getSession(req, res); - - // Check if multiple or no Account IDs are specified. If so: early return. - if (!address || Array.isArray(address)) { - const errorMsg = - "0x address not provided. Please provide a valid 0x address and try again."; - logger.error(errorMsg); - return res.status(400).send(errorMsg); - } - - // Check if there is a session. Only connected users may call this endpoint. - if (!siweSesh.address || siweSesh.address !== address) { - const errorMsg = - "You are not authorized to access this resource. Sign In With Ethereum, and try again."; - logger.error(errorMsg); - return res.status(401).send(errorMsg); - } - - // 1️⃣ Call the Loopring API to find the User's Loopring Account ID - const accountId = await getUserAddress(address); - - if (!accountId) { - const errorMsg = `No Loopring Account could be found for ${address}. Is your L2 account activated?`; - logger.error(errorMsg); - return res.status(400).send(errorMsg); - } - - // 2️⃣ Call the Loopring API to find the NFTs held by the user - const allNftIds = await getAllUserNftIds(accountId); - - if (!allNftIds) { - const errorMsg = `Unable to find any NFTs for ${address}.`; - logger.error(errorMsg); - return res.status(404).send(errorMsg); - } - - // 3️⃣ Check the user's NFT IDs against the config.ts to determine unlocks - const cids = findUnlockedCids(allNftIds); - - // 4️⃣ Call the Pinata API for each CID the user should have access to - const unlocks = await Promise.all( - cids.map(async (item) => { - return await getPinataIndexLink(item); - }) - ); - - if (!unlocks) { - return res.status(200).json({ unlocks: [] }); // API calls succeeded, but there are no unlocks. - } - - return res.status(200).json({ unlocks: unlocks }); // User has unlocks -}; - -export default withSessionRoute(handler); diff --git a/src/pages/api/env-status.ts b/src/pages/api/helpers/check-env-status.ts similarity index 93% rename from src/pages/api/env-status.ts rename to src/pages/api/helpers/check-env-status.ts index ee8896022..564d2f508 100644 --- a/src/pages/api/env-status.ts +++ b/src/pages/api/helpers/check-env-status.ts @@ -1,7 +1,7 @@ import type { NextApiRequest, NextApiResponse } from "next"; // Checks the .env file to spot undefined values. Helps prevent issues. -const handler = async (req: NextApiRequest, res: NextApiResponse) => { +const handler = async (_req: NextApiRequest, res: NextApiResponse) => { const envsToCheck = [ "NEXT_PUBLIC_PINATA_GATEWAY_URL", "PINATA_SUBMARINE_KEY", diff --git a/src/pages/api/helpers/generateUuid.ts b/src/pages/api/helpers/generate-uuid.ts similarity index 90% rename from src/pages/api/helpers/generateUuid.ts rename to src/pages/api/helpers/generate-uuid.ts index 1deb4e001..5d458a291 100644 --- a/src/pages/api/helpers/generateUuid.ts +++ b/src/pages/api/helpers/generate-uuid.ts @@ -2,7 +2,7 @@ import { v4 as uuidv4 } from "uuid"; import type { NextApiRequest, NextApiResponse } from "next"; export default async function handler( - req: NextApiRequest, + _req: NextApiRequest, res: NextApiResponse ) { const uuid = uuidv4(); diff --git a/src/pages/api/helpers/sampleUnlockable.ts b/src/pages/api/helpers/sample-unlockable.ts similarity index 94% rename from src/pages/api/helpers/sampleUnlockable.ts rename to src/pages/api/helpers/sample-unlockable.ts index 6a4d9f189..e02c02b8f 100644 --- a/src/pages/api/helpers/sampleUnlockable.ts +++ b/src/pages/api/helpers/sample-unlockable.ts @@ -3,7 +3,7 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { UnlockableV2 } from "@/src/config/types"; export default async function handler( - req: NextApiRequest, + _req: NextApiRequest, res: NextApiResponse ) { // This API Endpoint generates a sample UnlockableV2 object you can use for your `config.ts` file. @@ -11,7 +11,7 @@ export default async function handler( const now = new Date(); const dateObj = now.toISOString().replace("T", " ").split("Z")[0]; - const sampleUnlockable = { + const sampleUnlockable: UnlockableV2 = { id: uuidv4(), owner: "0x000000000000000000000000000000000000dEaD", metadata: { diff --git a/src/pages/api/nft/get-holders-nft-id.ts b/src/pages/api/nft/get-holders-nft-id.ts new file mode 100644 index 000000000..76e4542e3 --- /dev/null +++ b/src/pages/api/nft/get-holders-nft-id.ts @@ -0,0 +1,43 @@ +import type { NextApiRequest, NextApiResponse } from "next"; +import { + getMinterAndToken, + getNftData, + getNftHolders, +} from "@/src/utils/loopring"; +import { handleError, LoopgateError } from "@/src/middleware"; + +// Request holders for a NFT held on Loopring by querying NFT ID +const handler = async (req: NextApiRequest, res: NextApiResponse) => { + const { nftId } = req.query; + + if (!nftId || Array.isArray(nftId)) { + return handleError(res, LoopgateError.badRequest); + } + + // Call TheGraph API to find NFT Datas for a NFT ID + const theGraphRes = await getMinterAndToken(nftId); + + if (!theGraphRes) { + return handleError(res, LoopgateError.noTheGraphData); + } + + const nftDataRes = await getNftData( + theGraphRes.minter, + theGraphRes.tokenAddress, + nftId + ); + + if (!nftDataRes) { + return handleError(res, LoopgateError.noLoopringDataFound); + } + + const holders = await getNftHolders(nftDataRes.nftData); + + if (!holders) { + return handleError(res, LoopgateError.noHoldersFound); + } + + return res.status(200).json(holders); +}; + +export default handler; diff --git a/src/pages/api/getNftHolders.ts b/src/pages/api/nft/get-holders-nftdata.ts similarity index 50% rename from src/pages/api/getNftHolders.ts rename to src/pages/api/nft/get-holders-nftdata.ts index 650b0b809..cd12f62ba 100644 --- a/src/pages/api/getNftHolders.ts +++ b/src/pages/api/nft/get-holders-nftdata.ts @@ -1,27 +1,21 @@ import type { NextApiRequest, NextApiResponse } from "next"; import getNftHolders from "@/src/utils/loopring/getNftHolders"; -import logger from "@/src/utils/logger"; +import { handleError, LoopgateError } from "@/src/middleware"; -// Request NFTs on Loopring held by a user +// Request NFTs on Loopring held by a user by querying NFT Data const handler = async (req: NextApiRequest, res: NextApiResponse) => { const query = req.query; const { nftData } = query; - // Check if multiple or no Account IDs are specified. If so: early return. if (!nftData || Array.isArray(nftData)) { - const errorMsg = - "Invalid request. Please provide a valid Loopring 'NFT Data', and try again."; - logger.error(errorMsg); - return res.status(400).send(errorMsg); + return handleError(res, LoopgateError.badRequest); } // Call TheGraph API to find NFT Data for a NFT ID const holders = await getNftHolders(nftData); if (!holders) { - const errorMsg = `Unable to find holders for NFT Data '${nftData}'`; - logger.error(errorMsg); - return res.status(404).send(errorMsg); + return handleError(res, LoopgateError.noHoldersFound); } return res.status(200).json(holders); diff --git a/src/pages/api/getNftData.ts b/src/pages/api/nft/get-nft-data.ts similarity index 57% rename from src/pages/api/getNftData.ts rename to src/pages/api/nft/get-nft-data.ts index 4319b7073..83aca9824 100644 --- a/src/pages/api/getNftData.ts +++ b/src/pages/api/nft/get-nft-data.ts @@ -1,6 +1,7 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { getMinterAndToken, getNftData } from "@/src/utils/loopring"; import logger from "@/src/utils/logger"; +import { handleError, LoopgateError } from "@/src/middleware"; // Request NFTs Data for a Loopring NFT ID const handler = async (req: NextApiRequest, res: NextApiResponse) => { @@ -9,20 +10,14 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { // Check if multiple or no Account IDs are specified. If so: early return. if (!nftId || Array.isArray(nftId)) { - const errorMsg = - "Invalid request. Please provide a valid Loopring NFT ID, and try again."; - logger.error(errorMsg); - return res.status(400).send(errorMsg); + return handleError(res, LoopgateError.badRequest); } // Call TheGraph API to find NFT Datas for a NFT ID const theGraphRes = await getMinterAndToken(nftId); if (!theGraphRes) { - const errorMsg = - "Unable to retrieve data from TheGraph with this NFT ID. Please provide a valid Loopring NFT ID, and try again."; - logger.error(errorMsg); - return res.status(400).send(errorMsg); + return handleError(res, LoopgateError.noTheGraphData); } const nftDataRes = await getNftData( @@ -32,10 +27,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { ); if (!nftDataRes) { - const errorMsg = - "Unable to retrieve data from the Loopring API with this NFT ID. Please provide a valid Loopring NFT ID, and try again."; - logger.error(errorMsg); - return res.status(400).send(errorMsg); + return handleError(res, LoopgateError.noLoopringDataFound); } return res.status(200).json({ nftData: nftDataRes.nftData }); diff --git a/src/pages/api/getUserNfts.ts b/src/pages/api/nft/get-user-nfts.ts similarity index 50% rename from src/pages/api/getUserNfts.ts rename to src/pages/api/nft/get-user-nfts.ts index acf169343..59b5430ab 100644 --- a/src/pages/api/getUserNfts.ts +++ b/src/pages/api/nft/get-user-nfts.ts @@ -1,27 +1,21 @@ import type { NextApiRequest, NextApiResponse } from "next"; -import { getAllUserNftIds } from "../../utils/loopring"; -import logger from "@/src/utils/logger"; +import { getAllUserNftIds } from "../../../utils/loopring"; +import { handleError, LoopgateError } from "@/src/middleware"; // Request NFTs on Loopring held by a user const handler = async (req: NextApiRequest, res: NextApiResponse) => { - const query = req.query; - const { accountId } = query; + const { accountId } = req.query; // Check if multiple or no Account IDs are specified. If so: early return. if (!accountId || Array.isArray(accountId)) { - const errorMsg = - "Invalid Request: 0x address not provided. Please provide a valid 0x address and try again."; - logger.error(errorMsg); - return res.status(400).send(errorMsg); + return handleError(res, LoopgateError.noAddressProvided); } // Call Loopring API to find- and extract all user NFT IDs const allNftIds = await getAllUserNftIds(accountId); if (!allNftIds) { - const errorMsg = `Unable to find any NFTs for Loopring accountId '${accountId}'`; - logger.error(errorMsg); - return res.status(404).send(errorMsg); + return handleError(res, LoopgateError.noNftsFound); } return res.status(200).json(allNftIds); diff --git a/src/pages/api/upload/createUnlockable.ts b/src/pages/api/upload/createUnlockable.ts deleted file mode 100644 index 64d1ea5fb..000000000 --- a/src/pages/api/upload/createUnlockable.ts +++ /dev/null @@ -1,50 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from "next"; -import { withSessionRoute } from "@/src/utils/iron-session/withSession"; -import { - checkAuthentication, - handleError, - LoopgateError, -} from "@/src/middleware"; -import { createClient } from "@supabase/supabase-js"; -import { Database } from "@/src/utils/supabase/types"; - -const handler = async (req: NextApiRequest, res: NextApiResponse) => { - if (req.method !== "POST") { - return handleError(res, LoopgateError.noPostRequest); - } - - const auth = await checkAuthentication(req, res); - if (!auth) { - return handleError(res, LoopgateError.unauthorized); - } - - if (!req.body.uuid || !req.body.cid) { - return handleError(res, LoopgateError.badRequest); - } - - // Create server Supabase client - const Supabase = createClient( - process.env.NEXT_PUBLIC_SUPABASE_URL, - process.env.SUPABASE_SERVICE_ROLE - ); - - const { error } = await Supabase.from("unlockables").insert({ - id: req.body.uuid, - owner: auth.address, - content_type_id: 1, // IPFS Unlockable - content_url: req.body.cid, - criteria_unlock_amount: 1, - }); - - console.log(error); - - if (error) { - return handleError(res, [500, error.message]); - } - - return res.status(200).json({ - message: `Successfully created Unlockable with UUID of ${req.body.uuid}.`, - }); -}; - -export default withSessionRoute(handler); diff --git a/src/pages/api/upload/updateUnlockable.ts b/src/pages/api/upload/updateUnlockable.ts deleted file mode 100644 index 5b9fb4b98..000000000 --- a/src/pages/api/upload/updateUnlockable.ts +++ /dev/null @@ -1,94 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from "next"; -import { withSessionRoute } from "@/src/utils/iron-session/withSession"; -import { - checkAuthentication, - handleError, - LoopgateError, -} from "@/src/middleware"; -import { createClient } from "@supabase/supabase-js"; -import { Database } from "@/src/utils/supabase/types"; - -type UUID = string; - -interface UnlockCriterion { - unlockable_id: UUID; - nft_id: string; -} - -export interface UnlockableUpdateProps { - body: { - unlockable: { - uuid: UUID; - criteria_unlock_amount?: number; - description?: string; - name?: string; - }; - unlock_criteria: UnlockCriterion[]; - }; -} - -const handler = async (req: NextApiRequest, res: NextApiResponse) => { - if (req.method !== "POST") { - return handleError(res, LoopgateError.noPostRequest); - } - - const auth = await checkAuthentication(req, res); - if (!auth) { - return handleError(res, LoopgateError.unauthorized); - } - - if (!req.body.unlockable.uuid || req.body.unlockable.unlock_criteria) { - return handleError(res, LoopgateError.badRequest); - } - - // Create server Supabase client - const Supabase = createClient( - process.env.NEXT_PUBLIC_SUPABASE_URL, - process.env.SUPABASE_SERVICE_ROLE - ); - - const { error: updateUnlockableError } = await Supabase.from("unlockables") - .update({ - // Changing the ContentType and URL are not implemented, for now. - // content_type_id: req.body?.content_type_id, - // content_url: req.body?.cid, - criteria_unlock_amount: req.body.unlockable?.criteria_unlock_amount, - description: req.body.unlockable?.description, - name: req.body.unlockable?.name, - }) - .eq("id", req.body.unlockable.uuid); - - if (updateUnlockableError) { - return handleError(res, [500, updateUnlockableError.message]); - } - - const { error: deleteCriteriaError } = await Supabase.from("unlock_criteria") - .delete() - .eq("unlockable_id", req.body.uuid); - - if (deleteCriteriaError) { - return handleError(res, [500, deleteCriteriaError.message]); - } - - const unlockCriteria: { - unlockable_id: UUID; - nft_id: string; - owner: string; - }[] = req.body.unlockCriteria.map((x: UnlockCriterion) => { - return { ...x, owner: auth.address }; - }); - - const { error: insertCriteriaError } = await Supabase.from( - "unlock_criteria" - ).insert(unlockCriteria); - - if (insertCriteriaError) { - return handleError(res, [500, insertCriteriaError.message]); - } - - return res.status(200).json({ - message: `Successfully updated Unlockable and Unlock Criteria with UUID of ${req.body.uuid}.`, - }); -}; - -export default withSessionRoute(handler); diff --git a/src/pages/unlocks/[uuid].tsx b/src/pages/unlocks/[uuid].tsx index 077b4bcd5..ec0d22d59 100644 --- a/src/pages/unlocks/[uuid].tsx +++ b/src/pages/unlocks/[uuid].tsx @@ -44,7 +44,7 @@ export const getServerSideProps: GetServerSideProps = async (context) => { }; }; -const Page = ({ unlockable }: { unlockable: UnlockableV2 | undefined }) => { +const Page = ({ unlockable }: { unlockable: UnlockableV2 | null }) => { if (!unlockable) { return ( { "Successfully submarined your file! Now let's create an entry in the DB..." ); - const createUnlockableRes = await axios.post( - "/api/upload/createUnlockable", - { - uuid: submarineRes.data.uuid, - cid: submarineRes.data.cid, - } - ); + const createUnlockableRes = await axios.post("/api/unlockable/create", { + uuid: submarineRes.data.uuid, + cid: submarineRes.data.cid, + }); if (!createUnlockableRes) { console.error("Unable to add the Unlockable to the DB..."); diff --git a/src/utils/pinata/generateAccessLink.ts b/src/utils/pinata/generateAccessLink.ts index 4db6d7c1e..015f80bdc 100644 --- a/src/utils/pinata/generateAccessLink.ts +++ b/src/utils/pinata/generateAccessLink.ts @@ -1,15 +1,15 @@ -import sub from "./sub"; +import Submarine from "./submarine"; // Checks a submarined CID. If valid, generates an access link that is accessible for a set amount of time const generateAccessLink = async ( cid: string, unlockTimeInSec: number = 60 * 3 ) => { - const foundContent = await sub.getSubmarinedContentByCid(cid); + const foundContent = await Submarine.getSubmarinedContentByCid(cid); const folder = foundContent.items[0]; if (folder) { - const accessLink = await sub.generateAccessLink( + const accessLink = await Submarine.generateAccessLink( unlockTimeInSec, folder.id, cid diff --git a/src/utils/pinata/index.ts b/src/utils/pinata/index.ts index eb96da216..33012e654 100644 --- a/src/utils/pinata/index.ts +++ b/src/utils/pinata/index.ts @@ -1,4 +1,4 @@ -import sub from "./sub"; +import Submarine from "./submarine"; import generateAccessLink from "./generateAccessLink"; import listFolderContent from "./listFolderContent"; import checkPinataFolderForHtml from "./checkPinataFolderForHtml"; @@ -6,7 +6,7 @@ import getPinataIndexLink from "./getPinataIndexLink"; import formatAccessLink from "./formatAccessLink"; export { - sub, + Submarine, generateAccessLink, formatAccessLink, listFolderContent, diff --git a/src/utils/pinata/listFolderContent.ts b/src/utils/pinata/listFolderContent.ts index d53f9973e..a06f9ce22 100644 --- a/src/utils/pinata/listFolderContent.ts +++ b/src/utils/pinata/listFolderContent.ts @@ -1,7 +1,7 @@ -import sub from "./sub"; +import Submarine from "./submarine"; const listFolderContent = async (folderId: string) => { - return await sub.listFolderContent(folderId); + return await Submarine.listFolderContent(folderId); }; export default listFolderContent; diff --git a/src/utils/pinata/sub.ts b/src/utils/pinata/submarine.ts similarity index 55% rename from src/utils/pinata/sub.ts rename to src/utils/pinata/submarine.ts index 3417a202e..c106f1efe 100644 --- a/src/utils/pinata/sub.ts +++ b/src/utils/pinata/submarine.ts @@ -1,8 +1,8 @@ -import { Submarine } from "pinata-submarine"; +import { Submarine as SubmarineSDK } from "pinata-submarine"; -const sub = new Submarine( +const Submarine = new SubmarineSDK( process.env.PINATA_SUBMARINE_KEY || "Undefined Pinata Submarine Key", process.env.NEXT_PUBLIC_PINATA_GATEWAY_URL || "Undefined Pinata Gateway Url" ); -export default sub; +export default Submarine; diff --git a/src/utils/supabase/helpers/parseNftIdString.ts b/src/utils/supabase/helpers/parseNftIdString.ts index d4ffdbe62..079462638 100644 --- a/src/utils/supabase/helpers/parseNftIdString.ts +++ b/src/utils/supabase/helpers/parseNftIdString.ts @@ -4,9 +4,12 @@ * Output: ["0x00...00", 1x11.11] */ -const parseNftIdString = (nftIdString: string): string[] => { - const spacesRemoved = nftIdString.replaceAll(" ", ""); - return spacesRemoved.split(","); +const parseNftIdString = (nftIdString?: string): string[] => { + if (!nftIdString) { + return []; + } + + return nftIdString.replaceAll(" ", "").split(","); }; export default parseNftIdString; diff --git a/src/utils/supabase/supabase.ts b/src/utils/supabase/supabase.ts index 91aac13be..37dbd650f 100644 --- a/src/utils/supabase/supabase.ts +++ b/src/utils/supabase/supabase.ts @@ -6,11 +6,12 @@ const Supabase = createClient( process.env.NEXT_PUBLIC_SUPABASE_ANON || "Undefined supabase Anon" ); -export const getServiceSupabase = () => { - createClient( - process.env.NEXT_PUBLIC_SUPABASE_ANON || "", - process.env.SUPABASE_SERVICE_ROLE - ); -}; +// To research: Server Side initiation of Supabase with types +// export const getServiceSupabase = () => { +// createClient( +// process.env.NEXT_PUBLIC_SUPABASE_ANON || "", +// process.env.SUPABASE_SERVICE_ROLE +// ); +// }; export default Supabase; From e6552b22d36f8d1560007ebcc2a138c9b016cf70 Mon Sep 17 00:00:00 2001 From: 0xGeel Date: Fri, 26 May 2023 15:59:11 +0200 Subject: [PATCH 08/18] Add API routes for creating, deleting, updating Unlockables & verifying access --- src/pages/api/unlockable/create.ts | 48 ++++++++++++ src/pages/api/unlockable/delete.ts | 75 ++++++++++++++++++ src/pages/api/unlockable/update.ts | 94 +++++++++++++++++++++++ src/pages/api/unlockable/verify-access.ts | 76 ++++++++++++++++++ 4 files changed, 293 insertions(+) create mode 100644 src/pages/api/unlockable/create.ts create mode 100644 src/pages/api/unlockable/delete.ts create mode 100644 src/pages/api/unlockable/update.ts create mode 100644 src/pages/api/unlockable/verify-access.ts diff --git a/src/pages/api/unlockable/create.ts b/src/pages/api/unlockable/create.ts new file mode 100644 index 000000000..2364ce3ad --- /dev/null +++ b/src/pages/api/unlockable/create.ts @@ -0,0 +1,48 @@ +import type { NextApiRequest, NextApiResponse } from "next"; +import { withSessionRoute } from "@/src/utils/iron-session/withSession"; +import { + checkAuthentication, + handleError, + LoopgateError, +} from "@/src/middleware"; +import { createClient } from "@supabase/supabase-js"; +import { Database } from "@/src/utils/supabase/types"; + +const handler = async (req: NextApiRequest, res: NextApiResponse) => { + if (req.method !== "POST") { + return handleError(res, LoopgateError.noPostRequest); + } + + const auth = await checkAuthentication(req, res); + if (!auth) { + return handleError(res, LoopgateError.unauthorized); + } + + if (!req.body.uuid || !req.body.cid) { + return handleError(res, LoopgateError.badRequest); + } + + // Create server Supabase client + const Supabase = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL, + process.env.SUPABASE_SERVICE_ROLE + ); + + const { error } = await Supabase.from("unlockables").insert({ + id: req.body.uuid, + owner: auth.address, + content_type_id: 1, // IPFS Unlockable + content_url: req.body.cid, + criteria_unlock_amount: 1, + }); + + if (error) { + return handleError(res, [500, error.message]); + } + + return res.status(200).json({ + message: `Successfully created Unlockable with UUID of ${req.body.uuid}.`, + }); +}; + +export default withSessionRoute(handler); diff --git a/src/pages/api/unlockable/delete.ts b/src/pages/api/unlockable/delete.ts new file mode 100644 index 000000000..535f89699 --- /dev/null +++ b/src/pages/api/unlockable/delete.ts @@ -0,0 +1,75 @@ +import type { NextApiRequest, NextApiResponse } from "next"; +import { withSessionRoute } from "@/src/utils/iron-session/withSession"; +import { + checkAuthentication, + handleError, + LoopgateError, +} from "@/src/middleware"; +import { createClient } from "@supabase/supabase-js"; +import { Database } from "@/src/utils/supabase/types"; +import { Submarine } from "@/src/utils/pinata"; + +const handler = async (req: NextApiRequest, res: NextApiResponse) => { + if (req.method !== "POST") { + return handleError(res, LoopgateError.noPostRequest); + } + + const auth = await checkAuthentication(req, res); + if (!auth) { + return handleError(res, LoopgateError.unauthorized); + } + + if (!req.body.uuid || !req.body.cid || Array.isArray(req.body.cid)) { + return handleError(res, LoopgateError.badRequest); + } + + // Create server Supabase client + const Supabase = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL, + process.env.SUPABASE_SERVICE_ROLE + ); + + const { error: deleteUnlockableError } = await Supabase.from("unlockables") + .delete() + .eq("id", req.body.uuid); + + if (deleteUnlockableError) { + return handleError(res, [500, deleteUnlockableError.message]); + } + + const { error: deleteCriteriaError } = await Supabase.from("unlock_criteria") + .delete() + .eq("unlockable_id", req.body.uuid); + + if (deleteCriteriaError) { + return handleError(res, [500, deleteCriteriaError.message]); + } + + const { items: submarinedItems } = await Submarine.getSubmarinedContentByCid( + req.body.cid + ); + + if (!submarinedItems || submarinedItems.length == 0) { + return handleError(res, [ + 500, + `Unable to retrieve the file with CID '${req.query.cid}'.`, + ]); + } + + const submarineDeleteRes = await Submarine.deleteContent( + submarinedItems[0].id + ); + + if (!submarineDeleteRes) { + return handleError(res, [ + 500, + `Unable to delete the file with CID '${req.query.cid}'`, + ]); + } + + return res.status(200).json({ + message: `Successfully deleted the Unlockable with UUID of ${req.body.uuid} and CID of ${req.body.cid}.`, + }); +}; + +export default withSessionRoute(handler); diff --git a/src/pages/api/unlockable/update.ts b/src/pages/api/unlockable/update.ts new file mode 100644 index 000000000..5b9fb4b98 --- /dev/null +++ b/src/pages/api/unlockable/update.ts @@ -0,0 +1,94 @@ +import type { NextApiRequest, NextApiResponse } from "next"; +import { withSessionRoute } from "@/src/utils/iron-session/withSession"; +import { + checkAuthentication, + handleError, + LoopgateError, +} from "@/src/middleware"; +import { createClient } from "@supabase/supabase-js"; +import { Database } from "@/src/utils/supabase/types"; + +type UUID = string; + +interface UnlockCriterion { + unlockable_id: UUID; + nft_id: string; +} + +export interface UnlockableUpdateProps { + body: { + unlockable: { + uuid: UUID; + criteria_unlock_amount?: number; + description?: string; + name?: string; + }; + unlock_criteria: UnlockCriterion[]; + }; +} + +const handler = async (req: NextApiRequest, res: NextApiResponse) => { + if (req.method !== "POST") { + return handleError(res, LoopgateError.noPostRequest); + } + + const auth = await checkAuthentication(req, res); + if (!auth) { + return handleError(res, LoopgateError.unauthorized); + } + + if (!req.body.unlockable.uuid || req.body.unlockable.unlock_criteria) { + return handleError(res, LoopgateError.badRequest); + } + + // Create server Supabase client + const Supabase = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL, + process.env.SUPABASE_SERVICE_ROLE + ); + + const { error: updateUnlockableError } = await Supabase.from("unlockables") + .update({ + // Changing the ContentType and URL are not implemented, for now. + // content_type_id: req.body?.content_type_id, + // content_url: req.body?.cid, + criteria_unlock_amount: req.body.unlockable?.criteria_unlock_amount, + description: req.body.unlockable?.description, + name: req.body.unlockable?.name, + }) + .eq("id", req.body.unlockable.uuid); + + if (updateUnlockableError) { + return handleError(res, [500, updateUnlockableError.message]); + } + + const { error: deleteCriteriaError } = await Supabase.from("unlock_criteria") + .delete() + .eq("unlockable_id", req.body.uuid); + + if (deleteCriteriaError) { + return handleError(res, [500, deleteCriteriaError.message]); + } + + const unlockCriteria: { + unlockable_id: UUID; + nft_id: string; + owner: string; + }[] = req.body.unlockCriteria.map((x: UnlockCriterion) => { + return { ...x, owner: auth.address }; + }); + + const { error: insertCriteriaError } = await Supabase.from( + "unlock_criteria" + ).insert(unlockCriteria); + + if (insertCriteriaError) { + return handleError(res, [500, insertCriteriaError.message]); + } + + return res.status(200).json({ + message: `Successfully updated Unlockable and Unlock Criteria with UUID of ${req.body.uuid}.`, + }); +}; + +export default withSessionRoute(handler); diff --git a/src/pages/api/unlockable/verify-access.ts b/src/pages/api/unlockable/verify-access.ts new file mode 100644 index 000000000..a03d23ca1 --- /dev/null +++ b/src/pages/api/unlockable/verify-access.ts @@ -0,0 +1,76 @@ +import { NextApiRequest, NextApiResponse } from "next"; +import { siwe } from "@/src/utils/siwe"; +import { getUserAddress, getAllUserNftIds } from "@/src/utils/loopring"; +import { handleError, LoopgateError } from "@/src/middleware"; +import { + findUnlockableByUuid, + fetchUnlockableByUuid, +} from "@/src/utils/generic"; +import { getPinataIndexLink } from "@/src/utils/pinata"; +import { withSessionRoute } from "@/src/utils/iron-session/withSession"; + +const handler = async (req: NextApiRequest, res: NextApiResponse) => { + const query = req.query; + const { address, unlockableId } = query; + const siweSesh = await siwe.getSession(req, res); + + // Check validity of request + if ( + !address || + Array.isArray(address) || + !unlockableId || + Array.isArray(unlockableId) + ) { + return handleError(res, LoopgateError.noAddressProvided); + } + + // Check if there is a session. Only connected users may call this endpoint. + if (!siweSesh.address || siweSesh.address !== address) { + return handleError(res, LoopgateError.unauthorized); + } + + // 1️⃣ Call the Loopring API to find the User's Loopring Account ID + const accountId = await getUserAddress(address); + + if (!accountId) { + return handleError(res, LoopgateError.noLoopringAccount); + } + + const unlockable = + process.env.LOOPGATE_MODE === "supabase" + ? await fetchUnlockableByUuid(unlockableId) + : findUnlockableByUuid(unlockableId); + + if (!unlockable) { + return handleError(res, LoopgateError.noUnlockableFound); + } + + // 2️⃣ Call the Loopring API to find the NFTs held by the user + const userNftIds = await getAllUserNftIds(accountId); + + if (!userNftIds) { + return handleError(res, LoopgateError.noNftsFound); + } + + // 3️⃣ Check if the user meets the unlock criteria + const intersection = unlockable.unlockCriteria.nftId.filter((value) => + userNftIds.includes(value) + ); + + if (intersection.length < unlockable.unlockCriteria.unlockAmount) { + return handleError(res, LoopgateError.unlockReqNotMet); + } + + // 4️⃣ Get access link for the unlockable + const unlock = await getPinataIndexLink(unlockable.content.url); + + if (!unlock) { + return handleError(res, LoopgateError.noPinataContentFound); + } + + return res.status(200).json({ + unlock: unlock, + }); +}; + +export default withSessionRoute(handler); From 2697fb57fccee8ea840b6c376b4bfffcac6f35bb Mon Sep 17 00:00:00 2001 From: 0xGeel Date: Fri, 26 May 2023 17:57:27 +0200 Subject: [PATCH 09/18] Big refactor: consistency, exports, folder structure --- package.json | 2 +- src/components/UnlockablePage/Layout.tsx | 2 +- src/middleware/checkAuthentication.ts | 6 ++-- src/middleware/{ => error}/handleError.ts | 0 src/middleware/{ => error}/loopgateError.ts | 0 src/middleware/index.ts | 4 +-- src/middleware/ironSession/index.ts | 1 + .../ironSession}/withSession.ts | 0 .../siwe/configure.tsx} | 0 src/{utils => middleware}/siwe/index.ts | 0 src/{utils => middleware}/siwe/siwe.ts | 2 +- src/pages/_app.tsx | 4 +-- src/pages/api/helpers/sample-unlockable.ts | 3 +- src/pages/api/nft/get-holders-nft-id.ts | 2 +- src/pages/api/nft/get-holders-nftdata.ts | 2 +- src/pages/api/nft/get-nft-data.ts | 2 +- src/pages/api/nft/get-user-nfts.ts | 2 +- src/pages/api/session/me.ts | 4 +-- src/pages/api/siwe/[...route].ts | 2 +- src/pages/api/unlockable/create.ts | 4 +-- src/pages/api/unlockable/delete.ts | 6 ++-- src/pages/api/unlockable/update.ts | 20 ++++++------ src/pages/api/unlockable/verify-access.ts | 10 +++--- src/pages/api/upload/submarine.ts | 2 +- src/pages/index.tsx | 7 ++-- src/pages/unlocks/[uuid].tsx | 4 +-- src/pages/unlocks/browse.tsx | 4 +-- .../unlockable}/fetchAllUnlockables.ts | 8 ++--- .../unlockable}/fetchUnlockableByUuid.ts | 8 ++--- .../unlockable}/findAllUnlockables.ts | 8 ++--- .../unlockable}/findUnlockableByUuid.test.ts | 4 +-- .../unlockable}/findUnlockableByUuid.ts | 8 ++--- .../unlockable}/findUnlockedCids.test.ts | 4 +-- .../loopgate/unlockable}/findUnlockedCids.ts | 10 +++--- src/services/loopgate/unlockable/index.ts | 5 +++ .../loopring/_constants.ts | 0 src/{utils => services}/loopring/_types.ts | 0 .../loopring/extractNfts.ts | 0 .../loopring/getAllUserNftIds.ts | 0 .../loopring/getMinterAndToken.test.ts | 0 .../loopring/getMinterAndToken.ts | 0 .../loopring/getNftData.ts | 0 .../loopring/getNftHolders.test.ts | 0 .../loopring/getNftHolders.ts | 0 .../loopring/getUserAddress.test.ts | 0 .../loopring/getUserAddress.ts | 0 .../loopring/getUserNfts.test.ts | 0 .../loopring/getUserNfts.ts | 0 .../loopring/headerOpts.ts | 0 src/{utils => services}/loopring/index.ts | 0 .../loopring/rateLimitedAxios.ts | 0 src/{utils => services}/pinata/_types.ts | 0 .../pinata/checkPinataFolderForHtml.ts | 0 .../pinata/formatAccessLink.test.ts | 0 .../pinata/formatAccessLink.ts | 0 .../pinata/generateAccessLink.ts | 0 .../pinata/getPinataIndexLink.ts | 0 src/{utils => services}/pinata/index.ts | 0 .../pinata/listFolderContent.ts | 0 src/{utils => services}/pinata/submarine.ts | 0 src/services/supabase/helpers/index.ts | 1 + .../supabase/helpers/parseSqlUnlockable.ts | 6 ++-- src/{utils => services}/supabase/index.ts | 0 .../supabase/sql/content_types.sql | 0 .../supabase/sql/roles.sql | 0 .../supabase/sql/unlock_criteria.sql | 0 .../supabase/sql/unlockables.sql | 0 .../supabase/sql/users.sql | 0 .../sql/view/unlockables_with_criteria.sql | 0 src/{utils => services}/supabase/supabase.ts | 0 .../supabase/typeExtensions.ts | 0 src/{utils => services}/supabase/types.ts | 3 ++ src/services/wagmi/index.ts | 1 + src/{utils => services}/wagmi/wagmi.ts | 4 +-- .../{inline-styles.ts => inlineStyles.ts} | 0 src/utils/generic/checkIfContainsAll.test.ts | 2 +- src/utils/generic/checkIfContainsAll.ts | 4 +-- src/utils/generic/{cs.test.ts => cn.test.ts} | 2 +- src/utils/generic/cn.ts | 4 +-- src/utils/generic/formatRelativeDate.ts | 8 ++--- src/utils/generic/getCurrentYear.test.ts | 2 +- src/utils/generic/getCurrentYear.ts | 5 +-- src/utils/generic/index.ts | 32 ++++--------------- .../helpers => generic}/isUuid.test.ts | 2 +- .../{supabase/helpers => generic}/isUuid.ts | 4 +-- .../parseNftIdString.test.ts | 2 +- .../helpers => generic}/parseNftIdString.ts | 4 +-- src/utils/generic/truncate0x.ts | 4 +-- src/utils/generic/uuidToNumber.test.ts | 2 +- src/utils/generic/uuidToNumber.ts | 4 +-- src/utils/supabase/helpers/index.ts | 5 --- src/utils/wagmi/index.ts | 2 -- 92 files changed, 103 insertions(+), 150 deletions(-) rename src/middleware/{ => error}/handleError.ts (100%) rename src/middleware/{ => error}/loopgateError.ts (100%) create mode 100644 src/middleware/ironSession/index.ts rename src/{utils/iron-session => middleware/ironSession}/withSession.ts (100%) rename src/{utils/siwe/configureSIWE.tsx => middleware/siwe/configure.tsx} (100%) rename src/{utils => middleware}/siwe/index.ts (100%) rename src/{utils => middleware}/siwe/siwe.ts (92%) rename src/{utils/generic => services/loopgate/unlockable}/fetchAllUnlockables.ts (64%) rename src/{utils/generic => services/loopgate/unlockable}/fetchUnlockableByUuid.ts (76%) rename src/{utils/generic => services/loopgate/unlockable}/findAllUnlockables.ts (62%) rename src/{utils/generic => services/loopgate/unlockable}/findUnlockableByUuid.test.ts (85%) rename src/{utils/generic => services/loopgate/unlockable}/findUnlockableByUuid.ts (55%) rename src/{utils/generic => services/loopgate/unlockable}/findUnlockedCids.test.ts (88%) rename src/{utils/generic => services/loopgate/unlockable}/findUnlockedCids.ts (76%) create mode 100644 src/services/loopgate/unlockable/index.ts rename src/{utils => services}/loopring/_constants.ts (100%) rename src/{utils => services}/loopring/_types.ts (100%) rename src/{utils => services}/loopring/extractNfts.ts (100%) rename src/{utils => services}/loopring/getAllUserNftIds.ts (100%) rename src/{utils => services}/loopring/getMinterAndToken.test.ts (100%) rename src/{utils => services}/loopring/getMinterAndToken.ts (100%) rename src/{utils => services}/loopring/getNftData.ts (100%) rename src/{utils => services}/loopring/getNftHolders.test.ts (100%) rename src/{utils => services}/loopring/getNftHolders.ts (100%) rename src/{utils => services}/loopring/getUserAddress.test.ts (100%) rename src/{utils => services}/loopring/getUserAddress.ts (100%) rename src/{utils => services}/loopring/getUserNfts.test.ts (100%) rename src/{utils => services}/loopring/getUserNfts.ts (100%) rename src/{utils => services}/loopring/headerOpts.ts (100%) rename src/{utils => services}/loopring/index.ts (100%) rename src/{utils => services}/loopring/rateLimitedAxios.ts (100%) rename src/{utils => services}/pinata/_types.ts (100%) rename src/{utils => services}/pinata/checkPinataFolderForHtml.ts (100%) rename src/{utils => services}/pinata/formatAccessLink.test.ts (100%) rename src/{utils => services}/pinata/formatAccessLink.ts (100%) rename src/{utils => services}/pinata/generateAccessLink.ts (100%) rename src/{utils => services}/pinata/getPinataIndexLink.ts (100%) rename src/{utils => services}/pinata/index.ts (100%) rename src/{utils => services}/pinata/listFolderContent.ts (100%) rename src/{utils => services}/pinata/submarine.ts (100%) create mode 100644 src/services/supabase/helpers/index.ts rename src/{utils => services}/supabase/helpers/parseSqlUnlockable.ts (79%) rename src/{utils => services}/supabase/index.ts (100%) rename src/{utils => services}/supabase/sql/content_types.sql (100%) rename src/{utils => services}/supabase/sql/roles.sql (100%) rename src/{utils => services}/supabase/sql/unlock_criteria.sql (100%) rename src/{utils => services}/supabase/sql/unlockables.sql (100%) rename src/{utils => services}/supabase/sql/users.sql (100%) rename src/{utils => services}/supabase/sql/view/unlockables_with_criteria.sql (100%) rename src/{utils => services}/supabase/supabase.ts (100%) rename src/{utils => services}/supabase/typeExtensions.ts (100%) rename src/{utils => services}/supabase/types.ts (97%) create mode 100644 src/services/wagmi/index.ts rename src/{utils => services}/wagmi/wagmi.ts (92%) rename src/styles/{inline-styles.ts => inlineStyles.ts} (100%) rename src/utils/generic/{cs.test.ts => cn.test.ts} (93%) rename src/utils/{supabase/helpers => generic}/isUuid.test.ts (94%) rename src/utils/{supabase/helpers => generic}/isUuid.ts (67%) rename src/utils/{supabase/helpers => generic}/parseNftIdString.test.ts (96%) rename src/utils/{supabase/helpers => generic}/parseNftIdString.ts (68%) delete mode 100644 src/utils/supabase/helpers/index.ts delete mode 100644 src/utils/wagmi/index.ts diff --git a/package.json b/package.json index 40a2cb572..f14f9e096 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "lint": "next lint", "test": "jest", "test:watch": "jest --watch", - "supabase:types": "npx supabase gen types typescript --project-id wyskjtopiuiwkkqclyho --schema public > src/utils/supabase/types.ts" + "supabase:types": "npx supabase gen types typescript --project-id wyskjtopiuiwkkqclyho --schema public > src/services/supabase/types.ts" }, "dependencies": { "@heroicons/react": "^2.0.13", diff --git a/src/components/UnlockablePage/Layout.tsx b/src/components/UnlockablePage/Layout.tsx index e35607a3a..e3c894241 100644 --- a/src/components/UnlockablePage/Layout.tsx +++ b/src/components/UnlockablePage/Layout.tsx @@ -1,7 +1,7 @@ import Header from "../Header"; import Footer from "../Footer"; import { ReactNode } from "react"; -import { techPattern } from "@/src/styles/inline-styles"; +import { techPattern } from "@/src/styles/inlineStyles"; import { cn } from "@/src/utils/generic"; type Props = { diff --git a/src/middleware/checkAuthentication.ts b/src/middleware/checkAuthentication.ts index 632580566..302c450ca 100644 --- a/src/middleware/checkAuthentication.ts +++ b/src/middleware/checkAuthentication.ts @@ -1,6 +1,6 @@ import { GetServerSidePropsContext } from "next"; -import { siwe } from "@/src/utils/siwe"; -import Supabase from "@/src/utils/supabase"; +import { siwe } from "@/src/middleware/siwe"; +import Supabase from "@/src/services/supabase"; import logger from "@/src/utils/logger"; import { NextApiRequest, NextApiResponse } from "next"; @@ -46,8 +46,6 @@ export async function checkAuthentication( .eq("eth_address", siweSesh.address?.toLowerCase()) .single(); - console.log(data); - if ( !data?.amount || data.amount < parseInt(process.env.LOOPGATE_CC_THRESHOLD, 10) diff --git a/src/middleware/handleError.ts b/src/middleware/error/handleError.ts similarity index 100% rename from src/middleware/handleError.ts rename to src/middleware/error/handleError.ts diff --git a/src/middleware/loopgateError.ts b/src/middleware/error/loopgateError.ts similarity index 100% rename from src/middleware/loopgateError.ts rename to src/middleware/error/loopgateError.ts diff --git a/src/middleware/index.ts b/src/middleware/index.ts index 73eebf3f6..47307f1ad 100644 --- a/src/middleware/index.ts +++ b/src/middleware/index.ts @@ -1,4 +1,4 @@ export { checkAuthentication } from "./checkAuthentication"; export { redirectTo } from "./redirectTo"; -export { handleError } from "./handleError"; -export { LoopgateError } from "./loopgateError"; +export { handleError } from "./error/handleError"; +export { LoopgateError } from "./error/loopgateError"; diff --git a/src/middleware/ironSession/index.ts b/src/middleware/ironSession/index.ts new file mode 100644 index 000000000..59eb69737 --- /dev/null +++ b/src/middleware/ironSession/index.ts @@ -0,0 +1 @@ +export { withSessionRoute, withSessionSsr } from "./withSession"; diff --git a/src/utils/iron-session/withSession.ts b/src/middleware/ironSession/withSession.ts similarity index 100% rename from src/utils/iron-session/withSession.ts rename to src/middleware/ironSession/withSession.ts diff --git a/src/utils/siwe/configureSIWE.tsx b/src/middleware/siwe/configure.tsx similarity index 100% rename from src/utils/siwe/configureSIWE.tsx rename to src/middleware/siwe/configure.tsx diff --git a/src/utils/siwe/index.ts b/src/middleware/siwe/index.ts similarity index 100% rename from src/utils/siwe/index.ts rename to src/middleware/siwe/index.ts diff --git a/src/utils/siwe/siwe.ts b/src/middleware/siwe/siwe.ts similarity index 92% rename from src/utils/siwe/siwe.ts rename to src/middleware/siwe/siwe.ts index a0e8a266b..a7c492732 100644 --- a/src/utils/siwe/siwe.ts +++ b/src/middleware/siwe/siwe.ts @@ -1,4 +1,4 @@ -import { configureSIWE } from "./configureSIWE"; +import { configureSIWE } from "./configure"; // import { configureSIWE } from "connectkit-next-siwe"; // Replaced with an edited version on 06-02-2023 to circumvent a 'connectkit-next-siwe' WalletConnect through a Smart Wallet issue (Loopring Wallet). diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 9e2151b75..b16936ae5 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -4,8 +4,8 @@ import { WagmiConfig } from "wagmi"; import { ConnectKitProvider } from "connectkit"; import "../styles/globals.css"; -import { WagmiClient } from "../utils/wagmi"; -import { siwe } from "../utils/siwe"; +import { WagmiClient } from "../services/wagmi"; +import { siwe } from "../middleware/siwe"; import { overrides } from "../styles/ConnectKit/overrides"; import NextHeadBase from "../components/SEO/NextHeadBase"; import { inter, unbounded } from "../components/Fonts"; diff --git a/src/pages/api/helpers/sample-unlockable.ts b/src/pages/api/helpers/sample-unlockable.ts index e02c02b8f..aac1ef954 100644 --- a/src/pages/api/helpers/sample-unlockable.ts +++ b/src/pages/api/helpers/sample-unlockable.ts @@ -2,12 +2,11 @@ import { v4 as uuidv4 } from "uuid"; import type { NextApiRequest, NextApiResponse } from "next"; import { UnlockableV2 } from "@/src/config/types"; +// This API Endpoint generates a sample UnlockableV2 object you can use for your `config.ts` file. export default async function handler( _req: NextApiRequest, res: NextApiResponse ) { - // This API Endpoint generates a sample UnlockableV2 object you can use for your `config.ts` file. - const now = new Date(); const dateObj = now.toISOString().replace("T", " ").split("Z")[0]; diff --git a/src/pages/api/nft/get-holders-nft-id.ts b/src/pages/api/nft/get-holders-nft-id.ts index 76e4542e3..93897c241 100644 --- a/src/pages/api/nft/get-holders-nft-id.ts +++ b/src/pages/api/nft/get-holders-nft-id.ts @@ -3,7 +3,7 @@ import { getMinterAndToken, getNftData, getNftHolders, -} from "@/src/utils/loopring"; +} from "@/src/services/loopring"; import { handleError, LoopgateError } from "@/src/middleware"; // Request holders for a NFT held on Loopring by querying NFT ID diff --git a/src/pages/api/nft/get-holders-nftdata.ts b/src/pages/api/nft/get-holders-nftdata.ts index cd12f62ba..132a4b3ca 100644 --- a/src/pages/api/nft/get-holders-nftdata.ts +++ b/src/pages/api/nft/get-holders-nftdata.ts @@ -1,5 +1,5 @@ import type { NextApiRequest, NextApiResponse } from "next"; -import getNftHolders from "@/src/utils/loopring/getNftHolders"; +import getNftHolders from "@/src/services/loopring/getNftHolders"; import { handleError, LoopgateError } from "@/src/middleware"; // Request NFTs on Loopring held by a user by querying NFT Data diff --git a/src/pages/api/nft/get-nft-data.ts b/src/pages/api/nft/get-nft-data.ts index 83aca9824..ace5cb6c4 100644 --- a/src/pages/api/nft/get-nft-data.ts +++ b/src/pages/api/nft/get-nft-data.ts @@ -1,5 +1,5 @@ import type { NextApiRequest, NextApiResponse } from "next"; -import { getMinterAndToken, getNftData } from "@/src/utils/loopring"; +import { getMinterAndToken, getNftData } from "@/src/services/loopring"; import logger from "@/src/utils/logger"; import { handleError, LoopgateError } from "@/src/middleware"; diff --git a/src/pages/api/nft/get-user-nfts.ts b/src/pages/api/nft/get-user-nfts.ts index 59b5430ab..7ad130c8c 100644 --- a/src/pages/api/nft/get-user-nfts.ts +++ b/src/pages/api/nft/get-user-nfts.ts @@ -1,5 +1,5 @@ import type { NextApiRequest, NextApiResponse } from "next"; -import { getAllUserNftIds } from "../../../utils/loopring"; +import { getAllUserNftIds } from "../../../services/loopring"; import { handleError, LoopgateError } from "@/src/middleware"; // Request NFTs on Loopring held by a user diff --git a/src/pages/api/session/me.ts b/src/pages/api/session/me.ts index 0644fd6d7..7970a4ee4 100644 --- a/src/pages/api/session/me.ts +++ b/src/pages/api/session/me.ts @@ -1,6 +1,6 @@ import { NextApiRequest, NextApiResponse } from "next"; -import { siwe } from "@/src/utils/siwe"; -import { withSessionRoute } from "@/src/utils/iron-session/withSession"; +import { siwe } from "@/src/middleware/siwe"; +import { withSessionRoute } from "@/src/middleware/ironSession/withSession"; const handler = async (req: NextApiRequest, res: NextApiResponse) => { const { method } = req; diff --git a/src/pages/api/siwe/[...route].ts b/src/pages/api/siwe/[...route].ts index ca77e54a0..fe48727ea 100644 --- a/src/pages/api/siwe/[...route].ts +++ b/src/pages/api/siwe/[...route].ts @@ -1,2 +1,2 @@ -import { siwe } from "../../../utils/siwe"; +import { siwe } from "../../../middleware/siwe"; export default siwe.apiRouteHandler; diff --git a/src/pages/api/unlockable/create.ts b/src/pages/api/unlockable/create.ts index 2364ce3ad..a05de8d74 100644 --- a/src/pages/api/unlockable/create.ts +++ b/src/pages/api/unlockable/create.ts @@ -1,12 +1,12 @@ import type { NextApiRequest, NextApiResponse } from "next"; -import { withSessionRoute } from "@/src/utils/iron-session/withSession"; +import { withSessionRoute } from "@/src/middleware/ironSession/withSession"; import { checkAuthentication, handleError, LoopgateError, } from "@/src/middleware"; import { createClient } from "@supabase/supabase-js"; -import { Database } from "@/src/utils/supabase/types"; +import { Database } from "@/src/services/supabase/types"; const handler = async (req: NextApiRequest, res: NextApiResponse) => { if (req.method !== "POST") { diff --git a/src/pages/api/unlockable/delete.ts b/src/pages/api/unlockable/delete.ts index 535f89699..828f793fc 100644 --- a/src/pages/api/unlockable/delete.ts +++ b/src/pages/api/unlockable/delete.ts @@ -1,13 +1,13 @@ import type { NextApiRequest, NextApiResponse } from "next"; -import { withSessionRoute } from "@/src/utils/iron-session/withSession"; +import { withSessionRoute } from "@/src/middleware/ironSession/withSession"; import { checkAuthentication, handleError, LoopgateError, } from "@/src/middleware"; import { createClient } from "@supabase/supabase-js"; -import { Database } from "@/src/utils/supabase/types"; -import { Submarine } from "@/src/utils/pinata"; +import { Database } from "@/src/services/supabase/types"; +import { Submarine } from "@/src/services/pinata"; const handler = async (req: NextApiRequest, res: NextApiResponse) => { if (req.method !== "POST") { diff --git a/src/pages/api/unlockable/update.ts b/src/pages/api/unlockable/update.ts index 5b9fb4b98..86d11b756 100644 --- a/src/pages/api/unlockable/update.ts +++ b/src/pages/api/unlockable/update.ts @@ -1,12 +1,12 @@ import type { NextApiRequest, NextApiResponse } from "next"; -import { withSessionRoute } from "@/src/utils/iron-session/withSession"; +import { withSessionRoute } from "@/src/middleware/ironSession/withSession"; import { checkAuthentication, handleError, LoopgateError, } from "@/src/middleware"; import { createClient } from "@supabase/supabase-js"; -import { Database } from "@/src/utils/supabase/types"; +import { Database } from "@/src/services/supabase/types"; type UUID = string; @@ -55,6 +55,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { criteria_unlock_amount: req.body.unlockable?.criteria_unlock_amount, description: req.body.unlockable?.description, name: req.body.unlockable?.name, + unlisted: req.body.unlockable?.unlisted, }) .eq("id", req.body.unlockable.uuid); @@ -70,17 +71,14 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { return handleError(res, [500, deleteCriteriaError.message]); } - const unlockCriteria: { - unlockable_id: UUID; - nft_id: string; - owner: string; - }[] = req.body.unlockCriteria.map((x: UnlockCriterion) => { - return { ...x, owner: auth.address }; - }); - const { error: insertCriteriaError } = await Supabase.from( "unlock_criteria" - ).insert(unlockCriteria); + ).insert( + req.body.unlockCriteria.map((x: UnlockCriterion) => ({ + ...x, + owner: auth.address, + })) + ); if (insertCriteriaError) { return handleError(res, [500, insertCriteriaError.message]); diff --git a/src/pages/api/unlockable/verify-access.ts b/src/pages/api/unlockable/verify-access.ts index a03d23ca1..088cd3295 100644 --- a/src/pages/api/unlockable/verify-access.ts +++ b/src/pages/api/unlockable/verify-access.ts @@ -1,13 +1,13 @@ import { NextApiRequest, NextApiResponse } from "next"; -import { siwe } from "@/src/utils/siwe"; -import { getUserAddress, getAllUserNftIds } from "@/src/utils/loopring"; +import { siwe } from "@/src/middleware/siwe"; +import { getUserAddress, getAllUserNftIds } from "@/src/services/loopring"; import { handleError, LoopgateError } from "@/src/middleware"; import { findUnlockableByUuid, fetchUnlockableByUuid, -} from "@/src/utils/generic"; -import { getPinataIndexLink } from "@/src/utils/pinata"; -import { withSessionRoute } from "@/src/utils/iron-session/withSession"; +} from "@/src/services/loopgate/unlockable"; +import { getPinataIndexLink } from "@/src/services/pinata"; +import { withSessionRoute } from "@/src/middleware/ironSession/withSession"; const handler = async (req: NextApiRequest, res: NextApiResponse) => { const query = req.query; diff --git a/src/pages/api/upload/submarine.ts b/src/pages/api/upload/submarine.ts index 65bf4daf4..7aa3e2407 100644 --- a/src/pages/api/upload/submarine.ts +++ b/src/pages/api/upload/submarine.ts @@ -1,5 +1,5 @@ import type { NextApiRequest, NextApiResponse } from "next"; -import { withSessionRoute } from "@/src/utils/iron-session/withSession"; +import { withSessionRoute } from "@/src/middleware/ironSession/withSession"; import { checkAuthentication, handleError, diff --git a/src/pages/index.tsx b/src/pages/index.tsx index fd4a8b908..2fef18131 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,5 +1,8 @@ import { GetServerSideProps } from "next"; -import { fetchAllUnlockables, findAllUnlockables } from "../utils/generic"; +import { + fetchAllUnlockables, + findAllUnlockables, +} from "../services/loopgate/unlockable"; import { UnlockableV2 } from "../config/types"; import Header from "../components/Header"; @@ -8,7 +11,7 @@ import Hero from "../components/Pages/Home/Hero"; import ContentBlocks from "../components/Pages/Home/ContentBlocks"; import UseCases from "../components/Pages/Home/UseCases"; import CTABanner from "../components/Pages/Home/CTABanner"; -import { techPattern } from "../styles/inline-styles"; +import { techPattern } from "../styles/inlineStyles"; export const getServerSideProps: GetServerSideProps = async (context) => { context.res.setHeader( diff --git a/src/pages/unlocks/[uuid].tsx b/src/pages/unlocks/[uuid].tsx index ec0d22d59..1d704e7a0 100644 --- a/src/pages/unlocks/[uuid].tsx +++ b/src/pages/unlocks/[uuid].tsx @@ -1,13 +1,13 @@ import { fetchUnlockableByUuid, findUnlockableByUuid, -} from "@/src/utils/generic"; +} from "@/src/services/loopgate/unlockable"; import Layout from "@/src/components/UnlockablePage/Layout"; import FourOhFour from "@/src/components/UnlockablePage/404"; import UnlockCard from "@/src/components/UnlockablePage/UnlockCard/UnlockCard"; import { GetServerSideProps } from "next"; import { UnlockableV2 } from "@/src/config/types"; -import { isUuid } from "@/src/utils/supabase/helpers"; +import { isUuid } from "@/src/utils/generic"; import NextHeadBase from "@/src/components/SEO/NextHeadBase"; export const getServerSideProps: GetServerSideProps = async (context) => { diff --git a/src/pages/unlocks/browse.tsx b/src/pages/unlocks/browse.tsx index 9d079ed4e..524e26c8c 100644 --- a/src/pages/unlocks/browse.tsx +++ b/src/pages/unlocks/browse.tsx @@ -1,8 +1,8 @@ +import { truncate0x } from "@/src/utils/generic"; import { findAllUnlockables, fetchAllUnlockables, - truncate0x, -} from "@/src/utils/generic"; +} from "@/src/services/loopgate/unlockable"; import Layout from "@/src/components/UnlockablePage/Layout"; import FourOhFour from "@/src/components/UnlockablePage/404"; import { GetServerSideProps } from "next"; diff --git a/src/utils/generic/fetchAllUnlockables.ts b/src/services/loopgate/unlockable/fetchAllUnlockables.ts similarity index 64% rename from src/utils/generic/fetchAllUnlockables.ts rename to src/services/loopgate/unlockable/fetchAllUnlockables.ts index 542dfe026..def0bc1ec 100644 --- a/src/utils/generic/fetchAllUnlockables.ts +++ b/src/services/loopgate/unlockable/fetchAllUnlockables.ts @@ -1,7 +1,7 @@ -import Supabase from "../supabase"; -import { parseSqlUnlockable } from "../supabase/helpers"; +import Supabase from "../../supabase"; +import { parseSqlUnlockable } from "../../supabase/helpers"; -const fetchAllUnlockables = async (owner?: string) => { +export const fetchAllUnlockables = async (owner?: string) => { const { data: unlockables, error } = await Supabase.from( "unlockables_with_criteria" ) @@ -20,5 +20,3 @@ const fetchAllUnlockables = async (owner?: string) => { return parsedUnlockables; } }; - -export default fetchAllUnlockables; diff --git a/src/utils/generic/fetchUnlockableByUuid.ts b/src/services/loopgate/unlockable/fetchUnlockableByUuid.ts similarity index 76% rename from src/utils/generic/fetchUnlockableByUuid.ts rename to src/services/loopgate/unlockable/fetchUnlockableByUuid.ts index c762edc9a..e86a63ad9 100644 --- a/src/utils/generic/fetchUnlockableByUuid.ts +++ b/src/services/loopgate/unlockable/fetchUnlockableByUuid.ts @@ -1,5 +1,5 @@ -import Supabase from "../supabase"; -import { parseSqlUnlockable } from "../supabase/helpers"; +import Supabase from "../../supabase"; +import { parseSqlUnlockable } from "../../supabase/helpers"; // TODO: Inserting data https://supabase.com/docs/reference/javascript/insert // TODO: Updating data https://supabase.com/docs/reference/javascript/update @@ -7,7 +7,7 @@ import { parseSqlUnlockable } from "../supabase/helpers"; // TODO: Deleting data https://supabase.com/docs/reference/javascript/delete // RLS Policies: Only auth users can mutate -const fetchUnlockableByUuid = async (uuid: string) => { +export const fetchUnlockableByUuid = async (uuid: string) => { let { data: unlockables, error } = await Supabase.from( "unlockables_with_criteria" ) @@ -23,5 +23,3 @@ const fetchUnlockableByUuid = async (uuid: string) => { return parseSqlUnlockable(unlockables); } }; - -export default fetchUnlockableByUuid; diff --git a/src/utils/generic/findAllUnlockables.ts b/src/services/loopgate/unlockable/findAllUnlockables.ts similarity index 62% rename from src/utils/generic/findAllUnlockables.ts rename to src/services/loopgate/unlockable/findAllUnlockables.ts index cad7c145d..633e5be6f 100644 --- a/src/utils/generic/findAllUnlockables.ts +++ b/src/services/loopgate/unlockable/findAllUnlockables.ts @@ -1,7 +1,7 @@ -import { unlockablesV2 } from "../../config/config"; -import { UnlockableV2, ConfigError } from "../../config/types"; +import { unlockablesV2 } from "../../../config/config"; +import { UnlockableV2, ConfigError } from "../../../config/types"; -const findAllUnlockables = ( +export const findAllUnlockables = ( owner?: string, unlockablesArray: UnlockableV2[] = unlockablesV2 ) => { @@ -15,5 +15,3 @@ const findAllUnlockables = ( return unlockablesArray; }; - -export default findAllUnlockables; diff --git a/src/utils/generic/findUnlockableByUuid.test.ts b/src/services/loopgate/unlockable/findUnlockableByUuid.test.ts similarity index 85% rename from src/utils/generic/findUnlockableByUuid.test.ts rename to src/services/loopgate/unlockable/findUnlockableByUuid.test.ts index c2024433e..eddcbc6f8 100644 --- a/src/utils/generic/findUnlockableByUuid.test.ts +++ b/src/services/loopgate/unlockable/findUnlockableByUuid.test.ts @@ -1,5 +1,5 @@ -import { findUnlockableByUuid } from "./"; -import { ConfigError } from "../../config/types"; +import { findUnlockableByUuid } from "../../../utils/generic"; +import { ConfigError } from "../../../config/types"; describe("find unlockables based on UUID", () => { it("should return one result if the user meets the criteria for it", () => { diff --git a/src/utils/generic/findUnlockableByUuid.ts b/src/services/loopgate/unlockable/findUnlockableByUuid.ts similarity index 55% rename from src/utils/generic/findUnlockableByUuid.ts rename to src/services/loopgate/unlockable/findUnlockableByUuid.ts index 00236b1b0..050bd8df9 100644 --- a/src/utils/generic/findUnlockableByUuid.ts +++ b/src/services/loopgate/unlockable/findUnlockableByUuid.ts @@ -1,7 +1,7 @@ -import { unlockablesV2 } from "../../config/config"; -import { UnlockableV2, ConfigError } from "../../config/types"; +import { unlockablesV2 } from "../../../config/config"; +import { UnlockableV2, ConfigError } from "../../../config/types"; -const findUnlockableByUuid = ( +export const findUnlockableByUuid = ( uuid: string, unlockablesArray: UnlockableV2[] = unlockablesV2 ) => { @@ -11,5 +11,3 @@ const findUnlockableByUuid = ( return unlockablesArray.filter((item) => item.id === uuid)[0]; }; - -export default findUnlockableByUuid; diff --git a/src/utils/generic/findUnlockedCids.test.ts b/src/services/loopgate/unlockable/findUnlockedCids.test.ts similarity index 88% rename from src/utils/generic/findUnlockedCids.test.ts rename to src/services/loopgate/unlockable/findUnlockedCids.test.ts index 18694cfa6..782317978 100644 --- a/src/utils/generic/findUnlockedCids.test.ts +++ b/src/services/loopgate/unlockable/findUnlockedCids.test.ts @@ -1,5 +1,5 @@ -import findUnlockedCids from "./findUnlockedCids"; -import { Unlockable, ConfigError } from "../../config/types"; +import { findUnlockedCids } from "./findUnlockedCids"; +import { Unlockable, ConfigError } from "../../../config/types"; const mockUnlockables: Unlockable[] = [ { diff --git a/src/utils/generic/findUnlockedCids.ts b/src/services/loopgate/unlockable/findUnlockedCids.ts similarity index 76% rename from src/utils/generic/findUnlockedCids.ts rename to src/services/loopgate/unlockable/findUnlockedCids.ts index 5ba58b496..0ff04193d 100644 --- a/src/utils/generic/findUnlockedCids.ts +++ b/src/services/loopgate/unlockable/findUnlockedCids.ts @@ -1,6 +1,6 @@ -import { unlockables } from "../../config/config"; -import { checkIfContainsAll } from "./index"; -import { Unlockable, ConfigError } from "../../config/types"; +import { unlockables } from "../../../config/config"; +import { checkIfContainsAll } from "../../../utils/generic/index"; +import { Unlockable, ConfigError } from "../../../config/types"; // Compare NFTs owned by an individual to the configurated combinations to find unlockable content /** @@ -9,7 +9,7 @@ import { Unlockable, ConfigError } from "../../config/types"; * @param unlockablesArray * @returns CIDs of files unlocked by the user */ -const findUnlockedCids = ( +export const findUnlockedCids = ( nfts: string[], unlockablesArray: Unlockable[] = unlockables ) => { @@ -27,5 +27,3 @@ const findUnlockedCids = ( return cids; }; - -export default findUnlockedCids; diff --git a/src/services/loopgate/unlockable/index.ts b/src/services/loopgate/unlockable/index.ts new file mode 100644 index 000000000..a1f805ae7 --- /dev/null +++ b/src/services/loopgate/unlockable/index.ts @@ -0,0 +1,5 @@ +export { fetchAllUnlockables } from "./fetchAllUnlockables"; +export { fetchUnlockableByUuid } from "./fetchUnlockableByUuid"; +export { findAllUnlockables } from "./findAllUnlockables"; +export { findUnlockableByUuid } from "./findUnlockableByUuid"; +export { findUnlockedCids } from "./findUnlockedCids"; diff --git a/src/utils/loopring/_constants.ts b/src/services/loopring/_constants.ts similarity index 100% rename from src/utils/loopring/_constants.ts rename to src/services/loopring/_constants.ts diff --git a/src/utils/loopring/_types.ts b/src/services/loopring/_types.ts similarity index 100% rename from src/utils/loopring/_types.ts rename to src/services/loopring/_types.ts diff --git a/src/utils/loopring/extractNfts.ts b/src/services/loopring/extractNfts.ts similarity index 100% rename from src/utils/loopring/extractNfts.ts rename to src/services/loopring/extractNfts.ts diff --git a/src/utils/loopring/getAllUserNftIds.ts b/src/services/loopring/getAllUserNftIds.ts similarity index 100% rename from src/utils/loopring/getAllUserNftIds.ts rename to src/services/loopring/getAllUserNftIds.ts diff --git a/src/utils/loopring/getMinterAndToken.test.ts b/src/services/loopring/getMinterAndToken.test.ts similarity index 100% rename from src/utils/loopring/getMinterAndToken.test.ts rename to src/services/loopring/getMinterAndToken.test.ts diff --git a/src/utils/loopring/getMinterAndToken.ts b/src/services/loopring/getMinterAndToken.ts similarity index 100% rename from src/utils/loopring/getMinterAndToken.ts rename to src/services/loopring/getMinterAndToken.ts diff --git a/src/utils/loopring/getNftData.ts b/src/services/loopring/getNftData.ts similarity index 100% rename from src/utils/loopring/getNftData.ts rename to src/services/loopring/getNftData.ts diff --git a/src/utils/loopring/getNftHolders.test.ts b/src/services/loopring/getNftHolders.test.ts similarity index 100% rename from src/utils/loopring/getNftHolders.test.ts rename to src/services/loopring/getNftHolders.test.ts diff --git a/src/utils/loopring/getNftHolders.ts b/src/services/loopring/getNftHolders.ts similarity index 100% rename from src/utils/loopring/getNftHolders.ts rename to src/services/loopring/getNftHolders.ts diff --git a/src/utils/loopring/getUserAddress.test.ts b/src/services/loopring/getUserAddress.test.ts similarity index 100% rename from src/utils/loopring/getUserAddress.test.ts rename to src/services/loopring/getUserAddress.test.ts diff --git a/src/utils/loopring/getUserAddress.ts b/src/services/loopring/getUserAddress.ts similarity index 100% rename from src/utils/loopring/getUserAddress.ts rename to src/services/loopring/getUserAddress.ts diff --git a/src/utils/loopring/getUserNfts.test.ts b/src/services/loopring/getUserNfts.test.ts similarity index 100% rename from src/utils/loopring/getUserNfts.test.ts rename to src/services/loopring/getUserNfts.test.ts diff --git a/src/utils/loopring/getUserNfts.ts b/src/services/loopring/getUserNfts.ts similarity index 100% rename from src/utils/loopring/getUserNfts.ts rename to src/services/loopring/getUserNfts.ts diff --git a/src/utils/loopring/headerOpts.ts b/src/services/loopring/headerOpts.ts similarity index 100% rename from src/utils/loopring/headerOpts.ts rename to src/services/loopring/headerOpts.ts diff --git a/src/utils/loopring/index.ts b/src/services/loopring/index.ts similarity index 100% rename from src/utils/loopring/index.ts rename to src/services/loopring/index.ts diff --git a/src/utils/loopring/rateLimitedAxios.ts b/src/services/loopring/rateLimitedAxios.ts similarity index 100% rename from src/utils/loopring/rateLimitedAxios.ts rename to src/services/loopring/rateLimitedAxios.ts diff --git a/src/utils/pinata/_types.ts b/src/services/pinata/_types.ts similarity index 100% rename from src/utils/pinata/_types.ts rename to src/services/pinata/_types.ts diff --git a/src/utils/pinata/checkPinataFolderForHtml.ts b/src/services/pinata/checkPinataFolderForHtml.ts similarity index 100% rename from src/utils/pinata/checkPinataFolderForHtml.ts rename to src/services/pinata/checkPinataFolderForHtml.ts diff --git a/src/utils/pinata/formatAccessLink.test.ts b/src/services/pinata/formatAccessLink.test.ts similarity index 100% rename from src/utils/pinata/formatAccessLink.test.ts rename to src/services/pinata/formatAccessLink.test.ts diff --git a/src/utils/pinata/formatAccessLink.ts b/src/services/pinata/formatAccessLink.ts similarity index 100% rename from src/utils/pinata/formatAccessLink.ts rename to src/services/pinata/formatAccessLink.ts diff --git a/src/utils/pinata/generateAccessLink.ts b/src/services/pinata/generateAccessLink.ts similarity index 100% rename from src/utils/pinata/generateAccessLink.ts rename to src/services/pinata/generateAccessLink.ts diff --git a/src/utils/pinata/getPinataIndexLink.ts b/src/services/pinata/getPinataIndexLink.ts similarity index 100% rename from src/utils/pinata/getPinataIndexLink.ts rename to src/services/pinata/getPinataIndexLink.ts diff --git a/src/utils/pinata/index.ts b/src/services/pinata/index.ts similarity index 100% rename from src/utils/pinata/index.ts rename to src/services/pinata/index.ts diff --git a/src/utils/pinata/listFolderContent.ts b/src/services/pinata/listFolderContent.ts similarity index 100% rename from src/utils/pinata/listFolderContent.ts rename to src/services/pinata/listFolderContent.ts diff --git a/src/utils/pinata/submarine.ts b/src/services/pinata/submarine.ts similarity index 100% rename from src/utils/pinata/submarine.ts rename to src/services/pinata/submarine.ts diff --git a/src/services/supabase/helpers/index.ts b/src/services/supabase/helpers/index.ts new file mode 100644 index 000000000..21585114e --- /dev/null +++ b/src/services/supabase/helpers/index.ts @@ -0,0 +1 @@ +export { parseSqlUnlockable } from "./parseSqlUnlockable"; diff --git a/src/utils/supabase/helpers/parseSqlUnlockable.ts b/src/services/supabase/helpers/parseSqlUnlockable.ts similarity index 79% rename from src/utils/supabase/helpers/parseSqlUnlockable.ts rename to src/services/supabase/helpers/parseSqlUnlockable.ts index 96441c0f7..1e144b623 100644 --- a/src/utils/supabase/helpers/parseSqlUnlockable.ts +++ b/src/services/supabase/helpers/parseSqlUnlockable.ts @@ -1,7 +1,7 @@ import { UnlockableV2 } from "@/src/config/types"; -import parseNftIdString from "./parseNftIdString"; +import { parseNftIdString } from "@/src/utils/generic"; -const parseSqlUnlockable = (supabaseUnlockable: any): UnlockableV2 => { +export const parseSqlUnlockable = (supabaseUnlockable: any): UnlockableV2 => { const nftIds = parseNftIdString(supabaseUnlockable.nft_ids); const unlockable = { @@ -24,5 +24,3 @@ const parseSqlUnlockable = (supabaseUnlockable: any): UnlockableV2 => { return unlockable; }; - -export default parseSqlUnlockable; diff --git a/src/utils/supabase/index.ts b/src/services/supabase/index.ts similarity index 100% rename from src/utils/supabase/index.ts rename to src/services/supabase/index.ts diff --git a/src/utils/supabase/sql/content_types.sql b/src/services/supabase/sql/content_types.sql similarity index 100% rename from src/utils/supabase/sql/content_types.sql rename to src/services/supabase/sql/content_types.sql diff --git a/src/utils/supabase/sql/roles.sql b/src/services/supabase/sql/roles.sql similarity index 100% rename from src/utils/supabase/sql/roles.sql rename to src/services/supabase/sql/roles.sql diff --git a/src/utils/supabase/sql/unlock_criteria.sql b/src/services/supabase/sql/unlock_criteria.sql similarity index 100% rename from src/utils/supabase/sql/unlock_criteria.sql rename to src/services/supabase/sql/unlock_criteria.sql diff --git a/src/utils/supabase/sql/unlockables.sql b/src/services/supabase/sql/unlockables.sql similarity index 100% rename from src/utils/supabase/sql/unlockables.sql rename to src/services/supabase/sql/unlockables.sql diff --git a/src/utils/supabase/sql/users.sql b/src/services/supabase/sql/users.sql similarity index 100% rename from src/utils/supabase/sql/users.sql rename to src/services/supabase/sql/users.sql diff --git a/src/utils/supabase/sql/view/unlockables_with_criteria.sql b/src/services/supabase/sql/view/unlockables_with_criteria.sql similarity index 100% rename from src/utils/supabase/sql/view/unlockables_with_criteria.sql rename to src/services/supabase/sql/view/unlockables_with_criteria.sql diff --git a/src/utils/supabase/supabase.ts b/src/services/supabase/supabase.ts similarity index 100% rename from src/utils/supabase/supabase.ts rename to src/services/supabase/supabase.ts diff --git a/src/utils/supabase/typeExtensions.ts b/src/services/supabase/typeExtensions.ts similarity index 100% rename from src/utils/supabase/typeExtensions.ts rename to src/services/supabase/typeExtensions.ts diff --git a/src/utils/supabase/types.ts b/src/services/supabase/types.ts similarity index 97% rename from src/utils/supabase/types.ts rename to src/services/supabase/types.ts index 2a730ce89..fce570a2c 100644 --- a/src/utils/supabase/types.ts +++ b/src/services/supabase/types.ts @@ -78,6 +78,7 @@ export interface Database { id: string name: string | null owner: string + unlisted: boolean updated_at: string | null } Insert: { @@ -88,6 +89,7 @@ export interface Database { id?: string name?: string | null owner: string + unlisted?: boolean updated_at?: string | null } Update: { @@ -98,6 +100,7 @@ export interface Database { id?: string name?: string | null owner?: string + unlisted?: boolean updated_at?: string | null } } diff --git a/src/services/wagmi/index.ts b/src/services/wagmi/index.ts new file mode 100644 index 000000000..4c345198e --- /dev/null +++ b/src/services/wagmi/index.ts @@ -0,0 +1 @@ +export { WagmiClient } from "./wagmi"; diff --git a/src/utils/wagmi/wagmi.ts b/src/services/wagmi/wagmi.ts similarity index 92% rename from src/utils/wagmi/wagmi.ts rename to src/services/wagmi/wagmi.ts index a39357ec3..c5f08c6de 100644 --- a/src/utils/wagmi/wagmi.ts +++ b/src/services/wagmi/wagmi.ts @@ -10,7 +10,7 @@ const { provider, chains } = configureChains( [publicProvider()] ); -const WagmiClient = createClient({ +export const WagmiClient = createClient({ autoConnect: true, connectors: [ new InjectedConnector({ @@ -32,5 +32,3 @@ const WagmiClient = createClient({ ], provider, }); - -export default WagmiClient; diff --git a/src/styles/inline-styles.ts b/src/styles/inlineStyles.ts similarity index 100% rename from src/styles/inline-styles.ts rename to src/styles/inlineStyles.ts diff --git a/src/utils/generic/checkIfContainsAll.test.ts b/src/utils/generic/checkIfContainsAll.test.ts index 542d46258..d5e8674f6 100644 --- a/src/utils/generic/checkIfContainsAll.test.ts +++ b/src/utils/generic/checkIfContainsAll.test.ts @@ -1,4 +1,4 @@ -import checkIfContainsAll from "./checkIfContainsAll"; +import { checkIfContainsAll } from "./checkIfContainsAll"; import { ConfigError } from "../../config/types"; const mockConfig = ["0x1", "0x2", "0x3"]; diff --git a/src/utils/generic/checkIfContainsAll.ts b/src/utils/generic/checkIfContainsAll.ts index 5aeba41f6..bbc9afe3a 100644 --- a/src/utils/generic/checkIfContainsAll.ts +++ b/src/utils/generic/checkIfContainsAll.ts @@ -6,11 +6,9 @@ import { ConfigError } from "../../config/types"; * @param target * @returns whether the target array contains all values of the config array */ -const checkIfContainsAll = (config: string[], target: string[]) => { +export const checkIfContainsAll = (config: string[], target: string[]) => { if (config.length === 0) { throw new ConfigError("Empty config file"); } return config.every((x) => target.includes(x)); }; - -export default checkIfContainsAll; diff --git a/src/utils/generic/cs.test.ts b/src/utils/generic/cn.test.ts similarity index 93% rename from src/utils/generic/cs.test.ts rename to src/utils/generic/cn.test.ts index 683b3fc58..b2d39f326 100644 --- a/src/utils/generic/cs.test.ts +++ b/src/utils/generic/cn.test.ts @@ -1,4 +1,4 @@ -import cn from "./cn"; +import { cn } from "./cn"; const existingClass = "foo"; diff --git a/src/utils/generic/cn.ts b/src/utils/generic/cn.ts index d135cd82d..0f46f37ae 100644 --- a/src/utils/generic/cn.ts +++ b/src/utils/generic/cn.ts @@ -1,8 +1,6 @@ import { ClassValue, clsx } from "clsx"; import { twMerge } from "tailwind-merge"; -const cn = (...inputs: ClassValue[]) => { +export const cn = (...inputs: ClassValue[]) => { return twMerge(clsx(inputs)); }; - -export default cn; diff --git a/src/utils/generic/formatRelativeDate.ts b/src/utils/generic/formatRelativeDate.ts index cd442c26a..3a74605e0 100644 --- a/src/utils/generic/formatRelativeDate.ts +++ b/src/utils/generic/formatRelativeDate.ts @@ -1,8 +1,6 @@ import { formatDistance } from "date-fns"; -const formatRelativeDate = (date: Date | string) => { - const ts = new Date(date); - return formatDistance(ts, new Date(), { addSuffix: true }); +export const formatRelativeDate = (date: Date | string) => { + const timestamp = new Date(date); + return formatDistance(timestamp, new Date(), { addSuffix: true }); }; - -export default formatRelativeDate; diff --git a/src/utils/generic/getCurrentYear.test.ts b/src/utils/generic/getCurrentYear.test.ts index d5b5ea992..66705cb63 100644 --- a/src/utils/generic/getCurrentYear.test.ts +++ b/src/utils/generic/getCurrentYear.test.ts @@ -1,4 +1,4 @@ -import getCurrentYear from "./getCurrentYear"; +import { getCurrentYear } from "./getCurrentYear"; beforeEach(() => { jest.spyOn(Date.prototype, "getFullYear").mockReturnValue(2023); diff --git a/src/utils/generic/getCurrentYear.ts b/src/utils/generic/getCurrentYear.ts index bba8b8320..dafb8084b 100644 --- a/src/utils/generic/getCurrentYear.ts +++ b/src/utils/generic/getCurrentYear.ts @@ -1,7 +1,4 @@ -// Get the current year -const getCurrentYear = (): number => { +export const getCurrentYear = (): number => { const currentTime = new Date(); return currentTime.getFullYear(); }; - -export default getCurrentYear; diff --git a/src/utils/generic/index.ts b/src/utils/generic/index.ts index e9f28b926..6b3896bb8 100644 --- a/src/utils/generic/index.ts +++ b/src/utils/generic/index.ts @@ -1,25 +1,7 @@ -import checkIfContainsAll from "./checkIfContainsAll"; -import findUnlockedCids from "./findUnlockedCids"; -import getCurrentYear from "./getCurrentYear"; -import findUnlockableByUuid from "./findUnlockableByUuid"; -import findAllUnlockables from "./findAllUnlockables"; -import fetchUnlockableByUuid from "./fetchUnlockableByUuid"; -import fetchAllUnlockables from "./fetchAllUnlockables"; -import uuidToNumber from "./uuidToNumber"; -import truncate0x from "./truncate0x"; -import formatRelativeDate from "./formatRelativeDate"; -import cn from "./cn"; - -export { - checkIfContainsAll, - findUnlockedCids, - findUnlockableByUuid, - findAllUnlockables, - fetchUnlockableByUuid, - fetchAllUnlockables, - uuidToNumber, - getCurrentYear, - truncate0x, - formatRelativeDate, - cn, -}; +export { parseNftIdString } from "./parseNftIdString"; +export { cn } from "./cn"; +export { formatRelativeDate } from "./formatRelativeDate"; +export { getCurrentYear } from "./getCurrentYear"; +export { uuidToNumber } from "./uuidToNumber"; +export { truncate0x } from "./truncate0x"; +export { isUuid } from "./isUuid"; diff --git a/src/utils/supabase/helpers/isUuid.test.ts b/src/utils/generic/isUuid.test.ts similarity index 94% rename from src/utils/supabase/helpers/isUuid.test.ts rename to src/utils/generic/isUuid.test.ts index c2eaf709f..2904110e2 100644 --- a/src/utils/supabase/helpers/isUuid.test.ts +++ b/src/utils/generic/isUuid.test.ts @@ -1,4 +1,4 @@ -import isUuid from "./isUuid"; +import { isUuid } from "./isUuid"; describe("isUuid", () => { it("returns true for valid UUIDs", () => { diff --git a/src/utils/supabase/helpers/isUuid.ts b/src/utils/generic/isUuid.ts similarity index 67% rename from src/utils/supabase/helpers/isUuid.ts rename to src/utils/generic/isUuid.ts index 4d69b712e..2dfcd5e9b 100644 --- a/src/utils/supabase/helpers/isUuid.ts +++ b/src/utils/generic/isUuid.ts @@ -1,8 +1,6 @@ const uuidRegex = /^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/; -const isUuid = (potentialUuid: string): boolean => { +export const isUuid = (potentialUuid: string): boolean => { return uuidRegex.test(potentialUuid as string); }; - -export default isUuid; diff --git a/src/utils/supabase/helpers/parseNftIdString.test.ts b/src/utils/generic/parseNftIdString.test.ts similarity index 96% rename from src/utils/supabase/helpers/parseNftIdString.test.ts rename to src/utils/generic/parseNftIdString.test.ts index 20b63545e..83ff35d1b 100644 --- a/src/utils/supabase/helpers/parseNftIdString.test.ts +++ b/src/utils/generic/parseNftIdString.test.ts @@ -1,4 +1,4 @@ -import parseNftIdString from "./parseNftIdString"; +import { parseNftIdString } from "./parseNftIdString"; describe("parseNftIdString", () => { it("should parse a single NFT ID", () => { diff --git a/src/utils/supabase/helpers/parseNftIdString.ts b/src/utils/generic/parseNftIdString.ts similarity index 68% rename from src/utils/supabase/helpers/parseNftIdString.ts rename to src/utils/generic/parseNftIdString.ts index 079462638..a754f188b 100644 --- a/src/utils/supabase/helpers/parseNftIdString.ts +++ b/src/utils/generic/parseNftIdString.ts @@ -4,12 +4,10 @@ * Output: ["0x00...00", 1x11.11] */ -const parseNftIdString = (nftIdString?: string): string[] => { +export const parseNftIdString = (nftIdString?: string): string[] => { if (!nftIdString) { return []; } return nftIdString.replaceAll(" ", "").split(","); }; - -export default parseNftIdString; diff --git a/src/utils/generic/truncate0x.ts b/src/utils/generic/truncate0x.ts index 3107ed282..a266d3a10 100644 --- a/src/utils/generic/truncate0x.ts +++ b/src/utils/generic/truncate0x.ts @@ -1,4 +1,4 @@ -const truncate0x = (address: string) => { +export const truncate0x = (address: string) => { const len = address.length; if (!address.startsWith("0x") || len != 42) { @@ -7,5 +7,3 @@ const truncate0x = (address: string) => { return `${address.slice(0, 6)}…${address.slice(len - 4, len)}`; }; - -export default truncate0x; diff --git a/src/utils/generic/uuidToNumber.test.ts b/src/utils/generic/uuidToNumber.test.ts index 4d1fc76c6..5786480ab 100644 --- a/src/utils/generic/uuidToNumber.test.ts +++ b/src/utils/generic/uuidToNumber.test.ts @@ -1,4 +1,4 @@ -import uuidToNumber from "./uuidToNumber"; +import { uuidToNumber } from "./uuidToNumber"; describe("uuidToNumber", () => { it("should generate a number between 0 and 133742069", () => { diff --git a/src/utils/generic/uuidToNumber.ts b/src/utils/generic/uuidToNumber.ts index c359743e4..6b5120c9e 100644 --- a/src/utils/generic/uuidToNumber.ts +++ b/src/utils/generic/uuidToNumber.ts @@ -1,6 +1,6 @@ import { createHash } from "crypto"; -const uuidToNumber = (uuid: string) => { +export const uuidToNumber = (uuid: string) => { const hash = createHash("sha256").update(uuid).digest("hex"); const hexDigits = hash.slice(0, 4); const decimalValue = parseInt(hexDigits, 16); @@ -8,5 +8,3 @@ const uuidToNumber = (uuid: string) => { const randomNumber = Math.floor(percentage * 133742069); return randomNumber; }; - -export default uuidToNumber; diff --git a/src/utils/supabase/helpers/index.ts b/src/utils/supabase/helpers/index.ts deleted file mode 100644 index e34a7b18d..000000000 --- a/src/utils/supabase/helpers/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import parseNftIdString from "./parseNftIdString"; -import parseSqlUnlockable from "./parseSqlUnlockable"; -import isUuid from "./isUuid"; - -export { parseNftIdString, parseSqlUnlockable, isUuid }; diff --git a/src/utils/wagmi/index.ts b/src/utils/wagmi/index.ts deleted file mode 100644 index 6e29c6de4..000000000 --- a/src/utils/wagmi/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -import WagmiClient from "./wagmi"; -export { WagmiClient }; From bba15ff4ead3289b9e740fa9ce9ce971de371ca4 Mon Sep 17 00:00:00 2001 From: 0xGeel Date: Sat, 27 May 2023 08:13:03 +0200 Subject: [PATCH 10/18] Add "unlisted" property to Unlockables, move sql scripts to /dev/ --- dev/supabase/README.md | 11 +++++++ .../supabase/sql/tables}/content_types.sql | 0 .../sql => dev/supabase/sql/tables}/roles.sql | 2 ++ .../supabase/sql/tables}/unlock_criteria.sql | 0 .../supabase/sql/tables}/unlockables.sql | 32 ++++++++++--------- .../sql => dev/supabase/sql/tables}/users.sql | 2 ++ .../sql/view/unlockables_with_criteria.sql | 15 +++++++++ src/config/config.ts | 3 ++ src/config/types.ts | 3 +- .../unlockable/fetchAllUnlockables.ts | 3 +- .../unlockable/fetchUnlockableByUuid.ts | 6 ---- .../loopgate/unlockable/findAllUnlockables.ts | 6 ++-- .../unlockable/findUnlockableByUuid.test.ts | 2 +- .../loopgate/unlockable/findUnlockedCids.ts | 2 +- .../sql/view/unlockables_with_criteria.sql | 4 --- src/services/supabase/types.ts | 3 ++ src/utils/generic/index.ts | 1 + 17 files changed, 64 insertions(+), 31 deletions(-) create mode 100644 dev/supabase/README.md rename {src/services/supabase/sql => dev/supabase/sql/tables}/content_types.sql (100%) rename {src/services/supabase/sql => dev/supabase/sql/tables}/roles.sql (87%) rename {src/services/supabase/sql => dev/supabase/sql/tables}/unlock_criteria.sql (100%) rename {src/services/supabase/sql => dev/supabase/sql/tables}/unlockables.sql (64%) rename {src/services/supabase/sql => dev/supabase/sql/tables}/users.sql (89%) create mode 100644 dev/supabase/sql/view/unlockables_with_criteria.sql delete mode 100644 src/services/supabase/sql/view/unlockables_with_criteria.sql diff --git a/dev/supabase/README.md b/dev/supabase/README.md new file mode 100644 index 000000000..c6549d970 --- /dev/null +++ b/dev/supabase/README.md @@ -0,0 +1,11 @@ +# Supabase + +## TL;DR: + +- LoopGate stores Unlockables in a Supabase instance. The data inside can be created/read/updated/deleted with the Supabase JS SDK: a PostgREST client. Ex => `const { data } = await Supabase.from("unlockables_with_criteria").select(`\*`)` +- **Row Level Security (RLS)** applies: only those with the correct credentials should be able to access these methods. Read access for most `tables` and `views` is public, which means the data can be queried from the client using the `anon` key. The other operations require the `service key` or a JWT. These operations are done server-side. + +## Managing tables + +- New tables cannot be instantiated with the Supabase JS SDK. Instead, they need to be configured from within the supabase web client. The visual builder may be used, but using the SQL editor with documented queries may be more suitable. +- See `/dev/supabase/sql/*` for the SQL used to create/update/delete/test these tables and views. diff --git a/src/services/supabase/sql/content_types.sql b/dev/supabase/sql/tables/content_types.sql similarity index 100% rename from src/services/supabase/sql/content_types.sql rename to dev/supabase/sql/tables/content_types.sql diff --git a/src/services/supabase/sql/roles.sql b/dev/supabase/sql/tables/roles.sql similarity index 87% rename from src/services/supabase/sql/roles.sql rename to dev/supabase/sql/tables/roles.sql index 252093f8c..ff14fe603 100644 --- a/src/services/supabase/sql/roles.sql +++ b/dev/supabase/sql/tables/roles.sql @@ -1,3 +1,5 @@ +-- 🦻 NOTE: Currently not in use + -- Create and fill a table with the available user roles CREATE TABLE roles ( diff --git a/src/services/supabase/sql/unlock_criteria.sql b/dev/supabase/sql/tables/unlock_criteria.sql similarity index 100% rename from src/services/supabase/sql/unlock_criteria.sql rename to dev/supabase/sql/tables/unlock_criteria.sql diff --git a/src/services/supabase/sql/unlockables.sql b/dev/supabase/sql/tables/unlockables.sql similarity index 64% rename from src/services/supabase/sql/unlockables.sql rename to dev/supabase/sql/tables/unlockables.sql index 75f0c9632..fe01c62da 100644 --- a/src/services/supabase/sql/unlockables.sql +++ b/dev/supabase/sql/tables/unlockables.sql @@ -4,30 +4,32 @@ -- create extension 'uuid-ossp' with schema extensions; CREATE TABLE unlockables ( - id uuid default uuid_generate_v4(), - name text, - description text, - owner VARCHAR(42) NOT NULL, - content_type_id int NOT NULL, - content_url VARCHAR(200) NOT NULL, - criteria_unlock_amount int NOT NULL, - updated_at timestamp default current_timestamp, + id uuid not null default uuid_generate_v4(), + name text null, + description text null, + owner VARCHAR(42) not null, + unlisted boolean not null default true, + content_type_id int not null, + content_url VARCHAR(200) not null, + criteria_unlock_amount int not null, + updated_at timestamp without time zone default not null current_timestamp default current_timestamp, + created_at timestamp without time zone default not null default now(), -- PRIMARY KEY (id), FOREIGN KEY (owner) REFERENCES users(eth_address), FOREIGN KEY (content_type_id) REFERENCES content_types(content_type_id), -- - constraint unlock_amount_nonnegative check (criteria_unlock_amount > 0) + constraint unlock_amount_nonnegative check (criteria_unlock_amount > -2) ); -- Unlockable #1: HTML Blog Example -INSERT INTO unlockables (name, description, owner, content_type_id, content_url, criteria_unlock_amount) -VALUES ('Token Gating with NFTs: Unlocking New Ways to Bring Value', 'This exclusive article contains a primer on what Token Gating is, and provides four actionable prompts on how to implement it to bring value to members of your community.', '0x1337cc354aeaf15b0e98a609cd348df171174e14', 1, 'bafybeiehgpaip4f7jafzf7imgannx3nnv3ubaiwp6ph56mlyzijpqxi45m', 1); +INSERT INTO unlockables (name, description, owner, content_type_id, content_url, criteria_unlock_amount, unlisted) +VALUES ('Token Gating with NFTs: Unlocking New Ways to Bring Value', 'This exclusive article contains a primer on what Token Gating is, and provides four actionable prompts on how to implement it to bring value to members of your community.', '0x1337cc354aeaf15b0e98a609cd348df171174e14', 1, 'bafybeiehgpaip4f7jafzf7imgannx3nnv3ubaiwp6ph56mlyzijpqxi45m', 1, false); -- Unlockable #2: MP4 Video Example -INSERT INTO unlockables (name, description, owner, content_type_id, content_url, criteria_unlock_amount) -VALUES ('0x1337cc354aeaf15b0e98a609cd348df171174e14', 1, 'bafybeihx5eacyxeydcpvudwxa242rnjhn67femy46gzas5d2djb24ti5mi', 1); +INSERT INTO unlockables (name, description, owner, content_type_id, content_url, criteria_unlock_amount, unlisted) +VALUES ('0x1337cc354aeaf15b0e98a609cd348df171174e14', 1, 'bafybeihx5eacyxeydcpvudwxa242rnjhn67femy46gzas5d2djb24ti5mi', 1, false); -- Unlockable #3: Web Game Example -INSERT INTO unlockables (name, description, owner, content_type_id, content_url, criteria_unlock_amount) -VALUES ('Flappy Bird: Origins', 'An incredibly exclusive web game built in Godot 3, optimized for browsers. Dodge the obstacles, and fly for your life...', '0x1337cc354aeaf15b0e98a609cd348df171174e14', 1, 'bafybeihhx5v3saq3b7n55ub5q3atuw2udbqc5ictkv2ih7vd3hxptu22nu', 2); \ No newline at end of file +INSERT INTO unlockables (name, description, owner, content_type_id, content_url, criteria_unlock_amount, unlisted) +VALUES ('Flappy Bird: Origins', 'An incredibly exclusive web game built in Godot 3, optimized for browsers. Dodge the obstacles, and fly for your life...', '0x1337cc354aeaf15b0e98a609cd348df171174e14', 1, 'bafybeihhx5v3saq3b7n55ub5q3atuw2udbqc5ictkv2ih7vd3hxptu22nu', 2, false); \ No newline at end of file diff --git a/src/services/supabase/sql/users.sql b/dev/supabase/sql/tables/users.sql similarity index 89% rename from src/services/supabase/sql/users.sql rename to dev/supabase/sql/tables/users.sql index fa261927e..c2ffdcb45 100644 --- a/src/services/supabase/sql/users.sql +++ b/dev/supabase/sql/tables/users.sql @@ -1,3 +1,5 @@ +-- 🦻 NOTE: Currently not in use + -- Create and fill a table with users CREATE TABLE users ( diff --git a/dev/supabase/sql/view/unlockables_with_criteria.sql b/dev/supabase/sql/view/unlockables_with_criteria.sql new file mode 100644 index 000000000..048589ee9 --- /dev/null +++ b/dev/supabase/sql/view/unlockables_with_criteria.sql @@ -0,0 +1,15 @@ +----- Code to create the view +-- CREATE VIEW unlockables_with_criteria_v2 AS +-- SELECT u.id, u.name, u.description, u.unlisted, u.owner, u.content_url, u.criteria_unlock_amount, u.updated_at, STRING_AGG(c.nft_id, ', ') AS nft_ids +-- FROM unlockables u +-- LEFT JOIN unlock_criteria c ON u.id = c.unlockable_id +-- GROUP BY u.id, u.name, u.description, u.owner, u.content_url, u.criteria_unlock_amount, u.updated_at + +----- Test if it works +-- select * from unlockables_with_criteria_v2 + +-- ALTER VIEW unlockables_with_criteria RENAME TO unlockables_with_criteria_backup +-- ALTER VIEW unlockables_with_criteria_v2 RENAME TO unlockables_with_criteria + +----- Delete a previous one +-- DROP VIEW IF EXISTS unlockables_with_criteria_v2 \ No newline at end of file diff --git a/src/config/config.ts b/src/config/config.ts index feb1981d4..3fed994a6 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -55,6 +55,7 @@ const unlockablesV2: UnlockableV2[] = [ "This exclusive article contains a primer on what Token Gating is, and provides four actionable prompts on how to implement it to bring value to members of your community.", lastUpdated: "2023-03-13 16:05:23.481327", }, + unlisted: false, content: { type: "IPFS", url: "bafybeiehgpaip4f7jafzf7imgannx3nnv3ubaiwp6ph56mlyzijpqxi45m", @@ -72,6 +73,7 @@ const unlockablesV2: UnlockableV2[] = [ metadata: { lastUpdated: "2023-03-13 16:05:23.481327", }, + unlisted: false, content: { type: "IPFS", url: "bafybeihx5eacyxeydcpvudwxa242rnjhn67femy46gzas5d2djb24ti5mi", @@ -86,6 +88,7 @@ const unlockablesV2: UnlockableV2[] = [ { id: "3eade688-8839-4fd7-b97a-f7c5f5bfc6ad", owner: "0x1337CC354AeAf15B0E98A609cd348DF171174e14", + unlisted: false, metadata: { name: "Flappy Bird: Origins", description: diff --git a/src/config/types.ts b/src/config/types.ts index e5eb212ca..32cb6b8df 100644 --- a/src/config/types.ts +++ b/src/config/types.ts @@ -17,8 +17,9 @@ export interface UnlockCriteria { export interface UnlockableV2 { id: string; - owner: `0x${string}`; + owner: string; metadata: Metadata; + unlisted: boolean; content: { type: "IPFS"; url: string; diff --git a/src/services/loopgate/unlockable/fetchAllUnlockables.ts b/src/services/loopgate/unlockable/fetchAllUnlockables.ts index def0bc1ec..9398d9123 100644 --- a/src/services/loopgate/unlockable/fetchAllUnlockables.ts +++ b/src/services/loopgate/unlockable/fetchAllUnlockables.ts @@ -6,7 +6,8 @@ export const fetchAllUnlockables = async (owner?: string) => { "unlockables_with_criteria" ) .select(`*`) - .eq(owner ? "owner" : "", owner); + .eq(owner ? "owner" : "", owner) + .neq("unlisted", true); if (error) { throw error; diff --git a/src/services/loopgate/unlockable/fetchUnlockableByUuid.ts b/src/services/loopgate/unlockable/fetchUnlockableByUuid.ts index e86a63ad9..c8a7c99e9 100644 --- a/src/services/loopgate/unlockable/fetchUnlockableByUuid.ts +++ b/src/services/loopgate/unlockable/fetchUnlockableByUuid.ts @@ -1,12 +1,6 @@ import Supabase from "../../supabase"; import { parseSqlUnlockable } from "../../supabase/helpers"; -// TODO: Inserting data https://supabase.com/docs/reference/javascript/insert -// TODO: Updating data https://supabase.com/docs/reference/javascript/update -// OR: Upserting data https://supabase.com/docs/reference/javascript/upsert -// TODO: Deleting data https://supabase.com/docs/reference/javascript/delete -// RLS Policies: Only auth users can mutate - export const fetchUnlockableByUuid = async (uuid: string) => { let { data: unlockables, error } = await Supabase.from( "unlockables_with_criteria" diff --git a/src/services/loopgate/unlockable/findAllUnlockables.ts b/src/services/loopgate/unlockable/findAllUnlockables.ts index 633e5be6f..e8d9adbf4 100644 --- a/src/services/loopgate/unlockable/findAllUnlockables.ts +++ b/src/services/loopgate/unlockable/findAllUnlockables.ts @@ -10,8 +10,10 @@ export const findAllUnlockables = ( } if (owner) { - return unlockablesArray.filter((unlockable) => unlockable.owner === owner); + return unlockablesArray.filter( + (unlockable) => unlockable.owner === owner && !unlockable.unlisted + ); } - return unlockablesArray; + return unlockablesArray.filter((unlockable) => !unlockable.unlisted); }; diff --git a/src/services/loopgate/unlockable/findUnlockableByUuid.test.ts b/src/services/loopgate/unlockable/findUnlockableByUuid.test.ts index eddcbc6f8..a69e43cef 100644 --- a/src/services/loopgate/unlockable/findUnlockableByUuid.test.ts +++ b/src/services/loopgate/unlockable/findUnlockableByUuid.test.ts @@ -1,4 +1,4 @@ -import { findUnlockableByUuid } from "../../../utils/generic"; +import { findUnlockableByUuid } from "./findUnlockableByUuid"; import { ConfigError } from "../../../config/types"; describe("find unlockables based on UUID", () => { diff --git a/src/services/loopgate/unlockable/findUnlockedCids.ts b/src/services/loopgate/unlockable/findUnlockedCids.ts index 0ff04193d..c40e3bc3b 100644 --- a/src/services/loopgate/unlockable/findUnlockedCids.ts +++ b/src/services/loopgate/unlockable/findUnlockedCids.ts @@ -1,5 +1,5 @@ import { unlockables } from "../../../config/config"; -import { checkIfContainsAll } from "../../../utils/generic/index"; +import { checkIfContainsAll } from "@/src/utils/generic/checkIfContainsAll"; import { Unlockable, ConfigError } from "../../../config/types"; // Compare NFTs owned by an individual to the configurated combinations to find unlockable content diff --git a/src/services/supabase/sql/view/unlockables_with_criteria.sql b/src/services/supabase/sql/view/unlockables_with_criteria.sql deleted file mode 100644 index fcbd50b36..000000000 --- a/src/services/supabase/sql/view/unlockables_with_criteria.sql +++ /dev/null @@ -1,4 +0,0 @@ -CREATE VIEW unlockables_with_criteria AS - SELECT u.id, u.name, u.description, u.owner, u.content_url, u.criteria_unlock_amount, u.updated_at, c.nft_id - FROM unlockables u - LEFT JOIN unlock_criteria c ON u.id = c.unlockable_id diff --git a/src/services/supabase/types.ts b/src/services/supabase/types.ts index fce570a2c..c5a433daa 100644 --- a/src/services/supabase/types.ts +++ b/src/services/supabase/types.ts @@ -73,6 +73,7 @@ export interface Database { Row: { content_type_id: number content_url: string + created_at: string criteria_unlock_amount: number description: string | null id: string @@ -84,6 +85,7 @@ export interface Database { Insert: { content_type_id: number content_url: string + created_at?: string criteria_unlock_amount: number description?: string | null id?: string @@ -95,6 +97,7 @@ export interface Database { Update: { content_type_id?: number content_url?: string + created_at?: string criteria_unlock_amount?: number description?: string | null id?: string diff --git a/src/utils/generic/index.ts b/src/utils/generic/index.ts index 6b3896bb8..6c863fff9 100644 --- a/src/utils/generic/index.ts +++ b/src/utils/generic/index.ts @@ -5,3 +5,4 @@ export { getCurrentYear } from "./getCurrentYear"; export { uuidToNumber } from "./uuidToNumber"; export { truncate0x } from "./truncate0x"; export { isUuid } from "./isUuid"; +export { checkIfContainsAll } from "./checkIfContainsAll"; From 17e6bd60b76113b45e05349873357c9aeef65834 Mon Sep 17 00:00:00 2001 From: 0xGeel Date: Sat, 27 May 2023 11:07:46 +0200 Subject: [PATCH 11/18] =?UTF-8?q?More=20refactoring,=20type=20safety=20and?= =?UTF-8?q?=20linting=20=F0=9F=98=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .eslintrc.json | 7 +- dev/supabase/README.md | 4 + package-lock.json | 330 ++++++++++++++++++ package.json | 1 + src/components/UI/Input.tsx | 32 +- src/components/UI/RadioGroup.tsx | 1 + src/components/UI/Slider.tsx | 1 + src/components/UI/Textarea.tsx | 32 +- .../UnlockablePage/UnlockCard/Metadata.tsx | 3 +- .../UnlockCard/UnlockSection.tsx | 2 + src/config/types.ts | 1 + src/hooks/usePrefersReducedMotion.ts | 10 +- src/middleware/redirectTo.ts | 2 +- src/middleware/siwe/configure.tsx | 5 + src/pages/api/helpers/check-env-status.ts | 2 +- src/pages/api/nft/get-holders-nftdata.ts | 2 +- src/pages/api/nft/get-nft-data.ts | 1 - src/pages/api/upload/submarine.ts | 2 +- src/pages/upload/form.tsx | 4 + src/pages/upload/index.tsx | 2 + .../unlockable/fetchAllUnlockables.ts | 6 +- .../unlockable/fetchUnlockableByUuid.ts | 6 +- src/services/loopring/_constants.ts | 17 - src/services/loopring/getAllUserNftIds.ts | 62 ---- .../loopring/getMinterAndToken.test.ts | 28 -- src/services/loopring/getNftData.ts | 27 -- src/services/loopring/getNftHolders.test.ts | 13 - src/services/loopring/getUserNfts.test.ts | 16 - src/services/loopring/getUserNfts.ts | 19 - src/services/loopring/helpers/_constants.ts | 9 + src/services/loopring/{ => helpers}/_types.ts | 38 +- .../loopring/{ => helpers}/extractNfts.ts | 8 +- .../loopring/{ => helpers}/headerOpts.ts | 4 +- .../{ => helpers}/rateLimitedAxios.ts | 4 +- src/services/loopring/index.ts | 34 +- .../loopring/requests/getAllUserNftIds.ts | 58 +++ .../{ => requests}/getMinterAndToken.ts | 8 +- src/services/loopring/requests/getNftData.ts | 29 ++ .../loopring/{ => requests}/getNftHolders.ts | 12 +- .../{ => requests}/getUserAddress.test.ts | 2 +- .../loopring/{ => requests}/getUserAddress.ts | 10 +- src/services/pinata/_types.ts | 6 +- src/services/supabase/helpers/index.ts | 2 +- .../supabase/helpers/mapUnlockable.ts | 30 ++ .../supabase/helpers/parseSqlUnlockable.ts | 26 -- .../{typeExtensions.ts => typeExtenstions.ts} | 0 src/services/supabase/types.ts | 15 + src/utils/generic/parseNftIdString.ts | 4 +- src/utils/logger/logger.ts | 4 +- 49 files changed, 613 insertions(+), 328 deletions(-) delete mode 100644 src/services/loopring/_constants.ts delete mode 100644 src/services/loopring/getAllUserNftIds.ts delete mode 100644 src/services/loopring/getMinterAndToken.test.ts delete mode 100644 src/services/loopring/getNftData.ts delete mode 100644 src/services/loopring/getNftHolders.test.ts delete mode 100644 src/services/loopring/getUserNfts.test.ts delete mode 100644 src/services/loopring/getUserNfts.ts create mode 100644 src/services/loopring/helpers/_constants.ts rename src/services/loopring/{ => helpers}/_types.ts (62%) rename src/services/loopring/{ => helpers}/extractNfts.ts (54%) rename src/services/loopring/{ => helpers}/headerOpts.ts (76%) rename src/services/loopring/{ => helpers}/rateLimitedAxios.ts (57%) create mode 100644 src/services/loopring/requests/getAllUserNftIds.ts rename src/services/loopring/{ => requests}/getMinterAndToken.ts (81%) create mode 100644 src/services/loopring/requests/getNftData.ts rename src/services/loopring/{ => requests}/getNftHolders.ts (80%) rename src/services/loopring/{ => requests}/getUserAddress.test.ts (95%) rename src/services/loopring/{ => requests}/getUserAddress.ts (53%) create mode 100644 src/services/supabase/helpers/mapUnlockable.ts delete mode 100644 src/services/supabase/helpers/parseSqlUnlockable.ts rename src/services/supabase/{typeExtensions.ts => typeExtenstions.ts} (100%) diff --git a/.eslintrc.json b/.eslintrc.json index bffb357a7..c258ef0a4 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,3 +1,8 @@ { - "extends": "next/core-web-vitals" + "plugins": ["@typescript-eslint"], + "extends": ["next/core-web-vitals", "plugin:@typescript-eslint/recommended"], + "rules": { + "@typescript-eslint/no-unused-vars": "error", + "@typescript-eslint/no-explicit-any": "error" + } } diff --git a/dev/supabase/README.md b/dev/supabase/README.md index c6549d970..236cf96d5 100644 --- a/dev/supabase/README.md +++ b/dev/supabase/README.md @@ -9,3 +9,7 @@ - New tables cannot be instantiated with the Supabase JS SDK. Instead, they need to be configured from within the supabase web client. The visual builder may be used, but using the SQL editor with documented queries may be more suitable. - See `/dev/supabase/sql/*` for the SQL used to create/update/delete/test these tables and views. + +## Updating types + +Use `npm run supabase:types` to update the types in `src/services/supabase/types.ts` 🚀 diff --git a/package-lock.json b/package-lock.json index 4ea6a55c8..d94f6ceb7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -58,6 +58,7 @@ "@types/react": "^18.0.9", "@types/react-dom": "^18.0.3", "@types/uuid": "^9.0.1", + "@typescript-eslint/eslint-plugin": "^5.59.7", "autoprefixer": "^10.4.13", "eslint": "^8.15.0", "eslint-config-next": "^12.1.6", @@ -829,6 +830,30 @@ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", + "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, "node_modules/@eslint/eslintrc": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", @@ -3913,6 +3938,12 @@ "parse5": "^7.0.0" } }, + "node_modules/@types/json-schema": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", + "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", + "dev": true + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -4031,6 +4062,12 @@ "@types/node": "*" } }, + "node_modules/@types/semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", + "dev": true + }, "node_modules/@types/serve-static": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz", @@ -4095,6 +4132,87 @@ "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", "dev": true }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.59.7", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.7.tgz", + "integrity": "sha512-BL+jYxUFIbuYwy+4fF86k5vdT9lT0CNJ6HtwrIvGh0PhH8s0yy5rjaKH2fDCrz5ITHy07WCzVGNvAmjJh4IJFA==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.59.7", + "@typescript-eslint/type-utils": "5.59.7", + "@typescript-eslint/utils": "5.59.7", + "debug": "^4.3.4", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { + "version": "5.59.7", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.7.tgz", + "integrity": "sha512-FL6hkYWK9zBGdxT2wWEd2W8ocXMu3K94i3gvMrjXpx+koFYdYV7KprKfirpgY34vTGzEPPuKoERpP8kD5h7vZQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.59.7", + "@typescript-eslint/visitor-keys": "5.59.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { + "version": "5.59.7", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.7.tgz", + "integrity": "sha512-UnVS2MRRg6p7xOSATscWkKjlf/NDKuqo5TdbWck6rIRZbmKpVNTLALzNvcjIfHBE7736kZOFc/4Z3VcZwuOM/A==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.59.7", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.7.tgz", + "integrity": "sha512-tyN+X2jvMslUszIiYbF0ZleP+RqQsFVpGrKI6e0Eet1w8WmhsAtmzaqm8oM8WJQ1ysLwhnsK/4hYHJjOgJVfQQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.59.7", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@typescript-eslint/parser": { "version": "5.48.2", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.48.2.tgz", @@ -4139,6 +4257,90 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.59.7", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.7.tgz", + "integrity": "sha512-ozuz/GILuYG7osdY5O5yg0QxXUAEoI4Go3Do5xeu+ERH9PorHBPSdvD3Tjp2NN2bNLh1NJQSsQu2TPu/Ly+HaQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.59.7", + "@typescript-eslint/utils": "5.59.7", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { + "version": "5.59.7", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.7.tgz", + "integrity": "sha512-UnVS2MRRg6p7xOSATscWkKjlf/NDKuqo5TdbWck6rIRZbmKpVNTLALzNvcjIfHBE7736kZOFc/4Z3VcZwuOM/A==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "5.59.7", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.7.tgz", + "integrity": "sha512-4A1NtZ1I3wMN2UGDkU9HMBL+TIQfbrh4uS0WDMMpf3xMRursDbqEf1ahh6vAAe3mObt8k3ZATnezwG4pdtWuUQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.59.7", + "@typescript-eslint/visitor-keys": "5.59.7", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.59.7", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.7.tgz", + "integrity": "sha512-tyN+X2jvMslUszIiYbF0ZleP+RqQsFVpGrKI6e0Eet1w8WmhsAtmzaqm8oM8WJQ1ysLwhnsK/4hYHJjOgJVfQQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.59.7", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@typescript-eslint/types": { "version": "5.48.2", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.48.2.tgz", @@ -4179,6 +4381,128 @@ } } }, + "node_modules/@typescript-eslint/utils": { + "version": "5.59.7", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.7.tgz", + "integrity": "sha512-yCX9WpdQKaLufz5luG4aJbOpdXf/fjwGMcLFXZVPUz3QqLirG5QcwwnIHNf8cjLjxK4qtzTO8udUtMQSAToQnQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.59.7", + "@typescript-eslint/types": "5.59.7", + "@typescript-eslint/typescript-estree": "5.59.7", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { + "version": "5.59.7", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.7.tgz", + "integrity": "sha512-FL6hkYWK9zBGdxT2wWEd2W8ocXMu3K94i3gvMrjXpx+koFYdYV7KprKfirpgY34vTGzEPPuKoERpP8kD5h7vZQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.59.7", + "@typescript-eslint/visitor-keys": "5.59.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { + "version": "5.59.7", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.7.tgz", + "integrity": "sha512-UnVS2MRRg6p7xOSATscWkKjlf/NDKuqo5TdbWck6rIRZbmKpVNTLALzNvcjIfHBE7736kZOFc/4Z3VcZwuOM/A==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "5.59.7", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.7.tgz", + "integrity": "sha512-4A1NtZ1I3wMN2UGDkU9HMBL+TIQfbrh4uS0WDMMpf3xMRursDbqEf1ahh6vAAe3mObt8k3ZATnezwG4pdtWuUQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.59.7", + "@typescript-eslint/visitor-keys": "5.59.7", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.59.7", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.7.tgz", + "integrity": "sha512-tyN+X2jvMslUszIiYbF0ZleP+RqQsFVpGrKI6e0Eet1w8WmhsAtmzaqm8oM8WJQ1ysLwhnsK/4hYHJjOgJVfQQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.59.7", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, "node_modules/@typescript-eslint/visitor-keys": { "version": "5.48.2", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.2.tgz", @@ -11891,6 +12215,12 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, "node_modules/next": { "version": "13.3.4", "resolved": "https://registry.npmjs.org/next/-/next-13.3.4.tgz", diff --git a/package.json b/package.json index f14f9e096..fc6b85e93 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "@types/react": "^18.0.9", "@types/react-dom": "^18.0.3", "@types/uuid": "^9.0.1", + "@typescript-eslint/eslint-plugin": "^5.59.7", "autoprefixer": "^10.4.13", "eslint": "^8.15.0", "eslint-config-next": "^12.1.6", diff --git a/src/components/UI/Input.tsx b/src/components/UI/Input.tsx index 8f080988e..3f6758732 100644 --- a/src/components/UI/Input.tsx +++ b/src/components/UI/Input.tsx @@ -4,23 +4,21 @@ import * as React from "react"; import { cn } from "@/src/utils/generic"; -export interface InputProps - extends React.InputHTMLAttributes {} - -const Input = React.forwardRef( - ({ className, ...props }, ref) => { - return ( - - ); - } -); +const Input = React.forwardRef< + HTMLInputElement, + React.InputHTMLAttributes +>(({ className, ...props }, ref) => { + return ( + + ); +}); Input.displayName = "Input"; export { Input }; diff --git a/src/components/UI/RadioGroup.tsx b/src/components/UI/RadioGroup.tsx index c1f4f8c12..02b25bf5a 100644 --- a/src/components/UI/RadioGroup.tsx +++ b/src/components/UI/RadioGroup.tsx @@ -23,6 +23,7 @@ RadioGroup.displayName = RadioGroupPrimitive.Root.displayName; const RadioGroupItem = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef + // eslint-disable-next-line >(({ className, children, ...props }, ref) => { return ( , React.ComponentPropsWithoutRef + // eslint-disable-next-line >(({ className, value, ...props }, ref) => ( {} - -const Textarea = React.forwardRef( - ({ className, ...props }, ref) => { - return ( -