diff --git a/README.md b/README.md index 2770d3a..f5f1233 100644 --- a/README.md +++ b/README.md @@ -41,4 +41,10 @@ In its current configuration, the application can be easily linked to Netlify vi ## To Do +- [Update types automatically](https://supabase.com/docs/guides/api/rest/generating-types#update-types-automatically-with-github-actions) + Please see the Issues tab for `enhancement` tagged issues. + +## [Database setup](/docs/database-setup.md) + +## [Stripe webhook event listening](/docs/stripe-setup.md) diff --git a/docs/database-setup.md b/docs/database-setup.md new file mode 100644 index 0000000..0e7e4a4 --- /dev/null +++ b/docs/database-setup.md @@ -0,0 +1,239 @@ +# Database Setup + +Databases are stored in Supabase. The auth table for signing up and logging in users is created automatically when you set up Supabase Auth. + +## Initial Setup + +To add the tables for recording their active subscriptions and bought products, as well as products and prices, you can use the following SQL script in the Supabase SQL Editor. + +```sql +/** +* USERS +* Note: This table contains user data. Users should only be able to view and update their own data. +*/ +create table users ( + -- UUID from auth.users + id uuid references auth.users not null primary key, + full_name text, + avatar_url text, + -- The customer's billing address, stored in JSON format. + billing_address jsonb, + -- Stores your customer's payment instruments. + payment_method jsonb +); +alter table users + enable row level security; +create policy "Can view own user data." on users + for select using ((select auth.uid()) = id); +create policy "Can update own user data." on users + for update using ((select auth.uid()) = id); + +/** +* This trigger automatically creates a user entry when a new user signs up via Supabase Auth. +*/ +create function public.handle_new_user() +returns trigger as +$$ + begin + insert into public.users (id, full_name, avatar_url) + values (new.id, new.raw_user_meta_data->>'full_name', new.raw_user_meta_data->>'avatar_url'); + return new; + end; +$$ +language plpgsql security definer; + +create trigger on_auth_user_created + after insert on auth.users + for each row + execute procedure public.handle_new_user(); + +/** +* CUSTOMERS +* Note: this is a private table that contains a mapping of user IDs to Stripe customer IDs. +*/ +create table customers ( + -- UUID from auth.users + id uuid references auth.users not null primary key, + -- The user's customer ID in Stripe. User must not be able to update this. + stripe_customer_id text +); +alter table customers enable row level security; +-- No policies as this is a private table that the user must not have access to. + +/** +* PRODUCTS +* Note: products are created and managed in Stripe and synced to our DB via Stripe webhooks. +*/ +create table products ( + -- Product ID from Stripe, e.g. prod_1234. + id text primary key, + -- Whether the product is currently available for purchase. + active boolean, + -- The product's name, meant to be displayable to the customer. Whenever this product is sold via a subscription, name will show up on associated invoice line item descriptions. + name text, + -- The product's description, meant to be displayable to the customer. Use this field to optionally store a long form explanation of the product being sold for your own rendering purposes. + description text, + -- A URL of the product image in Stripe, meant to be displayable to the customer. + image text, + -- Set of key-value pairs, used to store additional information about the object in a structured format. + metadata jsonb +); +alter table products + enable row level security; +create policy "Allow public read-only access." on products + for select using (true); + +/** +* PRICES +* Note: prices are created and managed in Stripe and synced to our DB via Stripe webhooks. +*/ +create type pricing_type as enum ('one_time', 'recurring'); +create type pricing_plan_interval as enum ('day', 'week', 'month', 'year'); +create table prices ( + -- Price ID from Stripe, e.g. price_1234. + id text primary key, + -- The ID of the prduct that this price belongs to. + product_id text references products, + -- Whether the price can be used for new purchases. + active boolean, + -- A brief description of the price. + description text, + -- The unit amount as a positive integer in the smallest currency unit (e.g., 100 cents for US$1.00 or 100 for ¥100, a zero-decimal currency). + unit_amount bigint, + -- Three-letter ISO currency code, in lowercase. + currency text check (char_length(currency) = 3), + -- One of `one_time` or `recurring` depending on whether the price is for a one-time purchase or a recurring (subscription) purchase. + type pricing_type, + -- The frequency at which a subscription is billed. One of `day`, `week`, `month` or `year`. + interval pricing_plan_interval, + -- The number of intervals (specified in the `interval` attribute) between subscription billings. For example, `interval=month` and `interval_count=3` bills every 3 months. + interval_count integer, + -- Default number of trial days when subscribing a customer to this price using [`trial_from_plan=true`](https://stripe.com/docs/api#create_subscription-trial_from_plan). + trial_period_days integer, + -- Set of key-value pairs, used to store additional information about the object in a structured format. + metadata jsonb +); +alter table prices + enable row level security; +create policy "Allow public read-only access." on prices + for select using (true); + +/** +* SUBSCRIPTIONS +* Note: subscriptions are created and managed in Stripe and synced to our DB via Stripe webhooks. +*/ +create type subscription_status as enum ('trialing', 'active', 'canceled', 'incomplete', 'incomplete_expired', 'past_due', 'unpaid'); +create table subscriptions ( + -- Subscription ID from Stripe, e.g. sub_1234. + id text primary key, + user_id uuid references auth.users not null, + -- The status of the subscription object, one of subscription_status type above. + status subscription_status, + -- Set of key-value pairs, used to store additional information about the object in a structured format. + metadata jsonb, + -- ID of the price that created this subscription. + price_id text references prices, + -- Quantity multiplied by the unit amount of the price creates the amount of the subscription. Can be used to charge multiple seats. + quantity integer, + -- If true the subscription has been canceled by the user and will be deleted at the end of the billing period. + cancel_at_period_end boolean, + -- Time at which the subscription was created. + created timestamp with time zone default timezone('utc'::text, now()) not null, + -- Start of the current period that the subscription has been invoiced for. + current_period_start timestamp with time zone default timezone('utc'::text, now()) not null, + -- End of the current period that the subscription has been invoiced for. At the end of this period, a new invoice will be created. + current_period_end timestamp with time zone default timezone('utc'::text, now()) not null, + -- If the subscription has ended, the timestamp of the date the subscription ended. + ended_at timestamp with time zone default timezone('utc'::text, now()), + -- A date in the future at which the subscription will automatically get canceled. + cancel_at timestamp with time zone default timezone('utc'::text, now()), + -- If the subscription has been canceled, the date of that cancellation. If the subscription was canceled with `cancel_at_period_end`, `canceled_at` will still reflect the date of the initial cancellation request, not the end of the subscription period when the subscription is automatically moved to a canceled state. + canceled_at timestamp with time zone default timezone('utc'::text, now()), + -- If the subscription has a trial, the beginning of that trial. + trial_start timestamp with time zone default timezone('utc'::text, now()), + -- If the subscription has a trial, the end of that trial. + trial_end timestamp with time zone default timezone('utc'::text, now()) +); +alter table subscriptions + enable row level security; +create policy "Can only view own subs data." on subscriptions + for select using ((select auth.uid()) = user_id); + +/** + * REALTIME SUBSCRIPTIONS + * Only allow realtime listening on public tables. + */ +drop publication if exists supabase_realtime; +create publication supabase_realtime + for table products, prices; +``` + +## Generating Types + +To get the types for the tables, you can follow these instructions taken from [here](https://supabase.com/docs/guides/api/rest/generating-types#generating-types-using-supabase-cli): + +`npm i supabase@">=1.8.1" --save-dev` + +`npx supabase login` + +`npx supabase init` + +Replace `$PROJECT_REF` with your project reference: +`npx supabase gen types typescript --project-id "$PROJECT_REF" --schema public > types/supabase.ts` + +Replace `types/supabase.d.ts` with whatever file you want your types in: +`npx supabase gen types typescript --local > types/supabase.d.ts` + +## Local DB Development + +Ensure you have [Docker](https://docs.docker.com/get-docker/) installed and running. + +You can start and stop the local DB environment with: + +```bash +npx supabase start + +npx supabase stop +``` + +## Migrations + +There are two options to create a migration file: + +1. [Writing the SQL manually](https://supabase.com/docs/guides/cli/local-development#database-migrations) + + - Create new blank migration file + + `npx supabase migration new ` + + - Add the SQL you want to deploy as part of the migration e.g. + + ```sql + create table + employees ( + id bigint primary key generated always as identity, + name text, + email text, + created_at timestamptz default now() + ); + ``` + + - Reset the DB and apply the latest migrations + + `npx supabase db reset` + +2. [Generate SQL based on differences to the schema](https://supabase.com/docs/guides/cli/local-development#diffing-changes) + + - With the local DB running, enter the studio and make your changes to the schema + + - Generate the migration file containing the SQL with the differences in the schema + + `npx supabase db diff -f ` + +## Applying migrations + +Reset database in cloud + +> Prerequisite login using `npx supabase login` + +`npx supabase db reset --linked` diff --git a/docs/stripe-setup.md b/docs/stripe-setup.md new file mode 100644 index 0000000..df68b97 --- /dev/null +++ b/docs/stripe-setup.md @@ -0,0 +1,35 @@ +# Stripe webhook event listening + +> First ensure you've installed the [Stripe CLI](https://docs.stripe.com/stripe-cli) + +Log in using the stripe CLI: + +```bash +stripe login +``` + +Obtain your Webhook signing secret: + +```bash +stripe listen +``` + +Add the secret key to to your .env(.local): + +`PUBLIC_STRIPE_ENDPOINT_SECRET` + +### Testing the webhook locally + +You can use the [Stripe CLI to forward events to your local server](https://docs.stripe.com/webhooks): + +```bash +stripe listen --forward-to localhost:5173/api/webhook/stripe +``` + +To disable HTTPS certificate verification, use the --skip-verify optional flag. + +```bash +stripe listen --forward-to localhost:5173/api/webhook/stripe --skip-verify +``` + +TODO - Add instructions for what else to do to get the webhook working locally. diff --git a/package-lock.json b/package-lock.json index 2432aa8..e96f35e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,8 @@ "name": "SvelteKitSaasBoilerplate", "version": "0.0.1", "dependencies": { - "@supabase/ssr": "^0.1.0", - "@supabase/supabase-js": "^2.42.0", + "@supabase/ssr": "^0.4.0", + "@supabase/supabase-js": "^2.44.2", "stripe": "^15.4.0" }, "devDependencies": { @@ -33,6 +33,7 @@ "postcss": "^8.4.38", "prettier": "^3.1.1", "prettier-plugin-svelte": "^3.1.2", + "supabase": "^1.178.2", "svelte": "^4.2.8", "svelte-check": "^3.6.2", "tailwindcss": "^3.4.3", @@ -2578,6 +2579,19 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", @@ -2953,7 +2967,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -3012,17 +3025,19 @@ ] }, "node_modules/@supabase/auth-js": { - "version": "2.64.1", - "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.64.1.tgz", - "integrity": "sha512-tA2PXLoWEzhD0N1Vysree+HftfeWBbFV0E+taND5rj/pZTjkwKq/9GlrnXkbs5pnw+tsnABDRo2WLZmymihGdA==", + "version": "2.64.2", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.64.2.tgz", + "integrity": "sha512-s+lkHEdGiczDrzXJ1YWt2y3bxRi+qIUnXcgkpLSrId7yjBeaXBFygNjTaoZLG02KNcYwbuZ9qkEIqmj2hF7svw==", + "license": "MIT", "dependencies": { "@supabase/node-fetch": "^2.6.14" } }, "node_modules/@supabase/functions-js": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.3.1.tgz", - "integrity": "sha512-QyzNle/rVzlOi4BbVqxLSH828VdGY1RElqGFAj+XeVypj6+PVtMlD21G8SDnsPQDtlqqTtoGRgdMlQZih5hTuw==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.4.1.tgz", + "integrity": "sha512-8sZ2ibwHlf+WkHDUZJUXqqmPvWQ3UHN0W30behOJngVh/qHHekhJLCFbh0AjkE9/FqqXtf9eoVvmYgfCLk5tNA==", + "license": "MIT", "dependencies": { "@supabase/node-fetch": "^2.6.14" } @@ -3031,6 +3046,7 @@ "version": "2.6.15", "resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.15.tgz", "integrity": "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==", + "license": "MIT", "dependencies": { "whatwg-url": "^5.0.0" }, @@ -3039,17 +3055,19 @@ } }, "node_modules/@supabase/postgrest-js": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.15.2.tgz", - "integrity": "sha512-9/7pUmXExvGuEK1yZhVYXPZnLEkDTwxgMQHXLrN5BwPZZm4iUCL1YEyep/Z2lIZah8d8M433mVAUEGsihUj5KQ==", + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.15.7.tgz", + "integrity": "sha512-TJztay5lcnnKuXjIO/X/aaajOsP8qNeW0k3MqIFoOtRolj5MEAIy8rixNakRk3o23eVCdsuP3iMLYPvOOruH6Q==", + "license": "MIT", "dependencies": { "@supabase/node-fetch": "^2.6.14" } }, "node_modules/@supabase/realtime-js": { - "version": "2.9.4", - "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.9.4.tgz", - "integrity": "sha512-wdq+2hZpgw0r2ldRs87d3U08Y8BrsO1bZxPNqbImpYshAEkusDz4vufR8KaqujKxqewmXS6YnUhuRVdvSEIKCA==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.10.2.tgz", + "integrity": "sha512-qyCQaNg90HmJstsvr2aJNxK2zgoKh9ZZA8oqb7UT2LCh3mj9zpa3Iwu167AuyNxsxrUE8eEJ2yH6wLCij4EApA==", + "license": "MIT", "dependencies": { "@supabase/node-fetch": "^2.6.14", "@types/phoenix": "^1.5.4", @@ -3058,36 +3076,41 @@ } }, "node_modules/@supabase/ssr": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@supabase/ssr/-/ssr-0.1.0.tgz", - "integrity": "sha512-bIVrkqjAK5G3KjkIMKYKtAOlCgRRplEWjrlyRyXSOYtgDieiOhk2ZyNAPsEOa1By9OZVxuX5eAW1fitdnuxayw==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@supabase/ssr/-/ssr-0.4.0.tgz", + "integrity": "sha512-6WS3NUvHDhCPAFN2kJ79AQDO8+M9fJ7y2fYpxgZqIuJEpnnGsHDNnB5Xnv8CiaJIuRU+0pKboy62RVZBMfZ0Lg==", + "license": "MIT", "dependencies": { - "cookie": "^0.5.0", - "ramda": "^0.29.0" + "cookie": "^0.6.0" + }, + "optionalDependencies": { + "@rollup/rollup-linux-x64-gnu": "^4.9.5" }, "peerDependencies": { - "@supabase/supabase-js": "^2.33.1" + "@supabase/supabase-js": "^2.43.4" } }, "node_modules/@supabase/storage-js": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.5.5.tgz", - "integrity": "sha512-OpLoDRjFwClwc2cjTJZG8XviTiQH4Ik8sCiMK5v7et0MDu2QlXjCAW3ljxJB5+z/KazdMOTnySi+hysxWUPu3w==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.6.0.tgz", + "integrity": "sha512-REAxr7myf+3utMkI2oOmZ6sdplMZZ71/2NEIEMBZHL9Fkmm3/JnaOZVSRqvG4LStYj2v5WhCruCzuMn6oD/Drw==", + "license": "MIT", "dependencies": { "@supabase/node-fetch": "^2.6.14" } }, "node_modules/@supabase/supabase-js": { - "version": "2.42.7", - "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.42.7.tgz", - "integrity": "sha512-BEIEYe5KJpzd8Z3k4CKyjNuBmgSihDdE8MJO/Fg7O5h/lQg8qOp1MMWLWPP5aVKt4TYled/W82ePNJflqc2JbQ==", + "version": "2.44.2", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.44.2.tgz", + "integrity": "sha512-fouCwL1OxqftOwLNgdDUPlNnFuCnt30nS4kLcnTpe6NYKn1PmjxRRBFmKscgHs6FjWyU+32ZG4uBJ29+/BWiDw==", + "license": "MIT", "dependencies": { - "@supabase/auth-js": "2.64.1", - "@supabase/functions-js": "2.3.1", + "@supabase/auth-js": "2.64.2", + "@supabase/functions-js": "2.4.1", "@supabase/node-fetch": "2.6.15", - "@supabase/postgrest-js": "1.15.2", - "@supabase/realtime-js": "2.9.4", - "@supabase/storage-js": "2.5.5" + "@supabase/postgrest-js": "1.15.7", + "@supabase/realtime-js": "2.10.2", + "@supabase/storage-js": "2.6.0" } }, "node_modules/@surma/rollup-plugin-off-main-thread": { @@ -3195,15 +3218,6 @@ "vite": "^5.0.3" } }, - "node_modules/@sveltejs/kit/node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/@sveltejs/vite-plugin-svelte": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-3.1.0.tgz", @@ -3280,9 +3294,10 @@ } }, "node_modules/@types/phoenix": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.4.tgz", - "integrity": "sha512-B34A7uot1Cv0XtaHRYDATltAdKx0BvVKNgYNqE4WjtPUa4VQJM7kxeXcVKaH+KS+kCmZ+6w+QaUdcljiheiBJA==" + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.5.tgz", + "integrity": "sha512-xegpDuR+z0UqG9fwHqNoy3rI7JDlvaPh2TY47Fl80oq6g+hXT+c/LEuE43X48clZ6lOfANl5WrPur9fYO1RJ/w==", + "license": "MIT" }, "node_modules/@types/pug": { "version": "2.0.10", @@ -3313,6 +3328,7 @@ "version": "8.5.10", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -3582,6 +3598,19 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -3920,6 +3949,22 @@ } ] }, + "node_modules/bin-links": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-4.0.4.tgz", + "integrity": "sha512-cMtq4W5ZsEwcutJrVId+a/tjt8GSbS+h0oNkdl6+6rBuEv8Ot33Bevj5KPm40t309zuhVic8NjpuL42QCiJWWA==", + "dev": true, + "license": "ISC", + "dependencies": { + "cmd-shim": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "read-cmd-shim": "^4.0.0", + "write-file-atomic": "^5.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -3953,12 +3998,13 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, + "license": "MIT", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -4171,6 +4217,16 @@ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", "dev": true }, + "node_modules/cmd-shim": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-6.0.3.tgz", + "integrity": "sha512-FMabTRlc5t5zjdenF6mS0MBeFZm0XqHqeOkcskKFb/LYCcRQ5fVgLOHVc4Lq9CqABd9zhjwPjMBCJvMCziSVtA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/code-red": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz", @@ -4288,9 +4344,10 @@ "peer": true }, "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -4396,6 +4453,16 @@ "url": "https://opencollective.com/daisyui" } }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/data-view-buffer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", @@ -5190,6 +5257,30 @@ "reusify": "^1.0.4" } }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -5226,10 +5317,11 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -5299,6 +5391,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/fraction.js": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", @@ -5658,6 +5763,20 @@ "node": ">= 0.4" } }, + "node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/ico-endec": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/ico-endec/-/ico-endec-0.1.6.tgz", @@ -5963,6 +6082,7 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -6515,14 +6635,91 @@ } }, "node_modules/minipass": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, + "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" } }, + "node_modules/minizlib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.1.tgz", + "integrity": "sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.4", + "rimraf": "^5.0.5" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/minizlib/node_modules/glob": { + "version": "10.4.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.2.tgz", + "integrity": "sha512-GwMlUF6PkPo3Gk21UxkCohOv0PLcIXVtKyLlpEI28R/cO/4eNOdmLk3CMW1wROV/WR/EsZOWAfBbBOqYvs88/w==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minizlib/node_modules/jackspeak": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.0.tgz", + "integrity": "sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/minizlib/node_modules/rimraf": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.7.tgz", + "integrity": "sha512-nV6YcJo5wbLW77m+8KjH8aB/7/rxQy9SZ0HY5shnwULfS+9nmTtVXAJET5NdZmCzA4fPI/Hm1wo/Po/4mopOdg==", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -6624,6 +6821,45 @@ "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", "dev": true }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, "node_modules/node-releases": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", @@ -6648,6 +6884,16 @@ "node": ">=0.10.0" } }, + "node_modules/npm-normalize-package-bin": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", + "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -6759,6 +7005,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -6805,16 +7058,17 @@ "dev": true }, "node_modules/path-scurry": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz", - "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=16 || 14 >=14.18" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -7246,15 +7500,6 @@ "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", "dev": true }, - "node_modules/ramda": { - "version": "0.29.1", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.29.1.tgz", - "integrity": "sha512-OfxIeWzd4xdUNxlWhgFazxsA/nl3mS4/jGZI5n00uWOoSSFRhC1b6gl6xvmzUamgmqELraWp0J/qqVlXYPDPyA==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda" - } - }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -7298,6 +7543,16 @@ "pify": "^2.3.0" } }, + "node_modules/read-cmd-shim": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-4.0.0.tgz", + "integrity": "sha512-yILWifhaSEEytfXI76kB9xEEiG1AiozaCJZ83A87ytjRiN+jVibXjedjCRNjoZviinhG+4UkalO3mWTd8u5O0Q==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -8339,6 +8594,26 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/supabase": { + "version": "1.178.2", + "resolved": "https://registry.npmjs.org/supabase/-/supabase-1.178.2.tgz", + "integrity": "sha512-JdjNY56cF5PbuhFdhgdYah5qXOPRsLQn0kXaY7uapTFopdIFX4tRiFXqKSig8S0SdznF/y/f6vLdq30Sek1ZZQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "bin-links": "^4.0.3", + "https-proxy-agent": "^7.0.2", + "node-fetch": "^3.3.2", + "tar": "7.4.0" + }, + "bin": { + "supabase": "bin/supabase" + }, + "engines": { + "npm": ">=8" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -8625,6 +8900,24 @@ "node": ">= 14" } }, + "node_modules/tar": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.0.tgz", + "integrity": "sha512-XQs0S8fuAkQWuqhDeCdMlJXDX80D7EOVLDPVFkna9yQfzS+PHKgfxcei0jf6/+QAWcjqrnC8uM3fSAnrQl+XYg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/tar-fs": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", @@ -8650,6 +8943,42 @@ "streamx": "^2.15.0" } }, + "node_modules/tar/node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/tar/node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, "node_modules/temp-dir": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", @@ -8776,6 +9105,7 @@ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -8795,7 +9125,8 @@ "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" }, "node_modules/ts-api-utils": { "version": "1.3.0", @@ -9206,15 +9537,27 @@ } } }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "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==" + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" }, "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==", + "license": "MIT", "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -9784,10 +10127,25 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, + "node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/ws": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz", - "integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", "engines": { "node": ">=10.0.0" }, diff --git a/package.json b/package.json index fb9a97d..e8b4b0c 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "postcss": "^8.4.38", "prettier": "^3.1.1", "prettier-plugin-svelte": "^3.1.2", + "supabase": "^1.178.2", "svelte": "^4.2.8", "svelte-check": "^3.6.2", "tailwindcss": "^3.4.3", @@ -41,8 +42,8 @@ }, "type": "module", "dependencies": { - "@supabase/ssr": "^0.1.0", - "@supabase/supabase-js": "^2.42.0", + "@supabase/ssr": "^0.4.0", + "@supabase/supabase-js": "^2.44.2", "stripe": "^15.4.0" }, "engines": { diff --git a/src/app.d.ts b/src/app.d.ts index 5ca5b29..a604567 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -1,22 +1,23 @@ // See https://kit.svelte.dev/docs/types#app // for information about these interfaces // and what to do when importing types -import { SupabaseClient, Session, User } from '@supabase/supabase-js' +import { SupabaseClient, Session, User } from '@supabase/supabase-js'; +import type { Database } from '$types/supabase'; declare global { const __DATE__: string; const __RELOAD_SW__: boolean; namespace App { // interface Error {} interface Locals { - supabase: SupabaseClient - safeGetSession(): Promise<{ session: Session | null; user: User | null }> + supabase: SupabaseClient; + safeGetSession(): Promise<{ session: Session | null; user: User | null }>; userid: string; buildDate: string; periodicUpdates: boolean; } interface PageData { - session: Session | null - } + session: Session | null; + } // interface PageState {} // interface Platform {} } diff --git a/src/hooks.server.ts b/src/hooks.server.ts index 6d6e55e..f49fd46 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -5,45 +5,48 @@ import type { Handle } from '@sveltejs/kit'; export const handle: Handle = async ({ event, resolve }) => { event.locals.supabase = createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, { cookies: { - get: (key) => event.cookies.get(key), + getAll: () => event.cookies.getAll(), /** - * Note: You have to add the `path` variable to the - * set and remove method due to sveltekit's cookie API - * requiring this to be set, setting the path to an empty string - * will replicate previous/standard behaviour (https://kit.svelte.dev/docs/types#public-types-cookies) + * SvelteKit's cookies API requires `path` to be explicitly set in + * the cookie options. Setting `path` to `/` replicates previous/ + * standard behavior. */ - set: (key, value, options) => { - event.cookies.set(key, value, { ...options, path: '/' }); - }, - remove: (key, options) => { - event.cookies.delete(key, { ...options, path: '/' }); + setAll: (cookiesToSet) => { + cookiesToSet.forEach(({ name, value, options }) => { + event.cookies.set(name, value, { ...options, path: '/' }); + }); } } }); /** - * Unlike `supabase.auth.getSession`, which is unsafe on the server because it - * doesn't validate the JWT, this function validates the JWT by first calling - * `getUser` and aborts early if the JWT signature is invalid. + * Unlike `supabase.auth.getSession()`, which returns the session _without_ + * validating the JWT, this function also calls `getUser()` to validate the + * JWT before returning the session. */ event.locals.safeGetSession = async () => { + const { + data: { session } + } = await event.locals.supabase.auth.getSession(); + if (!session) { + return { session: null, user: null }; + } + const { data: { user }, error } = await event.locals.supabase.auth.getUser(); if (error) { + // JWT validation has failed return { session: null, user: null }; } - const { - data: { session } - } = await event.locals.supabase.auth.getSession(); return { session, user }; }; return resolve(event, { filterSerializedResponseHeaders(name) { - return name === 'content-range'; + return name === 'content-range' || name === 'x-supabase-api-version'; } }); }; diff --git a/src/lib/components/SignUp.svelte b/src/lib/components/SignUp.svelte index bc2053a..c1db49e 100644 --- a/src/lib/components/SignUp.svelte +++ b/src/lib/components/SignUp.svelte @@ -1,10 +1,15 @@ + +
+
+
+ + +
+ +
+ + +
+ +
+ +
+
+ +
+
+ +
+
+
diff --git a/src/routes/api/checkout/status/+page.svelte b/src/routes/api/checkout/status/+page.svelte deleted file mode 100644 index e69de29..0000000 diff --git a/src/routes/api/somebackendfunction/+page.svelte b/src/routes/api/somebackendfunction/+page.svelte deleted file mode 100644 index e69de29..0000000 diff --git a/src/routes/api/webhook/stripe/+server.ts b/src/routes/api/webhook/stripe/+server.ts new file mode 100644 index 0000000..7370213 --- /dev/null +++ b/src/routes/api/webhook/stripe/+server.ts @@ -0,0 +1,95 @@ +import type { RequestHandler } from '@sveltejs/kit'; +import stripe from 'stripe'; +import { PUBLIC_STRIPE_SECRET_KEY } from '$env/static/public'; +import { PUBLIC_STRIPE_ENDPOINT_SECRET } from '$env/static/public'; +import { + deletePriceRecord, + deleteProductRecord, + manageSubscriptionStatusChange, + upsertPriceRecord, + upsertProductRecord +} from '$lib/utils/supabase/admin'; + +const stripeClient = new stripe(PUBLIC_STRIPE_SECRET_KEY); + +export const POST: RequestHandler = async ({ request }) => { + const body = await request.text(); + + try { + let event = JSON.parse(body) as stripe.Event; + + // Get the signature sent by Stripe + const signature = request.headers.get('stripe-signature'); + + // Only verify the event if you have an endpoint secret defined. + // Otherwise use the basic event deserialized with JSON.parse + if (PUBLIC_STRIPE_ENDPOINT_SECRET && signature) { + try { + event = stripeClient.webhooks.constructEvent( + body, + signature, + PUBLIC_STRIPE_ENDPOINT_SECRET + ); + } catch (err) { + console.error(`⚠️ Webhook signature verification failed.`, err.message); + return { + status: 400, + body: {} + }; + } + } + + switch (event.type) { + case 'product.created': + case 'product.updated': + await upsertProductRecord(event.data.object as stripe.Product); + break; + case 'price.created': + case 'price.updated': + await upsertPriceRecord(event.data.object as stripe.Price); + break; + case 'price.deleted': + await deletePriceRecord(event.data.object as stripe.Price); + break; + case 'product.deleted': + await deleteProductRecord(event.data.object as stripe.Product); + break; + case 'customer.subscription.created': + case 'customer.subscription.updated': + case 'customer.subscription.deleted': { + const subscription = event.data.object as stripe.Subscription; + await manageSubscriptionStatusChange( + subscription.id, + subscription.customer as string, + event.type === 'customer.subscription.created' + ); + break; + } + case 'checkout.session.completed': { + const checkoutSession = event.data.object as stripe.Checkout.Session; + if (checkoutSession.mode === 'subscription') { + const subscriptionId = checkoutSession.subscription; + await manageSubscriptionStatusChange( + subscriptionId as string, + checkoutSession.customer as string, + true + ); + } + break; + } + default: + console.error(`Unhandled event type ${event.type}`); + } + + // Return a response to acknowledge receipt of the event + return new Response(JSON.stringify({ received: true }), { + status: 200 + }); + } catch (err) { + console.log(`Webhook Error: ${err.message}`); + return { + status: 400, + body: `Webhook Error: ${err.message}` + }; + } +}; diff --git a/src/routes/auth/confirm/+page.svelte b/src/routes/auth/confirm/+page.svelte deleted file mode 100644 index e69de29..0000000 diff --git a/src/routes/auth/error/+page.svelte b/src/routes/auth/error/+page.svelte new file mode 100644 index 0000000..5e995de --- /dev/null +++ b/src/routes/auth/error/+page.svelte @@ -0,0 +1 @@ +

Login error

diff --git a/src/routes/products/prices/[priceId]/+page.svelte b/src/routes/products/prices/[priceId]/+page.svelte deleted file mode 100644 index e69de29..0000000 diff --git a/src/routes/profile/+page.svelte b/src/routes/profile/+page.svelte deleted file mode 100644 index 6b9d79f..0000000 --- a/src/routes/profile/+page.svelte +++ /dev/null @@ -1,16 +0,0 @@ - - -
- {#if session?.user} - {session?.user?.email} - {:else} -

Please log in to view this page

- {/if} -
diff --git a/supabase/.gitignore b/supabase/.gitignore new file mode 100644 index 0000000..a3ad880 --- /dev/null +++ b/supabase/.gitignore @@ -0,0 +1,4 @@ +# Supabase +.branches +.temp +.env diff --git a/supabase/config.toml b/supabase/config.toml new file mode 100644 index 0000000..ac197fb --- /dev/null +++ b/supabase/config.toml @@ -0,0 +1,194 @@ +# A string used to distinguish different Supabase projects on the same host. Defaults to the +# working directory name when running `supabase init`. +project_id = "SvelteKitSaasBoilerplate" + +[api] +enabled = true +# Port to use for the API URL. +port = 54321 +# Schemas to expose in your API. Tables, views and stored procedures in this schema will get API +# endpoints. `public` is always included. +schemas = ["public", "graphql_public"] +# Extra schemas to add to the search_path of every request. `public` is always included. +extra_search_path = ["public", "extensions"] +# The maximum number of rows returns from a view, table, or stored procedure. Limits payload size +# for accidental or malicious requests. +max_rows = 1000 + +[db] +# Port to use for the local database URL. +port = 54322 +# Port used by db diff command to initialize the shadow database. +shadow_port = 54320 +# The database major version to use. This has to be the same as your remote database's. Run `SHOW +# server_version;` on the remote database to check. +major_version = 15 + +[db.pooler] +enabled = false +# Port to use for the local connection pooler. +port = 54329 +# Specifies when a server connection can be reused by other clients. +# Configure one of the supported pooler modes: `transaction`, `session`. +pool_mode = "transaction" +# How many server connections to allow per user/database pair. +default_pool_size = 20 +# Maximum number of client connections allowed. +max_client_conn = 100 + +[realtime] +enabled = true +# Bind realtime via either IPv4 or IPv6. (default: IPv4) +# ip_version = "IPv6" +# The maximum length in bytes of HTTP request headers. (default: 4096) +# max_header_length = 4096 + +[studio] +enabled = true +# Port to use for Supabase Studio. +port = 54323 +# External URL of the API server that frontend connects to. +api_url = "http://127.0.0.1" +# OpenAI API Key to use for Supabase AI in the Supabase Studio. +openai_api_key = "env(OPENAI_API_KEY)" + +# Email testing server. Emails sent with the local dev setup are not actually sent - rather, they +# are monitored, and you can view the emails that would have been sent from the web interface. +[inbucket] +enabled = true +# Port to use for the email testing server web interface. +port = 54324 +# Uncomment to expose additional ports for testing user applications that send emails. +# smtp_port = 54325 +# pop3_port = 54326 + +[storage] +enabled = true +# The maximum file size allowed (e.g. "5MB", "500KB"). +file_size_limit = "50MiB" + +[storage.image_transformation] +enabled = true + +[auth] +enabled = true +# The base URL of your website. Used as an allow-list for redirects and for constructing URLs used +# in emails. +site_url = "http://127.0.0.1:3000" +# A list of *exact* URLs that auth providers are permitted to redirect to post authentication. +additional_redirect_urls = ["https://127.0.0.1:3000"] +# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 (1 week). +jwt_expiry = 3600 +# If disabled, the refresh token will never expire. +enable_refresh_token_rotation = true +# Allows refresh tokens to be reused after expiry, up to the specified interval in seconds. +# Requires enable_refresh_token_rotation = true. +refresh_token_reuse_interval = 10 +# Allow/disallow new user signups to your project. +enable_signup = true +# Allow/disallow anonymous sign-ins to your project. +enable_anonymous_sign_ins = false +# Allow/disallow testing manual linking of accounts +enable_manual_linking = false + +[auth.email] +# Allow/disallow new user signups via email to your project. +enable_signup = true +# If enabled, a user will be required to confirm any email change on both the old, and new email +# addresses. If disabled, only the new email is required to confirm. +double_confirm_changes = true +# If enabled, users need to confirm their email address before signing in. +enable_confirmations = false +# Controls the minimum amount of time that must pass before sending another signup confirmation or password reset email. +max_frequency = "1s" + +# Use a production-ready SMTP server +# [auth.email.smtp] +# host = "smtp.sendgrid.net" +# port = 587 +# user = "apikey" +# pass = "env(SENDGRID_API_KEY)" +# admin_email = "admin@email.com" +# sender_name = "Admin" + +# Uncomment to customize email template +# [auth.email.template.invite] +# subject = "You have been invited" +# content_path = "./supabase/templates/invite.html" + +[auth.sms] +# Allow/disallow new user signups via SMS to your project. +enable_signup = true +# If enabled, users need to confirm their phone number before signing in. +enable_confirmations = false +# Template for sending OTP to users +template = "Your code is {{ .Code }} ." +# Controls the minimum amount of time that must pass before sending another sms otp. +max_frequency = "5s" + +# Use pre-defined map of phone number to OTP for testing. +# [auth.sms.test_otp] +# 4152127777 = "123456" + +# Configure logged in session timeouts. +# [auth.sessions] +# Force log out after the specified duration. +# timebox = "24h" +# Force log out if the user has been inactive longer than the specified duration. +# inactivity_timeout = "8h" + +# This hook runs before a token is issued and allows you to add additional claims based on the authentication method used. +# [auth.hook.custom_access_token] +# enabled = true +# uri = "pg-functions:////" + +# Configure one of the supported SMS providers: `twilio`, `twilio_verify`, `messagebird`, `textlocal`, `vonage`. +[auth.sms.twilio] +enabled = false +account_sid = "" +message_service_sid = "" +# DO NOT commit your Twilio auth token to git. Use environment variable substitution instead: +auth_token = "env(SUPABASE_AUTH_SMS_TWILIO_AUTH_TOKEN)" + +# Use an external OAuth provider. The full list of providers are: `apple`, `azure`, `bitbucket`, +# `discord`, `facebook`, `github`, `gitlab`, `google`, `keycloak`, `linkedin_oidc`, `notion`, `twitch`, +# `twitter`, `slack`, `spotify`, `workos`, `zoom`. +[auth.external.apple] +enabled = false +client_id = "" +# DO NOT commit your OAuth provider secret to git. Use environment variable substitution instead: +secret = "env(SUPABASE_AUTH_EXTERNAL_APPLE_SECRET)" +# Overrides the default auth redirectUrl. +redirect_uri = "" +# Overrides the default auth provider URL. Used to support self-hosted gitlab, single-tenant Azure, +# or any other third-party OIDC providers. +url = "" +# If enabled, the nonce check will be skipped. Required for local sign in with Google auth. +skip_nonce_check = false + +[edge_runtime] +enabled = true +# Configure one of the supported request policies: `oneshot`, `per_worker`. +# Use `oneshot` for hot reload, or `per_worker` for load testing. +policy = "oneshot" +inspector_port = 8083 + +[analytics] +enabled = false +port = 54327 +vector_port = 54328 +# Configure one of the supported backends: `postgres`, `bigquery`. +backend = "postgres" + +# Experimental features may be deprecated any time +[experimental] +# Configures Postgres storage engine to use OrioleDB (S3) +orioledb_version = "" +# Configures S3 bucket URL, eg. .s3-.amazonaws.com +s3_host = "env(S3_HOST)" +# Configures S3 bucket region, eg. us-east-1 +s3_region = "env(S3_REGION)" +# Configures AWS_ACCESS_KEY_ID for S3 bucket +s3_access_key = "env(S3_ACCESS_KEY)" +# Configures AWS_SECRET_ACCESS_KEY for S3 bucket +s3_secret_key = "env(S3_SECRET_KEY)" diff --git a/supabase/migrations/20240704190621_initial_migration.sql b/supabase/migrations/20240704190621_initial_migration.sql new file mode 100644 index 0000000..240c5a0 --- /dev/null +++ b/supabase/migrations/20240704190621_initial_migration.sql @@ -0,0 +1,159 @@ +/** +* USERS +* Note: This table contains user data. Users should only be able to view and update their own data. +*/ +create table users ( + -- UUID from auth.users + id uuid references auth.users not null primary key, + full_name text, + avatar_url text, + -- The customer's billing address, stored in JSON format. + billing_address jsonb, + -- Stores your customer's payment instruments. + payment_method jsonb +); +alter table users + enable row level security; +create policy "Can view own user data." on users + for select using ((select auth.uid()) = id); +create policy "Can update own user data." on users + for update using ((select auth.uid()) = id); + +/** +* This trigger automatically creates a user entry when a new user signs up via Supabase Auth. +*/ +create function public.handle_new_user() +returns trigger as +$$ + begin + insert into public.users (id, full_name, avatar_url) + values (new.id, new.raw_user_meta_data->>'full_name', new.raw_user_meta_data->>'avatar_url'); + return new; + end; +$$ +language plpgsql security definer; + +create trigger on_auth_user_created + after insert on auth.users + for each row + execute procedure public.handle_new_user(); + +/** +* CUSTOMERS +* Note: this is a private table that contains a mapping of user IDs to Stripe customer IDs. +*/ +create table customers ( + -- UUID from auth.users + id uuid references auth.users not null primary key, + -- The user's customer ID in Stripe. User must not be able to update this. + stripe_customer_id text +); +alter table customers enable row level security; +-- No policies as this is a private table that the user must not have access to. + +/** +* PRODUCTS +* Note: products are created and managed in Stripe and synced to our DB via Stripe webhooks. +*/ +create table products ( + -- Product ID from Stripe, e.g. prod_1234. + id text primary key, + -- Whether the product is currently available for purchase. + active boolean, + -- The product's name, meant to be displayable to the customer. Whenever this product is sold via a subscription, name will show up on associated invoice line item descriptions. + name text, + -- The product's description, meant to be displayable to the customer. Use this field to optionally store a long form explanation of the product being sold for your own rendering purposes. + description text, + -- A URL of the product image in Stripe, meant to be displayable to the customer. + image text, + -- Set of key-value pairs, used to store additional information about the object in a structured format. + metadata jsonb +); +alter table products + enable row level security; +create policy "Allow public read-only access." on products + for select using (true); + +/** +* PRICES +* Note: prices are created and managed in Stripe and synced to our DB via Stripe webhooks. +*/ +create type pricing_type as enum ('one_time', 'recurring'); +create type pricing_plan_interval as enum ('day', 'week', 'month', 'year'); +create table prices ( + -- Price ID from Stripe, e.g. price_1234. + id text primary key, + -- The ID of the prduct that this price belongs to. + product_id text references products, + -- Whether the price can be used for new purchases. + active boolean, + -- A brief description of the price. + description text, + -- The unit amount as a positive integer in the smallest currency unit (e.g., 100 cents for US$1.00 or 100 for ¥100, a zero-decimal currency). + unit_amount bigint, + -- Three-letter ISO currency code, in lowercase. + currency text check (char_length(currency) = 3), + -- One of `one_time` or `recurring` depending on whether the price is for a one-time purchase or a recurring (subscription) purchase. + type pricing_type, + -- The frequency at which a subscription is billed. One of `day`, `week`, `month` or `year`. + interval pricing_plan_interval, + -- The number of intervals (specified in the `interval` attribute) between subscription billings. For example, `interval=month` and `interval_count=3` bills every 3 months. + interval_count integer, + -- Default number of trial days when subscribing a customer to this price using [`trial_from_plan=true`](https://stripe.com/docs/api#create_subscription-trial_from_plan). + trial_period_days integer, + -- Set of key-value pairs, used to store additional information about the object in a structured format. + metadata jsonb +); +alter table prices + enable row level security; +create policy "Allow public read-only access." on prices + for select using (true); + +/** +* SUBSCRIPTIONS +* Note: subscriptions are created and managed in Stripe and synced to our DB via Stripe webhooks. +*/ +create type subscription_status as enum ('trialing', 'active', 'canceled', 'incomplete', 'incomplete_expired', 'past_due', 'unpaid'); +create table subscriptions ( + -- Subscription ID from Stripe, e.g. sub_1234. + id text primary key, + user_id uuid references auth.users not null, + -- The status of the subscription object, one of subscription_status type above. + status subscription_status, + -- Set of key-value pairs, used to store additional information about the object in a structured format. + metadata jsonb, + -- ID of the price that created this subscription. + price_id text references prices, + -- Quantity multiplied by the unit amount of the price creates the amount of the subscription. Can be used to charge multiple seats. + quantity integer, + -- If true the subscription has been canceled by the user and will be deleted at the end of the billing period. + cancel_at_period_end boolean, + -- Time at which the subscription was created. + created timestamp with time zone default timezone('utc'::text, now()) not null, + -- Start of the current period that the subscription has been invoiced for. + current_period_start timestamp with time zone default timezone('utc'::text, now()) not null, + -- End of the current period that the subscription has been invoiced for. At the end of this period, a new invoice will be created. + current_period_end timestamp with time zone default timezone('utc'::text, now()) not null, + -- If the subscription has ended, the timestamp of the date the subscription ended. + ended_at timestamp with time zone default timezone('utc'::text, now()), + -- A date in the future at which the subscription will automatically get canceled. + cancel_at timestamp with time zone default timezone('utc'::text, now()), + -- If the subscription has been canceled, the date of that cancellation. If the subscription was canceled with `cancel_at_period_end`, `canceled_at` will still reflect the date of the initial cancellation request, not the end of the subscription period when the subscription is automatically moved to a canceled state. + canceled_at timestamp with time zone default timezone('utc'::text, now()), + -- If the subscription has a trial, the beginning of that trial. + trial_start timestamp with time zone default timezone('utc'::text, now()), + -- If the subscription has a trial, the end of that trial. + trial_end timestamp with time zone default timezone('utc'::text, now()) +); +alter table subscriptions + enable row level security; +create policy "Can only view own subs data." on subscriptions + for select using ((select auth.uid()) = user_id); + +/** + * REALTIME SUBSCRIPTIONS + * Only allow realtime listening on public tables. + */ +drop publication if exists supabase_realtime; +create publication supabase_realtime + for table products, prices; \ No newline at end of file diff --git a/src/routes/api/checkout/+page.svelte b/supabase/seed.sql similarity index 100% rename from src/routes/api/checkout/+page.svelte rename to supabase/seed.sql diff --git a/types/supabase.d.ts b/types/supabase.d.ts new file mode 100644 index 0000000..d364511 --- /dev/null +++ b/types/supabase.d.ts @@ -0,0 +1,653 @@ +export type Json = + | string + | number + | boolean + | null + | { [key: string]: Json | undefined } + | Json[] + +export type Database = { + graphql_public: { + Tables: { + [_ in never]: never + } + Views: { + [_ in never]: never + } + Functions: { + graphql: { + Args: { + operationName?: string + query?: string + variables?: Json + extensions?: Json + } + Returns: Json + } + } + Enums: { + [_ in never]: never + } + CompositeTypes: { + [_ in never]: never + } + } + public: { + Tables: { + customers: { + Row: { + id: string + stripe_customer_id: string | null + } + Insert: { + id: string + stripe_customer_id?: string | null + } + Update: { + id?: string + stripe_customer_id?: string | null + } + Relationships: [ + { + foreignKeyName: "customers_id_fkey" + columns: ["id"] + isOneToOne: true + referencedRelation: "users" + referencedColumns: ["id"] + }, + ] + } + prices: { + Row: { + active: boolean | null + currency: string | null + description: string | null + id: string + interval: Database["public"]["Enums"]["pricing_plan_interval"] | null + interval_count: number | null + metadata: Json | null + product_id: string | null + trial_period_days: number | null + type: Database["public"]["Enums"]["pricing_type"] | null + unit_amount: number | null + } + Insert: { + active?: boolean | null + currency?: string | null + description?: string | null + id: string + interval?: Database["public"]["Enums"]["pricing_plan_interval"] | null + interval_count?: number | null + metadata?: Json | null + product_id?: string | null + trial_period_days?: number | null + type?: Database["public"]["Enums"]["pricing_type"] | null + unit_amount?: number | null + } + Update: { + active?: boolean | null + currency?: string | null + description?: string | null + id?: string + interval?: Database["public"]["Enums"]["pricing_plan_interval"] | null + interval_count?: number | null + metadata?: Json | null + product_id?: string | null + trial_period_days?: number | null + type?: Database["public"]["Enums"]["pricing_type"] | null + unit_amount?: number | null + } + Relationships: [ + { + foreignKeyName: "prices_product_id_fkey" + columns: ["product_id"] + isOneToOne: false + referencedRelation: "products" + referencedColumns: ["id"] + }, + ] + } + products: { + Row: { + active: boolean | null + description: string | null + id: string + image: string | null + metadata: Json | null + name: string | null + } + Insert: { + active?: boolean | null + description?: string | null + id: string + image?: string | null + metadata?: Json | null + name?: string | null + } + Update: { + active?: boolean | null + description?: string | null + id?: string + image?: string | null + metadata?: Json | null + name?: string | null + } + Relationships: [] + } + subscriptions: { + Row: { + cancel_at: string | null + cancel_at_period_end: boolean | null + canceled_at: string | null + created: string + current_period_end: string + current_period_start: string + ended_at: string | null + id: string + metadata: Json | null + price_id: string | null + quantity: number | null + status: Database["public"]["Enums"]["subscription_status"] | null + trial_end: string | null + trial_start: string | null + user_id: string + } + Insert: { + cancel_at?: string | null + cancel_at_period_end?: boolean | null + canceled_at?: string | null + created?: string + current_period_end?: string + current_period_start?: string + ended_at?: string | null + id: string + metadata?: Json | null + price_id?: string | null + quantity?: number | null + status?: Database["public"]["Enums"]["subscription_status"] | null + trial_end?: string | null + trial_start?: string | null + user_id: string + } + Update: { + cancel_at?: string | null + cancel_at_period_end?: boolean | null + canceled_at?: string | null + created?: string + current_period_end?: string + current_period_start?: string + ended_at?: string | null + id?: string + metadata?: Json | null + price_id?: string | null + quantity?: number | null + status?: Database["public"]["Enums"]["subscription_status"] | null + trial_end?: string | null + trial_start?: string | null + user_id?: string + } + Relationships: [ + { + foreignKeyName: "subscriptions_price_id_fkey" + columns: ["price_id"] + isOneToOne: false + referencedRelation: "prices" + referencedColumns: ["id"] + }, + { + foreignKeyName: "subscriptions_user_id_fkey" + columns: ["user_id"] + isOneToOne: false + referencedRelation: "users" + referencedColumns: ["id"] + }, + ] + } + users: { + Row: { + avatar_url: string | null + billing_address: Json | null + full_name: string | null + id: string + payment_method: Json | null + } + Insert: { + avatar_url?: string | null + billing_address?: Json | null + full_name?: string | null + id: string + payment_method?: Json | null + } + Update: { + avatar_url?: string | null + billing_address?: Json | null + full_name?: string | null + id?: string + payment_method?: Json | null + } + Relationships: [ + { + foreignKeyName: "users_id_fkey" + columns: ["id"] + isOneToOne: true + referencedRelation: "users" + referencedColumns: ["id"] + }, + ] + } + } + Views: { + [_ in never]: never + } + Functions: { + [_ in never]: never + } + Enums: { + pricing_plan_interval: "day" | "week" | "month" | "year" + pricing_type: "one_time" | "recurring" + subscription_status: + | "trialing" + | "active" + | "canceled" + | "incomplete" + | "incomplete_expired" + | "past_due" + | "unpaid" + } + CompositeTypes: { + [_ in never]: never + } + } + storage: { + Tables: { + buckets: { + Row: { + allowed_mime_types: string[] | null + avif_autodetection: boolean | null + created_at: string | null + file_size_limit: number | null + id: string + name: string + owner: string | null + owner_id: string | null + public: boolean | null + updated_at: string | null + } + Insert: { + allowed_mime_types?: string[] | null + avif_autodetection?: boolean | null + created_at?: string | null + file_size_limit?: number | null + id: string + name: string + owner?: string | null + owner_id?: string | null + public?: boolean | null + updated_at?: string | null + } + Update: { + allowed_mime_types?: string[] | null + avif_autodetection?: boolean | null + created_at?: string | null + file_size_limit?: number | null + id?: string + name?: string + owner?: string | null + owner_id?: string | null + public?: boolean | null + updated_at?: string | null + } + Relationships: [] + } + migrations: { + Row: { + executed_at: string | null + hash: string + id: number + name: string + } + Insert: { + executed_at?: string | null + hash: string + id: number + name: string + } + Update: { + executed_at?: string | null + hash?: string + id?: number + name?: string + } + Relationships: [] + } + objects: { + Row: { + bucket_id: string | null + created_at: string | null + id: string + last_accessed_at: string | null + metadata: Json | null + name: string | null + owner: string | null + owner_id: string | null + path_tokens: string[] | null + updated_at: string | null + version: string | null + } + Insert: { + bucket_id?: string | null + created_at?: string | null + id?: string + last_accessed_at?: string | null + metadata?: Json | null + name?: string | null + owner?: string | null + owner_id?: string | null + path_tokens?: string[] | null + updated_at?: string | null + version?: string | null + } + Update: { + bucket_id?: string | null + created_at?: string | null + id?: string + last_accessed_at?: string | null + metadata?: Json | null + name?: string | null + owner?: string | null + owner_id?: string | null + path_tokens?: string[] | null + updated_at?: string | null + version?: string | null + } + Relationships: [ + { + foreignKeyName: "objects_bucketId_fkey" + columns: ["bucket_id"] + isOneToOne: false + referencedRelation: "buckets" + referencedColumns: ["id"] + }, + ] + } + s3_multipart_uploads: { + Row: { + bucket_id: string + created_at: string + id: string + in_progress_size: number + key: string + owner_id: string | null + upload_signature: string + version: string + } + Insert: { + bucket_id: string + created_at?: string + id: string + in_progress_size?: number + key: string + owner_id?: string | null + upload_signature: string + version: string + } + Update: { + bucket_id?: string + created_at?: string + id?: string + in_progress_size?: number + key?: string + owner_id?: string | null + upload_signature?: string + version?: string + } + Relationships: [ + { + foreignKeyName: "s3_multipart_uploads_bucket_id_fkey" + columns: ["bucket_id"] + isOneToOne: false + referencedRelation: "buckets" + referencedColumns: ["id"] + }, + ] + } + s3_multipart_uploads_parts: { + Row: { + bucket_id: string + created_at: string + etag: string + id: string + key: string + owner_id: string | null + part_number: number + size: number + upload_id: string + version: string + } + Insert: { + bucket_id: string + created_at?: string + etag: string + id?: string + key: string + owner_id?: string | null + part_number: number + size?: number + upload_id: string + version: string + } + Update: { + bucket_id?: string + created_at?: string + etag?: string + id?: string + key?: string + owner_id?: string | null + part_number?: number + size?: number + upload_id?: string + version?: string + } + Relationships: [ + { + foreignKeyName: "s3_multipart_uploads_parts_bucket_id_fkey" + columns: ["bucket_id"] + isOneToOne: false + referencedRelation: "buckets" + referencedColumns: ["id"] + }, + { + foreignKeyName: "s3_multipart_uploads_parts_upload_id_fkey" + columns: ["upload_id"] + isOneToOne: false + referencedRelation: "s3_multipart_uploads" + referencedColumns: ["id"] + }, + ] + } + } + Views: { + [_ in never]: never + } + Functions: { + can_insert_object: { + Args: { + bucketid: string + name: string + owner: string + metadata: Json + } + Returns: undefined + } + extension: { + Args: { + name: string + } + Returns: string + } + filename: { + Args: { + name: string + } + Returns: string + } + foldername: { + Args: { + name: string + } + Returns: string[] + } + get_size_by_bucket: { + Args: Record + Returns: { + size: number + bucket_id: string + }[] + } + list_multipart_uploads_with_delimiter: { + Args: { + bucket_id: string + prefix_param: string + delimiter_param: string + max_keys?: number + next_key_token?: string + next_upload_token?: string + } + Returns: { + key: string + id: string + created_at: string + }[] + } + list_objects_with_delimiter: { + Args: { + bucket_id: string + prefix_param: string + delimiter_param: string + max_keys?: number + start_after?: string + next_token?: string + } + Returns: { + name: string + id: string + metadata: Json + updated_at: string + }[] + } + operation: { + Args: Record + Returns: string + } + search: { + Args: { + prefix: string + bucketname: string + limits?: number + levels?: number + offsets?: number + search?: string + sortcolumn?: string + sortorder?: string + } + Returns: { + name: string + id: string + updated_at: string + created_at: string + last_accessed_at: string + metadata: Json + }[] + } + } + Enums: { + [_ in never]: never + } + CompositeTypes: { + [_ in never]: never + } + } +} + +type PublicSchema = Database[Extract] + +export type Tables< + PublicTableNameOrOptions extends + | keyof (PublicSchema["Tables"] & PublicSchema["Views"]) + | { schema: keyof Database }, + TableName extends PublicTableNameOrOptions extends { schema: keyof Database } + ? keyof (Database[PublicTableNameOrOptions["schema"]]["Tables"] & + Database[PublicTableNameOrOptions["schema"]]["Views"]) + : never = never, +> = PublicTableNameOrOptions extends { schema: keyof Database } + ? (Database[PublicTableNameOrOptions["schema"]]["Tables"] & + Database[PublicTableNameOrOptions["schema"]]["Views"])[TableName] extends { + Row: infer R + } + ? R + : never + : PublicTableNameOrOptions extends keyof (PublicSchema["Tables"] & + PublicSchema["Views"]) + ? (PublicSchema["Tables"] & + PublicSchema["Views"])[PublicTableNameOrOptions] extends { + Row: infer R + } + ? R + : never + : never + +export type TablesInsert< + PublicTableNameOrOptions extends + | keyof PublicSchema["Tables"] + | { schema: keyof Database }, + TableName extends PublicTableNameOrOptions extends { schema: keyof Database } + ? keyof Database[PublicTableNameOrOptions["schema"]]["Tables"] + : never = never, +> = PublicTableNameOrOptions extends { schema: keyof Database } + ? Database[PublicTableNameOrOptions["schema"]]["Tables"][TableName] extends { + Insert: infer I + } + ? I + : never + : PublicTableNameOrOptions extends keyof PublicSchema["Tables"] + ? PublicSchema["Tables"][PublicTableNameOrOptions] extends { + Insert: infer I + } + ? I + : never + : never + +export type TablesUpdate< + PublicTableNameOrOptions extends + | keyof PublicSchema["Tables"] + | { schema: keyof Database }, + TableName extends PublicTableNameOrOptions extends { schema: keyof Database } + ? keyof Database[PublicTableNameOrOptions["schema"]]["Tables"] + : never = never, +> = PublicTableNameOrOptions extends { schema: keyof Database } + ? Database[PublicTableNameOrOptions["schema"]]["Tables"][TableName] extends { + Update: infer U + } + ? U + : never + : PublicTableNameOrOptions extends keyof PublicSchema["Tables"] + ? PublicSchema["Tables"][PublicTableNameOrOptions] extends { + Update: infer U + } + ? U + : never + : never + +export type Enums< + PublicEnumNameOrOptions extends + | keyof PublicSchema["Enums"] + | { schema: keyof Database }, + EnumName extends PublicEnumNameOrOptions extends { schema: keyof Database } + ? keyof Database[PublicEnumNameOrOptions["schema"]]["Enums"] + : never = never, +> = PublicEnumNameOrOptions extends { schema: keyof Database } + ? Database[PublicEnumNameOrOptions["schema"]]["Enums"][EnumName] + : PublicEnumNameOrOptions extends keyof PublicSchema["Enums"] + ? PublicSchema["Enums"][PublicEnumNameOrOptions] + : never +