diff --git a/apps/docs/components/Navigation/Footer.tsx b/apps/docs/components/Navigation/Footer.tsx index cbbb215e7a6..c21718dce72 100644 --- a/apps/docs/components/Navigation/Footer.tsx +++ b/apps/docs/components/Navigation/Footer.tsx @@ -1,6 +1,6 @@ import Image from 'next/image' import Link from 'next/link' -import { Button, IconGitHub, IconTwitter } from '~/../../packages/ui' +import { Button, IconGitHub, IconTwitter, IconYoutube } from '~/../../packages/ui' import footerData from '~/data/footer.json' import { useTheme } from 'common/Providers' @@ -20,6 +20,16 @@ const Footer = () => { ))}
+
- This reference documents every object and method available in Supabase's C# - library, [supabase-csharp](https://www.nuget.org/packages/supabase-csharp). You can - use supabase-csharp to interact with your Postgres database, listen to database changes, invoke - Deno Edge Functions, build login and user management functionality, and manage large files. + This reference documents every object and method available in Supabase's C# library, + [supabase-csharp](https://www.nuget.org/packages/supabase-csharp). You can use supabase-csharp to + interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, + build login and user management functionality, and manage large files. +
+ +
+

The C# client library is created and maintained by the Supabase community, and is not an official library. Please be tolerant of areas where the library is still being developed, and — as with all the libraries — feel free to contribute wherever you find issues.

+ +

