From 2ea99f37ceb726390ea3d077f25e0551a2c7d6cc Mon Sep 17 00:00:00 2001 From: Hassan El Mghari Date: Sun, 19 Mar 2023 13:49:21 -0400 Subject: [PATCH 01/23] first script --- package-lock.json | 130 +++++++++++++++++++++++++++++++++++++ package.json | 2 + pages/api/getUserEmails.ts | 30 +++++++++ rooms.csv | 4 ++ 4 files changed, 166 insertions(+) create mode 100644 pages/api/getUserEmails.ts create mode 100644 rooms.csv diff --git a/package-lock.json b/package-lock.json index ead470e6..8b19afb5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "framer-motion": "^8.2.4", "next": "latest", "next-auth": "^4.20.1", + "objects-to-csv": "^1.3.6", "prisma": "^4.11.0", "react": "18.2.0", "react-compare-slider": "^2.2.0", @@ -33,6 +34,7 @@ }, "devDependencies": { "@types/node": "18.11.3", + "@types/objects-to-csv": "^1.3.1", "@types/react": "18.0.21", "@types/react-dom": "18.0.6", "@types/request-ip": "^0.0.37", @@ -881,6 +883,12 @@ "integrity": "sha512-fNjDQzzOsZeKZu5NATgXUPsaFaTxeRgFXoosrHivTl8RGeV733OLawXsGfEk9a8/tySyZUyiZ6E8LcjPFZ2y1A==", "dev": true }, + "node_modules/@types/objects-to-csv": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@types/objects-to-csv/-/objects-to-csv-1.3.1.tgz", + "integrity": "sha512-RGZdFlzuj/3ARNTgrf/yJCKREZ2HGYP///wq3pxYWhsw47ImY0FFIBjyNcgdN9Us6lvIMdW6RfubpCAiJb65mg==", + "dev": true + }, "node_modules/@types/prop-types": { "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", @@ -1022,6 +1030,14 @@ "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" }, + "node_modules/async-csv": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/async-csv/-/async-csv-2.1.3.tgz", + "integrity": "sha512-mpsCN+D7mzZeqrlDw7UTPhvDQDlx1i819E9fbKIt8drkgED5FSOlBv3Rk/+sXdevnO2wwlRkVOQ4kdT0AyqPqQ==", + "dependencies": { + "csv": "^5.1.3" + } + }, "node_modules/autoprefixer": { "version": "10.4.13", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.13.tgz", @@ -1284,6 +1300,35 @@ "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==", "dev": true }, + "node_modules/csv": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/csv/-/csv-5.5.3.tgz", + "integrity": "sha512-QTaY0XjjhTQOdguARF0lGKm5/mEq9PD9/VhZZegHDIBq2tQwgNpHc3dneD4mGo2iJs+fTKv5Bp0fZ+BRuY3Z0g==", + "dependencies": { + "csv-generate": "^3.4.3", + "csv-parse": "^4.16.3", + "csv-stringify": "^5.6.5", + "stream-transform": "^2.1.3" + }, + "engines": { + "node": ">= 0.1.90" + } + }, + "node_modules/csv-generate": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/csv-generate/-/csv-generate-3.4.3.tgz", + "integrity": "sha512-w/T+rqR0vwvHqWs/1ZyMDWtHHSJaN06klRqJXBEpDJaM/+dZkso0OKh1VcuuYvK3XM53KysVNq8Ko/epCK8wOw==" + }, + "node_modules/csv-parse": { + "version": "4.16.3", + "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-4.16.3.tgz", + "integrity": "sha512-cO1I/zmz4w2dcKHVvpCr7JVRu8/FymG5OEpmvsZYlccYolPBLoVGKUHgNoc4ZGkFeFlWGEDmMyBM+TTqRdW/wg==" + }, + "node_modules/csv-stringify": { + "version": "5.6.5", + "resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-5.6.5.tgz", + "integrity": "sha512-PjiQ659aQ+fUTQqSrd1XEDnOr52jh30RBurfzkscaE2tPaFsDH5wOAHJiw8XAHphRknCwMUE9KRayc4K/NbO8A==" + }, "node_modules/date-fns": { "version": "2.29.3", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz", @@ -1706,6 +1751,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/mixme": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/mixme/-/mixme-0.5.9.tgz", + "integrity": "sha512-VC5fg6ySUscaWUpI4gxCBTQMH2RdUpNrk+MsbpCYtIvf9SBJdiUey4qE7BXviJsJR4nDQxCZ+3yaYNW3guz/Pw==", + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -1877,6 +1930,14 @@ "node": ">= 6" } }, + "node_modules/objects-to-csv": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/objects-to-csv/-/objects-to-csv-1.3.6.tgz", + "integrity": "sha512-383eSpS3hmgCksW85KIqBtcbgSW5DDVsCmzLoM6C3q4yzOX2rmtWxF4pbLJ76fz+ufA+4/SwAT4QdaY6IUWmAg==", + "dependencies": { + "async-csv": "^2.1.3" + } + }, "node_modules/oidc-token-hash": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.1.tgz", @@ -2340,6 +2401,14 @@ "node": ">=0.10.0" } }, + "node_modules/stream-transform": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/stream-transform/-/stream-transform-2.1.3.tgz", + "integrity": "sha512-9GHUiM5hMiCi6Y03jD2ARC1ettBXkQBoQAe7nJsPknnI0ow10aXjTnew8QtYQmLjzn974BnmWEAJgCY6ZP1DeQ==", + "dependencies": { + "mixme": "^0.5.1" + } + }, "node_modules/styled-components": { "version": "5.3.8", "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.8.tgz", @@ -3201,6 +3270,12 @@ "integrity": "sha512-fNjDQzzOsZeKZu5NATgXUPsaFaTxeRgFXoosrHivTl8RGeV733OLawXsGfEk9a8/tySyZUyiZ6E8LcjPFZ2y1A==", "dev": true }, + "@types/objects-to-csv": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@types/objects-to-csv/-/objects-to-csv-1.3.1.tgz", + "integrity": "sha512-RGZdFlzuj/3ARNTgrf/yJCKREZ2HGYP///wq3pxYWhsw47ImY0FFIBjyNcgdN9Us6lvIMdW6RfubpCAiJb65mg==", + "dev": true + }, "@types/prop-types": { "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", @@ -3322,6 +3397,14 @@ "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" }, + "async-csv": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/async-csv/-/async-csv-2.1.3.tgz", + "integrity": "sha512-mpsCN+D7mzZeqrlDw7UTPhvDQDlx1i819E9fbKIt8drkgED5FSOlBv3Rk/+sXdevnO2wwlRkVOQ4kdT0AyqPqQ==", + "requires": { + "csv": "^5.1.3" + } + }, "autoprefixer": { "version": "10.4.13", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.13.tgz", @@ -3499,6 +3582,32 @@ "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==", "dev": true }, + "csv": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/csv/-/csv-5.5.3.tgz", + "integrity": "sha512-QTaY0XjjhTQOdguARF0lGKm5/mEq9PD9/VhZZegHDIBq2tQwgNpHc3dneD4mGo2iJs+fTKv5Bp0fZ+BRuY3Z0g==", + "requires": { + "csv-generate": "^3.4.3", + "csv-parse": "^4.16.3", + "csv-stringify": "^5.6.5", + "stream-transform": "^2.1.3" + } + }, + "csv-generate": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/csv-generate/-/csv-generate-3.4.3.tgz", + "integrity": "sha512-w/T+rqR0vwvHqWs/1ZyMDWtHHSJaN06klRqJXBEpDJaM/+dZkso0OKh1VcuuYvK3XM53KysVNq8Ko/epCK8wOw==" + }, + "csv-parse": { + "version": "4.16.3", + "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-4.16.3.tgz", + "integrity": "sha512-cO1I/zmz4w2dcKHVvpCr7JVRu8/FymG5OEpmvsZYlccYolPBLoVGKUHgNoc4ZGkFeFlWGEDmMyBM+TTqRdW/wg==" + }, + "csv-stringify": { + "version": "5.6.5", + "resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-5.6.5.tgz", + "integrity": "sha512-PjiQ659aQ+fUTQqSrd1XEDnOr52jh30RBurfzkscaE2tPaFsDH5wOAHJiw8XAHphRknCwMUE9KRayc4K/NbO8A==" + }, "date-fns": { "version": "2.29.3", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz", @@ -3801,6 +3910,11 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==" }, + "mixme": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/mixme/-/mixme-0.5.9.tgz", + "integrity": "sha512-VC5fg6ySUscaWUpI4gxCBTQMH2RdUpNrk+MsbpCYtIvf9SBJdiUey4qE7BXviJsJR4nDQxCZ+3yaYNW3guz/Pw==" + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -3898,6 +4012,14 @@ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==" }, + "objects-to-csv": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/objects-to-csv/-/objects-to-csv-1.3.6.tgz", + "integrity": "sha512-383eSpS3hmgCksW85KIqBtcbgSW5DDVsCmzLoM6C3q4yzOX2rmtWxF4pbLJ76fz+ufA+4/SwAT4QdaY6IUWmAg==", + "requires": { + "async-csv": "^2.1.3" + } + }, "oidc-token-hash": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.1.tgz", @@ -4190,6 +4312,14 @@ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" }, + "stream-transform": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/stream-transform/-/stream-transform-2.1.3.tgz", + "integrity": "sha512-9GHUiM5hMiCi6Y03jD2ARC1ettBXkQBoQAe7nJsPknnI0ow10aXjTnew8QtYQmLjzn974BnmWEAJgCY6ZP1DeQ==", + "requires": { + "mixme": "^0.5.1" + } + }, "styled-components": { "version": "5.3.8", "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.8.tgz", diff --git a/package.json b/package.json index 776736c3..f9bdc5cd 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "framer-motion": "^8.2.4", "next": "latest", "next-auth": "^4.20.1", + "objects-to-csv": "^1.3.6", "prisma": "^4.11.0", "react": "18.2.0", "react-compare-slider": "^2.2.0", @@ -34,6 +35,7 @@ }, "devDependencies": { "@types/node": "18.11.3", + "@types/objects-to-csv": "^1.3.1", "@types/react": "18.0.21", "@types/react-dom": "18.0.6", "@types/request-ip": "^0.0.37", diff --git a/pages/api/getUserEmails.ts b/pages/api/getUserEmails.ts new file mode 100644 index 00000000..50dae106 --- /dev/null +++ b/pages/api/getUserEmails.ts @@ -0,0 +1,30 @@ +// Script to get all user emails from DB and add them to a newsletter + +// TODO: Need to also use prod DB instead of the test one + +import { NextApiRequest, NextApiResponse } from "next"; +import prisma from "../../lib/prismadb"; +import ObjectsToCsv from "objects-to-csv"; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + // const count = await prisma.user.count(); + // return res.status(200).json({ count }); + + let userEmails = await prisma.user.findMany({ + select: { + email: true, + }, + }); + + console.log({ userEmails }); + + let rooms = await prisma.room.findMany(); + + const csv = new ObjectsToCsv(userEmails); + await csv.toDisk("./userEmails.csv"); + + res.status(200).json("success"); +} diff --git a/rooms.csv b/rooms.csv new file mode 100644 index 00000000..423e5457 --- /dev/null +++ b/rooms.csv @@ -0,0 +1,4 @@ +id,replicateId,userId,inputImage,outputImage,prompt,createdAt,updatedAt +clfetpy2q00008hoxewsrusei,caugi63grjfhfot46pvr6oiuqq,clf1v9l1t00008hvfsts5p23u,https://upcdn.io/12a1xvS/image/uploads/2023/03/19/messy-livingroom-35SG.jpg?w=600&h=600&fit=max&q=70,https://replicate.delivery/pbxt/aTeSCKBAjB0feoxXfc4cZhj5l4aBUltIGuhZmKIboItpeXIFC/output_1.png,a modern living room,1679195626947,1679195626947 +clfetzbsi00048hox3slp5jtc,jvao2em65nce7hflz6aukbfyfe,clf1wjpy2001cjw08wyzrh27l,https://upcdn.io/12a1xvS/image/uploads/2023/03/19/living room-h236.jpg?w=600&h=600&fit=max&q=70,https://replicate.delivery/pbxt/B6udeVT0VxTQTSPv2F5bsTepZMXFeKg8Zuza3SlfeEe0nxQKE/output_1.png,a modern living room,1679196064626,1679196064626 +clfeues2l00008h4cizl34qr0,ryu7lwrmrnfpzjfe3f7lti6onu,clf1v9l1t00008hvfsts5p23u,https://upcdn.io/12a1xvS/image/uploads/2023/03/19/3dmodeling-6FZH.png?w=600&h=600&fit=max&q=70,https://replicate.delivery/pbxt/nNuUFFZUvWaYHFaNboEWx2kZeQwfjMlyFSjumCEnJuTvRDpQA/output_1.png,a modern living room,1679196785523,1679196785523 From 8fe80b2caa345d57ce0f91576c1f1329e4108e9d Mon Sep 17 00:00:00 2001 From: Hassan El Mghari Date: Wed, 22 Mar 2023 14:57:20 -0400 Subject: [PATCH 02/23] pricing finally working! --- .example.env | 4 + README.md | 9 + components/Header.tsx | 1 - components/RoomGenerator.tsx | 8 +- package-lock.json | 576 +++++++++++++----- package.json | 6 +- pages/api/generate.ts | 207 ++++--- pages/api/getUserEmails.ts | 7 +- pages/api/remaining.ts | 23 +- pages/api/stripe-webhook.ts | 115 ++++ pages/buy-credits.tsx | 42 ++ pages/dashboard.tsx | 12 +- pages/dream.tsx | 33 +- pages/index.tsx | 2 +- .../migration.sql | 13 + prisma/schema.prisma | 18 +- utils/redis.ts | 11 - 17 files changed, 761 insertions(+), 326 deletions(-) create mode 100644 pages/api/stripe-webhook.ts create mode 100644 pages/buy-credits.tsx create mode 100644 prisma/migrations/20230320191014_purchase_table/migration.sql delete mode 100644 utils/redis.ts diff --git a/.example.env b/.example.env index a2718269..c6d58ec5 100644 --- a/.example.env +++ b/.example.env @@ -14,3 +14,7 @@ GOOGLE_CLIENT_SECRET= DATABASE_URL= SHADOW_DATABASE_URL= NEXTAUTH_URL= + +# Pricing +STRIPE_SECRET_KEY= +STRIPE_WEBHOOK_SECRET= diff --git a/README.md b/README.md index 4f4b20bb..007a16bf 100644 --- a/README.md +++ b/README.md @@ -60,3 +60,12 @@ Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_mediu [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/Nutlope/roomGPT&env=REPLICATE_API_KEY,NEXTAUTH_SECRET,GOOGLE_CLIENT_ID,GOOGLE_CLIENT_SECRET,DATABASE_URL,SHADOW_DATABASE_URL,NEXTAUTH_URL&project-name=room-GPT&repo-name=roomGPT) > Note: You will need to configure auth by following the setup above by using Neon and next-auth + +When I create the PR: + +- Add Vercel preview URL to Google for auth as a redirect URL +- Add the webhook URL to Stripe +- Add DIRECT_DATABASE_URL to the specific preview environment for Neon to work +- Change environment variables on Vercel to Stripe production ones +- On Stripe, make sure products are in production like pricing table +- Test with a real credit card to make sure it's working diff --git a/components/Header.tsx b/components/Header.tsx index 51ec3d0b..20e0e709 100644 --- a/components/Header.tsx +++ b/components/Header.tsx @@ -18,7 +18,6 @@ export default function Header({ photo }: { photo?: string }) { {/* TODO: Eventually add a dropdown where folks can click to logout and buy credits */} {/* TODO: Maybe add another link to purchase credits next to dashboard */} - {/* TODO: Also add "see previous generations in our new dashboard" note */} {photo ? (
-
+
+