+ Huge thanks to official maintainer, [Joseph Schultz](https://github.com/acupofjose), and to [Ben + Randall](https://github.com/veleek) and [Rhuan Barros](https://github.com/rhuanbarros) for their + help. +

+
diff --git a/apps/docs/docs/ref/csharp/release-notes.mdx b/apps/docs/docs/ref/csharp/release-notes.mdx index 2f30b2cbed8..777621307dd 100644 --- a/apps/docs/docs/ref/csharp/release-notes.mdx +++ b/apps/docs/docs/ref/csharp/release-notes.mdx @@ -3,6 +3,39 @@ id: release-notes title: Release Notes --- +## 0.8.0 - 2023-01-31 + +- Update dependency: realtime-csharp@5.0.0 + - Re: [#21](https://github.com/supabase-community/realtime-csharp/pull/21) Provide API for `presence`, `broadcast` and `postgres_changes` + - [Major, New] `Channel.PostgresChanges` event will receive the wildcard `*` changes event, not `Channel.OnMessage`. + - [Major] `Channel.OnInsert`, `Channel.OnUpdate`, and `Channel.OnDelete` now conform to the server's payload of `Response.Payload.**Data**` + - [Major] `Channel.OnInsert`, `Channel.OnUpdate`, and `Channel.OnDelete` now return `PostgresChangesEventArgs` + - [Minor] Rename `Channel` to `RealtimeChannel` + - Supports better handling of disconnects in `RealtimeSocket` and adds a `Client.OnReconnect` event. + - [Minor] Moves `ChannelOptions` to `Channel.ChannelOptions` + - [Minor] Moves `ChannelStateChangedEventArgs` to `Channel.ChannelStateChangedEventArgs` + - [Minor] Moves `Push` to `Channel.Push` + - [Minor] Moves `Channel.ChannelState` to `Constants.ChannelState` + - [Minor] Moves `SocketResponse`, `SocketRequest`, `SocketResponsePayload`, `SocketResponseEventArgs`, and `SocketStateChangedEventArgs` to `Socket` namespace. + - [New] Adds `RealtimeBroadcast` + - [New] Adds `RealtimePresence` + - [Improvement] Better handling of disconnection/reconnection +- Update dependency: postgrest-csharp@3.1.3 + - Another fix for [#61](https://github.com/supabase-community/postgrest-csharp/issues/61) which further typechecks nullable values. + +## 0.7.2 - 2023-01-27 + +- Update dependency: gotrue-csharp@3.0.4 + - Makes `Session.CreatedAt` a publicly settable property, which should fix incorrect dates on retrieved `Session`s. +- Update dependency: postgrest-csharp@3.1.2 + - Fix [#61](https://github.com/supabase-community/postgrest-csharp/issues/61) which did not correctly parse Linq `Where` when encountering a nullable type. + - Add missing support for transforming for `== null` and `!= null` + +## 0.7.1 - 2023-01-17 + +- Update dependency: postgrest-csharp@3.1.1 + - Fix issue from supabase-community/supabase-csharp#48 where boolean model properties would not be evaluated in predicate expressions + ## 0.7.0 - 2023-01-16 - Update dependency: postgrest-csharp@3.1.0 diff --git a/apps/docs/layouts/guides/index.tsx b/apps/docs/layouts/guides/index.tsx index d5c1c811305..5f3b1d90f30 100644 --- a/apps/docs/layouts/guides/index.tsx +++ b/apps/docs/layouts/guides/index.tsx @@ -8,7 +8,6 @@ import components from '~/components' import { highlightSelectedTocItem } from '~/components/CustomHTMLElements/CustomHTMLElements.utils' import GuidesTableOfContents from '~/components/GuidesTableOfContents' import useHash from '~/hooks/useHash' -import { getPageType } from '~/lib/helpers' interface Props { meta: { @@ -17,6 +16,8 @@ interface Props { hide_table_of_contents?: boolean breadcrumb?: string subtitle?: string + video?: string + canonical?: string } children: any toc?: any @@ -67,12 +68,21 @@ const Layout: FC = (props) => { */} diff --git a/apps/docs/pages/guides/functions/cicd-workflow.mdx b/apps/docs/pages/guides/functions/cicd-workflow.mdx index fd7826ddf0e..e168c6c7642 100644 --- a/apps/docs/pages/guides/functions/cicd-workflow.mdx +++ b/apps/docs/pages/guides/functions/cicd-workflow.mdx @@ -4,6 +4,7 @@ export const meta = { id: 'cicd-workflow', title: 'CI / CD Workflow', description: 'How to deploy Supabase Edge Functions with a CI / CD pipeline.', + video: 'https://www.youtube.com/v/6OMVWiiycLs', } As described in the Supabase CLI [Environments Guide](/docs/guides/cli/managing-environments), you can use the [`setup-cli` GitHub Action](https://github.com/marketplace/actions/supabase-cli-action) to run Supabase CLI commands in your GitHub Actions, for example to deploy a Supabase Edge Function: diff --git a/apps/docs/pages/guides/functions/examples/cloudflare-turnstile.mdx b/apps/docs/pages/guides/functions/examples/cloudflare-turnstile.mdx index 560b3d9e454..ef38552855f 100644 --- a/apps/docs/pages/guides/functions/examples/cloudflare-turnstile.mdx +++ b/apps/docs/pages/guides/functions/examples/cloudflare-turnstile.mdx @@ -4,6 +4,7 @@ export const meta = { id: 'examples-cloudflare-turnstile', title: 'Cloudflare Turnstile', description: 'Protecting Forms with Cloudflare Turnstile.', + video: 'https://www.youtube.com/v/OwW0znboh60', }
diff --git a/apps/docs/pages/guides/functions/examples/connect-to-postgres.mdx b/apps/docs/pages/guides/functions/examples/connect-to-postgres.mdx new file mode 100644 index 00000000000..3b66a4affd9 --- /dev/null +++ b/apps/docs/pages/guides/functions/examples/connect-to-postgres.mdx @@ -0,0 +1,68 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'examples-postgres-on-the-edge', + title: 'Connect to Postgres', + description: 'Connecting to Postgres from Edge Functions.', +} + +
+ +
+ +Supabase Edge Functions allow you to go beyond HTTP and can connect to your Postgres Database directly! + +```ts index.ts +import * as postgres from 'https://deno.land/x/postgres@v0.14.2/mod.ts' +import { serve } from 'https://deno.land/std@0.168.0/http/server.ts' + +// Get the connection string from the environment variable "DATABASE_URL" +const databaseUrl = Deno.env.get('DATABASE_URL')! + +// Create a database pool with three connections that are lazily established +const pool = new postgres.Pool(databaseUrl, 3, true) + +serve(async (_req) => { + try { + // Grab a connection from the pool + const connection = await pool.connect() + + try { + // Run a query + const result = await connection.queryObject`SELECT * FROM animals` + const animals = result.rows // [{ id: 1, name: "Lion" }, ...] + console.log(animals) + + // Encode the result as pretty printed JSON + const body = JSON.stringify( + animals, + (key, value) => (typeof value === 'bigint' ? value.toString() : value), + 2 + ) + + // Return the response with the correct content type header + return new Response(body, { + status: 200, + headers: { + 'Content-Type': 'application/json; charset=utf-8', + }, + }) + } finally { + // Release the connection back into the pool + connection.release() + } + } catch (err) { + console.error(err) + return new Response(String(err?.message ?? err), { status: 500 }) + } +}) +``` + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/functions/examples/github-actions.mdx b/apps/docs/pages/guides/functions/examples/github-actions.mdx index 58a85484ab9..05f1584faf9 100644 --- a/apps/docs/pages/guides/functions/examples/github-actions.mdx +++ b/apps/docs/pages/guides/functions/examples/github-actions.mdx @@ -4,6 +4,7 @@ export const meta = { id: 'examples-github-actions', title: 'GitHub Actions', description: 'Deploying Edge Functions with GitHub Actions.', + video: 'https://www.youtube.com/v/l2KlzGrhB6w', }
diff --git a/apps/docs/pages/guides/functions/examples/og-image.mdx b/apps/docs/pages/guides/functions/examples/og-image.mdx index e4be6c85796..efa8eb99e59 100644 --- a/apps/docs/pages/guides/functions/examples/og-image.mdx +++ b/apps/docs/pages/guides/functions/examples/og-image.mdx @@ -4,6 +4,7 @@ export const meta = { id: 'examples-og-image', title: 'Generating OG Images', description: 'Generate Open Graph images with Deno and Supabase Edge Functions.', + video: 'https://www.youtube.com/v/jZgyOJGWayQ', }
diff --git a/apps/docs/pages/guides/functions/examples/storage-caching.mdx b/apps/docs/pages/guides/functions/examples/storage-caching.mdx index 6e4b6ff604e..c9cf4de7be9 100644 --- a/apps/docs/pages/guides/functions/examples/storage-caching.mdx +++ b/apps/docs/pages/guides/functions/examples/storage-caching.mdx @@ -4,6 +4,7 @@ export const meta = { id: 'examples-storage-caching', title: 'Caching Images with Supabase Storage CDN', description: 'Integrate Edge Functions with Supabase Storage to cache images on the Edge (CDN).', + video: 'https://www.youtube.com/v/wW6L52v9Ldo', }
diff --git a/apps/docs/pages/guides/functions/examples/stripe-webhooks.mdx b/apps/docs/pages/guides/functions/examples/stripe-webhooks.mdx index 5b75bb48878..b71a4ca2b36 100644 --- a/apps/docs/pages/guides/functions/examples/stripe-webhooks.mdx +++ b/apps/docs/pages/guides/functions/examples/stripe-webhooks.mdx @@ -4,6 +4,7 @@ export const meta = { id: 'examples-stripe-webhooks', title: 'Handling Stripe Webhooks', description: 'Handling signed Stripe Webhooks with Edge Functions.', + video: 'https://www.youtube.com/v/6OMVWiiycLs', }
diff --git a/apps/docs/pages/guides/functions/examples/telegram-bot.mdx b/apps/docs/pages/guides/functions/examples/telegram-bot.mdx index bac6a45eba2..ad87c1a7103 100644 --- a/apps/docs/pages/guides/functions/examples/telegram-bot.mdx +++ b/apps/docs/pages/guides/functions/examples/telegram-bot.mdx @@ -4,6 +4,7 @@ export const meta = { id: 'examples-telegram-bot', title: 'Telegram Bot', description: 'Building a Telegram Bot with Edge Functions.', + video: 'https://www.youtube.com/v/AWfE3a9J_uo', }
diff --git a/apps/docs/pages/guides/functions/import-maps.mdx b/apps/docs/pages/guides/functions/import-maps.mdx index 2379dbf2699..6d61e774b55 100644 --- a/apps/docs/pages/guides/functions/import-maps.mdx +++ b/apps/docs/pages/guides/functions/import-maps.mdx @@ -4,6 +4,7 @@ export const meta = { id: 'functions-import-maps', title: 'Import Maps', description: 'Using Import Maps.', + video: 'https://www.youtube.com/v/ILr3cneZuFk', }
diff --git a/apps/docs/pages/guides/functions/quickstart.mdx b/apps/docs/pages/guides/functions/quickstart.mdx index 9652a280493..e94aaa84244 100644 --- a/apps/docs/pages/guides/functions/quickstart.mdx +++ b/apps/docs/pages/guides/functions/quickstart.mdx @@ -5,6 +5,7 @@ export const meta = { title: 'Edge Functions Quickstart', description: 'Globally distributed TypeScript Functions.', sidebar_label: 'Quickstart', + video: 'https://www.youtube.com/v/rzglqRdZUQE', } Learn how to build an Edge Function locally and deploy it to the Supabase Platform in less than 7 minutes. diff --git a/apps/docs/pages/guides/getting-started/tutorials/with-nextjs.mdx b/apps/docs/pages/guides/getting-started/tutorials/with-nextjs.mdx index 78c6c444d1e..a63a164a42b 100644 --- a/apps/docs/pages/guides/getting-started/tutorials/with-nextjs.mdx +++ b/apps/docs/pages/guides/getting-started/tutorials/with-nextjs.mdx @@ -5,6 +5,7 @@ export const meta = { title: 'Quickstart: Next.js', description: 'Learn how to use Supabase in your Next App.', sidebar_label: 'Next.js', + video: 'https://www.youtube.com/v/0Fs96oZ4se0', } diff --git a/apps/docs/pages/guides/getting-started/tutorials/with-sveltekit.mdx b/apps/docs/pages/guides/getting-started/tutorials/with-sveltekit.mdx index 78f236a99d9..614dac516e2 100644 --- a/apps/docs/pages/guides/getting-started/tutorials/with-sveltekit.mdx +++ b/apps/docs/pages/guides/getting-started/tutorials/with-sveltekit.mdx @@ -24,7 +24,7 @@ Let's start building the Svelte app from scratch. ### Initialize a Svelte app We can use the [SvelteKit Skeleton Project](https://kit.svelte.dev/docs) to initialize -an app called `supabase-sveltekit` (for this tutorial you do not need TypeScript, ESLint, Prettier, or Playwright): +an app called `supabase-sveltekit` (for this tutorial we will be using TypeScript): ```bash npm init svelte@next supabase-sveltekit diff --git a/apps/docs/pages/guides/integrations/fezto.mdx b/apps/docs/pages/guides/integrations/fezto.mdx index 17d48db27f4..60007bc3ed3 100644 --- a/apps/docs/pages/guides/integrations/fezto.mdx +++ b/apps/docs/pages/guides/integrations/fezto.mdx @@ -4,6 +4,7 @@ export const meta = { id: 'fezto', title: 'Frontend Zero to One', description: 'Create an app automatically from your Supabase Postgres using OpenAPI', + video: 'https://www.youtube.com/v/GOC6a0_AlgI', } [Frontend Zero to One is](https://www.fezto.xyz) is a service which creates an app for your Supabase Postgres database on-the-fly without any drag and drop, using the OpenAPI spec provided by PostgREST. diff --git a/apps/docs/pages/guides/integrations/flutterflow.mdx b/apps/docs/pages/guides/integrations/flutterflow.mdx new file mode 100644 index 00000000000..b7a25f8d1a5 --- /dev/null +++ b/apps/docs/pages/guides/integrations/flutterflow.mdx @@ -0,0 +1,147 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'flutterflow', + title: 'FlutterFlow', + description: + 'FlutterFlow is a low-code tool that allows you to build Flutter apps incredibly fast.', + canonical: 'https://docs.flutterflow.io/actions/actions/backend-database/supabase', +} + +[FlutterFlow](https://flutterflow.io/) is a low-code builder for developing native mobile applications using Flutter. You can use the simple drag-and-drop interface to build your app faster than traditional development. + +
+ +
+ +This guide gives you a quick overview of implementing basic CRUD operations using FlutterFlow and Supabase. You can find the full docs on FlutterFlow and Supabase [here](https://docs.flutterflow.io/actions/actions/backend-database/supabase). + +## Step 1: Connect FlutterFlow to Supabase + +Before we dive into the code, this guide assumes that you have the following ready: + +- [Supabase](https://database.new/) project created +- Have setup tables in your Supabase project +- [FlutterFlow](https://app.flutterflow.io/) project created + +You can then connect your Supabase project to your FlutterFlow project with the following steps: + +1. In your Supabase project, navigate to Project Settings > API. Copy the Project URL. +2. Return to FlutterFlow, navigate to Settings and Integrations > Integrations > Supabase. Turn on the toggle (i.e., enable Supabase) and paste the API URL. +3. Similarly, from the Supabase API section, copy the anon key (under Project API keys) and paste it inside the FlutterFlow > Settings and Integrations > Integrations > Supabase > Anon Key. +4. Click on the Get Schema button. This will show the list of all tables with their schema (structure) created in Supabase. +5. (Optional) If you have defined an Array for any Column Data Type in Supabase, you must set its type here. To do so, tap the "Click to set Array type" and choose the right one. + + + +## Step 2: Inserting rows + +Go to your project page on FlutterFlow and follow the steps below to define the Action to any widget. + +1. Select the Widget (e.g., Button) on which you want to define the action. +2. Select Actions from the Properties panel (the right menu), and click Open. This will open an Action flow Editor in a new popup window. + 1. Click on + Add Action. + 2. On the right side, search and select the Supabase > Insert Row action. + 3. Set the Table to your table name (e.g., assignments). + 4. Under the Set Fields section, click on the + Add Field button. + 5. Click on the Field name and scroll down to find the Value Source dropdown and change it to From Variable. + 6. Click on UNSET and select Widget State > Name of the TextField. + 7. Similarly, add the field for the other UI elements. + + + +## Step 3: Selecting and displaying rows + +To query a Supabase table on a ListView: + +1. Select the ListView widget. Make sure you choose the ListView widget, not the ListTile. +2. Select Backend Query from the properties panel (the right menu), and click Add Backend Query. +3. Set the Query Type to Supabase Query. +4. Select your Table from the dropdown list +5. Set the Query Type to List of Rows. +6. Optional: If you want to display the limited result, say, for example, you have thousands of entries, but you want to display only 100, you can specify the limit. +7. Click Confirm. + + + +## Step 4: Updating rows + +Go to your project page on FlutterFlow and follow the steps below to define the Action to any widget. + +1. Select the Widget (e.g., Button) on which you want to define the action. +2. Select Actions from the Properties panel (the right menu), and click Open. This will open an Action flow Editor in a new popup window. + 1. Click on + Add Action. + 2. On the right side, search and select the Supabase > Update Row action. + 3. Set the Table to your table name (e.g., assignments). + 4. Optional: If you want to get the rows after the update is finished, enable the Return Matching Rows option. + 5. Now, you must set the row you want to update. Usually, this is done by finding a row in a table that matches the current row ID. To do so, click + Add Filter button inside the Matching Rows section. + 1. Set the Field Name to the field that contains the IDs. Typically, this is the id column. + 2. Set the Relation to Equal To because you want to find a row with the exact id. + 3. Into the Value Source, you can select the From Variable and provide the id of the row for which you just updated values in the UI. + 6. Under the Set Fields section, click on the + Add Field button. + 7. Click on the field name. + 8. Scroll down to find the Value Source dropdown and change it to From Variable. + 9. Click on UNSET and select Widget State > Name of the TextField. + 10. Similarly, add the field for the other UI elements. + +## Step 5: Deleting rows + +Go to your project page on FlutterFlow and follow the steps below to define the Action to any widget. + +1. Select the Widget (e.g., Button) on which you want to define the action. +2. Select Actions from the Properties panel (the right menu), and click Open. This will open an Action flow Editor in a new popup window. + 1. Click on + Add Action. + 2. On the right side, search and select the Supabase -> Delete Row action. + 3. Set the Table to your table name (e.g., assignments). + 4. Optional: Later, if you want to know which rows were deleted from a table, enable the Return Matching Rows option. + 5. Now, you must set the row you want to delete. Usually, this is done by finding a row in a table that matches the current row ID. To do so, click + Add Filter button inside the Matching Rows section. + 1. Set the Field Name to the field that contains the IDs. Typically, this is the id column. + 2. Set the Relation to Equal To because you want to find a row with the exact id. + 3. Into the Value Source, you can select the From Variable and provide the id of the row you want to delete. + + + +## Resources + +You can find more detailed guides on FlutterFlow’s docs. + +- [FlutterFlow Supabase available actions](https://docs.flutterflow.io/actions/actions/backend-database/supabase) +- [Retrieving Data from Supabase on FlutterFlow](https://docs.flutterflow.io/data-and-backend/supabase/supabase-database/retrieving-data) +- [Adding data to Supabase DB from FlutterFlow](https://docs.flutterflow.io/data-and-backend/supabase/supabase-database/adding-data) + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/integrations/keyri.mdx b/apps/docs/pages/guides/integrations/keyri.mdx index 70d53e23374..6e607205669 100644 --- a/apps/docs/pages/guides/integrations/keyri.mdx +++ b/apps/docs/pages/guides/integrations/keyri.mdx @@ -5,6 +5,7 @@ export const meta = { title: 'Keyri', description: 'QR authentication for an easy and flexible biometric solution across all platforms.', + video: 'https://www.youtube.com/v/jrjrcpc2PFQ', } Keyri can be used to incorporate sign-in-with-QR functionality into your Supabase app, allowing users to scan a QR code on your web app with your mobile app and be instantly logged into the web app without having to input any credentials. diff --git a/apps/docs/pages/guides/integrations/onesignal.mdx b/apps/docs/pages/guides/integrations/onesignal.mdx index 446242bd07c..3f270c6f60e 100644 --- a/apps/docs/pages/guides/integrations/onesignal.mdx +++ b/apps/docs/pages/guides/integrations/onesignal.mdx @@ -5,6 +5,7 @@ export const meta = { title: 'OneSignal', description: 'OneSignal allows you to send cloud messages to your users. Combine OneSignal with your Supabase apps and you can reach out to your users whenever there is a change in your database.', + video: 'https://www.youtube.com/v/mw0DLwItue4', } [OneSignal](https://onesignal.com/) is a tool that allows you to send messages across different channels such as the following to keep your users engaged. diff --git a/apps/docs/pages/guides/platform/custom-domains.mdx b/apps/docs/pages/guides/platform/custom-domains.mdx index 13fedb8e525..92ea5fd14a3 100644 --- a/apps/docs/pages/guides/platform/custom-domains.mdx +++ b/apps/docs/pages/guides/platform/custom-domains.mdx @@ -4,6 +4,7 @@ export const meta = { id: 'custom-domains', title: 'Custom Domains', description: 'Configuring a Custom Domain for your Supabase project.', + video: 'https://www.youtube.com/v/6rcGnW_Mh-0', } @@ -14,6 +15,15 @@ Custom Domains are currently in beta, and are being slowly made available to pro Custom domains allow you to present a branded experience to your users. Custom domains are available as a [add-on for projects on a paid tier](https://app.supabase.com/project/_/settings/billing/update). Setting up a custom domain requires [Owner or Admin permissions](/docs/guides/platform/access-control#manage-team-members) for the project. Currently, you must use a subdomain (e.g., `api.example.com`, rather than `example.com`) for the purposes of this guide. +
+ +
+ ## Configure a Custom Domain using the Supabase Dashboard Follow the **Custom Domains** steps in the [General Settings](https://app.supabase.com/project/_/settings/general) page in the Dashboard to set up a custom domain for your project. diff --git a/apps/docs/pages/guides/realtime/quickstart.mdx b/apps/docs/pages/guides/realtime/quickstart.mdx index 73dd9f90689..bd3910f0246 100644 --- a/apps/docs/pages/guides/realtime/quickstart.mdx +++ b/apps/docs/pages/guides/realtime/quickstart.mdx @@ -5,6 +5,7 @@ export const meta = { title: 'Realtime Quickstart', description: "Getting started with Realtime's Features", sidebar_label: 'Quickstart', + video: 'https://www.youtube.com/v/BelYEMJ2N00', } Learn how to build [multiplayer.dev](https://multiplayer.dev), a collaborative app that demonstrates Broadcast, Presence, and Postgres Changes using [Realtime](/docs/guides/realtime). diff --git a/apps/docs/pages/guides/resources/migrating-to-supabase/heroku.mdx b/apps/docs/pages/guides/resources/migrating-to-supabase/heroku.mdx index 51ab83dfb49..b2b5df2acd2 100644 --- a/apps/docs/pages/guides/resources/migrating-to-supabase/heroku.mdx +++ b/apps/docs/pages/guides/resources/migrating-to-supabase/heroku.mdx @@ -5,6 +5,7 @@ export const meta = { title: 'Migrate from Heroku to Supabase', description: 'Migrate your Heroku Postgres database to Supabase.', sidebar_label: 'Heroku', + video: 'https://www.youtube.com/v/xsRhPMphtZ4', } Supabase is one of the best [free alternatives to Heroku Postgres](https://supabase.com/alternatives/supabase-vs-heroku-postgres). This guide shows how to migrate your Heroku Postgres database to Supabase. This migration requires the [pg_dump](https://www.postgresql.org/docs/current/app-pgdump.html) and [psql](https://www.postgresql.org/docs/current/app-psql.html) CLI tools, which are installed automatically as part of the complete PostgreSQL installation package. diff --git a/apps/docs/pages/guides/storage.mdx b/apps/docs/pages/guides/storage.mdx index fd024d292c8..b9367046382 100644 --- a/apps/docs/pages/guides/storage.mdx +++ b/apps/docs/pages/guides/storage.mdx @@ -5,6 +5,7 @@ export const meta = { title: 'Storage', description: 'Use Supabase to store and serve files.', sidebar_label: 'Overview', + video: 'https://www.youtube.com/v/J9mTPY8rIXE', } Supabase Storage makes it simple to store and serve large files. diff --git a/apps/docs/pages/guides/storage/access-control.mdx b/apps/docs/pages/guides/storage/access-control.mdx index 4adfd73e135..5079e23aebc 100644 --- a/apps/docs/pages/guides/storage/access-control.mdx +++ b/apps/docs/pages/guides/storage/access-control.mdx @@ -4,6 +4,7 @@ export const meta = { id: 'storage-access-control', title: 'Access Control', description: 'Manage who can access your Supabase Storage files.', + video: 'https://www.youtube.com/v/4ERX__Y908k', } Supabase Storage is integrated with your [Postgres Database](/docs/guides/database). diff --git a/apps/docs/pages/guides/storage/image-transformations.mdx b/apps/docs/pages/guides/storage/image-transformations.mdx index 0200d777e6a..651edc980ae 100644 --- a/apps/docs/pages/guides/storage/image-transformations.mdx +++ b/apps/docs/pages/guides/storage/image-transformations.mdx @@ -5,6 +5,7 @@ export const meta = { title: 'Storage Image Transformations', description: 'Transform images with Storage', sidebar_label: 'Image Transformations', + video: 'https://www.youtube.com/v/dLqSmxX3r7I', } Supabase Storage offers the functionality to transform and resize images dynamically. Any image stored in your buckets can be transformed and optimized for fast delivery. diff --git a/apps/docs/pages/learn/auth-deep-dive/auth-deep-dive-jwts.mdx b/apps/docs/pages/learn/auth-deep-dive/auth-deep-dive-jwts.mdx index 766c8a04806..d33020220ed 100644 --- a/apps/docs/pages/learn/auth-deep-dive/auth-deep-dive-jwts.mdx +++ b/apps/docs/pages/learn/auth-deep-dive/auth-deep-dive-jwts.mdx @@ -4,6 +4,7 @@ export const meta = { id: 'auth-deep-dive-jwts', title: 'Part One: JWTs', description: 'Supabase Auth Deep Dive Part 1 - JWTs', + video: 'https://www.youtube.com/v/v3Exg5YpJvE', } ### About diff --git a/apps/docs/pages/learn/auth-deep-dive/auth-google-oauth.mdx b/apps/docs/pages/learn/auth-deep-dive/auth-google-oauth.mdx index 96a643aa696..c2b91673f74 100644 --- a/apps/docs/pages/learn/auth-deep-dive/auth-google-oauth.mdx +++ b/apps/docs/pages/learn/auth-deep-dive/auth-google-oauth.mdx @@ -4,6 +4,7 @@ export const meta = { id: 'auth-google-oauth', title: 'Part Five: Google Oauth', description: 'Supabase Deep Dive Part 5: Google OAuth Provider', + video: 'https://www.youtube.com/v/_XM9ziOzWk4', } ### About diff --git a/apps/docs/pages/learn/auth-deep-dive/auth-policies.mdx b/apps/docs/pages/learn/auth-deep-dive/auth-policies.mdx index 902fbe6f8be..636eef09903 100644 --- a/apps/docs/pages/learn/auth-deep-dive/auth-policies.mdx +++ b/apps/docs/pages/learn/auth-deep-dive/auth-policies.mdx @@ -4,6 +4,7 @@ export const meta = { id: 'auth-policies', title: 'Part Three: Policies', description: 'Supabase Auth Deep Dive Part 3: User Based Access Policies', + video: 'https://www.youtube.com/v/0LvCOlELs5U', } ### About diff --git a/apps/docs/pages/learn/auth-deep-dive/auth-row-level-security.mdx b/apps/docs/pages/learn/auth-deep-dive/auth-row-level-security.mdx index 66687b36a7f..95a03060bc9 100644 --- a/apps/docs/pages/learn/auth-deep-dive/auth-row-level-security.mdx +++ b/apps/docs/pages/learn/auth-deep-dive/auth-row-level-security.mdx @@ -4,6 +4,7 @@ export const meta = { id: 'auth-row-level-security', title: 'Part Two: Row Level Security', description: 'Supabase Auth Deep Dive Part Two - Row Level Security', + video: 'https://www.youtube.com/v/qY_iQ10IUhs', } ### About diff --git a/apps/docs/public/img/integrations/logos/flutterflow_logo.png b/apps/docs/public/img/integrations/logos/flutterflow_logo.png new file mode 100644 index 00000000000..189e7ddc37d Binary files /dev/null and b/apps/docs/public/img/integrations/logos/flutterflow_logo.png differ diff --git a/apps/docs/public/img/integrations/logos/illa_logo.png b/apps/docs/public/img/integrations/logos/illa_logo.png new file mode 100644 index 00000000000..ea541c278f4 Binary files /dev/null and b/apps/docs/public/img/integrations/logos/illa_logo.png differ diff --git a/apps/docs/public/videos/guides/integrations/flutterflow/connect-flutterflow-to-supabase.mp4 b/apps/docs/public/videos/guides/integrations/flutterflow/connect-flutterflow-to-supabase.mp4 new file mode 100644 index 00000000000..ea539a1b3e5 Binary files /dev/null and b/apps/docs/public/videos/guides/integrations/flutterflow/connect-flutterflow-to-supabase.mp4 differ diff --git a/apps/docs/public/videos/guides/integrations/flutterflow/delete.mp4 b/apps/docs/public/videos/guides/integrations/flutterflow/delete.mp4 new file mode 100644 index 00000000000..ae82cf46081 Binary files /dev/null and b/apps/docs/public/videos/guides/integrations/flutterflow/delete.mp4 differ diff --git a/apps/docs/public/videos/guides/integrations/flutterflow/insert.mp4 b/apps/docs/public/videos/guides/integrations/flutterflow/insert.mp4 new file mode 100644 index 00000000000..b72a940c825 Binary files /dev/null and b/apps/docs/public/videos/guides/integrations/flutterflow/insert.mp4 differ diff --git a/apps/docs/public/videos/guides/integrations/flutterflow/select.mp4 b/apps/docs/public/videos/guides/integrations/flutterflow/select.mp4 new file mode 100644 index 00000000000..e3b0e8ae0c4 Binary files /dev/null and b/apps/docs/public/videos/guides/integrations/flutterflow/select.mp4 differ diff --git a/apps/www/_blog/2020-06-01-supabase-alpha-may-2020.mdx b/apps/www/_blog/2020-06-01-supabase-alpha-may-2020.mdx index 2d04b6b60b8..afacbae0458 100644 --- a/apps/www/_blog/2020-06-01-supabase-alpha-may-2020.mdx +++ b/apps/www/_blog/2020-06-01-supabase-alpha-may-2020.mdx @@ -9,6 +9,7 @@ authorURL: https://github.com/kiwicopple tags: - supabase date: '06-01-2020' +video: https://www.youtube.com/v/e4qXmcEFaUs --- It has been a monster month at [Supabase](/) and we're back with a video update. It's a longer video this month because we have a couple of housekeeping items. diff --git a/apps/www/_blog/2020-11-02-supabase-alpha-october-2020.mdx b/apps/www/_blog/2020-11-02-supabase-alpha-october-2020.mdx index 42ed611f260..9e4cc3dc193 100644 --- a/apps/www/_blog/2020-11-02-supabase-alpha-october-2020.mdx +++ b/apps/www/_blog/2020-11-02-supabase-alpha-october-2020.mdx @@ -10,6 +10,7 @@ image: supabase-october-2020.png tags: - supabase date: '11-02-2020' +video: https://www.youtube.com/v/1gNDMMsUPI0 --- We're now 8 months into building Supabase. We're focused on performance, stability, and reliability but that hasn't prevented us from shipping some great features. diff --git a/apps/www/_blog/2020-12-01-supabase-alpha-november-2020.mdx b/apps/www/_blog/2020-12-01-supabase-alpha-november-2020.mdx index 851b05010b9..7910f61bc42 100644 --- a/apps/www/_blog/2020-12-01-supabase-alpha-november-2020.mdx +++ b/apps/www/_blog/2020-12-01-supabase-alpha-november-2020.mdx @@ -10,6 +10,7 @@ image: supabase-november-2020.png tags: - supabase date: '12-01-2020' +video: https://www.youtube.com/v/unC_de7iytA --- We've been building for 9 months now, and we're getting even closer to Beta. diff --git a/apps/www/_blog/2020-12-02-case-study-monitoro.mdx b/apps/www/_blog/2020-12-02-case-study-monitoro.mdx index 72b5d324b96..4bfceaf4e42 100644 --- a/apps/www/_blog/2020-12-02-case-study-monitoro.mdx +++ b/apps/www/_blog/2020-12-02-case-study-monitoro.mdx @@ -11,6 +11,7 @@ tags: - case-study - no-code date: '12-02-2020' +video: https://www.youtube.com/v/8A6_pg41M2s --- Omar Kamali is the founder of [Monitoro](https://www.monitoro.xyz/), a service for scraping countless websites 24/7 to notify customers when these websites change. To create this product, Monitoro's team needed to handle massive data throughput whilst ensuring the product was reliable and resilient to satisfy a rapidly growing user base. diff --git a/apps/www/_blog/2021-01-02-supabase-beta-december-2020.mdx b/apps/www/_blog/2021-01-02-supabase-beta-december-2020.mdx index 507b63ff479..7045c3712b9 100644 --- a/apps/www/_blog/2021-01-02-supabase-beta-december-2020.mdx +++ b/apps/www/_blog/2021-01-02-supabase-beta-december-2020.mdx @@ -11,6 +11,7 @@ thumb: supabase-december-2020.png tags: - supabase date: '01-02-2021' +video: https://www.youtube.com/v/ofSm4BJkZ1g --- After 10 hectic months of building, Supabase is now in Beta. diff --git a/apps/www/_blog/2021-02-02-supabase-beta-january-2021.mdx b/apps/www/_blog/2021-02-02-supabase-beta-january-2021.mdx index d549beff71a..10e86ed878e 100644 --- a/apps/www/_blog/2021-02-02-supabase-beta-january-2021.mdx +++ b/apps/www/_blog/2021-02-02-supabase-beta-january-2021.mdx @@ -11,6 +11,7 @@ thumb: supabase-january-2021.png tags: - supabase date: '02-02-2021' +video: https://www.youtube.com/v/DlybOLANG4s --- New year, new features. We've been busy at Supabase during January and our community has been even busier. Here's a few things you'll find interesting. diff --git a/apps/www/_blog/2021-03-02-supabase-beta-february-2021.mdx b/apps/www/_blog/2021-03-02-supabase-beta-february-2021.mdx index 63cc53838f8..2a14b71e615 100644 --- a/apps/www/_blog/2021-03-02-supabase-beta-february-2021.mdx +++ b/apps/www/_blog/2021-03-02-supabase-beta-february-2021.mdx @@ -10,6 +10,7 @@ image: /images/blog/release-feb-2021.jpg tags: - release-notes date: '03-02-2021' +video: https://www.youtube.com/v/h-ses99G45g --- Supabase is an open source Firebase alternative. We've now been building for one year. Here's what we released last month. diff --git a/apps/www/_blog/2021-03-11-using-supabase-replit.mdx b/apps/www/_blog/2021-03-11-using-supabase-replit.mdx index 4b609ee47f1..e0856ab9753 100644 --- a/apps/www/_blog/2021-03-11-using-supabase-replit.mdx +++ b/apps/www/_blog/2021-03-11-using-supabase-replit.mdx @@ -13,6 +13,7 @@ tags: - node-js - postgres date: '03-11-2021' +video: https://www.youtube.com/v/lQ5iIxaYduI --- [Replit.com](http://replit.com) is an awesome new browser based IDE where you can code alone or collaboratively with friends using their awesome multiplayer features! It's particularly useful for education, and sharing code examples with others. diff --git a/apps/www/_blog/2021-04-06-supabase-beta-march-2021.mdx b/apps/www/_blog/2021-04-06-supabase-beta-march-2021.mdx index 7d0618917e9..ba28fa69618 100644 --- a/apps/www/_blog/2021-04-06-supabase-beta-march-2021.mdx +++ b/apps/www/_blog/2021-04-06-supabase-beta-march-2021.mdx @@ -10,6 +10,7 @@ thumb: march-2021/release-mar-2021.jpg tags: - release-notes date: '2021-04-06' +video: https://www.youtube.com/v/TtLxxaYE1rA --- Launch week, Storage, Supabase CLI, Connection Pooling, Supabase UI, and Pricing. Here's what we released last month. diff --git a/apps/www/_blog/2021-05-03-supabase-beta-april-2021.mdx b/apps/www/_blog/2021-05-03-supabase-beta-april-2021.mdx index e7d3215ca92..fb40d53ea65 100644 --- a/apps/www/_blog/2021-05-03-supabase-beta-april-2021.mdx +++ b/apps/www/_blog/2021-05-03-supabase-beta-april-2021.mdx @@ -10,6 +10,7 @@ thumb: april-2021/release-apr-2021.jpg tags: - release-notes date: '2021-05-05' +video: https://www.youtube.com/v/uWJmUTCFdak --- This month was a "gardening" month for Supabase. The team focused on stability, security, and community support. diff --git a/apps/www/_blog/2021-06-02-supabase-beta-may-2021.mdx b/apps/www/_blog/2021-06-02-supabase-beta-may-2021.mdx index b81565782e5..6469fa46f1d 100644 --- a/apps/www/_blog/2021-06-02-supabase-beta-may-2021.mdx +++ b/apps/www/_blog/2021-06-02-supabase-beta-may-2021.mdx @@ -10,6 +10,7 @@ thumb: 2021-may/release-may-2021.jpg tags: - release-notes date: '2021-06-02' +video: https://www.youtube.com/v/qETcl3SUfzU --- Apple & Twitter Logins, Supabase Grid, Go & Swift Libraries. Lots of great stuff to try out this month. diff --git a/apps/www/_blog/2021-07-02-supabase-beta-june-2021.mdx b/apps/www/_blog/2021-07-02-supabase-beta-june-2021.mdx index 2d4e0ca9bde..d42f3c7bbb6 100644 --- a/apps/www/_blog/2021-07-02-supabase-beta-june-2021.mdx +++ b/apps/www/_blog/2021-07-02-supabase-beta-june-2021.mdx @@ -11,6 +11,7 @@ tags: - release-notes date: '2021-06-02' toc_depth: 3 +video: https://www.youtube.com/v/m3yRPNyYolk --- Supabase is gearing up for another Launch Week on July the 26th. Until then, here's a few new things to try. diff --git a/apps/www/_blog/2021-07-28-supabase-auth-passwordless-sms-login.mdx b/apps/www/_blog/2021-07-28-supabase-auth-passwordless-sms-login.mdx index 92623917793..72e22de1d6a 100644 --- a/apps/www/_blog/2021-07-28-supabase-auth-passwordless-sms-login.mdx +++ b/apps/www/_blog/2021-07-28-supabase-auth-passwordless-sms-login.mdx @@ -12,6 +12,7 @@ tags: - auth date: '2021-07-28' toc_depth: 3 +video: https://www.youtube.com/v/akScoPO01bc --- Since launching Supabase Auth [last summer](https://news.ycombinator.com/item?id=24072051) it's proven to be a key part of the Supabase Stack. diff --git a/apps/www/_blog/2021-08-12-supabase-beta-july-2021.mdx b/apps/www/_blog/2021-08-12-supabase-beta-july-2021.mdx index 51818654cc2..c38bb4d977c 100644 --- a/apps/www/_blog/2021-08-12-supabase-beta-july-2021.mdx +++ b/apps/www/_blog/2021-08-12-supabase-beta-july-2021.mdx @@ -11,6 +11,7 @@ tags: - release-notes date: '2021-08-12' toc_depth: 2 +video: https://www.youtube.com/v/Vj5fPA-vjfw --- July ended with our second "Launch Week", where we shipped something every day for a week. There's a lot to get through this time! diff --git a/apps/www/_blog/2021-09-10-supabase-beta-august-2021.mdx b/apps/www/_blog/2021-09-10-supabase-beta-august-2021.mdx index fd43ba55d67..1bc892b92a2 100644 --- a/apps/www/_blog/2021-09-10-supabase-beta-august-2021.mdx +++ b/apps/www/_blog/2021-09-10-supabase-beta-august-2021.mdx @@ -11,6 +11,7 @@ tags: - release-notes date: '2021-09-10' toc_depth: 3 +video: https://www.youtube.com/v/YYpTh2DAvho --- We've raised $30M and shipped a bunch of features. Let's dive into what's been happening at Supabase during the month of August. diff --git a/apps/www/_blog/2021-10-04-supabase-beta-sept-2021.mdx b/apps/www/_blog/2021-10-04-supabase-beta-sept-2021.mdx index 09202fdfb1f..587aa3d2207 100644 --- a/apps/www/_blog/2021-10-04-supabase-beta-sept-2021.mdx +++ b/apps/www/_blog/2021-10-04-supabase-beta-sept-2021.mdx @@ -11,6 +11,7 @@ tags: - release-notes date: '2021-10-04' toc_depth: 3 +video: https://www.youtube.com/v/5fsKMTeBKKY --- Did you know it's been 2 years since the [first commit](https://github.com/supabase/realtime/commit/175f649784147af80acfc9ff5be9d160285c76ea) to Realtime, our real-time engine for Postgres? Before we even existed as a company! diff --git a/apps/www/_blog/2021-11-05-supabase-beta-october-2021.mdx b/apps/www/_blog/2021-11-05-supabase-beta-october-2021.mdx index 41c6b0df8de..526f2b8cf11 100644 --- a/apps/www/_blog/2021-11-05-supabase-beta-october-2021.mdx +++ b/apps/www/_blog/2021-11-05-supabase-beta-october-2021.mdx @@ -11,6 +11,7 @@ tags: - release-notes date: '2021-11-07' toc_depth: 3 +video: https://www.youtube.com/v/yL5WbAKAKjE --- We released three new Auth providers, multi-schema support, and we're gearing up for another Launch Week. diff --git a/apps/www/_blog/2021-11-29-community-day-lw3.mdx b/apps/www/_blog/2021-11-29-community-day-lw3.mdx index 801bfc24217..3bebaafceac 100644 --- a/apps/www/_blog/2021-11-29-community-day-lw3.mdx +++ b/apps/www/_blog/2021-11-29-community-day-lw3.mdx @@ -12,6 +12,7 @@ tags: - community date: '2021-11-29' toc_depth: 2 +video: https://www.youtube.com/v/6ptUrE4QRPQ --- Supabase combines existing open-source tools with our own open-source contributions to provide a delightful experience for developers. As part of this goal we hope to build a community of communities, bringing together developers from many different tools, as well as new developers looking to get involved with open source. diff --git a/apps/www/_blog/2021-12-01-realtime-row-level-security-in-postgresql.mdx b/apps/www/_blog/2021-12-01-realtime-row-level-security-in-postgresql.mdx index 6fd02352523..b8a3c3e6e40 100644 --- a/apps/www/_blog/2021-12-01-realtime-row-level-security-in-postgresql.mdx +++ b/apps/www/_blog/2021-12-01-realtime-row-level-security-in-postgresql.mdx @@ -13,6 +13,7 @@ tags: - security date: '2021-12-01' toc_depth: 3 +video: https://www.youtube.com/v/zHvatf2wySI --- Realtime is a server that listens to changes in your PostgreSQL database and broadcasts the changes to clients through a websocket connection. diff --git a/apps/www/_blog/2021-12-02-supabase-acquires-logflare.mdx b/apps/www/_blog/2021-12-02-supabase-acquires-logflare.mdx index c2bd70d3dd4..4e3a9e61dea 100644 --- a/apps/www/_blog/2021-12-02-supabase-acquires-logflare.mdx +++ b/apps/www/_blog/2021-12-02-supabase-acquires-logflare.mdx @@ -12,6 +12,7 @@ tags: - logging date: '2021-12-02' toc_depth: 3 +video: https://www.youtube.com/v/MdKWR_Zpu50 --- Back in May 2020, we [tweeted about](https://twitter.com/supabase/status/1265922290237071361?lang=bg) one of the most impressive products we had ever used: Logflare, a log ingestion platform. diff --git a/apps/www/_blog/2022-01-20-supabase-beta-december-2021.mdx b/apps/www/_blog/2022-01-20-supabase-beta-december-2021.mdx index f3de6a9cc46..11cafb38ef9 100644 --- a/apps/www/_blog/2022-01-20-supabase-beta-december-2021.mdx +++ b/apps/www/_blog/2022-01-20-supabase-beta-december-2021.mdx @@ -11,6 +11,7 @@ tags: - release-notes date: '2022-01-20' toc_depth: 3 +video: https://www.youtube.com/v/I6nnp9AINJk --- Happy 2022. We're looking forward to a year of shipping product and helping our users build great things! diff --git a/apps/www/_blog/2022-02-22-supabase-beta-january-2022.mdx b/apps/www/_blog/2022-02-22-supabase-beta-january-2022.mdx index fdac06a1687..81c0e8d98b1 100644 --- a/apps/www/_blog/2022-02-22-supabase-beta-january-2022.mdx +++ b/apps/www/_blog/2022-02-22-supabase-beta-january-2022.mdx @@ -11,6 +11,7 @@ tags: - release-notes date: '2022-02-22' toc_depth: 3 +video: https://www.youtube.com/v/codAs9-NeHM --- This month's beta update is more stacked than the Superbowl (\*Supa-bowl) halftime show. Here’s all of the highlights from January... diff --git a/apps/www/_blog/2022-03-25-supabase-launch-week-four.mdx b/apps/www/_blog/2022-03-25-supabase-launch-week-four.mdx index eb62a5b3e30..8edfcd91dde 100644 --- a/apps/www/_blog/2022-03-25-supabase-launch-week-four.mdx +++ b/apps/www/_blog/2022-03-25-supabase-launch-week-four.mdx @@ -11,6 +11,7 @@ tags: - launch-week date: '2022-03-25' toc_depth: 2 +video: https://www.youtube.com/v/bpzQCViwLLA --- By now you probably know the drill. Supabase Launch Week: we launch one thing, or sometimes a couple, diff --git a/apps/www/_blog/2022-03-28-community-day-lw4.mdx b/apps/www/_blog/2022-03-28-community-day-lw4.mdx index 466918dc4cd..4600e2b03bb 100644 --- a/apps/www/_blog/2022-03-28-community-day-lw4.mdx +++ b/apps/www/_blog/2022-03-28-community-day-lw4.mdx @@ -12,6 +12,7 @@ tags: - community date: '2022-03-28' toc_depth: 3 +video: https://www.youtube.com/v/aRgHKofrupU --- Supabase combines existing open-source tools with our own open-source contributions to provide a delightful experience for all developers. As part of this, we’re building a community of communities, bringing together developers from many different backgrounds, as well as new developers looking to get involved with open source. diff --git a/apps/www/_blog/2022-03-31-supabase-edge-functions.mdx b/apps/www/_blog/2022-03-31-supabase-edge-functions.mdx index fe17cc6ef21..d1446ef35cd 100644 --- a/apps/www/_blog/2022-03-31-supabase-edge-functions.mdx +++ b/apps/www/_blog/2022-03-31-supabase-edge-functions.mdx @@ -9,6 +9,7 @@ tags: - functions date: '2022-03-31' toc_depth: 3 +video: https://www.youtube.com/v/rzglqRdZUQE --- Today we're launching one of our [most](https://twitter.com/lau_cazanove/status/1506530181946691592) [requested](https://twitter.com/edgarasben/status/1506653203458363393) and [highly-](https://twitter.com/runjep/status/1507462077216088069)[anticipated](https://twitter.com/marcopolotwo/status/1506431782362632195) [features](https://github.com/supabase/supabase/discussions/4269) — Edge Functions. Edge Functions let you execute Typescript code close to your users, no matter where they're located. diff --git a/apps/www/_blog/2022-04-01-supabase-realtime-with-multiplayer-features.mdx b/apps/www/_blog/2022-04-01-supabase-realtime-with-multiplayer-features.mdx index 63600d109ee..584e9d26dc8 100644 --- a/apps/www/_blog/2022-04-01-supabase-realtime-with-multiplayer-features.mdx +++ b/apps/www/_blog/2022-04-01-supabase-realtime-with-multiplayer-features.mdx @@ -9,6 +9,7 @@ tags: - realtime date: '2022-04-01T22:30:00' toc_depth: 3 +video: https://www.youtube.com/v/BelYEMJ2N00 --- Today is the 1st of April, or April Fool's Day, but I like to think of today as Supabase Realtime Day where we announce and demo the next version of Realtime. diff --git a/apps/www/_blog/2022-07-13-supabase-auth-helpers-with-sveltekit-support.mdx b/apps/www/_blog/2022-07-13-supabase-auth-helpers-with-sveltekit-support.mdx index 66f222c9750..8fcd5b9ff1d 100644 --- a/apps/www/_blog/2022-07-13-supabase-auth-helpers-with-sveltekit-support.mdx +++ b/apps/www/_blog/2022-07-13-supabase-auth-helpers-with-sveltekit-support.mdx @@ -8,6 +8,7 @@ tags: - release-notes date: '2022-07-13' toc_depth: 9 +video: https://www.youtube.com/v/VgYPGOr3F-4 --- We've been hard at work making an Auth experience that is as smooth as possible for Supabase developers. One of the challenges has been creating a simple experience for server-side rendering (SSR) environments. We [previously released](https://supabase.com/blog/community-day#server-side-auth-for-nextjs-and-nuxt-sveltekit-and-remix-coming-soon) our Auth Helpers libraries with support for React and NextJS, and today we’re announcing some exciting updates. diff --git a/apps/www/_blog/2022-08-10-launch-week-5-hackathon.mdx b/apps/www/_blog/2022-08-10-launch-week-5-hackathon.mdx index 818acc5081d..dd12a99bee8 100644 --- a/apps/www/_blog/2022-08-10-launch-week-5-hackathon.mdx +++ b/apps/www/_blog/2022-08-10-launch-week-5-hackathon.mdx @@ -9,6 +9,7 @@ tags: - hackathon date: '2022-08-10' toc_depth: 3 +video: https://www.youtube.com/v/rI3Ik7GyYEw --- # The Supabase Open Source Hackathon diff --git a/apps/www/_blog/2022-08-12-supabase-series-b.mdx b/apps/www/_blog/2022-08-12-supabase-series-b.mdx index 24893bdbec3..0e6c33cc749 100644 --- a/apps/www/_blog/2022-08-12-supabase-series-b.mdx +++ b/apps/www/_blog/2022-08-12-supabase-series-b.mdx @@ -8,6 +8,7 @@ tags: - supabase date: '2022-08-12' toc_depth: 3 +video: https://www.youtube.com/v/4t_63HT3rZY --- Supabase raised $80M in May, bringing our total funding to $116M. This comes one year after our Series A, and so we're revisiting the plans diff --git a/apps/www/_blog/2022-08-15-supabase-cli-v1-and-admin-api-beta.mdx b/apps/www/_blog/2022-08-15-supabase-cli-v1-and-admin-api-beta.mdx index 1b554f476c6..5bc9888e6ee 100644 --- a/apps/www/_blog/2022-08-15-supabase-cli-v1-and-admin-api-beta.mdx +++ b/apps/www/_blog/2022-08-15-supabase-cli-v1-and-admin-api-beta.mdx @@ -8,6 +8,7 @@ tags: - launch-week date: '2022-08-15' toc_depth: 3 +video: https://www.youtube.com/v/OpPOaJI_Z28 --- Today we are moving the Supabase CLI v1 out of beta. The Supabase CLI is capable of managing database migrations and generating TypeScript types. Follow these [install instructions](https://supabase.com/docs/guides/cli) to get started. diff --git a/apps/www/_blog/2022-08-16-supabase-js-v2.mdx b/apps/www/_blog/2022-08-16-supabase-js-v2.mdx index 07cf3fd54dc..5ca0414db64 100644 --- a/apps/www/_blog/2022-08-16-supabase-js-v2.mdx +++ b/apps/www/_blog/2022-08-16-supabase-js-v2.mdx @@ -8,6 +8,7 @@ tags: - launch-week date: '2022-08-16' toc_depth: 3 +video: https://www.youtube.com/v/iqZlPtl_b-I ---
diff --git a/apps/www/_blog/2022-08-17-supabase-soc2.mdx b/apps/www/_blog/2022-08-17-supabase-soc2.mdx index 06afc949188..7febf307779 100644 --- a/apps/www/_blog/2022-08-17-supabase-soc2.mdx +++ b/apps/www/_blog/2022-08-17-supabase-soc2.mdx @@ -8,6 +8,7 @@ tags: - launch-week date: '2022-08-17' toc_depth: 3 +video: https://www.youtube.com/v/6bGQotxisoY --- Supabase is now SOC2 Type 1 compliant. Let’s dig into what that means, and explain the process we went through to get there. If you’re building a SaaS product, this blog post should provide a rough guide to getting your SOC2 certification. diff --git a/apps/www/_blog/2022-08-18-supabase-realtime-multiplayer-general-availability.mdx b/apps/www/_blog/2022-08-18-supabase-realtime-multiplayer-general-availability.mdx index f41ae23b202..a30b10b8d00 100644 --- a/apps/www/_blog/2022-08-18-supabase-realtime-multiplayer-general-availability.mdx +++ b/apps/www/_blog/2022-08-18-supabase-realtime-multiplayer-general-availability.mdx @@ -8,6 +8,7 @@ tags: - launch-week date: '2022-08-18' toc_depth: 3 +video: https://www.youtube.com/v/CGZr5tybW18 --- During our last Launch Week, we teased our plans for "multiplayer" features. If you need a refresher, diff --git a/apps/www/_blog/2022-08-19-launch-week-5-community-day.mdx b/apps/www/_blog/2022-08-19-launch-week-5-community-day.mdx index d7a63f6595e..dda88680ffd 100644 --- a/apps/www/_blog/2022-08-19-launch-week-5-community-day.mdx +++ b/apps/www/_blog/2022-08-19-launch-week-5-community-day.mdx @@ -9,6 +9,7 @@ tags: - community date: '2022-08-19' toc_depth: 2 +video: https://www.youtube.com/v/4A6RKPp1b70 --- Supabase combines existing open-source tools with our own open-source contributions to provide a delightful experience for developers of all skill levels. Just as versatile as our platform, we're building a community of communities, bringing together developers from many different backgrounds, as well as new developers looking to get involved with open source. diff --git a/apps/www/_blog/2022-08-24-building-a-realtime-trello-board-with-supabase-and-angular.mdx b/apps/www/_blog/2022-08-24-building-a-realtime-trello-board-with-supabase-and-angular.mdx index a31291f27e2..c0e20a82629 100644 --- a/apps/www/_blog/2022-08-24-building-a-realtime-trello-board-with-supabase-and-angular.mdx +++ b/apps/www/_blog/2022-08-24-building-a-realtime-trello-board-with-supabase-and-angular.mdx @@ -10,6 +10,7 @@ tags: - community date: '2022-08-24' toc_depth: 2 +video: https://www.youtube.com/v/jmCiI_OSarA --- Everyone can code up a little hello world example quickly with a platform like [Supabase](https://supabase.com/) - but what about a _real world_ project of bigger scale? diff --git a/apps/www/_blog/2022-11-17-fetching-and-caching-supabase-data-in-next-js-server-components.mdx b/apps/www/_blog/2022-11-17-fetching-and-caching-supabase-data-in-next-js-server-components.mdx index 91f7c558d7e..4b1954e318c 100644 --- a/apps/www/_blog/2022-11-17-fetching-and-caching-supabase-data-in-next-js-server-components.mdx +++ b/apps/www/_blog/2022-11-17-fetching-and-caching-supabase-data-in-next-js-server-components.mdx @@ -8,6 +8,7 @@ tags: - Next.js date: '2022-11-17' toc_depth: 3 +video: https://www.youtube.com/v/QH0P5xZt5wY --- The biggest announcement from Next.js Conf 2022 was the [release of Next.js 13](https://nextjs.org/blog/next-13), which introduces a collection of improvements, most exciting of which is Server Components. The combination of Server Components and Suspense allow for a more streamlined, reimagined way to fetch and cache data in Next.js applications. This provides excellent DX improvements — such as async components — and aligns the Next framework even closer with the future of React. diff --git a/apps/www/_blog/2022-12-09-who-we-hire.mdx b/apps/www/_blog/2022-12-09-who-we-hire.mdx index 4ebfa438775..1047b27e691 100644 --- a/apps/www/_blog/2022-12-09-who-we-hire.mdx +++ b/apps/www/_blog/2022-12-09-who-we-hire.mdx @@ -1,6 +1,7 @@ --- title: 'Who We Hire at Supabase' description: Traits we look for to maintain a culture of shipping fast and often +video: https://www.youtube.com/v/-BG9XptyCKI author: ant_wilson image: who-we-hire.jpg thumb: who-we-hire-cover.jpg diff --git a/apps/www/_blog/2022-12-12-new-supabase-docs-built-with-nextjs.mdx b/apps/www/_blog/2022-12-12-new-supabase-docs-built-with-nextjs.mdx index 9f725b9ab84..78bae4b10ed 100644 --- a/apps/www/_blog/2022-12-12-new-supabase-docs-built-with-nextjs.mdx +++ b/apps/www/_blog/2022-12-12-new-supabase-docs-built-with-nextjs.mdx @@ -8,6 +8,7 @@ tags: - launch-week date: '2022-12-12' toc_depth: 3 +video: https://www.youtube.com/v/Q1Amk6iDlF8 youtubeHero: https://www.youtube-nocookie.com/embed/Q1Amk6iDlF8 --- @@ -102,4 +103,3 @@ Docs are more than just the tech behind them. The goal of docs is to give you, t - [Point in Time Recovery is now available](https://supabase.com/blog/postgres-point-in-time-recovery) - [Custom Domain Names are now available](https://supabase.com/blog/custom-domain-names) - [Wrap Up: everything we shipped](https://supabase.com/blog/launch-week-6-wrap-up) - diff --git a/apps/www/_blog/2022-12-13-storage-image-resizing-smart-cdn.mdx b/apps/www/_blog/2022-12-13-storage-image-resizing-smart-cdn.mdx index deb677996d5..02b1284ca5b 100644 --- a/apps/www/_blog/2022-12-13-storage-image-resizing-smart-cdn.mdx +++ b/apps/www/_blog/2022-12-13-storage-image-resizing-smart-cdn.mdx @@ -8,6 +8,7 @@ tags: - launch-week date: '2022-12-13' toc_depth: 3 +video: https://www.youtube.com/v/NpEl20iuOtg youtubeHero: https://www.youtube-nocookie.com/embed/NpEl20iuOtg --- diff --git a/apps/www/_blog/2022-12-14-mfa-auth-via-rls.mdx b/apps/www/_blog/2022-12-14-mfa-auth-via-rls.mdx index bfb8e02b25d..70e89a15e82 100644 --- a/apps/www/_blog/2022-12-14-mfa-auth-via-rls.mdx +++ b/apps/www/_blog/2022-12-14-mfa-auth-via-rls.mdx @@ -11,6 +11,7 @@ tags: - saml date: '2022-12-14' toc_depth: 3 +video: https://www.youtube.com/v/He7LI2mv9v0 --- Today, we’re releasing Multi Factor Authentication for everyone. diff --git a/apps/www/_blog/2022-12-15-postgres-foreign-data-wrappers-rust.mdx b/apps/www/_blog/2022-12-15-postgres-foreign-data-wrappers-rust.mdx index 62147a24dd0..28beba1875d 100644 --- a/apps/www/_blog/2022-12-15-postgres-foreign-data-wrappers-rust.mdx +++ b/apps/www/_blog/2022-12-15-postgres-foreign-data-wrappers-rust.mdx @@ -8,6 +8,7 @@ tags: - launch-week date: '2022-12-15' toc_depth: 3 +video: https://www.youtube.com/v/QA2qC5F-4OU --- Today we're releasing [Supabase Wrappers](https://github.com/supabase/wrappers), a framework for building Postgres Foreign Data Wrappers (FDW) which connects Postgres to external systems. diff --git a/apps/www/_blog/2022-12-16-launch-week-6-community-day.mdx b/apps/www/_blog/2022-12-16-launch-week-6-community-day.mdx index 81cbbfdf6d7..78facd9830a 100644 --- a/apps/www/_blog/2022-12-16-launch-week-6-community-day.mdx +++ b/apps/www/_blog/2022-12-16-launch-week-6-community-day.mdx @@ -9,6 +9,7 @@ tags: - community date: '2022-12-16' toc_depth: 3 +video: https://www.youtube.com/v/hw9Q-NjASbU --- Supabase is a collaborative company. We work with, sponsor, and support as many open source tools as possible. The great thing about this approach is that each tool brings their own community and it's starting to feel like Supabase is really just a “community of communities”. Far too much has happened in the Supabase community in the last 3 months for us to possibly feature it all here, so here's a collection of the highlights! diff --git a/apps/www/_blog/2022-12-16-launch-week-6-wrap-up.mdx b/apps/www/_blog/2022-12-16-launch-week-6-wrap-up.mdx index f8435ebed42..d609f5c361c 100644 --- a/apps/www/_blog/2022-12-16-launch-week-6-wrap-up.mdx +++ b/apps/www/_blog/2022-12-16-launch-week-6-wrap-up.mdx @@ -8,6 +8,7 @@ tags: - launch-week date: '2022-12-16' toc_depth: 3 +video: https://www.youtube.com/v/hw9Q-NjASbU --- That's a wrap on Supabase Launch Week Day 6. Here's everything we shipped in one long blog post. diff --git a/apps/www/_blog/2022-12-16-vault-now-in-beta.mdx b/apps/www/_blog/2022-12-16-vault-now-in-beta.mdx index 37a6397743f..2b02cda5cb3 100644 --- a/apps/www/_blog/2022-12-16-vault-now-in-beta.mdx +++ b/apps/www/_blog/2022-12-16-vault-now-in-beta.mdx @@ -11,6 +11,7 @@ tags: - planetpg date: '2022-12-16' toc_depth: 3 +video: https://www.youtube.com/v/QHLPNDrdN2w --- During our last Launch Week we [announced](/blog/supabase-vault) Supabase Vault as our “one more thing”. Today we're releasing it progressively across the platform. diff --git a/apps/www/pages/blog/[slug].tsx b/apps/www/pages/blog/[slug].tsx index 36ac58de88c..f5762169a36 100644 --- a/apps/www/pages/blog/[slug].tsx +++ b/apps/www/pages/blog/[slug].tsx @@ -134,7 +134,15 @@ function BlogPostPage(props: any) { description: props.blog.description, url: `https://supabase.com/blog/${props.blog.slug}`, type: 'article', - + videos: props.blog.video && [ + { + // youtube based video meta + url: props.blog.video, + type: 'application/x-shockwave-flash', + width: 640, + height: 385, + }, + ], article: { // // to do: add expiration and modified dates diff --git a/package-lock.json b/package-lock.json index 49ce07f0d47..29773e5561f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20305,6 +20305,23 @@ "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", "dev": true }, + "node_modules/ip-address": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-8.1.0.tgz", + "integrity": "sha512-Wz91gZKpNKoXtqvY8ScarKYwhXoK4r/b5QuT+uywe/azv0/nUCo7Bh0IRRI7F9DHR06kJNWtzMGLIbXavngbKA==", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "1.1.2" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ip-address/node_modules/sprintf-js": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", + "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==" + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -22939,6 +22956,11 @@ "node": ">=4.0.0" } }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" + }, "node_modules/jsdom": { "version": "16.7.0", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", @@ -38330,6 +38352,7 @@ "generate-password": "^1.7.0", "html-to-image": "^1.10.8", "immutability-helper": "^3.1.1", + "ip-address": "^8.1.0", "json-logic-js": "^2.0.2", "lodash": "^4.17.21", "lottie-react": "^2.2.1", @@ -54260,6 +54283,22 @@ "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", "dev": true }, + "ip-address": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-8.1.0.tgz", + "integrity": "sha512-Wz91gZKpNKoXtqvY8ScarKYwhXoK4r/b5QuT+uywe/azv0/nUCo7Bh0IRRI7F9DHR06kJNWtzMGLIbXavngbKA==", + "requires": { + "jsbn": "1.1.0", + "sprintf-js": "1.1.2" + }, + "dependencies": { + "sprintf-js": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", + "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==" + } + } + }, "ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -56351,6 +56390,11 @@ } } }, + "jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" + }, "jsdom": { "version": "16.7.0", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", @@ -64872,6 +64916,7 @@ "generate-password": "^1.7.0", "html-to-image": "^1.10.8", "immutability-helper": "^3.1.1", + "ip-address": "^8.1.0", "jest": "^27.4.0", "jest-canvas-mock": "^2.3.1", "json-logic-js": "^2.0.2", diff --git a/packages/ui/src/components/Alert/Alert.tsx b/packages/ui/src/components/Alert/Alert.tsx index d5736e07a82..2ab878f6611 100644 --- a/packages/ui/src/components/Alert/Alert.tsx +++ b/packages/ui/src/components/Alert/Alert.tsx @@ -13,7 +13,7 @@ import { IconCheckCircle } from '../Icon/icons/IconCheckCircle' interface Props { variant?: 'success' | 'danger' | 'warning' | 'info' | 'neutral' className?: string - title: string + title: string | React.ReactNode withIcon?: boolean closable?: boolean children?: React.ReactNode @@ -21,10 +21,7 @@ interface Props { actions?: React.ReactNode } -const icons: Record< - 'success' | 'danger' | 'warning' | 'info' | 'neutral', - React.ReactElement -> = { +const icons: Record<'success' | 'danger' | 'warning' | 'info' | 'neutral', React.ReactElement> = { danger: , success: , warning: , @@ -51,10 +48,7 @@ function Alert({ if (className) containerClasses.push(className) - let descriptionClasses = [ - __styles.description, - __styles.variant[variant].description, - ] + let descriptionClasses = [__styles.description, __styles.variant[variant].description] let closeButtonClasses = [__styles.close] return ( @@ -62,19 +56,12 @@ function Alert({ {visible && (
{withIcon ? ( -
- {withIcon && icons[variant]} -
+
{withIcon && icons[variant]}
) : null} {icon && icon}
-

+

{title}

{children}
diff --git a/packages/ui/src/components/Input/Input.tsx b/packages/ui/src/components/Input/Input.tsx index a1caec8de16..a78fade9330 100644 --- a/packages/ui/src/components/Input/Input.tsx +++ b/packages/ui/src/components/Input/Input.tsx @@ -97,7 +97,7 @@ function Input({ // }, [errors, touched]) function onCopy(value: any) { - navigator.clipboard.writeText(value).then( + navigator.clipboard.writeText(value)?.then( function () { /* clipboard successfully set */ setCopyLabel('Copied') diff --git a/spec/common-client-libs-sections.json b/spec/common-client-libs-sections.json index 79489c67aea..c48f87c6b99 100644 --- a/spec/common-client-libs-sections.json +++ b/spec/common-client-libs-sections.json @@ -28,7 +28,8 @@ "reference_javascript_v1", "reference_dart_v0", "reference_dart_v1", - "reference_python_v2" + "reference_python_v2", + "reference_csharp_v0" ] }, { @@ -36,7 +37,12 @@ "id": "upgrade-guide", "slug": "upgrade-guide", "type": "markdown", - "excludes": ["reference_javascript_v2", "reference_dart_v1", "reference_python_v2"] + "excludes": [ + "reference_javascript_v2", + "reference_dart_v1", + "reference_python_v2", + "reference_csharp_v0" + ] }, { "title": "Database", @@ -842,7 +848,6 @@ { "title": "Misc", "excludes": ["reference_dart_v1", "reference_dart_v0"], - "items": [ { "title": "Release Notes", diff --git a/spec/supabase_csharp_v0.yml b/spec/supabase_csharp_v0.yml index 4291bdfbb95..9f2c963685b 100644 --- a/spec/supabase_csharp_v0.yml +++ b/spec/supabase_csharp_v0.yml @@ -13,7 +13,7 @@ info: libraries: - name: 'C#' id: 'csharp' - version: '0.0.1' + version: '0.8.0' functions: - id: sign-up @@ -283,7 +283,9 @@ functions: Performs vertical filtering with SELECT. title: 'Fetch data: Select()' notes: | - - **Column Names must match names in database, not names specified on model properties.** + - **LINQ expressions do not currently support parsing embedded resource columns. For these cases, `string` will need to be used.** + - **When using string Column Names to select, they must match names in database, not names specified on model properties.** + - Additional information on modeling + querying Joins and Inner Joins can be found [in the `postgrest-csharp README`](https://github.com/supabase-community/postgrest-csharp/blob/master/README.md#foreign-keys-join-tables-and-relationships) - By default, Supabase projects will return a maximum of 1,000 rows. This setting can be changed in Project API Settings. It's recommended that you keep it low to limit the payload size of accidental or malicious requests. You can use `range()` queries to paginate through your data. - `From()` can be combined with [Modifiers](/docs/reference/csharp/using-modifiers) - `From()` can be combined with [Filters](/docs/reference/csharp/using-filters) @@ -294,6 +296,23 @@ functions: isSpotlight: true code: | ```c# + // Given the following Model (City.cs) + [Table("cities")] + class City : BaseModel + { + [PrimaryKey("id")] + public int Id { get; set; } + + [Column("name")] + public string Name { get; set; } + + [Column("country_id")] + public int CountryId { get; set; } + + //... etc. + } + + // A result can be fetched like so. var result = await supabase.From().Get(); var cities = result.Models ``` @@ -302,6 +321,23 @@ functions: description: You can select specific fields from your tables. code: | ```c# + // Given the following Model (Movie.cs) + [Table("movies")] + class Movie : BaseModel + { + [PrimaryKey("id")] + public int Id { get; set; } + + [Column("name")] + public string Name { get; set; } + + [Column("created_at")] + public DateTime CreatedAt { get; set; } + + //... etc. + } + + // A result can be fetched like so. var result = await supabase .From() .Select(x => new object[] {x.Name, x.CreatedAt}) @@ -313,7 +349,7 @@ functions: code: | ```c# var data = await supabase - .From() + .From() .Select("id, supplier:supplier_id(name), purchaser:purchaser_id(name)") .Get(); ``` @@ -432,14 +468,14 @@ functions: - `Update()` is typically called using a model as an argument or from a hydrated model. examples: - id: updating-your-data-with-filter - name: Update your data + name: Update your data using Filter isSpotlight: true code: | ```c# var update = await supabase .From() .Where(x => x.Name == "Auckland") - .Set(x => new KeyValuePair(x.Name, "Middle Earth")) + .Set(x => x.Name, "Middle Earth") .Update(); ``` - id: updating-your-data @@ -556,13 +592,68 @@ functions: ``` - id: subscribe + title: 'Realtime.Channel' description: | Subscribe to realtime changes in your database. - title: 'From().On()' notes: | - Realtime is disabled by default for new Projects for better database performance and security. You can turn it on by [managing replication](/docs/guides/api#managing-realtime). - If you want to receive the "previous" data for updates and deletes, you will need to set `REPLICA IDENTITY` to `FULL`, like this: `ALTER TABLE your_table REPLICA IDENTITY FULL;` examples: + - id: listen-to-broadcast + name: Listen to broadcast messages + isSpotlight: true + code: | + ```c# + class CursorBroadcast : BaseBroadcast + { + [JsonProperty("cursorX")] + public int CursorX {get; set;} + + [JsonProperty("cursorY")] + public int CursorY {get; set;} + } + + var channel = supabase.Realtime.Channel("any"); + var broadcast = channel.Register(); + + broadcast.OnBroadcast += (sender, _) => + { + var data = broadcast.Current(); + }; + + await channel.Subscribe(); + + // Send a broadcast + await broadcast.Send("cursor", new CursorBroadcast { CursorX = 123, CursorY = 456 }); + ``` + - id: listen-to-presence-sync + name: Listen to presence sync + isSpotlight: true + code: | + ```c# + class UserPresence : BasePresence + { + [JsonProperty("cursorX")] + public bool IsTyping {get; set;} + + [JsonProperty("onlineAt")] + public DateTime OnlineAt {get; set;} + } + + var channel = supabase.Realtime.Channel("any"); + var presenceKey = Guid.NewGuid().ToString(); + var presence = channel.Register(presenceKey); + + presence.OnSync += (sender, _) => + { + var state = presence.CurrentState; + }; + + await channel.Subscribe(); + + // Send a presence update + await presence.Track(new UserPresence { IsTyping = false, OnlineAt = DateTime.Now }); + ``` - id: listening-to-a-specific-table name: Listening to a specific table isSpotlight: true @@ -570,7 +661,7 @@ functions: ```c# await supabase.From().On(ChannelEventType.All, (sender, args) => { - Debug.WriteLine(args.Response.Payload); + Debug.WriteLine(args.Response.Payload.Data); }); ``` - id: listen-to-all-database-changes @@ -579,9 +670,9 @@ functions: ```c# var channel = supabase.Realtime.Channel("realtime", "public", "*"); - channel.OnMessage += (sender, args) => + channel.OnPostgresChanges += (sender, args) => { - Debug.WriteLine(args.Response.Payload); + Debug.WriteLine(args.Response.Payload.Data); }; await channel.Subscribe(); @@ -592,7 +683,7 @@ functions: ```c# await supabase.From().On(ChannelEventType.Insert, (sender, args) => { - Debug.WriteLine(args.Response.Payload); + Debug.WriteLine(args.Response.Payload.Data); }); ``` - id: listening-to-updates @@ -608,7 +699,7 @@ functions: ```c# await supabase.From().On(ChannelEventType.Update, (sender, args) => { - Debug.WriteLine(args.Response.Payload); + Debug.WriteLine(args.Response.Payload.Data); }); ``` - id: listening-to-deletes @@ -624,7 +715,7 @@ functions: ```c# await supabase.From().On(ChannelEventType.Delete, (sender, args) => { - Debug.WriteLine(args.Response.Payload); + Debug.WriteLine(args.Response.Payload.Data); }); ``` - id: listening-to-row-level-changes @@ -676,93 +767,6 @@ functions: var channels = supabase.Realtime.Subscriptions; ``` - # - id: stream - # description: | - # Notifies of data at the queried table. - # title: 'stream()' - # notes: | - # - `stream()` will emit the initial data as well as any further change on the database as `Stream` of `List>` by combining Postgrest and Realtime. - # - Takes a list of primary key columns as its argument. - # examples: - # - id: listening-to-a-specific-table - # name: Listening to a specific table - # isSpotlight: true - # code: | - # ```dart - # supabase.from('countries') - # .stream(primaryKey: ['id']) - # .listen((List> data) { - # // Do something awesome with the data - # }); - # ``` - # - id: listening-to-a-specific-rows-within-a-table - # name: Listening to a specific rows within a table - # description: | - # You can listen to individual rows using the format `{table}:{col}=eq.{val}` - where `{col}` is the column name, and `{val}` is the value which you want to match. - # This syntax is the as how you can filter data in Realtime - # code: | - # ```dart - # supabase.from('countries') - # .stream(primaryKey: ['id']) - # .eq('id', '120') - # .listen((List> data) { - # // Do something awesome with the data - # }); - # ``` - # - id: with-order - # name: With `order()` - # code: | - # ```dart - # supabase.from('countries') - # .stream(primaryKey: ['id']) - # .order('name', ascending: true) - # .listen((List> data) { - # // Do something awesome with the data - # }); - # ``` - # - id: with-limit - # name: With `limit()` - # code: | - # ```dart - # supabase.from('countries') - # .stream(primaryKey: ['id']) - # .order('name', ascending: true) - # .limit(10) - # .listen((List> data) { - # // Do something awesome with the data - # }); - # ``` - # - id: using-stream-with-stream-builder - # name: Using `stream()` with `StreamBuilder` - # description: | - # When using `stream()` with a `StreamBuilder` within your Flutter application, make sure to store your stream in a variable to prevent refetching upon rebuilding. - # code: | - # ```dart - # final supabase = Supabase.instance.client; - - # class MyWidget extends StatefulWidget { - # const MyWidget({Key? key}) : super(key: key); - - # @override - # State createState() => _MyWidgetState(); - # } - - # class _MyWidgetState extends State { - # // Persist the stream in a local variable to prevent refetching upon rebuilds - # final _stream = supabase.from('countries').stream(primaryKey: ['id']); - - # @override - # Widget build(BuildContext context) { - # return StreamBuilder( - # stream: _stream, - # builder: (context, snapshot) { - # // Return your widget with the data from the snapshot - # }, - # ); - # } - # } - # ``` - - id: list-buckets description: | Retrieves the details of all Storage buckets within an existing product. @@ -1043,7 +1047,7 @@ functions: code: | ```c# var result = await supabase.From() - .Select("name, country_id") + .Select(x => new object[] { x.Name, x.CountryId }) .Limit(10) .Get(); ``` @@ -1069,8 +1073,8 @@ functions: code: | ```c# var result = await supabase.From() - .Select("name, country_id") - .Order("id", Ordering.Descending) + .Select(x => new object[] { x.Name, x.CountryId }) + .Order(x => x.Id, Ordering.Descending) .Get(); ``` - id: with-embedded-resources @@ -1079,7 +1083,7 @@ functions: ```c# var result = await supabase.From() .Select("name, cities(name)") - .Filter("name", Operator.Equals, "United States") + .Filter(x => x.Name == "United States") .Order("cities", "name", Ordering.Descending) .Get(); ``` @@ -1111,7 +1115,7 @@ functions: code: | ```c# var result = await supabase.From() - .Select("name, country_id") + .Select(x => new object[] { x.Name, x.CountryId }) .Offset(25) .Get(); ``` @@ -1127,7 +1131,7 @@ functions: code: | ```c# var result = await supabase.From() - .Select("name, country_id") + .Select(x => new object[] { x.Name, x.CountryId }) .Single(); ``` @@ -1138,19 +1142,15 @@ functions: Filters can be used on `Select()`, `Update()`, and `Delete()` queries. - If a Database function returns a table response, you can also apply filters. + **Note: LINQ expressions do not currently support parsing embedded resource columns. For these cases, `string` will need to be used.** examples: - id: applying-filters name: Applying Filters - description: | - Filters must be applied after any of `Select()`, `Update()`, `Upsert()`, - `Delete()`, and `rpc()` and before - [modifiers](/docs/reference/csharp/using-modifiers). code: | ```c# var result = await supabase.From() - .Select("name, country_id") - .Filter("name", Operator.Equals, "The Shire") + .Select(x => new object[] { x.Name, x.CountryId }) + .Where(x => x.Name == "The Shire") .Single(); ``` - id: filter-by-value-within-json-column @@ -1171,26 +1171,9 @@ functions: (1, 'Michael', '{ "postcode": 90210 }'), (2, 'Jane', null); ``` - response: | - ```json - { - "data": [ - { - "id": 1, - "name": "Michael", - "address": { - "postcode": 90210 - } - } - ], - "status": 200, - "statusText": "OK" - } - ``` code: | ```c# var result = await supabase.From() - .Select("name, country_id") .Filter("address->postcode", Operator.Equals, 90210) .Get(); ``` @@ -1203,46 +1186,7 @@ functions: .Filter("cities.name", Operator.Equals, "Bali") .Get(); ``` - data: - sql: | - ```sql - create table - countries (id int8 primary key, name text); - create table - cities ( - id int8 primary key, - country_id int8 not null references countries, - name text - ); - insert into - countries (id, name) - values - (1, 'Germany'), - (2, 'Indonesia'); - insert into - cities (id, country_id, name) - values - (1, 2, 'Bali'), - (2, 1, 'Munich'); - ``` - response: | - ```json - { - "data": [ - { - "name": "Indonesia", - "cities": [ - { - "name": "Bali" - } - ] - } - ], - "status": 200, - "statusText": "OK" - } - ``` - id: or title: Or() description: | @@ -1253,33 +1197,17 @@ functions: isSpotlight: true code: | ```c# - var filters = new List - { - new QueryFilter("id", Operator.Equals, 20), - new QueryFilter("id", Operator.Equals, 30) - }; - var result = await supabase.From() - .Or(filters) + .Where(x => x.Id == 20 || x.Id == 30) .Get(); ``` - id: use-or-with-and name: Use `or` with `and` code: | ```c# - var filters = new List - { - new QueryFilter("id", Operator.Equals, 20), - new QueryFilter("id", Operator.Equals, 30), - new QueryFilter(Operator.And, new List - { - new QueryFilter("name", Operator.Equals, "New Zealand"), - new QueryFilter("name", Operator.Equals, "France") - }) - }; - var result = await supabase.From() - .Or(filters) + .Where(x => x.Population > 300000 || x.BirthRate < 0.6) + .Where(x => x.Name != "Mordor") .Get(); ``` @@ -1294,8 +1222,8 @@ functions: code: | ```c# var result = await supabase.From() - .Select("id, name") - .Not("name", Operator.Equals, "Paris") + .Select(x => new object[] { x.Name, x.CountryId }) + .Where(x => x.Name != "Paris") .Get(); ``` @@ -1342,17 +1270,9 @@ functions: code: | ```c# var result = await supabase.From() - .Filter("name", Operator.Equals, "Bali") + .Where(x => x.Name == "Bali") .Get(); ``` - - id: with-delete - name: With `Delete()` - code: | - ```dart - await supabase.From() - .Filter("name", Operator.Equals, "Bali") - .Delete(); - ``` - id: neq title: Operator.NotEqual @@ -1365,18 +1285,10 @@ functions: code: | ```c# var result = await supabase.From() - .Select("name, country_id") - .Filter("name", Operator.NotEqual, "Bali") + .Select(x => new object[] { x.Name, x.CountryId }) + .Where(x => x.Name != "Bali") .Get(); ``` - - id: with-delete - name: With `Delete()` - code: | - ```c# - await supabase.From() - .Filter("name", Operator.NotEqual, "Bali") - .Delete(); - ``` - id: gt title: Operator.GreaterThan @@ -1389,18 +1301,10 @@ functions: code: | ```c# var result = await supabase.From() - .Select("name, country_id") - .Filter("country_id", Operator.GreaterThan, 250) + .Select(x => new object[] { x.Name, x.CountryId }) + .Where(x => x.CountryId > 250) .Get(); ``` - - id: with-delete - name: With `Delete()` - code: | - ```c# - await supabase.From() - .Filter("country_id", Operator.GreaterThan, 250) - .Delete(); - ``` - id: gte title: Operator.GreaterThanOrEqual @@ -1413,18 +1317,10 @@ functions: code: | ```c# var result = await supabase.From() - .Select("name, country_id") - .Filter("country_id", Operator.GreaterThanOrEqual, 250) + .Select(x => new object[] { x.Name, x.CountryId }) + .Where(x => x.CountryId >= 250) .Get(); ``` - - id: with-delete - name: With `Delete()` - code: | - ```c# - await supabase.From() - .Filter("country_id", Operator.GreaterThanOrEqual, 250) - .Delete(); - ``` - id: lt title: Operator.LessThan @@ -1438,23 +1334,14 @@ functions: ```c# var result = await supabase.From() .Select("name, country_id") - .Filter("country_id", Operator.LessThan, 250) + .Where(x => x.CountryId < 250) .Get(); ``` - - id: with-delete - name: With `Delete()` - code: | - ```c# - await supabase.From() - .Filter("country_id", Operator.LessThan, 250) - .Delete(); - ``` - id: lte title: Operator.LessThanOrEqual description: | Finds all rows whose value on the stated `column` is less than or equal to the specified `value`. - $ref: '@supabase/postgrest-js.PostgrestFilterBuilder.lte' examples: - id: with-select name: With `Select()` @@ -1462,18 +1349,9 @@ functions: code: | ```c# var result = await supabase.From() - .Select("name, country_id") - .Filter("country_id", Operator.LessThanOrEqual, 250) + .Where(x => x.CountryId <= 250) .Get(); ``` - - id: with-delete - name: With `Delete()` - code: | - ```c# - await supabase.From() - .Filter("country_id", Operator.LessThanOrEqual, 250) - .Delete(); - ``` - id: like title: Operator.Like @@ -1486,18 +1364,9 @@ functions: code: | ```c# var result = await supabase.From() - .Select("name, country_id") - .Filter("name", Operator.Like, "%la%") + .Filter(x => x.Name, Operator.Like, "%la%") .Get(); ``` - - id: with-delete - name: With `Delete()` - code: | - ```c# - await supabase.From() - .Filter("name", Operator.Like, "%la%") - .Delete(); - ``` - id: ilike title: Operator.ILike @@ -1510,16 +1379,8 @@ functions: code: | ```c# await supabase.From() - .Filter("name", Operator.ILike, "%la%") - .Delete(); - ``` - - id: with-delete - name: With `Delete()` - code: | - ```c# - await supabase.From() - .Filter("name", Operator.ILike, "%la%") - .Delete(); + .Filter(x => x.Name, Operator.ILike, "%la%") + .Get(); ``` - id: is @@ -1533,18 +1394,9 @@ functions: code: | ```c# var result = await supabase.From() - .Select("name, country_id") - .Filter("name", Operator.Is, null) + .Where(x => x.Name == null .Get(); ``` - - id: with-delete - name: With `Delete()` - code: | - ```c# - await supabase.From() - .Filter("name", Operator.Is, null) - .Delete(); - ``` - id: in title: Operator.In @@ -1557,18 +1409,9 @@ functions: code: | ```c# var result = await supabase.From() - .Select("name, country_id") - .Filter("name", Operator.In, new List { "Rio de Janiero", "San Francisco" }) + .Filter(x => x.Name, Operator.In, new List { "Rio de Janiero", "San Francisco" }) .Get(); ``` - - id: with-delete - name: With `Delete()` - code: | - ```c# - await supabase.From() - .Filter("name", Operator.In, new List { "Rio de Janiero", "San Francisco" }) - .Delete(); - ``` - id: contains title: Operator.Contains @@ -1579,18 +1422,9 @@ functions: code: | ```c# var result = await supabase.From() - .Select("name, country_id, main_exports") - .Filter("main_exports", Operator.Contains, new List { "oil", "fish" }) + .Filter(x => x.MainExports, Operator.Contains, new List { "oil", "fish" }) .Get(); ``` - - id: with-delete - name: With `Delete()` - code: | - ```c# - await supabase.From() - .Filter("main_exports", Operator.Contains, new List { "oil", "fish" }) - .Delete(); - ``` - id: contained-by title: Operator.ContainedIn @@ -1601,18 +1435,9 @@ functions: code: | ```c# var result = await supabase.From() - .Select("name, country_id, main_exports") - .Filter("main_exports", Operator.ContainedIn, new List { "oil", "fish" }) + .Filter(x => x.MainExports, Operator.ContainedIn, new List { "oil", "fish" }) .Get(); ``` - - id: with-delete - name: With `Delete()` - code: | - ```c# - await supabase.From() - .Filter("main_exports", Operator.ContainedIn, new List { "oil", "fish" }) - .Delete(); - ``` - id: text-search title: Operator.[FTS,PLFTS,PHFTS,WFTS] (Full Text Search) @@ -1624,8 +1449,8 @@ functions: code: | ```c# var result = await supabase.From() - .Select("catchphrase") - .Filter("catchphrase", Operator.FTS, new FullTextSearchConfig("'fat' & 'cat", "english")) + .Select(x => x.Catchphrase) + .Filter(x => x.Catchphrase, Operator.FTS, new FullTextSearchConfig("'fat' & 'cat", "english")) .Get(); ``` - id: basic-normalization @@ -1634,8 +1459,8 @@ functions: code: | ```c# var result = await supabase.From() - .Select("catchphrase") - .Filter("catchphrase", Operator.PLFTS, new FullTextSearchConfig("'fat' & 'cat", "english")) + .Select(x => x.Catchphrase) + .Filter(x => x.Catchphrase, Operator.PLFTS, new FullTextSearchConfig("'fat' & 'cat", "english")) .Get(); ``` - id: full-normalization @@ -1644,8 +1469,8 @@ functions: code: | ```c# var result = await supabase.From() - .Select("catchphrase") - .Filter("catchphrase", Operator.PHFTS, new FullTextSearchConfig("'fat' & 'cat", "english")) + .Select(x => x.Catchphrase) + .Filter(x => x.Catchphrase, Operator.PHFTS, new FullTextSearchConfig("'fat' & 'cat", "english")) .Get(); ``` - id: web-search @@ -1663,7 +1488,7 @@ functions: code: | ```c# var result = await supabase.From() - .Select("catchphrase") - .Filter("catchphrase", Operator.WFTS, new FullTextSearchConfig("'fat' & 'cat", "english")) + .Select(x => x.Catchphrase) + .Filter(x => x.Catchphrase, Operator.WFTS, new FullTextSearchConfig("'fat' & 'cat", "english")) .Get(); ``` diff --git a/studio/components/interfaces/Billing/Subscription/Subscription.tsx b/studio/components/interfaces/Billing/Subscription/Subscription.tsx index aa49e690d66..f055eaa1bbc 100644 --- a/studio/components/interfaces/Billing/Subscription/Subscription.tsx +++ b/studio/components/interfaces/Billing/Subscription/Subscription.tsx @@ -62,7 +62,7 @@ const Subscription: FC = ({ ? 0 : Object.keys(usage) .map((productKey) => { - return usage[productKey].cost + return usage[productKey].cost ?? 0 }) .reduce((prev, current) => prev + current, 0) diff --git a/studio/components/interfaces/Database/index.ts b/studio/components/interfaces/Database/index.ts index d866b7d8c8c..94c664c2854 100644 --- a/studio/components/interfaces/Database/index.ts +++ b/studio/components/interfaces/Database/index.ts @@ -15,7 +15,7 @@ import PublicationsTables from './Publications/PublicationsTables' import BackupsList from './Backups/BackupsList' -import ConnectionPooling from './Pooling/ConnectionPooling' +import ConnectionPooling from '../Settings/Database/ConnectionPooling' import CreateTrigger from './Triggers/CreateTrigger' import DeleteTrigger from './Triggers/DeleteTrigger' diff --git a/studio/components/interfaces/Home/Home.constants.ts b/studio/components/interfaces/Home/Home.constants.ts index d7e8d778540..0bbcef3fcda 100644 --- a/studio/components/interfaces/Home/Home.constants.ts +++ b/studio/components/interfaces/Home/Home.constants.ts @@ -10,7 +10,7 @@ export const CLIENT_LIBRARIES = [ language: 'Python', officialSupport: false, releaseState: 'Alpha', - docsUrl: undefined, + docsUrl: 'https://supabase.com/docs/reference/python/introduction', gitUrl: 'https://github.com/supabase/supabase-py', }, { diff --git a/studio/components/interfaces/Organization/BillingSettings/PaymentMethods.tsx b/studio/components/interfaces/Organization/BillingSettings/PaymentMethods.tsx index fd357010b70..799445695b1 100644 --- a/studio/components/interfaces/Organization/BillingSettings/PaymentMethods.tsx +++ b/studio/components/interfaces/Organization/BillingSettings/PaymentMethods.tsx @@ -104,7 +104,12 @@ const PaymentMethods: FC = ({

Payment methods

-

Charges will be deducted from the default card

+

+ When adding a new payment method, either remove the old one or go to your projects' + subscription to explicitly update the payment method. Marking a payment method as + "default" is only relevant for new projects or if there are no other payment methods on + your account. +

{!canReadPaymentMethods ? ( diff --git a/studio/components/interfaces/Organization/TeamSettings/InviteMemberButton.tsx b/studio/components/interfaces/Organization/TeamSettings/InviteMemberButton.tsx index 1d5e2c86b1a..f18b914c361 100644 --- a/studio/components/interfaces/Organization/TeamSettings/InviteMemberButton.tsx +++ b/studio/components/interfaces/Organization/TeamSettings/InviteMemberButton.tsx @@ -130,8 +130,14 @@ const InviteMemberButton: FC = ({ user, members = [], roles = [], rolesAd useEffect(() => { if (roles) { resetForm({ - values: { ...initialValues, role: roles[0].id }, - initialValues: { ...initialValues, role: roles[0].id }, + values: { + ...initialValues, + role: roles.find((role) => role.name === 'Developer')?.id, + }, + initialValues: { + ...initialValues, + role: roles.find((role) => role.name === 'Developer')?.id, + }, }) } }, [roles]) @@ -147,7 +153,7 @@ const InviteMemberButton: FC = ({ user, members = [], roles = [], rolesAd
{roles && ( +
+
+ +

CIDR Block Size

+ + + + + + +
+ + Classless inter-domain routing (CIDR) notation is the notation + used to identify networks and hosts in the networks. The block + size tells us how many bits we need to take for the network + prefix, and is a value between 0 to 32. + +
+
+
+
+ } + id="cidrBlockSize" + name="cidrBlockSize" + type="number" + placeholder="0" + min={0} + max={32} + /> +
+ + + + + + {isValidCIDR ? ( + +
+

+ The address range{' '} + + {values.ipAddress}/{values.cidrBlockSize} + {' '} + will be restricted +

+

+ Selected address space: {addressRange.start}{' '} + to {addressRange.end}{' '} +

+

+ Number of addresses: {availableAddresses} +

+
+
+ ) : ( + +
+
+

+ A summary of your restriction will be shown here after entering a valid IP + address and CIDR block size +

+
+
+
+ )} +
+ + +
+ + ) + }} + + + ) +} + +export default AddRestrictionModal diff --git a/studio/components/interfaces/Settings/Database/NetworkRestrictions/AllowAllModal.tsx b/studio/components/interfaces/Settings/Database/NetworkRestrictions/AllowAllModal.tsx new file mode 100644 index 00000000000..084d12c3d89 --- /dev/null +++ b/studio/components/interfaces/Settings/Database/NetworkRestrictions/AllowAllModal.tsx @@ -0,0 +1,63 @@ +import { FC, useState } from 'react' +import { Modal, Button } from 'ui' + +import { useStore, useParams } from 'hooks' +import { useNetworkRestrictionsApplyMutation } from 'data/network-restrictions/network-retrictions-apply-mutation' + +interface Props { + visible: boolean + onClose: () => void +} + +const AllowAllModal: FC = ({ visible, onClose }) => { + const { ui } = useStore() + const { ref } = useParams() + const [isSubmitting, setIsSubmitting] = useState(false) + const { mutateAsync: applyNetworkRestrictions } = useNetworkRestrictionsApplyMutation() + + const onSubmit = async () => { + if (!ref) return console.error('Project ref is required') + + setIsSubmitting(true) + try { + await applyNetworkRestrictions({ projectRef: ref, dbAllowedCidrs: ['0.0.0.0/0'] }) + onClose() + } catch (error: any) { + ui.setNotification({ + category: 'error', + message: `Failed to update restriction: ${error.message}`, + }) + } finally { + setIsSubmitting(false) + } + } + + return ( + + +
+

+ This will allow any IP address to access your project's database. Are you sure? +

+
+
+
+ + +
+
+ ) +} + +export default AllowAllModal diff --git a/studio/components/interfaces/Settings/Database/NetworkRestrictions/DisallowAllModal.tsx b/studio/components/interfaces/Settings/Database/NetworkRestrictions/DisallowAllModal.tsx new file mode 100644 index 00000000000..537d509515e --- /dev/null +++ b/studio/components/interfaces/Settings/Database/NetworkRestrictions/DisallowAllModal.tsx @@ -0,0 +1,71 @@ +import { FC, useState } from 'react' +import { Modal, Button } from 'ui' + +import { useStore, useParams } from 'hooks' +import { useNetworkRestrictionsApplyMutation } from 'data/network-restrictions/network-retrictions-apply-mutation' +import InformationBox from 'components/ui/InformationBox' + +interface Props { + visible: boolean + onClose: () => void +} + +const DisallowAllModal: FC = ({ visible, onClose }) => { + const { ui } = useStore() + const { ref } = useParams() + const [isSubmitting, setIsSubmitting] = useState(false) + const { mutateAsync: applyNetworkRestrictions } = useNetworkRestrictionsApplyMutation() + + const onSubmit = async () => { + if (!ref) return console.error('Project ref is required') + + setIsSubmitting(true) + try { + await applyNetworkRestrictions({ projectRef: ref, dbAllowedCidrs: ['127.0.0.1/32'] }) + onClose() + } catch (error: any) { + ui.setNotification({ + category: 'error', + message: `Failed to update restriction: ${error.message}`, + }) + } finally { + setIsSubmitting(false) + } + } + + return ( + + +
+

+ This will prevent any external IP addresses from accessing your project's database. Are + you sure? +

+ +
+
+
+ + +
+
+ ) +} + +export default DisallowAllModal diff --git a/studio/components/interfaces/Settings/Database/NetworkRestrictions/NetworkRestrictions.tsx b/studio/components/interfaces/Settings/Database/NetworkRestrictions/NetworkRestrictions.tsx new file mode 100644 index 00000000000..30539d96d4b --- /dev/null +++ b/studio/components/interfaces/Settings/Database/NetworkRestrictions/NetworkRestrictions.tsx @@ -0,0 +1,195 @@ +import Link from 'next/link' +import { useState } from 'react' +import { Button, IconAlertCircle, IconExternalLink, IconGlobe, IconLock } from 'ui' + +import { useParams } from 'hooks' +import Panel from 'components/ui/Panel' +import { FormPanel, FormHeader } from 'components/ui/Forms' +import ShimmeringLoader from 'components/ui/ShimmeringLoader' +import AddRestrictionModal from './AddRestrictionModal' +import RemoveRestrictionModal from './RemoveRestrictionModal' +import DisallowAllModal from './DisallowAllModal' +import AllowAllModal from './AllowAllModal' +import { useNetworkRestrictionsQuery } from 'data/network-restrictions/network-restrictions-query' + +const NetworkRestrictions = ({}) => { + const { ref } = useParams() + + const [isAddingAddress, setIsAddingAddress] = useState(false) + const [isAllowingAll, setIsAllowingAll] = useState(false) + const [isDisallowingAll, setIsDisallowingAll] = useState(false) + const [selectedRestrictionToRemove, setSelectedRestrictionToRemove] = useState() + const { data, isLoading } = useNetworkRestrictionsQuery({ projectRef: ref }) + + const hasAccessToRestrictions = data?.entitlement === 'allowed' + const restrictedIps = data?.config.dbAllowedCidrs ?? [] + const restrictionStatus = data?.status ?? '' + + const hasApplyError = restrictedIps.length === 0 && restrictionStatus === 'stored' + const isUninitialized = restrictedIps.length === 0 && restrictionStatus.length === 0 + const isAllowedAll = restrictedIps.includes('0.0.0.0/0') + const isDisallowedAll = restrictedIps.length === 1 && restrictedIps[0] === '127.0.0.1/32' + + if (!hasAccessToRestrictions) return <> + + return ( + <> +
+
+ +
+ + + + + + +
+
+ {isLoading ? ( + + +
+ + + +
+
+
+ ) : hasApplyError ? ( + + +
+
+
+ +

Your network restrictions were not applied correctly

+
+

+ Please try to add your network restrictions again +

+
+
+ + +
+
+
+
+ ) : ( + + {isUninitialized || isAllowedAll ? ( +
+
+
+

+ Your database can be accessed by all IP addresses +

+

+ You may start limiting access to your database by adding a network + restriction. +

+
+
+
+ +
+
+ ) : isDisallowedAll ? ( +
+
+ +
+

+ Your database cannot be + accessed externally +

+

+ All external IP addresses have been disallowed from accessing your project's + database. +

+

+ Note: Restrictions only apply to your database, and not to Supabase services +

+
+
+
+ +
+
+ ) : ( +
+
+
+

+ Only the following IP addresses have access to your database +

+

+ You may remove all of them to allow all IP addresses to have access to your + database +

+

+ Note: Restrictions only apply to your database, and not to Supabase services +

+
+
+ + +
+
+ {restrictedIps.map((ip) => { + return ( +
+
+ +

{ip}

+
+ +
+ ) + })} +
+ )} +
+ )} +
+ + setIsAllowingAll(false)} /> + setIsDisallowingAll(false)} /> + + setIsAddingAddress(false)} + /> + setSelectedRestrictionToRemove(undefined)} + /> + + ) +} + +export default NetworkRestrictions diff --git a/studio/components/interfaces/Settings/Database/NetworkRestrictions/NetworkRestrictions.utils.ts b/studio/components/interfaces/Settings/Database/NetworkRestrictions/NetworkRestrictions.utils.ts new file mode 100644 index 00000000000..36999d6bb0b --- /dev/null +++ b/studio/components/interfaces/Settings/Database/NetworkRestrictions/NetworkRestrictions.utils.ts @@ -0,0 +1,22 @@ +import { Address4 } from 'ip-address' + +const privateIPRanges = [ + new Address4('10.0.0.0/8'), + new Address4('172.16.0.0/12'), + new Address4('192.168.0.0/16'), +] + +export const checkIfPrivate = (cidr: string) => { + const address = new Address4(cidr) + const res = privateIPRanges.map((range) => address.isInSubnet(range)) + return res.includes(true) +} + +export const getAddressEndRange = (cidr: string) => { + try { + const address = new Address4(cidr) + return { start: address.startAddress().address, end: address.endAddress().address } + } catch (error: any) { + return undefined + } +} diff --git a/studio/components/interfaces/Settings/Database/NetworkRestrictions/RemoveRestrictionModal.tsx b/studio/components/interfaces/Settings/Database/NetworkRestrictions/RemoveRestrictionModal.tsx new file mode 100644 index 00000000000..cd2a4f7f654 --- /dev/null +++ b/studio/components/interfaces/Settings/Database/NetworkRestrictions/RemoveRestrictionModal.tsx @@ -0,0 +1,89 @@ +import { FC, useState } from 'react' +import { Alert, Button, Form, Input, Modal } from 'ui' + +import { useStore, useParams } from 'hooks' +import { useNetworkRestrictionsApplyMutation } from 'data/network-restrictions/network-retrictions-apply-mutation' + +interface Props { + visible: boolean + restrictedIps: string[] + selectedRestriction?: string + onClose: () => void +} + +const RemoveRestrictionModal: FC = ({ + visible, + restrictedIps, + selectedRestriction, + onClose, +}) => { + const { ui } = useStore() + const { ref } = useParams() + const [isSubmitting, setIsSubmitting] = useState(false) + const { mutateAsync: applyNetworkRestrictions } = useNetworkRestrictionsApplyMutation() + + const isRemovingOnlyRestriction = + restrictedIps.length === 1 && restrictedIps[0] === selectedRestriction + + const onSubmit = async () => { + if (!ref) return console.error('Project ref is required') + + setIsSubmitting(true) + const dbAllowedCidrs = restrictedIps.filter((ip) => ip !== selectedRestriction) + + try { + if (dbAllowedCidrs.length === 0) { + await applyNetworkRestrictions({ projectRef: ref, dbAllowedCidrs: ['0.0.0.0/0'] }) + } else { + await applyNetworkRestrictions({ projectRef: ref, dbAllowedCidrs }) + } + onClose() + } catch (error: any) { + ui.setNotification({ + category: 'error', + message: `Failed to remove restriction: ${error.message}`, + }) + } finally { + setIsSubmitting(false) + } + } + + return ( + + +
+

+ The IPv4 address {selectedRestriction} will be removed + from your list of network restrictions + {isRemovingOnlyRestriction + ? '.' + : ", and no longer have access to your project's database."} +

+ {isRemovingOnlyRestriction && ( + + Removing all network restrictions will default to your database being accessible from + all IP addresses. + + )} +
+
+
+ + +
+
+ ) +} + +export default RemoveRestrictionModal diff --git a/studio/components/interfaces/Settings/Database/index.ts b/studio/components/interfaces/Settings/Database/index.ts new file mode 100644 index 00000000000..7ca7e01bbd0 --- /dev/null +++ b/studio/components/interfaces/Settings/Database/index.ts @@ -0,0 +1,4 @@ +import ConnectionPooling from './ConnectionPooling' +import NetworkRestrictions from './NetworkRestrictions/NetworkRestrictions' + +export { ConnectionPooling, NetworkRestrictions } diff --git a/studio/components/interfaces/Settings/General/CustomDomainConfig/CustomDomainVerify.tsx b/studio/components/interfaces/Settings/General/CustomDomainConfig/CustomDomainVerify.tsx index 1314aaa2e03..3e6c8b0c35c 100644 --- a/studio/components/interfaces/Settings/General/CustomDomainConfig/CustomDomainVerify.tsx +++ b/studio/components/interfaces/Settings/General/CustomDomainConfig/CustomDomainVerify.tsx @@ -1,7 +1,7 @@ import Link from 'next/link' import { useState } from 'react' import { observer } from 'mobx-react-lite' -import { Button, IconAlertCircle, IconExternalLink, IconHelpCircle, IconRefreshCw } from 'ui' +import { Alert, Button, IconAlertCircle, IconExternalLink, IconHelpCircle, IconRefreshCw } from 'ui' import { useStore } from 'hooks' import { ProjectSettingsResponse } from 'data/config/project-settings-query' @@ -20,7 +20,6 @@ export type CustomDomainVerifyProps = { const CustomDomainVerify = ({ projectRef, customDomain, settings }: CustomDomainVerifyProps) => { const { ui } = useStore() - const [isNotVerifiedYet, setIsNotVerifiedYet] = useState(false) const { mutate: reverifyCustomDomain, isLoading: isReverifyLoading } = @@ -29,9 +28,13 @@ const CustomDomainVerify = ({ projectRef, customDomain, settings }: CustomDomain if (res.status === '2_initiated') setIsNotVerifiedYet(true) }, }) - const { mutateAsync: deleteCustomDomain, isLoading: isDeleting } = useCustomDomainDeleteMutation() + const hasCAAErrors = customDomain.ssl.validation_errors?.reduce( + (acc, error) => acc || error.message.includes('caa_error'), + false + ) + const onReverifyCustomDomain = () => { if (!projectRef) { throw new Error('Project ref is required') @@ -111,6 +114,18 @@ const CustomDomainVerify = ({ projectRef, customDomain, settings }: CustomDomain + {hasCAAErrors && ( + + Please add a CAA record allowing "digicert.com" to issue certificates for{' '} + {customDomain.hostname}. For example:{' '} + 0 issue "digitcert.com" + + )} +
diff --git a/studio/components/interfaces/Settings/General/index.ts b/studio/components/interfaces/Settings/General/index.ts index 108fd79d1a7..8ff5b36ea73 100644 --- a/studio/components/interfaces/Settings/General/index.ts +++ b/studio/components/interfaces/Settings/General/index.ts @@ -1,6 +1,7 @@ import General from './General' import Infrastructure from './Infrastructure/Infrastructure' +import CustomDomainConfig from './CustomDomainConfig/CustomDomainConfig' import DeleteProjectPanel from './DeleteProjectPanel/DeleteProjectPanel' import DeleteProjectButton from './DeleteProjectPanel/DeleteProjectButton' -export { General, Infrastructure, DeleteProjectPanel, DeleteProjectButton } +export { General, Infrastructure, CustomDomainConfig, DeleteProjectPanel, DeleteProjectButton } diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.tsx index 519141c7087..37d15fae541 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.tsx @@ -191,7 +191,7 @@ const ColumnEditor: FC = ({ label="Name" type="text" descriptionText="Recommended to use lowercase and use an underscore to separate words e.g. column_name" - placeholder='column_name' + placeholder="column_name" error={errors.name} value={columnFields?.name ?? ''} onChange={(event: any) => onUpdateField({ name: event.target.value })} @@ -224,7 +224,28 @@ const ColumnEditor: FC = ({ Data Type} + header={ + + + + + + } + > + Data Type + + } > = ({ enumTypes={enumTypes} error={errors.format} disabled={!isUndefined(columnFields?.foreignKey)} - description={ - - - - - - } onOptionSelect={(format: string) => onUpdateField({ format, defaultValue: null })} /> {isUndefined(columnFields.foreignKey) && ( diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnType.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnType.tsx index 7265c2976d1..3323ef8d2e9 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnType.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnType.tsx @@ -1,8 +1,25 @@ import React, { FC, ReactNode } from 'react' -import { IconCalendar, IconType, IconHash, Listbox, IconToggleRight, Input } from 'ui' +import { + IconCalendar, + IconType, + IconHash, + Listbox, + IconToggleRight, + Input, + Alert, + IconAlertCircle, + Button, + IconExternalLink, +} from 'ui' import type { PostgresType } from '@supabase/postgres-meta' -import { POSTGRES_DATA_TYPES, POSTGRES_DATA_TYPE_OPTIONS } from '../SidePanelEditor.constants' +import { + POSTGRES_DATA_TYPES, + POSTGRES_DATA_TYPE_OPTIONS, + RECOMMENDED_ALTERNATIVE_DATA_TYPE, +} from '../SidePanelEditor.constants' import { PostgresDataTypeOption } from '../SidePanelEditor.types' +import InformationBox from 'components/ui/InformationBox' +import Link from 'next/link' interface Props { value: string @@ -32,6 +49,7 @@ const ColumnType: FC = ({ // @ts-ignore const availableTypes = POSTGRES_DATA_TYPES.concat(enumTypes.map((type) => type.name)) const isAvailableType = value ? availableTypes.includes(value) : true + const recommendation = RECOMMENDED_ALTERNATIVE_DATA_TYPE[value] if (!isAvailableType) { return ( @@ -74,75 +92,106 @@ const ColumnType: FC = ({ } return ( - onOptionSelect(value)} - optionsWidth={480} - > - - --- - +
+ onOptionSelect(value)} + optionsWidth={480} + > + + --- + - {/* + {/* Weird issue with Listbox here 1. Can't do render conditionally (&&) within Listbox hence why using Fragment 2. Can't wrap these 2 components within a Fragment conditional (enumTypes.length) as selecting the enumType option will not render it in the Listbox component */} - {enumTypes.length > 0 ? ( - - User-defined Enumerated Types - - ) : ( - <> - )} + {enumTypes.length > 0 ? ( + + User-defined Enumerated Types + + ) : ( + <> + )} - {enumTypes.length > 0 ? ( - // @ts-ignore - enumTypes.map((enumType: PostgresType) => ( + {enumTypes.length > 0 ? ( + // @ts-ignore + enumTypes.map((enumType: PostgresType) => ( + { + return
+ }} + > +
+

{enumType.name}

+
+ + )) + ) : ( + <> + )} + + + PostgreSQL Data Types + + + {POSTGRES_DATA_TYPE_OPTIONS.map((option: PostgresDataTypeOption) => ( { - return
- }} + key={option.name} + value={option.name} + label={option.name} + addOnBefore={() => inferIcon(option.type)} >
-

{enumType.name}

+ {option.name} + {option.description}
- )) - ) : ( - <> - )} - - - PostgreSQL Data Types - - - {POSTGRES_DATA_TYPE_OPTIONS.map((option: PostgresDataTypeOption) => ( - inferIcon(option.type)} + ))} + + {recommendation !== undefined && ( + + It is recommended to use {recommendation.alternative}{' '} + instead + + } > -
- {option.name} - {option.description} +

+ Postgres recommends against using the data type {value}{' '} + unless you have a very specific use case. +

+
+ + + + + +
- - ))} - + + )} +
) } diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.constants.ts b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.constants.ts index 93d1de22237..585853061ee 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.constants.ts +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.constants.ts @@ -16,6 +16,29 @@ export const POSTGRES_DATA_TYPES = sortBy( concat(NUMERICAL_TYPES, JSON_TYPES, TEXT_TYPES, DATETIME_TYPES, OTHER_DATA_TYPES) ) +export const RECOMMENDED_ALTERNATIVE_DATA_TYPE: { + [key: string]: { alternative: string; reference: string } +} = { + varchar: { + alternative: 'text', + reference: + "https://wiki.postgresql.org/wiki/Don't_Do_This#Don.27t_use_varchar.28n.29_by_default", + }, + json: { + alternative: 'jsonb', + reference: 'https://www.postgresql.org/docs/current/datatype-json.html', + }, + timetz: { + alternative: 'timestamptz', + reference: "https://wiki.postgresql.org/wiki/Don't_Do_This#Don.27t_use_timetz", + }, + timestamp: { + alternative: 'timestamptz', + reference: + "https://wiki.postgresql.org/wiki/Don't_Do_This#Don.27t_use_timestamp_.28without_time_zone.29", + }, +} + export const POSTGRES_DATA_TYPE_OPTIONS: PostgresDataTypeOption[] = [ { name: 'int2', @@ -47,6 +70,11 @@ export const POSTGRES_DATA_TYPE_OPTIONS: PostgresDataTypeOption[] = [ description: 'Exact numeric of selectable precision', type: 'number', }, + { + name: 'json', + description: 'Textual JSON data', + type: 'json', + }, { name: 'jsonb', description: 'Binary JSON data, decomposed', @@ -57,6 +85,11 @@ export const POSTGRES_DATA_TYPE_OPTIONS: PostgresDataTypeOption[] = [ description: 'Variable-length character string', type: 'text', }, + { + name: 'varchar', + description: 'Variable-length character string', + type: 'text', + }, { name: 'uuid', description: 'Universally unique identifier', @@ -72,6 +105,16 @@ export const POSTGRES_DATA_TYPE_OPTIONS: PostgresDataTypeOption[] = [ description: 'Time of day (no time zone)', type: 'time', }, + { + name: 'timetz', + description: 'Time of day, including time zone', + type: 'time', + }, + { + name: 'timestamp', + description: 'Date and time (no time zone)', + type: 'time', + }, { name: 'timestamptz', description: 'Date and time, including time zone', diff --git a/studio/components/ui/Forms/FormSection.tsx b/studio/components/ui/Forms/FormSection.tsx index 7d98c189c5c..af4a88d593b 100644 --- a/studio/components/ui/Forms/FormSection.tsx +++ b/studio/components/ui/Forms/FormSection.tsx @@ -29,17 +29,28 @@ const FormSection = ({ } const FormSectionLabel = ({ - className = '', children, + className = '', + description, }: { - className?: string children: React.ReactNode | string + className?: string + description?: React.ReactNode }) => { - return ( - - ) + if (description !== undefined) { + return ( +
+ + {description} +
+ ) + } else { + return ( + + ) + } } const Shimmer = () => ( diff --git a/studio/data/custom-domains/custom-domains-query.ts b/studio/data/custom-domains/custom-domains-query.ts index 182e701a7da..8d5f4f2ea14 100644 --- a/studio/data/custom-domains/custom-domains-query.ts +++ b/studio/data/custom-domains/custom-domains-query.ts @@ -39,6 +39,9 @@ type Ssl = { txt_name: string txt_value: string }[] + validation_errors?: { + message: string + }[] } type OwnershipVerification = { diff --git a/studio/data/network-restrictions/keys.ts b/studio/data/network-restrictions/keys.ts new file mode 100644 index 00000000000..264e0497d4c --- /dev/null +++ b/studio/data/network-restrictions/keys.ts @@ -0,0 +1,4 @@ +export const networkRestrictionKeys = { + list: (projectRef: string | undefined) => + ['projects', projectRef, 'network-restrictions'] as const, +} diff --git a/studio/data/network-restrictions/network-restrictions-query.ts b/studio/data/network-restrictions/network-restrictions-query.ts new file mode 100644 index 00000000000..41b70733b8f --- /dev/null +++ b/studio/data/network-restrictions/network-restrictions-query.ts @@ -0,0 +1,75 @@ +import { useCallback } from 'react' +import { useQuery, useQueryClient, UseQueryOptions } from '@tanstack/react-query' + +import { networkRestrictionKeys } from './keys' +import { get } from 'lib/common/fetch' +import { API_ADMIN_URL } from 'lib/constants' + +export type NetworkRestrictionsVariables = { projectRef?: string } + +export type NetworkRestrictionsResponse = { + entitlement: 'disallowed' | 'allowed' + status: '' | 'stored' | 'applied' + config: { dbAllowedCidrs: string[] } + old_config?: { dbAllowedCidrs: string[] } + error?: any +} + +export async function getNetworkRestrictions( + { projectRef }: NetworkRestrictionsVariables, + signal?: AbortSignal +) { + if (!projectRef) throw new Error('projectRef is required') + + const response = (await get(`${API_ADMIN_URL}/projects/${projectRef}/network-restrictions`, { + signal, + })) as NetworkRestrictionsResponse + + // Not allowed error is a valid response to denote if a project + // has access to the network restrictions UI, so we'll handle it here + if (response.error) { + const isNotAllowedError = + (response.error as any)?.code === 400 && + (response.error as any)?.message?.includes('not allowed to set up network restrictions') + + if (isNotAllowedError) { + return { + entitlement: 'disallowed', + config: { dbAllowedCidrs: [] }, + status: '', + } as NetworkRestrictionsResponse + } else { + throw response.error + } + } + + return response +} + +export type NetworkRestrictionsData = Awaited> +export type NetworkRestrictionsError = unknown + +export const useNetworkRestrictionsQuery = ( + { projectRef }: NetworkRestrictionsVariables, + { + enabled = true, + ...options + }: UseQueryOptions = {} +) => + useQuery( + networkRestrictionKeys.list(projectRef), + ({ signal }) => getNetworkRestrictions({ projectRef }, signal), + { enabled: enabled && typeof projectRef !== 'undefined', ...options } + ) + +export const useNetworkRestrictionsPrefetch = ({ projectRef }: NetworkRestrictionsVariables) => { + const client = useQueryClient() + + return useCallback(() => { + if (projectRef) { + client.prefetchQuery(networkRestrictionKeys.list(projectRef), ({ signal }) => + getNetworkRestrictions({ projectRef }, signal) + ) + } + }, [projectRef]) +} diff --git a/studio/data/network-restrictions/network-retrictions-apply-mutation.ts b/studio/data/network-restrictions/network-retrictions-apply-mutation.ts new file mode 100644 index 00000000000..7321b5d9f64 --- /dev/null +++ b/studio/data/network-restrictions/network-retrictions-apply-mutation.ts @@ -0,0 +1,48 @@ +import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query' +import { post } from 'lib/common/fetch' +import { API_ADMIN_URL } from 'lib/constants' +import { networkRestrictionKeys } from './keys' + +export type NetworkRestrictionsApplyVariables = { + projectRef: string + dbAllowedCidrs: string[] +} + +export async function applyNetworkRestrictions({ + projectRef, + dbAllowedCidrs, +}: NetworkRestrictionsApplyVariables) { + if (!projectRef) throw new Error('projectRef is required') + + const response = await post( + `${API_ADMIN_URL}/projects/${projectRef}/network-restrictions/apply`, + { dbAllowedCidrs } + ) + if (response.error) throw response.error + + return response +} + +type NetworkRestrictionsApplyData = Awaited> + +export const useNetworkRestrictionsApplyMutation = ({ + onSuccess, + ...options +}: Omit< + UseMutationOptions, + 'mutationFn' +> = {}) => { + const queryClient = useQueryClient() + + return useMutation( + (vars) => applyNetworkRestrictions(vars), + { + async onSuccess(data, variables, context) { + const { projectRef } = variables + await queryClient.invalidateQueries(networkRestrictionKeys.list(projectRef)) + await onSuccess?.(data, variables, context) + }, + ...options, + } + ) +} diff --git a/studio/lib/constants/prism.ts b/studio/lib/constants/prism.ts index 47bf84c5a0d..473c542bcf3 100644 --- a/studio/lib/constants/prism.ts +++ b/studio/lib/constants/prism.ts @@ -29,7 +29,7 @@ export function dart(Prism: any) { className, { // variables and parameters - // this to support class names (or generic parameters) which do not contain a lower case letter (also works for methods) + // supports class names (or generic parameters) which do not contain a lower case letter (also works for methods) pattern: RegExp(packagePrefix + /[A-Z]\w*(?=\s+\w+\s*[;,=()])/.source), lookbehind: true, inside: className.inside, diff --git a/studio/lib/gotrue.ts b/studio/lib/gotrue.ts index e4c6383a150..3e6d7f258f6 100644 --- a/studio/lib/gotrue.ts +++ b/studio/lib/gotrue.ts @@ -39,7 +39,7 @@ export const getIdentity = (gotrueUser: User) => { } // NOTE: do not use any imports in this function, -// as it is use standalone in the documents head +// as it is used standalone in the documents head export const getReturnToPath = (fallback = '/projects') => { const searchParams = new URLSearchParams(location.search) let returnTo = searchParams.get('returnTo') ?? fallback diff --git a/studio/lib/helpers.ts b/studio/lib/helpers.ts index da727d7ec87..fa4fff11400 100644 --- a/studio/lib/helpers.ts +++ b/studio/lib/helpers.ts @@ -110,7 +110,7 @@ export const filterSensitiveProjectProps = (project: any) => { } /** - * Returns undefine if the string isn't parse-able + * Returns undefined if the string isn't parse-able */ export const tryParseInt = (str: string) => { try { @@ -120,7 +120,7 @@ export const tryParseInt = (str: string) => { } } -// Used as checker for memoised components +// Used as checker for memoized components export const propsAreEqual = (prevProps: any, nextProps: any) => { try { Object.keys(prevProps).forEach((key) => { diff --git a/studio/localStores/storageExplorer/StorageExplorerStore.js b/studio/localStores/storageExplorer/StorageExplorerStore.js index 6d75f5f36db..4596d565f6e 100644 --- a/studio/localStores/storageExplorer/StorageExplorerStore.js +++ b/studio/localStores/storageExplorer/StorageExplorerStore.js @@ -1485,7 +1485,7 @@ class StorageExplorerStore { .list(formattedPathToFolder, options) folderContents = folderContents.concat(data) options.offset += options.limit - if (data.length < options.limit) { + if ((data || []).length < options.limit) { break } } diff --git a/studio/package.json b/studio/package.json index 0e6a6fc37a7..1e7e8d7ea37 100644 --- a/studio/package.json +++ b/studio/package.json @@ -45,6 +45,7 @@ "generate-password": "^1.7.0", "html-to-image": "^1.10.8", "immutability-helper": "^3.1.1", + "ip-address": "^8.1.0", "json-logic-js": "^2.0.2", "lodash": "^4.17.21", "lottie-react": "^2.2.1", diff --git a/studio/pages/project/[ref]/settings/database.tsx b/studio/pages/project/[ref]/settings/database.tsx index 386132dec63..37f0b2aabab 100644 --- a/studio/pages/project/[ref]/settings/database.tsx +++ b/studio/pages/project/[ref]/settings/database.tsx @@ -1,7 +1,7 @@ import useSWR from 'swr' +import { toJS } from 'mobx' import * as Tooltip from '@radix-ui/react-tooltip' import { FC, useState, useRef, useEffect } from 'react' -import { toJS } from 'mobx' import { observer } from 'mobx-react-lite' import { useRouter } from 'next/router' import { debounce } from 'lodash' @@ -18,17 +18,20 @@ import { API_URL, DEFAULT_MINIMUM_PASSWORD_STRENGTH } from 'lib/constants' import { SettingsLayout } from 'components/layouts' import PasswordStrengthBar from 'components/ui/PasswordStrengthBar' import Panel from 'components/ui/Panel' -import ConnectionPooling from 'components/interfaces/Database/Pooling/ConnectionPooling' +import { ConnectionPooling, NetworkRestrictions } from 'components/interfaces/Settings/Database' const ProjectSettings: NextPageWithLayout = () => { const router = useRouter() const { ref } = router.query + const networkRestrictions = useFlag('networkRestrictions') + return ( -
+
+ {networkRestrictions && }
) diff --git a/studio/pages/project/[ref]/settings/general.tsx b/studio/pages/project/[ref]/settings/general.tsx index 118d21cb6fe..a1981c1e607 100644 --- a/studio/pages/project/[ref]/settings/general.tsx +++ b/studio/pages/project/[ref]/settings/general.tsx @@ -1,12 +1,18 @@ import { observer } from 'mobx-react-lite' + import { NextPageWithLayout } from 'types' import { SettingsLayout } from 'components/layouts' -import { General, Infrastructure, DeleteProjectPanel } from 'components/interfaces/Settings/General' -import CustomDomainConfig from 'components/interfaces/Settings/General/CustomDomainConfig/CustomDomainConfig' +import { + General, + Infrastructure, + CustomDomainConfig, + DeleteProjectPanel, +} from 'components/interfaces/Settings/General' const ProjectSettings: NextPageWithLayout = () => { + // [Joshen] Opting for larger gap instead of gap-8 as compared to other pages for better grouping of content return ( -
+
diff --git a/studio/pages/vercel/integrate.tsx b/studio/pages/vercel/integrate.tsx index 7da27b95c78..f986fdd8174 100644 --- a/studio/pages/vercel/integrate.tsx +++ b/studio/pages/vercel/integrate.tsx @@ -10,8 +10,7 @@ import Divider from 'components/ui/Divider' import { Dictionary } from 'components/grid' import { useStore, withAuth } from 'hooks' -import { API_URL, IS_PLATFORM } from 'lib/constants' -import { PROJECT_ENDPOINT_PROTOCOL } from 'pages/api/constants' +import { API_URL } from 'lib/constants' import { get } from 'lib/common/fetch' import { VERCEL_INTEGRATION_CONFIGS, @@ -370,8 +369,8 @@ const ProjectLinks: FC = observer(() => { // Then create env for vercel project with supabase project const vercelEnvs = prepareVercelEvns(defaultVercelEnvs, { - endpoint: `${projectDetails.apiService.protocol ?? 'https'}://${ - projectDetails.apiService.endpoint ?? '-' + endpoint: `${projectDetails.autoApiService.protocol ?? 'https'}://${ + projectDetails.autoApiService.endpoint ?? '-' }`, anon_key: projectDetails.autoApiService.defaultApiKey, service_key: projectDetails.autoApiService.serviceApiKey, diff --git a/studio/sentry.client.config.js b/studio/sentry.client.config.js index ccda8e0e31f..456f62c88a0 100644 --- a/studio/sentry.client.config.js +++ b/studio/sentry.client.config.js @@ -38,7 +38,7 @@ Sentry.init({ // the URL params instead of the store, but we cannot repro locally, staging nor on prod // Safe to ignore since it's not a user-facing issue + we've not received any user feedback/report about it // Ref: https://github.com/supabase/supabase/pull/9729 - 'The provided `href` (/org/[slug]/settings) value is missing query values (slug)', + 'The provided `href` (/org/[slug]/general) value is missing query values (slug)', ], beforeSend: (event) => filterConsoleErrors(event), }) diff --git a/studio/styles/grid.scss b/studio/styles/grid.scss index 7da30942b20..c1548ad0bfe 100644 --- a/studio/styles/grid.scss +++ b/studio/styles/grid.scss @@ -32,7 +32,7 @@ @apply border-scale-400 dark:border-scale-500 border-r-4 shadow-none; } -.rdg-cell-selected { +.rdg-cell[aria-selected='true'] { box-shadow: inset 0 0 0 1px #24b47e; }