Original

Original room @@ -27,7 +27,7 @@ export function RoomGeneration({ width={400} height={400} src={generated} - className="w-full rounded-2xl sm:mt-0 mt-2 aspect-auto" + className="rounded-2xl h-full sm:mt-0 mt-2" />
diff --git a/package-lock.json b/package-lock.json index 8b19afb5..3d8d2751 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,12 +11,12 @@ "@next-auth/prisma-adapter": "^1.0.5", "@prisma/client": "^4.11.0", "@tailwindcss/forms": "^0.5.3", - "@upstash/ratelimit": "^0.3.8", - "@upstash/redis": "^1.19.1", "@vercel/analytics": "^0.1.9-beta.4", "date-fns": "^2.29.3", "date-fns-tz": "^2.0.0", "framer-motion": "^8.2.4", + "micro": "^10.0.1", + "micro-cors": "^0.1.1", "next": "latest", "next-auth": "^4.20.1", "objects-to-csv": "^1.3.6", @@ -29,10 +29,12 @@ "react-uploader": "^3.10.0", "react-use-measure": "^2.1.1", "request-ip": "^3.3.0", + "stripe": "^11.15.0", "swr": "^2.1.0", "uploader": "^3.9.0" }, "devDependencies": { + "@types/micro-cors": "^0.1.3", "@types/node": "18.11.3", "@types/objects-to-csv": "^1.3.1", "@types/react": "18.0.21", @@ -877,11 +879,28 @@ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==" }, + "node_modules/@types/micro": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/@types/micro/-/micro-7.3.7.tgz", + "integrity": "sha512-MFsX7eCj0Tg3TtphOQvANNvNtFpya+s/rYOCdV6o+DFjOQPFi2EVRbBALjbbgZTXUaJP1Q281MJiJOD40d0UxQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/micro-cors": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@types/micro-cors/-/micro-cors-0.1.3.tgz", + "integrity": "sha512-f4aMXqEw9YjfdKX87m1LecvZJ2Mhz5maIHXjIvm5K6OTPe9auaTQwaFk4OZYS9zY6zdzfxqs2cEmwJAF7C9Y8A==", + "dev": true, + "dependencies": { + "@types/micro": "^7.3.7" + } + }, "node_modules/@types/node": { "version": "18.11.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.3.tgz", - "integrity": "sha512-fNjDQzzOsZeKZu5NATgXUPsaFaTxeRgFXoosrHivTl8RGeV733OLawXsGfEk9a8/tySyZUyiZ6E8LcjPFZ2y1A==", - "dev": true + "integrity": "sha512-fNjDQzzOsZeKZu5NATgXUPsaFaTxeRgFXoosrHivTl8RGeV733OLawXsGfEk9a8/tySyZUyiZ6E8LcjPFZ2y1A==" }, "node_modules/@types/objects-to-csv": { "version": "1.3.1", @@ -935,36 +954,6 @@ "resolved": "https://registry.npmjs.org/@upload-io/upload-api-client-upload-js/-/upload-api-client-upload-js-2.15.0.tgz", "integrity": "sha512-UGqT4wxCDhTTKnTLvqtKCyPJSswA2z6/Z6/sMakWGdJBBuwCHcOyXgoWafbPILrgWp3oMMQjnTfwwMBmR0O54w==" }, - "node_modules/@upstash/core-analytics": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/@upstash/core-analytics/-/core-analytics-0.0.5.tgz", - "integrity": "sha512-E+y9ENEUYzR5/coaxEze977NkwtyyF6g54gED2aVvwGi83V1wc9ngLt+jO4fUR+pnQlDtrtH/yquN0q3X4sBhQ==", - "dependencies": { - "@upstash/redis": "^1.19.3", - "next": "13.1.6", - "react": "^18.2.0", - "zod": "^3.20.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@upstash/ratelimit": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@upstash/ratelimit/-/ratelimit-0.3.8.tgz", - "integrity": "sha512-+DM2k2hqQRhMCnqUIVzqYCd3+hczDGiZOS6a/JXPP1sVFjpsCiVYdme5SdGeKuqzHYQ021nhnKCuAuk0zYrNVg==", - "dependencies": { - "@upstash/core-analytics": "0.0.5" - } - }, - "node_modules/@upstash/redis": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/@upstash/redis/-/redis-1.20.1.tgz", - "integrity": "sha512-mYwV8uvQROJnq5aOxmQH3KDa0E8+DRdU0zJ/VGQV84HrhQQNy0IsLFFTo97WEhZAlHcJnVbrbwqUK9disWqx/w==", - "dependencies": { - "isomorphic-fetch": "^3.0.0" - } - }, "node_modules/@vercel/analytics": { "version": "0.1.9-beta.4", "resolved": "https://registry.npmjs.org/@vercel/analytics/-/analytics-0.1.9-beta.4.tgz", @@ -1137,6 +1126,26 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/camelcase-css": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", @@ -1246,6 +1255,14 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", @@ -1378,6 +1395,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/detective": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.1.tgz", @@ -1532,6 +1557,19 @@ "node": ">=6.9.0" } }, + "node_modules/get-intrinsic": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", + "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -1570,6 +1608,17 @@ "node": ">=4" } }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hey-listen": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/hey-listen/-/hey-listen-1.0.8.tgz", @@ -1588,6 +1637,37 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/http-errors": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", + "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -1637,15 +1717,6 @@ "node": ">=0.12.0" } }, - "node_modules/isomorphic-fetch": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", - "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", - "dependencies": { - "node-fetch": "^2.6.1", - "whatwg-fetch": "^3.4.1" - } - }, "node_modules/jose": { "version": "4.13.1", "resolved": "https://registry.npmjs.org/jose/-/jose-4.13.1.tgz", @@ -1723,6 +1794,35 @@ "node": ">= 8" } }, + "node_modules/micro": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/micro/-/micro-10.0.1.tgz", + "integrity": "sha512-9uwZSsUrqf6+4FLLpiPj5TRWQv5w5uJrJwsx1LR/TjqvQmKC1XnGQ9OHrFwR3cbZ46YqPqxO/XJCOpWnqMPw2Q==", + "dependencies": { + "arg": "4.1.0", + "content-type": "1.0.4", + "raw-body": "2.4.1" + }, + "bin": { + "micro": "dist/src/bin/micro.js" + }, + "engines": { + "node": ">= 16.0.0" + } + }, + "node_modules/micro-cors": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/micro-cors/-/micro-cors-0.1.1.tgz", + "integrity": "sha512-6WqIahA5sbQR1Gjexp1VuWGFDKbZZleJb/gy1khNGk18a6iN1FdTcr3Q8twaxkV5H94RjxIBjirYbWCehpMBFw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/micro/node_modules/arg": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.0.tgz", + "integrity": "sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg==" + }, "node_modules/micromatch": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", @@ -1876,25 +1976,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, "node_modules/node-releases": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.8.tgz", @@ -1930,6 +2011,14 @@ "node": ">= 6" } }, + "node_modules/object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/objects-to-csv": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/objects-to-csv/-/objects-to-csv-1.3.6.tgz", @@ -2179,6 +2268,20 @@ "resolved": "https://registry.npmjs.org/progress-smoother/-/progress-smoother-1.4.0.tgz", "integrity": "sha512-ctIG/KF/3DQ1zQHMWbXjcnicggTsEzZEA2JEnJNrPtK88tmqN9cfQf01yfLWGCBF93R5XN1EUe/z/IV5NKrmCw==" }, + "node_modules/qs": { + "version": "6.11.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.1.tgz", + "integrity": "sha512-0wsrzgTz/kAVIeuxSjnpGC56rzYtr6JT/2BwEvMaPhFIoYa1aGO8LbzuU1R0uUYQkLpWBTOj0l/CLAJB64J6nQ==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -2209,6 +2312,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/raw-body": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.1.tgz", + "integrity": "sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA==", + "dependencies": { + "bytes": "3.1.0", + "http-errors": "1.7.3", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", @@ -2371,6 +2488,11 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, "node_modules/scheduler": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", @@ -2388,11 +2510,29 @@ "semver": "bin/semver.js" } }, + "node_modules/setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, "node_modules/shallowequal": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/source-map-js": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", @@ -2401,6 +2541,14 @@ "node": ">=0.10.0" } }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/stream-transform": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/stream-transform/-/stream-transform-2.1.3.tgz", @@ -2409,6 +2557,18 @@ "mixme": "^0.5.1" } }, + "node_modules/stripe": { + "version": "11.15.0", + "resolved": "https://registry.npmjs.org/stripe/-/stripe-11.15.0.tgz", + "integrity": "sha512-HkXz/o4hVvBLSSgTLo3iZQW19PV+DiOdwsj26jnrRcOgpZ+dpGgzpudL/yTWEho7lWcz3cRNov3d4REZ1Qtfbg==", + "dependencies": { + "@types/node": ">=8.1.0", + "qs": "^6.11.0" + }, + "engines": { + "node": ">=12.*" + } + }, "node_modules/styled-components": { "version": "5.3.8", "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.8.tgz", @@ -2573,10 +2733,13 @@ "node": ">=8.0" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + "node_modules/toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "engines": { + "node": ">=0.6" + } }, "node_modules/tslib": { "version": "2.4.1", @@ -2596,6 +2759,14 @@ "node": ">=4.2.0" } }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/update-browserslist-db": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", @@ -2661,25 +2832,6 @@ "uuid": "dist/bin/uuid" } }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/whatwg-fetch": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", - "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -2701,14 +2853,6 @@ "engines": { "node": ">= 6" } - }, - "node_modules/zod": { - "version": "3.20.6", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.20.6.tgz", - "integrity": "sha512-oyu0m54SGCtzh6EClBVqDDlAYRz4jrVtKwQ7ZnsEmMI9HnzuZFj8QFwAY1M5uniIYACdGvv0PBWPF2kO0aNofA==", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } } }, "dependencies": { @@ -3264,11 +3408,28 @@ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==" }, + "@types/micro": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/@types/micro/-/micro-7.3.7.tgz", + "integrity": "sha512-MFsX7eCj0Tg3TtphOQvANNvNtFpya+s/rYOCdV6o+DFjOQPFi2EVRbBALjbbgZTXUaJP1Q281MJiJOD40d0UxQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/micro-cors": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@types/micro-cors/-/micro-cors-0.1.3.tgz", + "integrity": "sha512-f4aMXqEw9YjfdKX87m1LecvZJ2Mhz5maIHXjIvm5K6OTPe9auaTQwaFk4OZYS9zY6zdzfxqs2cEmwJAF7C9Y8A==", + "dev": true, + "requires": { + "@types/micro": "^7.3.7" + } + }, "@types/node": { "version": "18.11.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.3.tgz", - "integrity": "sha512-fNjDQzzOsZeKZu5NATgXUPsaFaTxeRgFXoosrHivTl8RGeV733OLawXsGfEk9a8/tySyZUyiZ6E8LcjPFZ2y1A==", - "dev": true + "integrity": "sha512-fNjDQzzOsZeKZu5NATgXUPsaFaTxeRgFXoosrHivTl8RGeV733OLawXsGfEk9a8/tySyZUyiZ6E8LcjPFZ2y1A==" }, "@types/objects-to-csv": { "version": "1.3.1", @@ -3322,33 +3483,6 @@ "resolved": "https://registry.npmjs.org/@upload-io/upload-api-client-upload-js/-/upload-api-client-upload-js-2.15.0.tgz", "integrity": "sha512-UGqT4wxCDhTTKnTLvqtKCyPJSswA2z6/Z6/sMakWGdJBBuwCHcOyXgoWafbPILrgWp3oMMQjnTfwwMBmR0O54w==" }, - "@upstash/core-analytics": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/@upstash/core-analytics/-/core-analytics-0.0.5.tgz", - "integrity": "sha512-E+y9ENEUYzR5/coaxEze977NkwtyyF6g54gED2aVvwGi83V1wc9ngLt+jO4fUR+pnQlDtrtH/yquN0q3X4sBhQ==", - "requires": { - "@upstash/redis": "^1.19.3", - "next": "13.1.6", - "react": "^18.2.0", - "zod": "^3.20.0" - } - }, - "@upstash/ratelimit": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@upstash/ratelimit/-/ratelimit-0.3.8.tgz", - "integrity": "sha512-+DM2k2hqQRhMCnqUIVzqYCd3+hczDGiZOS6a/JXPP1sVFjpsCiVYdme5SdGeKuqzHYQ021nhnKCuAuk0zYrNVg==", - "requires": { - "@upstash/core-analytics": "0.0.5" - } - }, - "@upstash/redis": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/@upstash/redis/-/redis-1.20.1.tgz", - "integrity": "sha512-mYwV8uvQROJnq5aOxmQH3KDa0E8+DRdU0zJ/VGQV84HrhQQNy0IsLFFTo97WEhZAlHcJnVbrbwqUK9disWqx/w==", - "requires": { - "isomorphic-fetch": "^3.0.0" - } - }, "@vercel/analytics": { "version": "0.1.9-beta.4", "resolved": "https://registry.npmjs.org/@vercel/analytics/-/analytics-0.1.9-beta.4.tgz", @@ -3460,6 +3594,20 @@ "update-browserslist-db": "^1.0.9" } }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, "camelcase-css": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", @@ -3540,6 +3688,11 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, "convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", @@ -3637,6 +3790,11 @@ "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.1.tgz", "integrity": "sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==" }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==" + }, "detective": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.1.tgz", @@ -3749,6 +3907,16 @@ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "peer": true }, + "get-intrinsic": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", + "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + } + }, "glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -3775,6 +3943,11 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + }, "hey-listen": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/hey-listen/-/hey-listen-1.0.8.tgz", @@ -3795,6 +3968,31 @@ } } }, + "http-errors": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", + "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, "is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -3829,15 +4027,6 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, - "isomorphic-fetch": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", - "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", - "requires": { - "node-fetch": "^2.6.1", - "whatwg-fetch": "^3.4.1" - } - }, "jose": { "version": "4.13.1", "resolved": "https://registry.npmjs.org/jose/-/jose-4.13.1.tgz", @@ -3891,6 +4080,28 @@ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" }, + "micro": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/micro/-/micro-10.0.1.tgz", + "integrity": "sha512-9uwZSsUrqf6+4FLLpiPj5TRWQv5w5uJrJwsx1LR/TjqvQmKC1XnGQ9OHrFwR3cbZ46YqPqxO/XJCOpWnqMPw2Q==", + "requires": { + "arg": "4.1.0", + "content-type": "1.0.4", + "raw-body": "2.4.1" + }, + "dependencies": { + "arg": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.0.tgz", + "integrity": "sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg==" + } + } + }, + "micro-cors": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/micro-cors/-/micro-cors-0.1.1.tgz", + "integrity": "sha512-6WqIahA5sbQR1Gjexp1VuWGFDKbZZleJb/gy1khNGk18a6iN1FdTcr3Q8twaxkV5H94RjxIBjirYbWCehpMBFw==" + }, "micromatch": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", @@ -3978,14 +4189,6 @@ "uuid": "^8.3.2" } }, - "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "requires": { - "whatwg-url": "^5.0.0" - } - }, "node-releases": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.8.tgz", @@ -4012,6 +4215,11 @@ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==" }, + "object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==" + }, "objects-to-csv": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/objects-to-csv/-/objects-to-csv-1.3.6.tgz", @@ -4166,6 +4374,14 @@ "resolved": "https://registry.npmjs.org/progress-smoother/-/progress-smoother-1.4.0.tgz", "integrity": "sha512-ctIG/KF/3DQ1zQHMWbXjcnicggTsEzZEA2JEnJNrPtK88tmqN9cfQf01yfLWGCBF93R5XN1EUe/z/IV5NKrmCw==" }, + "qs": { + "version": "6.11.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.1.tgz", + "integrity": "sha512-0wsrzgTz/kAVIeuxSjnpGC56rzYtr6JT/2BwEvMaPhFIoYa1aGO8LbzuU1R0uUYQkLpWBTOj0l/CLAJB64J6nQ==", + "requires": { + "side-channel": "^1.0.4" + } + }, "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -4176,6 +4392,17 @@ "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" }, + "raw-body": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.1.tgz", + "integrity": "sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA==", + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.3", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, "react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", @@ -4288,6 +4515,11 @@ "queue-microtask": "^1.2.2" } }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, "scheduler": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", @@ -4302,16 +4534,36 @@ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "peer": true }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, "shallowequal": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, "source-map-js": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==" + }, "stream-transform": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/stream-transform/-/stream-transform-2.1.3.tgz", @@ -4320,6 +4572,15 @@ "mixme": "^0.5.1" } }, + "stripe": { + "version": "11.15.0", + "resolved": "https://registry.npmjs.org/stripe/-/stripe-11.15.0.tgz", + "integrity": "sha512-HkXz/o4hVvBLSSgTLo3iZQW19PV+DiOdwsj26jnrRcOgpZ+dpGgzpudL/yTWEho7lWcz3cRNov3d4REZ1Qtfbg==", + "requires": { + "@types/node": ">=8.1.0", + "qs": "^6.11.0" + } + }, "styled-components": { "version": "5.3.8", "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.8.tgz", @@ -4429,10 +4690,10 @@ "is-number": "^7.0.0" } }, - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" }, "tslib": { "version": "2.4.1", @@ -4445,6 +4706,11 @@ "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", "dev": true }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" + }, "update-browserslist-db": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", @@ -4489,25 +4755,6 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "whatwg-fetch": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", - "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==" - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -4523,11 +4770,6 @@ "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" - }, - "zod": { - "version": "3.20.6", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.20.6.tgz", - "integrity": "sha512-oyu0m54SGCtzh6EClBVqDDlAYRz4jrVtKwQ7ZnsEmMI9HnzuZFj8QFwAY1M5uniIYACdGvv0PBWPF2kO0aNofA==" } } } diff --git a/package.json b/package.json index f9bdc5cd..d7c08c6d 100644 --- a/package.json +++ b/package.json @@ -12,12 +12,12 @@ "@next-auth/prisma-adapter": "^1.0.5", "@prisma/client": "^4.11.0", "@tailwindcss/forms": "^0.5.3", - "@upstash/ratelimit": "^0.3.8", - "@upstash/redis": "^1.19.1", "@vercel/analytics": "^0.1.9-beta.4", "date-fns": "^2.29.3", "date-fns-tz": "^2.0.0", "framer-motion": "^8.2.4", + "micro": "^10.0.1", + "micro-cors": "^0.1.1", "next": "latest", "next-auth": "^4.20.1", "objects-to-csv": "^1.3.6", @@ -30,10 +30,12 @@ "react-uploader": "^3.10.0", "react-use-measure": "^2.1.1", "request-ip": "^3.3.0", + "stripe": "^11.15.0", "swr": "^2.1.0", "uploader": "^3.9.0" }, "devDependencies": { + "@types/micro-cors": "^0.1.3", "@types/node": "18.11.3", "@types/objects-to-csv": "^1.3.1", "@types/react": "18.0.21", diff --git a/pages/api/generate.ts b/pages/api/generate.ts index d1e254a4..d7197104 100644 --- a/pages/api/generate.ts +++ b/pages/api/generate.ts @@ -1,6 +1,4 @@ -import { Ratelimit } from "@upstash/ratelimit"; import type { NextApiRequest, NextApiResponse } from "next"; -import redis from "../../utils/redis"; import { getServerSession } from "next-auth/next"; import { authOptions } from "./auth/[...nextauth]"; import prisma from "../../lib/prismadb"; @@ -19,15 +17,6 @@ interface ExtendedNextApiRequest extends NextApiRequest { }; } -// Create a new ratelimiter, that allows 3 requests per 24 hours -const ratelimit = redis - ? new Ratelimit({ - redis: redis, - limiter: Ratelimit.fixedWindow(3, "1440 m"), - analytics: true, - }) - : undefined; - export default async function handler( req: ExtendedNextApiRequest, res: NextApiResponse @@ -38,109 +27,131 @@ export default async function handler( return res.status(500).json("Login to upload."); } - // Rate Limiter Code - if (ratelimit) { - const identifier = session.user.email; - const result = await ratelimit.limit(identifier!); - res.setHeader("X-RateLimit-Limit", result.limit); - res.setHeader("X-RateLimit-Remaining", result.remaining); - - // Calcualte the remaining time until generations are reset - const diff = Math.abs( - new Date(result.reset).getTime() - new Date().getTime() - ); - const hours = Math.floor(diff / 1000 / 60 / 60); - const minutes = Math.floor(diff / 1000 / 60) - hours * 60; + // Get user from DB + const user = await prisma.user.findUnique({ + where: { + email: session.user.email!, + }, + select: { + credits: true, + }, + }); - if (!result.success) { - return res - .status(429) - .json( - `Your generations will renew in ${hours} hours and ${minutes} minutes. Email hassan@hey.com if you have any questions.` - ); - } + // Check if user has any credits left + if (user?.credits === 0) { + return res.status(400).json(`You have no generations left`); } - const { imageUrl, theme, room } = req.body; - const prompt = - room === "Gaming Room" - ? "a room for gaming with gaming computers, gaming consoles, and gaming chairs" - : `a ${theme.toLowerCase()} ${room.toLowerCase()}`; - - // POST request to Replicate to start the image restoration generation process - let startResponse = await fetch("https://api.replicate.com/v1/predictions", { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: "Token " + process.env.REPLICATE_API_KEY, + // If they have credits, decrease their credits by one and continue + await prisma.user.update({ + where: { + email: session.user.email!, }, - body: JSON.stringify({ - version: - "854e8727697a057c525cdb45ab037f64ecca770a1769cc52287c2e56472a247b", - input: { - image: imageUrl, - prompt: prompt, - a_prompt: - "best quality, extremely detailed, photo from Pinterest, interior, cinematic photo, ultra-detailed, ultra-realistic, award-winning", - n_prompt: - "longbody, lowres, bad anatomy, bad hands, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality", + data: { + credits: { + decrement: 1, }, - }), + }, }); - let jsonStartResponse = await startResponse.json(); + try { + const { imageUrl, theme, room } = req.body; + const prompt = + room === "Gaming Room" + ? "a room for gaming with gaming computers, gaming consoles, and gaming chairs" + : `a ${theme.toLowerCase()} ${room.toLowerCase()}`; - let endpointUrl = jsonStartResponse.urls.get; - const originalImage = jsonStartResponse.input.image; - const roomId = jsonStartResponse.id; + // POST request to Replicate to start the image restoration generation process + let startResponse = await fetch( + "https://api.replicate.com/v1/predictions", + { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: "Token " + process.env.REPLICATE_API_KEY, + }, + body: JSON.stringify({ + version: + "854e8727697a057c525cdb45ab037f64ecca770a1769cc52287c2e56472a247b", + input: { + image: imageUrl, + prompt: prompt, + a_prompt: + "best quality, extremely detailed, photo from Pinterest, interior, cinematic photo, ultra-detailed, ultra-realistic, award-winning", + n_prompt: + "longbody, lowres, bad anatomy, bad hands, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality", + }, + }), + } + ); - // GET request to get the status of the image restoration process & return the result when it's ready - let generatedImage: string | null = null; - while (!generatedImage) { - // Loop in 1s intervals until the alt text is ready - console.log("polling for result..."); - let finalResponse = await fetch(endpointUrl, { - method: "GET", - headers: { - "Content-Type": "application/json", - Authorization: "Token " + process.env.REPLICATE_API_KEY, - }, - }); - let jsonFinalResponse = await finalResponse.json(); + let jsonStartResponse = await startResponse.json(); + + let endpointUrl = jsonStartResponse.urls.get; + const originalImage = jsonStartResponse.input.image; + const roomId = jsonStartResponse.id; + + // GET request to get the status of the image restoration process & return the result when it's ready + let generatedImage: string | null = null; + while (!generatedImage) { + // Loop in 1s intervals until the alt text is ready + let finalResponse = await fetch(endpointUrl, { + method: "GET", + headers: { + "Content-Type": "application/json", + Authorization: "Token " + process.env.REPLICATE_API_KEY, + }, + }); + let jsonFinalResponse = await finalResponse.json(); - if (jsonFinalResponse.status === "succeeded") { - generatedImage = jsonFinalResponse.output[1] as string; - } else if (jsonFinalResponse.status === "failed") { - break; + if (jsonFinalResponse.status === "succeeded") { + generatedImage = jsonFinalResponse.output[1] as string; + } else if (jsonFinalResponse.status === "failed") { + break; + } else { + await new Promise((resolve) => setTimeout(resolve, 1000)); + } + } + + if (generatedImage) { + await prisma.room.create({ + data: { + replicateId: roomId, + user: { + connect: { + email: session.user.email!, + }, + }, + inputImage: originalImage, + outputImage: generatedImage, + prompt: prompt, + }, + }); } else { - await new Promise((resolve) => setTimeout(resolve, 1000)); + throw new Error("Failed to restore image"); } - } - // TODO: get rid of rate limiting code and decrease the number of credits on the User's table - if (generatedImage) { - await prisma.room.create({ + res.status(200).json( + generatedImage + ? { + original: originalImage, + generated: generatedImage, + id: roomId, + } + : "Failed to restore image" + ); + } catch (error) { + await prisma.user.update({ + where: { + email: session.user.email!, + }, data: { - replicateId: roomId, - user: { - connect: { - email: session.user.email!, - }, + credits: { + increment: 1, }, - inputImage: originalImage, - outputImage: generatedImage, - prompt: prompt, }, }); + console.error(error); + res.status(500).json("Failed to restore image"); } - - res.status(200).json( - generatedImage - ? { - original: originalImage, - generated: generatedImage, - id: roomId, - } - : "Failed to restore image" - ); } diff --git a/pages/api/getUserEmails.ts b/pages/api/getUserEmails.ts index 50dae106..529fbee1 100644 --- a/pages/api/getUserEmails.ts +++ b/pages/api/getUserEmails.ts @@ -1,7 +1,5 @@ // Script to get all user emails from DB and add them to a newsletter -// TODO: Need to also use prod DB instead of the test one - import { NextApiRequest, NextApiResponse } from "next"; import prisma from "../../lib/prismadb"; import ObjectsToCsv from "objects-to-csv"; @@ -16,13 +14,10 @@ export default async function handler( let userEmails = await prisma.user.findMany({ select: { email: true, + name: true, }, }); - console.log({ userEmails }); - - let rooms = await prisma.room.findMany(); - const csv = new ObjectsToCsv(userEmails); await csv.toDisk("./userEmails.csv"); diff --git a/pages/api/remaining.ts b/pages/api/remaining.ts index 97ba0faf..b8e6780d 100644 --- a/pages/api/remaining.ts +++ b/pages/api/remaining.ts @@ -1,7 +1,7 @@ import type { NextApiRequest, NextApiResponse } from "next"; -import redis from "../../utils/redis"; import { getServerSession } from "next-auth/next"; import { authOptions } from "./auth/[...nextauth]"; +import prisma from "../../lib/prismadb"; export default async function handler( req: NextApiRequest, @@ -14,16 +14,15 @@ export default async function handler( return res.status(401).json("Login to upload."); } - // Query the redis database by email to get the number of generations left - const identifier = session.user.email; - const windowDuration = 24 * 60 * 60 * 1000; - const bucket = Math.floor(Date.now() / windowDuration); + // Query the database by email to get the number of generations left + const user = await prisma.user.findUnique({ + where: { + email: session.user.email!, + }, + select: { + credits: true, + }, + }); - const usedGenerations = - (await redis?.get(`@upstash/ratelimit:${identifier!}:${bucket}`)) || 0; - - const remainingGenerations = - Number(usedGenerations) > 3 ? 0 : 3 - Number(usedGenerations); - - return res.status(200).json({ remainingGenerations }); + return res.status(200).json({ remainingGenerations: user?.credits }); } diff --git a/pages/api/stripe-webhook.ts b/pages/api/stripe-webhook.ts new file mode 100644 index 00000000..563f8781 --- /dev/null +++ b/pages/api/stripe-webhook.ts @@ -0,0 +1,115 @@ +import { NextApiRequest, NextApiResponse } from "next"; +import prisma from "../../lib/prismadb"; +import Stripe from "stripe"; +import { buffer } from "micro"; +import Cors from "micro-cors"; + +const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { + apiVersion: "2022-11-15", +}); + +export const config = { + api: { + bodyParser: false, + }, +}; + +const webhookSecret: string = process.env.STRIPE_WEBHOOK_SECRET || ""; + +const cors = Cors({ + allowMethods: ["POST", "HEAD"], +}); + +const webhookHandler = async (req: NextApiRequest, res: NextApiResponse) => { + if (req.method === "POST") { + const buf = await buffer(req); + const sig = req.headers["stripe-signature"]!; + + let event: Stripe.Event; + + try { + event = stripe.webhooks.constructEvent( + buf.toString(), + sig, + webhookSecret + ); + } catch (err) { + const errorMessage = err instanceof Error ? err.message : "Unknown error"; + // On error, log and return the error message. + if (err! instanceof Error) console.log(err); + console.log(`❌ Error message: ${errorMessage}`); + res.status(400).send(`Webhook Error: ${errorMessage}`); + return; + } + + // Successfully constructed event. + console.log("✅ Success:", event.id); + + // Cast event data to Stripe object. + if (event.type === "payment_intent.succeeded") { + const paymentIntent = event.data.object as Stripe.PaymentIntent; + console.log(`💰 PaymentIntent: ${JSON.stringify(paymentIntent)}`); + + // @ts-ignore + const userEmail = paymentIntent.charges.data[0].billing_details.email; // API version mismatch - TODO: TEST IN PROD + let creditAmount = 0; + + switch (paymentIntent.amount) { + case 500: + case 1000: + creditAmount = 20; + break; + case 1500: + case 3000: + creditAmount = 80; + break; + case 2500: + creditAmount = 160; + break; + case 5000: + case 10000: + creditAmount = 400; + break; + } + await prisma.user.update({ + where: { + email: userEmail, + }, + data: { + credits: { + increment: creditAmount, + }, + }, + }); + + await prisma.purchase.create({ + data: { + creditAmount: creditAmount, + user: { + connect: { + email: userEmail, + }, + }, + }, + }); + } else if (event.type === "payment_intent.payment_failed") { + const paymentIntent = event.data.object as Stripe.PaymentIntent; + console.log( + `❌ Payment failed: ${paymentIntent.last_payment_error?.message}` + ); + } else if (event.type === "charge.succeeded") { + const charge = event.data.object as Stripe.Charge; + console.log(`💵 Charge id: ${charge.id}`); + } else { + console.warn(`🤷‍♀️ Unhandled event type: ${event.type}`); + } + + // Return a response to acknowledge receipt of the event. + res.json({ received: true }); + } else { + res.setHeader("Allow", "POST"); + res.status(405).end("Method Not Allowed"); + } +}; + +export default cors(webhookHandler as any); diff --git a/pages/buy-credits.tsx b/pages/buy-credits.tsx new file mode 100644 index 00000000..4f1a0b4b --- /dev/null +++ b/pages/buy-credits.tsx @@ -0,0 +1,42 @@ +import { useSession } from "next-auth/react"; +import Script from "next/script"; + +export default function Pricing() { + const { data: session, status } = useSession(); + + // TODO: See if I can refactor this to import code from an npm library OR use a custom API route and build this code myself + return ( + <> +