diff --git a/apps/docs/components/Navigation/Navigation.constants.ts b/apps/docs/components/Navigation/Navigation.constants.ts index cd670c149ac..b0d769e6ea3 100644 --- a/apps/docs/components/Navigation/Navigation.constants.ts +++ b/apps/docs/components/Navigation/Navigation.constants.ts @@ -305,6 +305,7 @@ export const menuItems: NavMenu = { { name: 'Authsignal', url: '/guides/integrations/authsignal', items: [] }, { name: 'Clerk', url: '/guides/integrations/clerk', items: [] }, { name: 'keyri', url: '/guides/integrations/keyri', items: [] }, + { name: 'Picket', url: '/guides/integrations/picket', items: [] }, { name: 'Stytch', url: '/guides/integrations/stytch', items: [] }, { name: 'SuperTokens', url: '/guides/integrations/supertokens', items: [] }, ], diff --git a/apps/docs/components/reference/CLICommandSection.tsx b/apps/docs/components/reference/CLICommandSection.tsx index 9f685157482..f7e86190b4e 100644 --- a/apps/docs/components/reference/CLICommandSection.tsx +++ b/apps/docs/components/reference/CLICommandSection.tsx @@ -16,7 +16,7 @@ interface ICommonFunc { } interface IRefFunctionSection { - funcData: any + funcData: ICommonFunc commonFuncData: ICommonFunc spec: any } diff --git a/apps/docs/components/reference/RefFunctionSection.tsx b/apps/docs/components/reference/RefFunctionSection.tsx index 1c4e9668a23..59e0c0a6abd 100644 --- a/apps/docs/components/reference/RefFunctionSection.tsx +++ b/apps/docs/components/reference/RefFunctionSection.tsx @@ -10,22 +10,7 @@ import { extractTsDocNode, generateParameters } from '~/lib/refGenerator/helpers import RefDetailCollapse from '~/components/reference/RefDetailCollapse' import { Fragment } from 'react' - -interface ICommonFunc { - id: string - title: string - slug: string - product: string - libs: string - items: ICommonFunc[] -} - -interface IRefFunctionSection { - funcData: any - commonFuncData: ICommonFunc - spec: any - typeSpec?: any -} +import { IRefFunctionSection } from './Reference.types' const RefFunctionSection: React.FC = (props) => { const item = props.spec.functions.find((x: any) => x.id === props.funcData.id) diff --git a/apps/docs/components/reference/RefSectionHandler.tsx b/apps/docs/components/reference/RefSectionHandler.tsx index d54e5d616bb..76f1e0cc3e1 100644 --- a/apps/docs/components/reference/RefSectionHandler.tsx +++ b/apps/docs/components/reference/RefSectionHandler.tsx @@ -1,35 +1,31 @@ -import { MDXRemote } from 'next-mdx-remote' import { useRouter } from 'next/router' import { useEffect } from 'react' -import components from '~/components' import RefEducationSection from '~/components/reference/RefEducationSection' import RefFunctionSection from '~/components/reference/RefFunctionSection' import RefSubLayout from '~/layouts/ref/RefSubLayout' import ApiOperationSection from './ApiOperationSection' import CliCommandSection from './CLICommandSection' +import { IAPISpec, ICommonFunc, IRefStaticDoc, ISpec, TypeSpec } from './Reference.types' -interface Props { - sections: any[] // to do - spec: any // to do - typeSpec: any // to do - pageProps: any // to do, from staticProps - +interface RefSectionHandlerProps { + sections: ICommonFunc[] + spec?: ISpec | IAPISpec + typeSpec?: TypeSpec + pageProps: { docs: IRefStaticDoc[] } type: 'client-lib' | 'cli' | 'api' } -const RefSectionHandler = (props) => { +const RefSectionHandler = (props: RefSectionHandlerProps) => { const router = useRouter() const slug = router.query.slug[0] - const isNewDocs = process.env.NEXT_PUBLIC_NEW_DOCS === 'true' - // When user lands on a url like http://supabase.com/docs/reference/javascript/sign-up // find the #sign-up element and scroll to that useEffect(() => { - if (isNewDocs && document && slug !== 'start') { + if (document && slug !== 'start') { document.querySelector(`#${slug}`) && document.querySelector(`#${slug}`).scrollIntoView() } }) diff --git a/apps/docs/components/reference/Reference.types.ts b/apps/docs/components/reference/Reference.types.ts new file mode 100644 index 00000000000..04a9c04b7da --- /dev/null +++ b/apps/docs/components/reference/Reference.types.ts @@ -0,0 +1,109 @@ +import { enrichedOperation } from '~/lib/refGenerator/helpers' + +export interface ISpec { + openref: any + info: { + id: string + title: string + description: string + definition: string + libraries: any + slugPrefix: string + specUrl: string + } + functions: IFunctionDefinition[] +} + +export interface IAPISpec { + info: { + title: string + description?: string + version: string + contact?: {} + } + operations: enrichedOperation[] + sections: any +} + +export interface IFunctionDefinition { + title: string + id: string + $ref: string + description: string + examples?: [] +} + +export interface ICommonFunc { + id: string + title: string + slug: string + product: string + type: string + parent?: string + items: ICommonFunc[] +} + +export interface IRefFunctionSection { + funcData: any + commonFuncData: ICommonFunc + spec: any + typeSpec?: TypeSpec +} + +export interface IRefStaticDoc { + id: string + title: string + meta: { + id: string + title: string + hideTitle: boolean + } + content: { + compiledSource: string + frontmatter: {} + scope: {} + } +} + +export type TypeSpec = { + name: string + children: TypeSpecChild[] +} + +export type TypeSpecChild = { + id: number + name: string + kind: number + kindString?: string + flags?: {} + originalName?: string + children?: TypeSpecChild[] + defaultValue?: string + default?: any + groups?: any + sources?: any + target?: any + comment?: TypeSpecChildComment + typeParameter?: any + implementedTypes?: any + extendedTypes?: any + dereferenced?: TypeSpecChild + extendedBy?: any + indexSignature?: any + extendsType?: any + objectType?: any + trueType?: any + falseType?: any + type?: any + signatures?: any + overwrites?: any + inheritedFrom?: any + implementationOf?: any +} + +export interface TypeSpecChildComment { + shortText?: string + text?: string | null + returns?: string | null + tags?: any +} diff --git a/apps/docs/lib/mdx/handleRefStaticProps.tsx b/apps/docs/lib/mdx/handleRefStaticProps.tsx index c9c47b4496d..c97245387f9 100644 --- a/apps/docs/lib/mdx/handleRefStaticProps.tsx +++ b/apps/docs/lib/mdx/handleRefStaticProps.tsx @@ -1,33 +1,12 @@ -import generateOldRefMarkdown from '~/lib/mdx/generateOldRefMarkdown' import generateRefMarkdown from '~/lib/mdx/generateRefMarkdown' -async function handleRefStaticProps(sections, params, librarypath, urlPath) { +async function handleRefStaticProps(sections, librarypath) { let markdownContent = await generateRefMarkdown(sections, librarypath) - /* - * old content generation - * this is for grabbing to old markdown files - */ - - let slug - if (params.slug.length > 1) { - slug = `docs/reference${urlPath}/${params.slug.join('/')}` - } else { - slug = `docs/reference${urlPath}/${params.slug[0]}` - } - - /* - * handle old ref pages - */ - if (process.env.NEXT_PUBLIC_NEW_DOCS === 'false') { - const oldMarkdown = await generateOldRefMarkdown(slug) - return oldMarkdown - } else { - return { - props: { - docs: markdownContent, - }, - } + return { + props: { + docs: markdownContent, + }, } } diff --git a/apps/docs/lib/refGenerator/helpers.ts b/apps/docs/lib/refGenerator/helpers.ts index 9c14a80167f..83864fa5540 100644 --- a/apps/docs/lib/refGenerator/helpers.ts +++ b/apps/docs/lib/refGenerator/helpers.ts @@ -218,6 +218,12 @@ export type enrichedOperation = OpenAPIV3.OperationObject & { operationId: string operation: string responseList: [] + description?: string + parameters?: [] + responses?: {} + security?: [] + summary?: string + tags?: [] } export function gen_v3(spec: OpenAPIV3.Document, dest: string, { apiUrl }: { apiUrl: string }) { diff --git a/apps/docs/pages/guides/auth/auth-helpers/nextjs-server-components.mdx b/apps/docs/pages/guides/auth/auth-helpers/nextjs-server-components.mdx index 4b31c0a1011..a49f6c3c2ca 100644 --- a/apps/docs/pages/guides/auth/auth-helpers/nextjs-server-components.mdx +++ b/apps/docs/pages/guides/auth/auth-helpers/nextjs-server-components.mdx @@ -139,7 +139,7 @@ Middleware runs before every route declared in the `matcher` array. Since we don > -Create a new file at `/pages/middleware.js` and populate with the following: +Create a new file at `/app/middleware.js` and populate with the following: ```jsx title="middleware.js" import { createMiddlewareSupabaseClient } from '@supabase/auth-helpers-nextjs' @@ -166,7 +166,7 @@ export const config = { -Create a new file at `/middleware.ts` (a sibling of /pages) and populate with the following: +Create a new file at `/app/middleware.ts` and populate with the following: ```tsx title="middleware.ts" import { createMiddlewareSupabaseClient } from '@supabase/auth-helpers-nextjs' @@ -220,11 +220,13 @@ export default function SupabaseListener({ accessToken }) { const router = useRouter() useEffect(() => { - supabase.auth.onAuthStateChange((event, session) => { + const { data: subscription } = supabase.auth.onAuthStateChange((event, session) => { if (session?.access_token !== accessToken) { - router.reload() + router.refresh() } }) + + return () => subscription.unsubscribe() }, [accessToken]) return null diff --git a/apps/docs/pages/guides/auth/auth-helpers/sveltekit.mdx b/apps/docs/pages/guides/auth/auth-helpers/sveltekit.mdx index e6d1d141100..bcdcef28fc2 100644 --- a/apps/docs/pages/guides/auth/auth-helpers/sveltekit.mdx +++ b/apps/docs/pages/guides/auth/auth-helpers/sveltekit.mdx @@ -39,7 +39,7 @@ yarn add @supabase/auth-helpers-sveltekit ### Configuration -Set up the fillowing env vars. For local development you can set them in a `.env` file. See an [example](https://github.com/supabase/auth-helpers/blob/main/examples/sveltekit/.env.example). +Set up the following env vars. For local development you can set them in a `.env` file. See an [example](https://github.com/supabase/auth-helpers/blob/main/examples/sveltekit/.env.example). ```bash # Find these in your Supabase project settings > API diff --git a/apps/docs/pages/guides/auth/overview.mdx b/apps/docs/pages/guides/auth/overview.mdx index 97044096e13..d46b0eb3e9d 100644 --- a/apps/docs/pages/guides/auth/overview.mdx +++ b/apps/docs/pages/guides/auth/overview.mdx @@ -1,5 +1,5 @@ import Layout from '~/layouts/DefaultGuideLayout' -import NewContent from '~/pages/new/auth.mdx' + import { useRouter } from 'next/router' export const meta = { @@ -174,12 +174,6 @@ When users sign up, Supabase assigns them a unique ID. You can reference this ID -export const Page = ({ children }) => { - const router = useRouter() - if (process.env.NEXT_PUBLIC_NEW_DOCS === 'true' && !router.asPath.includes('overview')) { - return NewContent() - } - return -} +export const Page = ({ children }) => export default Page diff --git a/apps/docs/pages/guides/database/extensions/postgis.mdx b/apps/docs/pages/guides/database/extensions/postgis.mdx index f3be14956fd..6f241b0774e 100644 --- a/apps/docs/pages/guides/database/extensions/postgis.mdx +++ b/apps/docs/pages/guides/database/extensions/postgis.mdx @@ -95,9 +95,9 @@ You can insert geographical data through SQL or through our API. insert into public.restaurants (name, location) values - ('Supa Burger', st_point(-73.946823 40.807416)), - ('Supa Pizza', st_point(-73.94581 40.807475)), - ('Supa Taco', st_point(-73.945826 40.80629)); + ('Supa Burger', st_point(-73.946823, 40.807416)), + ('Supa Pizza', st_point(-73.94581, 40.807475)), + ('Supa Taco', st_point(-73.945826, 40.80629)); ``` @@ -143,7 +143,7 @@ await supabase.from('restaurants').insert([ -Notice the order in which you pass the latitude and longitude. Longitude comes first, and is because longitude represents the x-axis of the location. Another thing to watch for is that there is no comma between the two values, just a single space. +Notice the order in which you pass the latitude and longitude. Longitude comes first, and is because longitude represents the x-axis of the location. Another thing to watch for is when inserting data from the client library, there is no comma between the two values, just a single space. At this point, if you go into your Supabase dashboard and look at the data, you will notice that the value of the `location` column looks something like this. diff --git a/apps/docs/pages/guides/database/full-text-search.mdx b/apps/docs/pages/guides/database/full-text-search.mdx index 51de6e5bdaa..c3fa7beaa92 100644 --- a/apps/docs/pages/guides/database/full-text-search.mdx +++ b/apps/docs/pages/guides/database/full-text-search.mdx @@ -108,8 +108,7 @@ const { data, error } = await supabase.from('books').select().eq('title', 'Harry final result = await client .from('books') .select() - .eq('title', 'Harry') - .execute(); + .eq('title', 'Harry'); ``` @@ -146,8 +145,7 @@ const { data, error } = await supabase.from('books').select().textSearch('title' final result = await client .from('books') .select() - .textSearch('title', "'Harry'") - .execute(); + .textSearch('title', "'Harry'"); ``` @@ -191,8 +189,7 @@ const { data, error } = await supabase.from('books').select().textSearch('descri final result = await client .from('books') .select() - .textSearch('description', "'big'") - .execute(); + .textSearch('description', "'big'"); ``` @@ -278,8 +275,7 @@ const { data, error } = await supabase final result = await client .from('books') .select() - .textSearch('description', "'little' & 'big'") - .execute(); + .textSearch('description', "'little' & 'big'"); ``` @@ -331,8 +327,7 @@ const { data, error } = await supabase final result = await client .from('books') .select() - .textSearch('description', "'little' | 'big'") - .execute(); + .textSearch('description', "'little' | 'big'"); ``` @@ -430,8 +425,7 @@ const { data, error } = await supabase.from('books').select().textSearch('fts', final result = await client .from('books') .select() - .textSearch('fts', "'little' & 'big'") - .execute(); + .textSearch('fts', "'little' & 'big'"); ``` @@ -488,8 +482,7 @@ const { data, error } = await supabase final result = await client .from('books') .select() - .textSearch('description', "'big' <-> 'dreams'") - .execute(); + .textSearch('description', "'big' <-> 'dreams'"); ``` @@ -531,8 +524,7 @@ const { data, error } = await supabase final result = await client .from('books') .select() - .textSearch('description', "'year' <2> 'school'") - .execute(); + .textSearch('description', "'year' <2> 'school'"); ``` @@ -577,8 +569,7 @@ const { data, error } = await supabase final result = await client .from('books') .select() - .textSearch('description', "'big' & !'little'") - .execute(); + .textSearch('description', "'big' & !'little'"); ``` diff --git a/apps/docs/pages/guides/database/functions.mdx b/apps/docs/pages/guides/database/functions.mdx index 0204ef78eac..3c547cf93fd 100644 --- a/apps/docs/pages/guides/database/functions.mdx +++ b/apps/docs/pages/guides/database/functions.mdx @@ -88,9 +88,8 @@ Reference: [rpc()](../../reference/javascript/rpc) ```dart -final res = await supabase - .rpc('hello_world') - .execute(); +final data = await supabase + .rpc('hello_world'); ``` Reference: [rpc()](../../reference/dart/rpc) @@ -199,10 +198,9 @@ const { data, error } = supabase.rpc('get_planets').eq('id', 1) ```dart -final res = await supabase +final data = await supabase .rpc('get_planets') - .eq('id', 1) - .execute(); + .eq('id', 1); ``` @@ -254,9 +252,8 @@ const { data, error } = await supabase.rpc('add_planet', { name: 'Jakku' }) ```dart -final res = await supabase - .rpc('add_planet', params: { 'name': 'Jakku' }) - .execute(); +final data = await supabase + .rpc('add_planet', params: { 'name': 'Jakku' }); ``` diff --git a/apps/docs/pages/guides/database/tables.mdx b/apps/docs/pages/guides/database/tables.mdx index 78d1b62115a..3a8caa24c19 100644 --- a/apps/docs/pages/guides/database/tables.mdx +++ b/apps/docs/pages/guides/database/tables.mdx @@ -206,7 +206,7 @@ const { data, error } = await supabase ```sql -final res = await supabase +await supabase .from('movies') .insert([{ name: 'The Empire Strikes Back', @@ -214,7 +214,7 @@ final res = await supabase }, { name: 'Return of the Jedi', description: 'After a daring mission to rescue Han Solo from Jabba the Hutt, the Rebels dispatch to Endor to destroy the second Death Star.' - }]).execute(); + }]); ``` diff --git a/apps/docs/pages/guides/getting-started.mdx b/apps/docs/pages/guides/getting-started.mdx index 5cb4efb70e2..535ee897ec6 100644 --- a/apps/docs/pages/guides/getting-started.mdx +++ b/apps/docs/pages/guides/getting-started.mdx @@ -85,28 +85,28 @@ export const webapps = [ title: 'Angular', href: '/guides/getting-started/tutorials/with-angular', description: - 'Learn how to build a user management app with Nuxt 3 and Supabase Database, Auth, and Storage functionality.', + 'Learn how to build a user management app with Angular and Supabase Database, Auth, and Storage functionality.', icon: '/docs/img/icons/angular-icon', }, { title: 'RedwoodJS', href: '/guides/getting-started/tutorials/with-redwoodjs', description: - 'Learn how to build a user management app with Nuxt 3 and Supabase Database, Auth, and Storage functionality.', + 'Learn how to build a user management app with RedwoodJS and Supabase Database, Auth, and Storage functionality.', icon: '/docs/img/icons/redwood-icon', }, { title: 'Svelte', href: '/guides/getting-started/tutorials/with-svelte', description: - 'Learn how to build a user management app with Nuxt 3 and Supabase Database, Auth, and Storage functionality.', + 'Learn how to build a user management app with Svelte and Supabase Database, Auth, and Storage functionality.', icon: '/docs/img/icons/svelte-icon', }, { title: 'SvelteKit', href: '/guides/getting-started/tutorials/with-sveltekit', description: - 'Learn how to build a user management app with Nuxt 3 and Supabase Database, Auth, and Storage functionality.', + 'Learn how to build a user management app with SvelteKit and Supabase Database, Auth, and Storage functionality.', icon: '/docs/img/icons/svelte-icon', }, { @@ -120,28 +120,28 @@ export const webapps = [ title: 'Expo', href: '/guides/getting-started/tutorials/with-expo', description: - 'Learn how to build a user management app with React and Supabase Database, Auth, and Storage functionality.', + 'Learn how to build a user management app with Expo and Supabase Database, Auth, and Storage functionality.', icon: '/docs/img/icons/expo-icon', }, { title: 'Ionic React', href: '/guides/getting-started/tutorials/with-ionic-react', description: - 'Learn how to build a user management app with React and Supabase Database, Auth, and Storage functionality.', + 'Learn how to build a user management app with Ionic React and Supabase Database, Auth, and Storage functionality.', icon: '/docs/img/icons/ionic-icon', }, { title: 'Ionic Vue', href: '/guides/getting-started/tutorials/with-ionic-vue', description: - 'Learn how to build a user management app with Flutter and Supabase Database, Auth, and Storage functionality.', + 'Learn how to build a user management app with Ionic Vue and Supabase Database, Auth, and Storage functionality.', icon: '/docs/img/icons/ionic-icon', }, { title: 'Ionic Angular', href: '/guides/getting-started/tutorials/with-ionic-angular', description: - 'Learn how to build a user management app with NextJS and Supabase Database, Auth, and Storage functionality.', + 'Learn how to build a user management app with Ionic Angular and Supabase Database, Auth, and Storage functionality.', icon: '/docs/img/icons/ionic-icon', }, ] diff --git a/apps/docs/pages/guides/getting-started/quickstarts/nextjs.mdx b/apps/docs/pages/guides/getting-started/quickstarts/nextjs.mdx index 67d258c8575..b51a4d88b72 100644 --- a/apps/docs/pages/guides/getting-started/quickstarts/nextjs.mdx +++ b/apps/docs/pages/guides/getting-started/quickstarts/nextjs.mdx @@ -86,7 +86,7 @@ export const meta = { ```js lib/supabaseClient.js import { createClient } from '@supabase/supabase-js' - const supabase = createClient('https://.supabase.co', '') + export const supabase = createClient('https://.supabase.co', '') ``` 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 2dc0cca9d0d..bbac06e6019 100644 --- a/apps/docs/pages/guides/getting-started/tutorials/with-sveltekit.mdx +++ b/apps/docs/pages/guides/getting-started/tutorials/with-sveltekit.mdx @@ -56,8 +56,7 @@ import { env } from '$env/dynamic/public' export const supabase = createClient(env.PUBLIC_SUPABASE_URL, env.PUBLIC_SUPABASE_ANON_KEY) ``` -And one optional step is to update the CSS file `public/global.css` to make the app look nice. -You can find the full contents of this file [here](https://raw.githubusercontent.com/supabase/supabase/master/examples/user-management/svelte-user-management/src/app.css). +Optionally, update `src/routes/style.css` with the [CSS from the example](https://raw.githubusercontent.com/supabase/supabase/master/examples/user-management/svelte-user-management/src/app.css). ### Supabase Auth Helpers diff --git a/apps/docs/pages/guides/integrations/picket.mdx b/apps/docs/pages/guides/integrations/picket.mdx new file mode 100644 index 00000000000..d33fa8f7988 --- /dev/null +++ b/apps/docs/pages/guides/integrations/picket.mdx @@ -0,0 +1,572 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'picket', + title: 'Picket', + description: + "Get the best of web2 and web3. Picket allows your users to log in with their wallet without sacrificing Supabase's awesome data management and security features.", +} + +[Picket](https://picketapi.com) is a developer-first, multi-chain web3 auth platform. With Picket, you can easily authenticate users via their wallets and token gate anything. + +This guide steps through building a simple todo list Next.js application with Picket and Supabase. We use Picket to allow users to login into our app with their wallets and leverage Supabase's Row Level Security (RLS) to securely store user information off-chain. + +> Checkout a [live demo](https://picket-supabase-auth-example-v36z.vercel.app/) of a Picket + Supabase integration + +The code for this guide is based of [this example repo](https://github.com/picketapi/picket-supabase-auth-example). + +## Requirements + +- You have [Supabase](https://supabase.com) account. If you don't, sign up at https://supabase.com/ +- You have a [Picket](https://picketapi.com) account. If you don't, sign up at https://picketapi.com/ +- You've read the [Picket Setup Guide](https://docs.picketapi.com/picket-docs/quick-start-guides/quick-start-guides/start-here-setup) +- Familiarity with [React](https://reactjs.org/) and [Next.js](https://nextjs.org/) + +## Step 1: Create a Picket Project + +First, we'll create a new project in our [Picket dashboard](https://picketapi.com/dashboard). + +Click the `Create New Project` button at the top of the Projects section on your [Picket dashboard](https://picketapi.com/dashboard). Edit the project to give it a memorable name. + +![Picket project settings](/docs/img/guides/integrations/picket/picket_project.png) + +We're done for now! We'll revisit this project when we are setting up environment variables in our app. + +## Step 2: Create a Supabase Project + +From your [Supabase dashboard](https://app.supabase.com/), click `New project`. + +Enter a `Name` for your Supabase project. + +Enter a secure `Database Password`. + +Select the any `Region`. + +Click `Create new project`. + +![Supabase project settings](/docs/img/guides/integrations/picket/supabase_project.png) + +## Step 3: Create new New Table with RLS in Supabase + +### Create a `todos` Table + +From the sidebar menu in the [Supabase dashboard](https://app.supabase.com/), click `Table editor`, then `New table`. + +Enter `todos` as the `Name` field. + +Select `Enable Row Level Security (RLS)`. + +Create four columns: + +- `name` as `text` +- `wallet_address` as `text` +- `completed` as `bool` with the default value `false` +- `created_at` as timestamptz with a default value of `now()` + +Click `Save` to create the new table. + +![todos table](/docs/img/guides/integrations/picket/new_table.png) + +### Setup Row Level Security (RLS) + +Now we want to make sure that only the `todos` owner, the user's `wallet_address`, can access their todos. The key component of the this RLS policy is the expression + +```sql +((jwt() ->> 'walletAddress'::text) = wallet_address) +``` + +This expression checks that the wallet address in the requesting JWT access token is the same as the `wallet_address` in the `todos` table. + +![RLS policy](/docs/img/guides/integrations/picket/rls_policy.png) + +## Step 4: Create a Next.js app + +Now, let's start building! + +Create a [new Typescript Next.js app](https://nextjs.org/docs/getting-started) + +```bash +npx create-next-app@latest --typescript +``` + +Create a `.env.local` file and enter the following values + +- `NEXT_PUBLIC_PICKET_PUBLISHABLE_KEY` => Copy the publishable key from the Picket project you created in step 1 +- `PICKET_PROJECT_SECRET_KEY` => Copy the secret key from the Picket project you created in the step 1 +- `NEXT_PUBLIC_SUPABASE_URL` => You can find this URL under "Settings > API" in your Supabase project +- `NEXT_PUBLIC_SUPABASE_ANON_KEY` => You can find this project API key under "Settings > API" in your Supabase project +- `SUAPBASE_JWT_SECRET`=> You can find this secret under "Settings > API" in your Supabase project + +```txt +NEXT_PUBLIC_PICKET_PUBLISHABLE_KEY="YOUR_PICKET_PUBLISHABLE_KEY" +PICKET_PROJECT_SECRET_KEY="YOUR_PICKET_PROJECT_SECRET_KEY" +NEXT_PUBLIC_SUPABASE_URL="YOUR_SUPABASE_URL" +NEXT_PUBLIC_SUPABASE_ANON_KEY="YOUR_SUPABASE_ANON_KEY" +SUPABASE_JWT_SECRET="YOUR_SUPABASE_JWT_SECRET" +``` + +## Step 5: Setup Picket for Wallet Login + +> For more information on how to setup [Picket](https://picketapi.com) in your Next.js app, checkout the [Picket getting started guide](https://docs.picketapi.com/picket-docs/quick-start-guides/quick-start-guides/wallet-login) +> After initializing our app, we can setup Picket. + +Install the Picket [React](/picket-docs/reference/libraries-and-sdks/react-sdk-picket-react) and [Node](/picket-docs/reference/libraries-and-sdks/node.js-library-picket-node) libraries + +```bash +npm i @picketapi/picket-react @picketapi/picket-node +``` + +Update `pages/_app.tsx` to setup the `PicketProvider` + +```tsx +import '../styles/globals.css' +import type { AppProps } from 'next/app' + +import { PicketProvider } from '@picketapi/picket-react' + +export default function App({ Component, pageProps }: AppProps) { + return ( + + + + ) +} +``` + +Update `pages/index.tsx` to let users log in and out with their wallet + +```tsx +import { GetServerSideProps } from 'next' +import { useRouter } from 'next/router' +import { useCallback } from 'react' + +import styles from '../styles/Home.module.css' + +import { usePicket } from '@picketapi/picket-react' +import { cookieName } from '../utils/supabase' + +type Props = { + loggedIn: boolean +} + +export default function Home(props: Props) { + const { loggedIn } = props + const { login, logout, authState } = usePicket() + const router = useRouter() + + const handleLogin = useCallback(async () => { + let auth = authState + // no need to re-login if they've already connected with Picket + if (!auth) { + // login with Picket + auth = await login() + } + + // login failed + if (!auth) return + + // create a corresponding supabase access token + await fetch('/api/login', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + accessToken: auth.accessToken, + }), + }) + // redirect to their todos page + router.push('/todos') + }, [authState, login, router]) + + const handleLogout = useCallback(async () => { + // clear both picket and supabase session + await logout() + await fetch('/api/logout', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }) + // refresh the page + router.push('/') + }, [logout, router]) + + return ( +
+
+ {loggedIn ? ( + + ) : ( + + )} +
+
+ ) +} + +export const getServerSideProps: GetServerSideProps = async ({ req }) => { + // get supabase token server-side + const accessToken = req.cookies[cookieName] + + if (!accessToken) { + return { + props: { + loggedIn: false, + }, + } + } + + return { + props: { + loggedIn: true, + }, + } +} +``` + +## Step 6: Issue a Supabase JWT on Wallet Login + +Great, now we have setup a typical Picket Next.js app. Next, we need to implement the log in/out API routes to allow users to securely query our Supabase project. + +First, install dependencies + +```bash +npm install @supabase/supabase-js jsonwebtoken cookie js-cookie +``` + +Create a utility function to create a Supabase client with a custom access token in `utils/supabase.ts` + +```ts +import { createClient, SupabaseClientOptions } from '@supabase/supabase-js' + +export const cookieName = 'sb-access-token' + +const getSupabase = (accessToken: string) => { + const options: SupabaseClientOptions<'public'> = {} + + if (accessToken) { + options.global = { + headers: { + // This gives Supabase information about the user (wallet) making the request + Authorization: `Bearer ${accessToken}`, + }, + } + } + + const supabase = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, + options + ) + + return supabase +} + +export { getSupabase } +``` + +Create new api route `pages/api/login.ts`. This route validates the Picket access token then issues another equivalent Supabase access token for us to use with the Supabase client. + +```ts +import type { NextApiRequest, NextApiResponse } from 'next' +import jwt from 'jsonwebtoken' +import cookie from 'cookie' +import Picket from '@picketapi/picket-node' + +import { cookieName } from '../../utils/supabase' + +// create picket node client with your picket secret api key +const picket = new Picket(process.env.PICKET_PROJECT_SECRET_KEY!) + +const expToExpiresIn = (exp: number) => exp - Math.floor(Date.now() / 1000) + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + const { accessToken } = req.body + // omit expiration time,.it will conflict with jwt.sign + const { exp, ...payload } = await picket.validate(accessToken) + const expiresIn = expToExpiresIn(exp) + + const supabaseJWT = jwt.sign( + { + ...payload, + }, + process.env.SUPABASE_JWT_SECRET!, + { + expiresIn, + } + ) + + // Set a new cookie with the name + res.setHeader( + 'Set-Cookie', + cookie.serialize(cookieName, supabaseJWT, { + path: '/', + secure: process.env.NODE_ENV !== 'development', + // allow the cookie to be accessed client-side + httpOnly: false, + sameSite: 'strict', + maxAge: expiresIn, + }) + ) + res.status(200).json({}) +} +``` + +And now create an equivalent logout api route `/pages/api/logout.ts` to delete the Supabase access token cookie. + +```ts +import type { NextApiRequest, NextApiResponse } from 'next' +import cookie from 'cookie' + +import { cookieName } from '../../utils/supabase' + +export default async function handler(_req: NextApiRequest, res: NextApiResponse) { + // Clear the supabase cookie + res.setHeader( + 'Set-Cookie', + cookie.serialize(cookieName, '', { + path: '/', + maxAge: -1, + }) + ) + + res.status(200).json({}) +} +``` + +We can now login and logout to the app with our wallet! + +## Step 7: Interacting with Data in Supabase + +Now that we can login to the app, it's time to start interacting with Supabase. Let's make a todo list page for authenticated users. + +Create a new file `pages/todos.tsx` + +```tsx +import { GetServerSideProps } from 'next' +import Head from 'next/head' +import Link from 'next/link' +import { useState, useMemo } from 'react' +import jwt from 'jsonwebtoken' +import Cookies from 'js-cookie' + +import styles from '../styles/Home.module.css' + +import { getSupabase, cookieName } from '../utils/supabase' + +type Todo = { + name: string + completed: boolean +} + +type Props = { + walletAddress: string + todos: Todo[] +} + +const displayWalletAddress = (walletAddress: string) => + `${walletAddress.slice(0, 6)}...${walletAddress.slice(-4)}` + +export default function Todos(props: Props) { + const { walletAddress } = props + const [todos, setTodos] = useState(props.todos) + + // avoid re-creating supabase client every render + const supabase = useMemo(() => { + const accessToken = Cookies.get(cookieName) + return getSupabase(accessToken || '') + }, []) + + return ( +
+ + Picket 💜 Supabase + + +
+

Your Personal Todo List

+
+

Welcome {displayWalletAddress(walletAddress)},

+

+ Your todo list is stored in Supabase and are only accessible to you and your wallet + address. Picket + Supabase makes it easy to build scalable, hybrid web2 and web3 apps. + Use Supabase to store non-critical or private data off-chain like user app preferences + or todo lists. +

+
+
+

Todo List

+ {todos.map((todo) => ( +
+ { + await supabase.from('todos').upsert({ + wallet_address: walletAddress, + name: todo.name, + completed: !todo.completed, + }) + setTodos((todos) => + todos.map((t) => (t.name === todo.name ? { ...t, completed: !t.completed } : t)) + ) + }} + /> + + {todo.name} + +
+ ))} +
+ + Go back home → + +
+
+
+
+ ) +} + +export const getServerSideProps: GetServerSideProps = async ({ req }) => { + // example of fetching data server-side + const accessToken = req.cookies[cookieName] + + // require authentication + if (!accessToken) { + return { + redirect: { + destination: '/', + }, + props: { + walletAddress: '', + todos: [], + }, + } + } + + // check if logged in user has completed the tutorial + const supabase = getSupabase(accessToken) + const { walletAddress } = jwt.decode(accessToken) as { + walletAddress: string + } + + // get todos for the users + // if none exist, create the default todos + let { data } = await supabase.from('todos').select('*') + + if (!data || data.length === 0) { + let error = null + ;({ data, error } = await supabase + .from('todos') + .insert([ + { + wallet_address: walletAddress, + name: 'Complete the Picket + Supabase Tutorial', + completed: true, + }, + { + wallet_address: walletAddress, + name: 'Create a Picket Account (https://picketapi.com/)', + completed: false, + }, + { + wallet_address: walletAddress, + name: 'Read the Picket Docs (https://docs.picketapi.com/)', + completed: false, + }, + { + wallet_address: walletAddress, + name: 'Build an Awesome Web3 Experience', + completed: false, + }, + ]) + .select('*')) + + if (error) { + // log error and redirect home + console.error(error) + return { + redirect: { + destination: '/', + }, + props: { + walletAddress: '', + todos: [], + }, + } + } + } + + return { + props: { + walletAddress, + todos: data as Todo[], + }, + } +} +``` + +This is a long file, but don't be intimidated. The page is actually straightforward. It + +1. Verifies server-side that the user is authenticated and if they are not redirects them to the homepage +2. Checks to see if they already have `todos` . If so, it returns them. If not, it initializes them for the users +3. We render the `todos` and when the user selects or deselects a todo, we update the data in the database + +## Step 8: Try it Out! + +And that's it. If you haven't already, run your app to test it out yourself + +```bash +# start the app +npm run dev +# open http://localhost:3000 +``` + +### What's Next? + +- Explore the [Picket documentation](https://docs.picketapi.com/picket-docs/) +- Play with the live [Picket + Supabase demo](https://picket-supabase-auth-example-v36z.vercel.app/) +- Checkout Picket's [example Github repositories](https://github.com/picketapi) + +### Common Use-Case for Picket + Supabase + +- Account linking. Allow users to associate their wallet address(es) with their existing web2 account in your app +- Leverage Supabase's awesome libraries and ecosystem while still enabling wallet login +- Store app-specific data, like user preferences, about your user's wallet adress off-chain +- Cache on-chain data to improve your DApp's performance + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/resources/supabase-cli/local-development.mdx b/apps/docs/pages/guides/resources/supabase-cli/local-development.mdx index eb4d4572a8b..b0f45364b52 100644 --- a/apps/docs/pages/guides/resources/supabase-cli/local-development.mdx +++ b/apps/docs/pages/guides/resources/supabase-cli/local-development.mdx @@ -31,6 +31,13 @@ Make sure you have these installed on your local machine: supabase login ``` + + +If you installed the Supabase CLI via NPM you may have to use `npx supabase login`. + + + + ## Initialize your project Create a new folder for your project and start a new git repository: diff --git a/apps/docs/pages/guides/tutorials.mdx b/apps/docs/pages/guides/tutorials.mdx index 39f553feea8..887788bdb39 100644 --- a/apps/docs/pages/guides/tutorials.mdx +++ b/apps/docs/pages/guides/tutorials.mdx @@ -84,31 +84,31 @@ export const webapps = [ icon: '/docs/img/icons/nuxt-icon', }, { - title: 'Anglular', + title: 'Angular', href: '/guides/tutorials/with-angular', description: - 'Learn how to build a user management app with Nuxt 3 and Supabase Database, Auth, and Storage functionality.', + 'Learn how to build a user management app with Angular and Supabase Database, Auth, and Storage functionality.', icon: '/docs/img/icons/angular-icon', }, { title: 'RedwoodJS', href: '/guides/tutorials/with-redwoodjs', description: - 'Learn how to build a user management app with Nuxt 3 and Supabase Database, Auth, and Storage functionality.', + 'Learn how to build a user management app with RedwoodJS and Supabase Database, Auth, and Storage functionality.', icon: '/docs/img/icons/redwood-icon', }, { title: 'Svelte', href: '/guides/tutorials/with-svelte', description: - 'Learn how to build a user management app with Nuxt 3 and Supabase Database, Auth, and Storage functionality.', + 'Learn how to build a user management app with Svelte and Supabase Database, Auth, and Storage functionality.', icon: '/docs/img/icons/svelte-icon', }, { title: 'SvelteKit', href: '/guides/tutorials/with-sveltekit', description: - 'Learn how to build a user management app with Nuxt 3 and Supabase Database, Auth, and Storage functionality.', + 'Learn how to build a user management app with SvelteKit and Supabase Database, Auth, and Storage functionality.', icon: '/docs/img/icons/svelte-icon', }, ] @@ -125,7 +125,7 @@ export const mobile = [ title: 'Expo', href: '/guides/tutorials/with-expo', description: - 'Learn how to build a user management app with React and Supabase Database, Auth, and Storage functionality.', + 'Learn how to build a user management app with Expo and Supabase Database, Auth, and Storage functionality.', icon: '/docs/img/icons/expo-icon', }, ] @@ -135,21 +135,21 @@ export const lowCode = [ title: 'Ionic React', href: '/guides/tutorials/with-ionic-react', description: - 'Learn how to build a user management app with React and Supabase Database, Auth, and Storage functionality.', + 'Learn how to build a user management app with Ionic React and Supabase Database, Auth, and Storage functionality.', icon: '/docs/img/icons/ionic-icon', }, { title: 'Ionic Vue', href: '/guides/tutorials/with-ionic-vue', description: - 'Learn how to build a user management app with Flutter and Supabase Database, Auth, and Storage functionality.', + 'Learn how to build a user management app with Ionic Vue and Supabase Database, Auth, and Storage functionality.', icon: '/docs/img/icons/ionic-icon', }, { title: 'Ionic Angular', href: '/guides/tutorials/with-ionic-angular', description: - 'Learn how to build a user management app with NextJS and Supabase Database, Auth, and Storage functionality.', + 'Learn how to build a user management app with Ionic Angular and Supabase Database, Auth, and Storage functionality.', icon: '/docs/img/icons/ionic-icon', }, ] diff --git a/apps/docs/pages/reference/api/[...slug].tsx b/apps/docs/pages/reference/api/[...slug].tsx index 58d617d5cac..382c882a190 100644 --- a/apps/docs/pages/reference/api/[...slug].tsx +++ b/apps/docs/pages/reference/api/[...slug].tsx @@ -15,8 +15,8 @@ export default function Config(props) { return } -export async function getStaticProps({ params }: { params: { slug: string[] } }) { - return handleRefStaticProps(sections, params, '/api', '/api') +export async function getStaticProps() { + return handleRefStaticProps(sections, '/api') } export function getStaticPaths() { diff --git a/apps/docs/pages/reference/cli/[...slug].tsx b/apps/docs/pages/reference/cli/[...slug].tsx index 2e4a2218b3e..3555095ee72 100644 --- a/apps/docs/pages/reference/cli/[...slug].tsx +++ b/apps/docs/pages/reference/cli/[...slug].tsx @@ -14,8 +14,8 @@ export default function CliRef(props) { return } -export async function getStaticProps({ params }: { params: { slug: string[] } }) { - return handleRefStaticProps(sections, params, '/cli', '/cli') +export async function getStaticProps() { + return handleRefStaticProps(sections, '/cli') } export function getStaticPaths() { diff --git a/apps/docs/pages/reference/dart/[...slug].tsx b/apps/docs/pages/reference/dart/[...slug].tsx index 4fecc97170d..f35f74c6120 100644 --- a/apps/docs/pages/reference/dart/[...slug].tsx +++ b/apps/docs/pages/reference/dart/[...slug].tsx @@ -9,11 +9,11 @@ import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps' const sections = flattenSections(clientLibsCommonSections) export default function JSReference(props) { - return + return } -export async function getStaticProps({ params }: { params: { slug: string[] } }) { - return handleRefStaticProps(sections, params, '/dart', '/dart') +export async function getStaticProps() { + return handleRefStaticProps(sections, '/dart') } export function getStaticPaths() { diff --git a/apps/docs/pages/reference/dart/crawlers/[...slug].tsx b/apps/docs/pages/reference/dart/crawlers/[...slug].tsx index c12f3cabbf2..036933a1682 100644 --- a/apps/docs/pages/reference/dart/crawlers/[...slug].tsx +++ b/apps/docs/pages/reference/dart/crawlers/[...slug].tsx @@ -29,13 +29,14 @@ export default function JSReference(props) { spec={spec} typeSpec={typeSpec} pageProps={props} + type="client-lib" /> ) } -export async function getStaticProps({ params }: { params: { slug: string[] } }) { - return handleRefStaticProps(sections, params, '/dart', '/dart') +export async function getStaticProps() { + return handleRefStaticProps(sections, '/dart') } export function getStaticPaths() { diff --git a/apps/docs/pages/reference/dart/v0/[...slug].tsx b/apps/docs/pages/reference/dart/v0/[...slug].tsx index f0daf4e9d6e..4b6a485d9c6 100644 --- a/apps/docs/pages/reference/dart/v0/[...slug].tsx +++ b/apps/docs/pages/reference/dart/v0/[...slug].tsx @@ -9,11 +9,11 @@ import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps' const sections = flattenSections(clientLibsCommonSections) export default function JSReference(props) { - return + return } -export async function getStaticProps({ params }: { params: { slug: string[] } }) { - return handleRefStaticProps(sections, params, '/dart/v0', '/dart/v0') +export async function getStaticProps() { + return handleRefStaticProps(sections, '/dart/v0') } export function getStaticPaths() { diff --git a/apps/docs/pages/reference/dart/v0/crawlers/[...slug].tsx b/apps/docs/pages/reference/dart/v0/crawlers/[...slug].tsx index 2e67a5b5c8e..8539659431d 100644 --- a/apps/docs/pages/reference/dart/v0/crawlers/[...slug].tsx +++ b/apps/docs/pages/reference/dart/v0/crawlers/[...slug].tsx @@ -28,13 +28,14 @@ export default function JSReference(props) { spec={spec} typeSpec={typeSpec} pageProps={props} + type="client-lib" /> ) } -export async function getStaticProps({ params }: { params: { slug: string[] } }) { - return handleRefStaticProps(sections, params, '/dart/v0', '/dart/v0') +export async function getStaticProps() { + return handleRefStaticProps(sections, '/dart/v0') } export function getStaticPaths() { diff --git a/apps/docs/pages/reference/javascript/[...slug].tsx b/apps/docs/pages/reference/javascript/[...slug].tsx index cab58348221..3cbbcc08694 100644 --- a/apps/docs/pages/reference/javascript/[...slug].tsx +++ b/apps/docs/pages/reference/javascript/[...slug].tsx @@ -10,11 +10,19 @@ import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps' const sections = flattenSections(clientLibsCommonSections) export default function JSReference(props) { - return + return ( + + ) } -export async function getStaticProps({ params }: { params: { slug: string[] } }) { - return handleRefStaticProps(sections, params, '/javascript', '/javascript') +export async function getStaticProps() { + return handleRefStaticProps(sections, '/javascript') } export function getStaticPaths() { diff --git a/apps/docs/pages/reference/javascript/crawlers/[...slug].tsx b/apps/docs/pages/reference/javascript/crawlers/[...slug].tsx index 2e6e0b271a9..67c72c5151c 100644 --- a/apps/docs/pages/reference/javascript/crawlers/[...slug].tsx +++ b/apps/docs/pages/reference/javascript/crawlers/[...slug].tsx @@ -29,13 +29,14 @@ export default function JSReference(props) { spec={spec} typeSpec={typeSpec} pageProps={props} + type="client-lib" /> ) } -export async function getStaticProps({ params }: { params: { slug: string[] } }) { - return handleRefStaticProps(sections, params, '/javascript', '/javascript') +export async function getStaticProps() { + return handleRefStaticProps(sections, '/javascript') } export function getStaticPaths() { diff --git a/apps/docs/pages/reference/javascript/v1/[...slug].tsx b/apps/docs/pages/reference/javascript/v1/[...slug].tsx index d54c9df6d94..4dd32c156ca 100644 --- a/apps/docs/pages/reference/javascript/v1/[...slug].tsx +++ b/apps/docs/pages/reference/javascript/v1/[...slug].tsx @@ -10,11 +10,19 @@ import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps' const sections = flattenSections(clientLibsCommonSections) export default function JSReference(props) { - return + return ( + + ) } -export async function getStaticProps({ params }: { params: { slug: string[] } }) { - return handleRefStaticProps(sections, params, '/javascript/v1', '/javascript/v1') +export async function getStaticProps() { + return handleRefStaticProps(sections, '/javascript/v1') } export function getStaticPaths() { diff --git a/apps/docs/pages/reference/javascript/v1/crawlers/[...slug].tsx b/apps/docs/pages/reference/javascript/v1/crawlers/[...slug].tsx index bbb38f4598a..18c498125ef 100644 --- a/apps/docs/pages/reference/javascript/v1/crawlers/[...slug].tsx +++ b/apps/docs/pages/reference/javascript/v1/crawlers/[...slug].tsx @@ -28,13 +28,14 @@ export default function JSReference(props) { spec={spec} typeSpec={typeSpec} pageProps={props} + type="client-lib" /> ) } -export async function getStaticProps({ params }: { params: { slug: string[] } }) { - return handleRefStaticProps(sections, params, '/javascript/v1', '/javascript/v1') +export async function getStaticProps() { + return handleRefStaticProps(sections, '/javascript/v1') } export function getStaticPaths() { diff --git a/apps/docs/pages/reference/self-hosting-auth/[...slug].tsx b/apps/docs/pages/reference/self-hosting-auth/[...slug].tsx index 819fd773892..c7b2382af16 100644 --- a/apps/docs/pages/reference/self-hosting-auth/[...slug].tsx +++ b/apps/docs/pages/reference/self-hosting-auth/[...slug].tsx @@ -12,11 +12,11 @@ const sections = flattenSections(selfHostingAuthCommonSections) const spec = gen_v3(authSpec, 'wat', { apiUrl: 'apiv0' }) export default function JSReference(props) { - return + return } -export async function getStaticProps({ params }: { params: { slug: string[] } }) { - return handleRefStaticProps(sections, params, '/self-hosting-auth', '/self-hosting-auth') +export async function getStaticProps() { + return handleRefStaticProps(sections, '/self-hosting-auth') } export function getStaticPaths() { diff --git a/apps/docs/pages/reference/self-hosting-realtime/[...slug].tsx b/apps/docs/pages/reference/self-hosting-realtime/[...slug].tsx index 240ea180f0c..49e91894a06 100644 --- a/apps/docs/pages/reference/self-hosting-realtime/[...slug].tsx +++ b/apps/docs/pages/reference/self-hosting-realtime/[...slug].tsx @@ -9,11 +9,11 @@ import { gen_v3 } from '~/lib/refGenerator/helpers' const sections = flattenSections(selfHostingRealtimeCommonSections) export default function JSReference(props) { - return + return } -export async function getStaticProps({ params }: { params: { slug: string[] } }) { - return handleRefStaticProps(sections, params, '/self-hosting-realtime', '/self-hosting-realtime') +export async function getStaticProps() { + return handleRefStaticProps(sections, '/self-hosting-realtime') } export function getStaticPaths() { diff --git a/apps/docs/pages/reference/self-hosting-storage/[...slug].tsx b/apps/docs/pages/reference/self-hosting-storage/[...slug].tsx index 2d2de874622..1b4b99fb9c8 100644 --- a/apps/docs/pages/reference/self-hosting-storage/[...slug].tsx +++ b/apps/docs/pages/reference/self-hosting-storage/[...slug].tsx @@ -12,11 +12,11 @@ const sections = flattenSections(selfHostingStorageCommonSections) const spec = gen_v3(storageSpec, 'wat', { apiUrl: 'apiv0' }) export default function JSReference(props) { - return + return } -export async function getStaticProps({ params }: { params: { slug: string[] } }) { - return handleRefStaticProps(sections, params, '/self-hosting-storage', '/self-hosting-storage') +export async function getStaticProps() { + return handleRefStaticProps(sections, '/self-hosting-storage') } export function getStaticPaths() { diff --git a/apps/docs/public/img/guides/integrations/picket/new_table.png b/apps/docs/public/img/guides/integrations/picket/new_table.png new file mode 100644 index 00000000000..72aa9b21ea2 Binary files /dev/null and b/apps/docs/public/img/guides/integrations/picket/new_table.png differ diff --git a/apps/docs/public/img/guides/integrations/picket/picket_project.png b/apps/docs/public/img/guides/integrations/picket/picket_project.png new file mode 100644 index 00000000000..6d359e9af83 Binary files /dev/null and b/apps/docs/public/img/guides/integrations/picket/picket_project.png differ diff --git a/apps/docs/public/img/guides/integrations/picket/rls_policy.png b/apps/docs/public/img/guides/integrations/picket/rls_policy.png new file mode 100644 index 00000000000..347038ebe90 Binary files /dev/null and b/apps/docs/public/img/guides/integrations/picket/rls_policy.png differ diff --git a/apps/docs/public/img/guides/integrations/picket/supabase_project.png b/apps/docs/public/img/guides/integrations/picket/supabase_project.png new file mode 100644 index 00000000000..97c441b6ef8 Binary files /dev/null and b/apps/docs/public/img/guides/integrations/picket/supabase_project.png differ diff --git a/apps/docs/public/sitemap.xml b/apps/docs/public/sitemap.xml index 38b0e06220a..2e86001d252 100644 --- a/apps/docs/public/sitemap.xml +++ b/apps/docs/public/sitemap.xml @@ -78,6 +78,12 @@ 0.5 + + https://supabase.com/docs/guides/self-hosting + weekly + 0.5 + + https://supabase.com/docs/guides/storage weekly @@ -126,6 +132,78 @@ 0.5 + + https://supabase.com/docs/guides/auth/auth-captcha + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/auth-email-templates + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/auth-email + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/auth-helpers + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/auth-magic-link + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/auth-mfa + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/managing-user-data + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/overview + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/phone-login + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/row-level-security + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/server-side-rendering + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/social-login + weekly + 0.5 + + https://supabase.com/docs/guides/database/arrays weekly @@ -216,78 +294,6 @@ 0.5 - - https://supabase.com/docs/guides/auth/auth-captcha - weekly - 0.5 - - - - https://supabase.com/docs/guides/auth/auth-email-templates - weekly - 0.5 - - - - https://supabase.com/docs/guides/auth/auth-email - weekly - 0.5 - - - - https://supabase.com/docs/guides/auth/auth-helpers - weekly - 0.5 - - - - https://supabase.com/docs/guides/auth/auth-magic-link - weekly - 0.5 - - - - https://supabase.com/docs/guides/auth/auth-mfa - weekly - 0.5 - - - - https://supabase.com/docs/guides/auth/managing-user-data - weekly - 0.5 - - - - https://supabase.com/docs/guides/auth/overview - weekly - 0.5 - - - - https://supabase.com/docs/guides/auth/phone-login - weekly - 0.5 - - - - https://supabase.com/docs/guides/auth/row-level-security - weekly - 0.5 - - - - https://supabase.com/docs/guides/auth/server-side-rendering - weekly - 0.5 - - - - https://supabase.com/docs/guides/auth/social-login - weekly - 0.5 - - https://supabase.com/docs/guides/functions/auth weekly @@ -570,12 +576,6 @@ 0.5 - - https://supabase.com/docs/guides/resources/self-hosting - weekly - 0.5 - - https://supabase.com/docs/guides/resources/supabase-cli weekly @@ -612,6 +612,12 @@ 0.5 + + https://supabase.com/docs/guides/self-hosting/docker + weekly + 0.5 + + https://supabase.com/docs/learn/auth-deep-dive/auth-deep-dive-jwts weekly @@ -642,42 +648,6 @@ 0.5 - - https://supabase.com/docs/guides/database/extensions/http - weekly - 0.5 - - - - https://supabase.com/docs/guides/database/extensions/pgcron - weekly - 0.5 - - - - https://supabase.com/docs/guides/database/extensions/pgnet - weekly - 0.5 - - - - https://supabase.com/docs/guides/database/extensions/pgtap - weekly - 0.5 - - - - https://supabase.com/docs/guides/database/extensions/plv8 - weekly - 0.5 - - - - https://supabase.com/docs/guides/database/extensions/uuid-ossp - weekly - 0.5 - - https://supabase.com/docs/guides/auth/auth-helpers/auth-ui weekly @@ -829,25 +799,43 @@ - https://supabase.com/docs/guides/functions/examples/og-image + https://supabase.com/docs/guides/database/extensions/http weekly 0.5 - https://supabase.com/docs/guides/getting-started/quickstarts/nextjs + https://supabase.com/docs/guides/database/extensions/pgcron weekly 0.5 - https://supabase.com/docs/guides/getting-started/quickstarts/nuxtjs + https://supabase.com/docs/guides/database/extensions/pgnet weekly 0.5 - https://supabase.com/docs/guides/getting-started/quickstarts/reactjs + https://supabase.com/docs/guides/database/extensions/pgtap + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/plv8 + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/postgis + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/uuid-ossp weekly 0.5 @@ -936,6 +924,30 @@ 0.5 + + https://supabase.com/docs/guides/functions/examples/og-image + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/quickstarts/nextjs + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/quickstarts/nuxtjs + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/quickstarts/reactjs + weekly + 0.5 + + https://supabase.com/docs/guides/platform/sso/azure weekly @@ -954,6 +966,24 @@ 0.5 + + https://supabase.com/docs/guides/resources/supabase-cli/local-development + weekly + 0.5 + + + + https://supabase.com/docs/guides/resources/supabase-cli/managing-environments + weekly + 0.5 + + + + https://supabase.com/docs/guides/self-hosting/auth/config + weekly + 0.5 + + https://supabase.com/docs/guides/resources/migrating-to-supabase/firebase-auth weekly @@ -979,19 +1009,13 @@ - https://supabase.com/docs/guides/resources/self-hosting/docker + https://supabase.com/docs/guides/self-hosting/realtime/config weekly 0.5 - https://supabase.com/docs/guides/resources/supabase-cli/local-development - weekly - 0.5 - - - - https://supabase.com/docs/guides/resources/supabase-cli/managing-environments + https://supabase.com/docs/guides/self-hosting/storage/config weekly 0.5 @@ -3326,6 +3350,24 @@ 0.5 + + https://supabase.com/docs/reference/cli/supabase-network-bans + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-network-bans-remove + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-network-bans-get + weekly + 0.5 + + https://supabase.com/docs/reference/cli/supabase-migration weekly @@ -3488,6 +3530,12 @@ 0.5 + + https://supabase.com/docs/reference/cli/supabase-db-dump + weekly + 0.5 + + https://supabase.com/docs/reference/cli/supabase-db-diff weekly diff --git a/apps/www/_blog/2020-07-09-postgresql-templates.mdx b/apps/www/_blog/2020-07-09-postgresql-templates.mdx index 7550d0a5b3c..e0908248c7f 100644 --- a/apps/www/_blog/2020-07-09-postgresql-templates.mdx +++ b/apps/www/_blog/2020-07-09-postgresql-templates.mdx @@ -8,6 +8,7 @@ author_image_url: https://avatars0.githubusercontent.com/u/26374889?s=400&u=f5e3 authorURL: https://github.com/dragarcia tags: - postgres + - planetpg date: '07-09-2020' --- diff --git a/apps/www/_blog/2020-07-17-postgresql-physical-logical-backups.mdx b/apps/www/_blog/2020-07-17-postgresql-physical-logical-backups.mdx index c6d41b41283..e1a6fc1801e 100644 --- a/apps/www/_blog/2020-07-17-postgresql-physical-logical-backups.mdx +++ b/apps/www/_blog/2020-07-17-postgresql-physical-logical-backups.mdx @@ -8,6 +8,7 @@ author_image_url: https://avatars0.githubusercontent.com/u/26374889?s=400&u=f5e3 authorURL: https://github.com/dragarcia tags: - postgres + - planetpg date: '07-07-2020' --- diff --git a/apps/www/_blog/2020-08-02-continuous-postgresql-backup-walg.mdx b/apps/www/_blog/2020-08-02-continuous-postgresql-backup-walg.mdx index de0b4c6e801..5ef8452afd8 100644 --- a/apps/www/_blog/2020-08-02-continuous-postgresql-backup-walg.mdx +++ b/apps/www/_blog/2020-08-02-continuous-postgresql-backup-walg.mdx @@ -8,6 +8,7 @@ author_image_url: https://avatars0.githubusercontent.com/u/26374889?s=400&u=f5e3 authorURL: https://github.com/dragarcia tags: - postgres + - planetpg date: '08-02-2020' --- diff --git a/apps/www/_blog/2020-11-18-postgresql-views.mdx b/apps/www/_blog/2020-11-18-postgresql-views.mdx index 5237a9c9675..8f20842d9c6 100644 --- a/apps/www/_blog/2020-11-18-postgresql-views.mdx +++ b/apps/www/_blog/2020-11-18-postgresql-views.mdx @@ -9,6 +9,7 @@ authorURL: https://github.com/dragarcia image: postgres-views.png tags: - postgres + - planetpg date: '11-18-2020' --- diff --git a/apps/www/_blog/2021-02-27-cracking-postgres-interview.mdx b/apps/www/_blog/2021-02-27-cracking-postgres-interview.mdx index 8d1f2cf067a..9e83a52bb99 100644 --- a/apps/www/_blog/2021-02-27-cracking-postgres-interview.mdx +++ b/apps/www/_blog/2021-02-27-cracking-postgres-interview.mdx @@ -11,6 +11,7 @@ thumb: elephants.png tags: - sql - postgres + - planetpg date: '02-27-2021' --- diff --git a/apps/www/_blog/2021-03-05-postgres-as-a-cron-server.mdx b/apps/www/_blog/2021-03-05-postgres-as-a-cron-server.mdx index 28adb8d679f..c879a5ca8cb 100644 --- a/apps/www/_blog/2021-03-05-postgres-as-a-cron-server.mdx +++ b/apps/www/_blog/2021-03-05-postgres-as-a-cron-server.mdx @@ -10,6 +10,7 @@ image: supabase-postgres-cron.png thumb: supabase-postgres-cron.png tags: - postgres + - planetpg date: '03-05-2021' --- diff --git a/apps/www/_blog/2021-07-01-roles-postgres-hooks.mdx b/apps/www/_blog/2021-07-01-roles-postgres-hooks.mdx index b81e9211b3c..9c45c3ff78a 100644 --- a/apps/www/_blog/2021-07-01-roles-postgres-hooks.mdx +++ b/apps/www/_blog/2021-07-01-roles-postgres-hooks.mdx @@ -9,6 +9,7 @@ image: postgres-hooks/og-postgres-hooks.jpg thumb: postgres-hooks/cover-postgres-hooks.jpg tags: - postgres + - planetpg date: '2021-07-02' --- diff --git a/apps/www/_blog/2022-03-08-audit.mdx b/apps/www/_blog/2022-03-08-audit.mdx index 88993d48113..f482933101f 100644 --- a/apps/www/_blog/2022-03-08-audit.mdx +++ b/apps/www/_blog/2022-03-08-audit.mdx @@ -9,6 +9,7 @@ image: supa-audit/postgres_auditing_in_150_lines_of_SQL.png thumb: supa-audit/postgres_auditing_in_150_lines_of_SQL.png tags: - postgres + - planetpg date: '2022-03-08' toc_depth: 3 --- diff --git a/apps/www/_blog/2022-06-28-partial-postgresql-data-dumps-with-rls.mdx b/apps/www/_blog/2022-06-28-partial-postgresql-data-dumps-with-rls.mdx index b2f6e508cc1..1ddc4baaf0a 100644 --- a/apps/www/_blog/2022-06-28-partial-postgresql-data-dumps-with-rls.mdx +++ b/apps/www/_blog/2022-06-28-partial-postgresql-data-dumps-with-rls.mdx @@ -7,6 +7,7 @@ thumb: partial-dumps/og-partial-dumps-with-rls.png tags: - postgresql - data + - planetpg date: '2022-06-28' toc_depth: 3 --- diff --git a/apps/www/_blog/2022-07-18-seen-by-in-postgresql.mdx b/apps/www/_blog/2022-07-18-seen-by-in-postgresql.mdx index 36575dff7ba..32414a702a9 100644 --- a/apps/www/_blog/2022-07-18-seen-by-in-postgresql.mdx +++ b/apps/www/_blog/2022-07-18-seen-by-in-postgresql.mdx @@ -6,6 +6,7 @@ image: seen-by/seen-by-postgresql-thumb.png thumb: seen-by/seen-by-postgresql-thumb.png tags: - postgres + - planetpg date: '2022-07-18' toc_depth: 3 --- diff --git a/apps/www/_blog/2022-09-08-choosing-a-postgres-primary-key.mdx b/apps/www/_blog/2022-09-08-choosing-a-postgres-primary-key.mdx index 7b96861b886..497bf573aaf 100644 --- a/apps/www/_blog/2022-09-08-choosing-a-postgres-primary-key.mdx +++ b/apps/www/_blog/2022-09-08-choosing-a-postgres-primary-key.mdx @@ -6,6 +6,7 @@ image: primary-keys/postgres-primary-key.jpg thumb: primary-keys/postgres-primary-key.jpg tags: - postgres + - planetpg date: '2022-09-08' toc_depth: 3 --- diff --git a/apps/www/_blog/2022-10-03-postgres-wasm.mdx b/apps/www/_blog/2022-10-03-postgres-wasm.mdx index 89f2c6947fb..13121a74747 100644 --- a/apps/www/_blog/2022-10-03-postgres-wasm.mdx +++ b/apps/www/_blog/2022-10-03-postgres-wasm.mdx @@ -6,6 +6,7 @@ image: postgres-wasm/postgres-wasm.jpg thumb: postgres-wasm/postgres-wasm.jpg tags: - postgres + - planetpg date: '2022-10-03' toc_depth: 3 --- diff --git a/apps/www/_blog/2022-10-14-postgres-full-text-search-vs-the-rest.mdx b/apps/www/_blog/2022-10-14-postgres-full-text-search-vs-the-rest.mdx index 763143a6c6d..1a587021735 100644 --- a/apps/www/_blog/2022-10-14-postgres-full-text-search-vs-the-rest.mdx +++ b/apps/www/_blog/2022-10-14-postgres-full-text-search-vs-the-rest.mdx @@ -6,6 +6,7 @@ image: postgres-fts-vs-the-rest/postgres-full-text-search.jpg thumb: postgres-fts-vs-the-rest/postgres-full-text-search.jpg tags: - postgres + - planetpg date: '2022-10-14' toc_depth: 3 --- diff --git a/apps/www/_blog/2022-10-27-postgresql-commitfest.mdx b/apps/www/_blog/2022-10-27-postgresql-commitfest.mdx index 0e9a8af3f3f..929650f9591 100644 --- a/apps/www/_blog/2022-10-27-postgresql-commitfest.mdx +++ b/apps/www/_blog/2022-10-27-postgresql-commitfest.mdx @@ -6,6 +6,7 @@ image: what-is-postgres-commitfest/what-is-postgres-commitfest.jpg thumb: what-is-postgres-commitfest/what-is-postgres-commitfest.jpg tags: - postgres + - planetpg date: '2022-10-27' toc_depth: 3 --- diff --git a/apps/www/_blog/2022-11-24-sql-or-nosql-both-with-postgresql.mdx b/apps/www/_blog/2022-11-24-sql-or-nosql-both-with-postgresql.mdx index 5198e23f3df..d36e20d6b0f 100644 --- a/apps/www/_blog/2022-11-24-sql-or-nosql-both-with-postgresql.mdx +++ b/apps/www/_blog/2022-11-24-sql-or-nosql-both-with-postgresql.mdx @@ -6,6 +6,7 @@ image: 2022-11-24-sql-nosql-postgresql/sql-nosql-postgresql.jpg thumb: 2022-11-24-sql-nosql-postgresql/sql-nosql-postgresql.jpg tags: - postgres + - planetpg date: '2022-11-24' toc_depth: 3 --- diff --git a/apps/www/_blog/2022-12-01-transparent-column-encryption-with-postgres.mdx b/apps/www/_blog/2022-12-01-transparent-column-encryption-with-postgres.mdx index 739236e69d8..0759568c18b 100644 --- a/apps/www/_blog/2022-12-01-transparent-column-encryption-with-postgres.mdx +++ b/apps/www/_blog/2022-12-01-transparent-column-encryption-with-postgres.mdx @@ -6,6 +6,7 @@ image: transparent-column-encryption-with-postgres.jpg thumb: transparent-column-encryption-with-postgres.jpg tags: - postgres + - planetpg date: '2022-12-01' toc_depth: 3 --- diff --git a/apps/www/_blog/2022-12-16-new-in-postgres-15.mdx b/apps/www/_blog/2022-12-16-new-in-postgres-15.mdx index 6eb78cee729..dffd813c4e0 100644 --- a/apps/www/_blog/2022-12-16-new-in-postgres-15.mdx +++ b/apps/www/_blog/2022-12-16-new-in-postgres-15.mdx @@ -7,6 +7,7 @@ thumb: lw6-community/pg15.png tags: - postgres - launch-week + - planetpg date: '2022-12-16' toc_depth: 3 --- diff --git a/apps/www/_blog/2022-12-16-pg-graphql-v1.mdx b/apps/www/_blog/2022-12-16-pg-graphql-v1.mdx index d72dcdfd1d4..6104a05411a 100644 --- a/apps/www/_blog/2022-12-16-pg-graphql-v1.mdx +++ b/apps/www/_blog/2022-12-16-pg-graphql-v1.mdx @@ -8,6 +8,7 @@ tags: - graphql - postgres - launch-week + - planetpg date: '2022-12-16' toc_depth: 3 --- diff --git a/apps/www/_blog/2022-12-16-postgres-point-in-time-recovery.mdx b/apps/www/_blog/2022-12-16-postgres-point-in-time-recovery.mdx index 3e9007dbe0b..b53160ee936 100644 --- a/apps/www/_blog/2022-12-16-postgres-point-in-time-recovery.mdx +++ b/apps/www/_blog/2022-12-16-postgres-point-in-time-recovery.mdx @@ -8,6 +8,7 @@ tags: - launch-week - postgres - backups + - planetpg date: '2022-12-16' toc_depth: 3 --- diff --git a/apps/www/_blog/2022-12-16-postgrest-11-prerelease.mdx b/apps/www/_blog/2022-12-16-postgrest-11-prerelease.mdx index 138a576eefc..52a4f18fa44 100644 --- a/apps/www/_blog/2022-12-16-postgrest-11-prerelease.mdx +++ b/apps/www/_blog/2022-12-16-postgrest-11-prerelease.mdx @@ -7,6 +7,7 @@ thumb: lw6-community/postgrest.png tags: - postgres - launch-week + - planetpg date: '2022-12-16' toc_depth: 3 --- 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 141e75ed838..37a6397743f 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 @@ -8,6 +8,7 @@ tags: - launch-week - postgres - encryption + - planetpg date: '2022-12-16' toc_depth: 3 --- diff --git a/apps/www/data/Pricing.json b/apps/www/data/Pricing.json index 7d2dd4e24ae..dd2589b08f3 100644 --- a/apps/www/data/Pricing.json +++ b/apps/www/data/Pricing.json @@ -194,6 +194,7 @@ }, { "title": "Asset transformations", + "tooltip": "Origin images are only relevant for image transformations. Images in the bucket which are not transformed do not count towards this limit.", "tiers": { "free": false, "pro": "Unlimited transformations for 100 origin images, then 5$ per 1000 origin images", diff --git a/apps/www/lib/rss.tsx b/apps/www/lib/rss.tsx index 36fb38f5825..c0409f8393d 100644 --- a/apps/www/lib/rss.tsx +++ b/apps/www/lib/rss.tsx @@ -1,3 +1,4 @@ +import authors from 'lib/authors.json' const dayjs = require('dayjs') var utc = require('dayjs/plugin/utc') var advancedFormat = require('dayjs/plugin/advancedFormat') @@ -17,8 +18,29 @@ const generateRssItem = (post: any): string => { ` } -export const generateRss = (posts: any[]): string => { - return ` +// we generate a main rss.xml flie as well as individual files for +// authors who publish under the `planetpg` tag +export const generateRss = (posts: any[], authorID?: string): string => { + const authorInfo = authors.find((item) => item.author_id === authorID) + + if (authorID) { + return ` + + + 'Postgres | Supabase Blog + https://supabase.com/blog + Latest Postgres news from ${authorInfo?.author} at Supabase + en + ${dayjs(posts[0].date) + .utc() + .format('ddd, DD MMM YYYY HH:hh:mm ZZ')} + + ${posts.map(generateRssItem).join('')} + + +` + } else { + return ` Blog - Supabase @@ -33,4 +55,5 @@ export const generateRss = (posts: any[]): string => { ` + } } diff --git a/apps/www/pages/blog.tsx b/apps/www/pages/blog.tsx index 3e020c9533b..a14bbd8feef 100644 --- a/apps/www/pages/blog.tsx +++ b/apps/www/pages/blog.tsx @@ -24,6 +24,19 @@ export async function getStaticProps() { // rss feed is added via component in render return fs.writeFileSync('./public/rss.xml', rss) + // generate a series of rss feeds for each author (for PlanetPG) + const planetPgPosts = allPostsData.filter((post: any) => post.tags.includes('planetpg')) + const planetPgAuthors = planetPgPosts.map((post: any) => post.author.split(',')) + const uniquePlanetPgAuthors = new Set([].concat(...planetPgAuthors)) + + uniquePlanetPgAuthors.forEach((author) => { + const authorPosts = planetPgPosts.filter((post: any) => post.author.includes(author)) + if (authorPosts.length > 0) { + const authorRss = generateRss(authorPosts, author) + fs.writeFileSync(`./public/planetpg-${author}-rss.xml`, authorRss) + } + }) + return { props: { blogs: allPostsData, diff --git a/apps/www/pages/supasquad.mdx b/apps/www/pages/supasquad.mdx index 012750bedac..e305eab4b73 100644 --- a/apps/www/pages/supasquad.mdx +++ b/apps/www/pages/supasquad.mdx @@ -37,7 +37,7 @@ Spread the word on social channels and help to answer Supabase-related questions ### Builder -Build Supabase examples, blog about them, and them add to the [SupaSquad GitHub Org](https://github.com/supasquad). +Build Supabase examples, blog about them, and add them to the [SupaSquad GitHub Org](https://github.com/supasquad). ### Author diff --git a/apps/www/public/planetpg-angelico_de_los_reyes-rss.xml b/apps/www/public/planetpg-angelico_de_los_reyes-rss.xml new file mode 100644 index 00000000000..84fe8fab257 --- /dev/null +++ b/apps/www/public/planetpg-angelico_de_los_reyes-rss.xml @@ -0,0 +1,40 @@ + + + + 'Postgres | Supabase Blog + https://supabase.com/blog + Latest Postgres news from Angelico de los Reyes at Supabase + en + Thu, 15 Dec 2022 03:03:30 +0000 + + + https://supabase.com/blog/postgres-point-in-time-recovery + Point in Time Recovery is now available for Pro projects + https://supabase.com/blog/postgres-point-in-time-recovery + We're making PITR available for more projects, with a new Dashboard UI that makes it simple to use. + Thu, 15 Dec 2022 03:03:30 +0000 + + + https://supabase.com/blog/continuous-postgresql-backup-walg + Continuous PostgreSQL Backups using WAL-G + https://supabase.com/blog/continuous-postgresql-backup-walg + Have you ever wanted to restore your database's state to a particular moment in time? This post explains how, using WAL-G. + Sun, 02 Aug 2020 02:02:30 +0000 + + + https://supabase.com/blog/postgresql-templates + What are PostgreSQL Templates? + https://supabase.com/blog/postgresql-templates + What are PostgreSQL templates and what are they used for? + Thu, 09 Jul 2020 02:02:30 +0000 + + + https://supabase.com/blog/postgresql-physical-logical-backups + Physical vs Logical Backups in PostgreSQL + https://supabase.com/blog/postgresql-physical-logical-backups + What are physical and logical backups in Postgres? + Tue, 07 Jul 2020 02:02:30 +0000 + + + + diff --git a/apps/www/public/planetpg-ant_wilson-rss.xml b/apps/www/public/planetpg-ant_wilson-rss.xml new file mode 100644 index 00000000000..07cfe4056b8 --- /dev/null +++ b/apps/www/public/planetpg-ant_wilson-rss.xml @@ -0,0 +1,19 @@ + + + + 'Postgres | Supabase Blog + https://supabase.com/blog + Latest Postgres news from Ant Wilson at Supabase + en + Sat, 27 Feb 2021 03:03:30 +0000 + + + https://supabase.com/blog/cracking-postgres-interview + Cracking PostgreSQL Interview Questions + https://supabase.com/blog/cracking-postgres-interview + Understand the top PostgreSQL Interview Questions + Sat, 27 Feb 2021 03:03:30 +0000 + + + + diff --git a/apps/www/public/planetpg-burggraf-rss.xml b/apps/www/public/planetpg-burggraf-rss.xml new file mode 100644 index 00000000000..26233a86f1e --- /dev/null +++ b/apps/www/public/planetpg-burggraf-rss.xml @@ -0,0 +1,26 @@ + + + + 'Postgres | Supabase Blog + https://supabase.com/blog + Latest Postgres news from Mark Burggraf at Supabase + en + Wed, 23 Nov 2022 03:03:30 +0000 + + + https://supabase.com/blog/sql-or-nosql-both-with-postgresql + SQL or NoSQL? Why not use both (with PostgreSQL)? + https://supabase.com/blog/sql-or-nosql-both-with-postgresql + How to turn Postgres into an easy-to-use NoSQL database that retains all the power of SQL + Wed, 23 Nov 2022 03:03:30 +0000 + + + https://supabase.com/blog/postgres-wasm + Postgres WASM by Snaplet and Supabase + https://supabase.com/blog/postgres-wasm + We're open sourcing postgres-wasm, a PostgresQL server that runs inside a browser, with our friends at Snaplet. + Sun, 02 Oct 2022 02:02:30 +0000 + + + + diff --git a/apps/www/public/planetpg-michel-rss.xml b/apps/www/public/planetpg-michel-rss.xml new file mode 100644 index 00000000000..3bcd14a76b8 --- /dev/null +++ b/apps/www/public/planetpg-michel-rss.xml @@ -0,0 +1,26 @@ + + + + 'Postgres | Supabase Blog + https://supabase.com/blog + Latest Postgres news from Michel Pelletier at Supabase + en + Thu, 15 Dec 2022 03:03:30 +0000 + + + https://supabase.com/blog/vault-now-in-beta + Supabase Vault is now in Beta + https://supabase.com/blog/vault-now-in-beta + A Postgres extension to store encrypted secrets and encrypt data. + Thu, 15 Dec 2022 03:03:30 +0000 + + + https://supabase.com/blog/transparent-column-encryption-with-postgres + Transparent Column Encryption with Postgres + https://supabase.com/blog/transparent-column-encryption-with-postgres + Using pgsodium's Transparent Column Encryption to encrypt data and provide your users with row-level encryption. + Wed, 30 Nov 2022 03:03:30 +0000 + + + + diff --git a/apps/www/public/planetpg-oli_rice-rss.xml b/apps/www/public/planetpg-oli_rice-rss.xml new file mode 100644 index 00000000000..77f01e50bdf --- /dev/null +++ b/apps/www/public/planetpg-oli_rice-rss.xml @@ -0,0 +1,26 @@ + + + + 'Postgres | Supabase Blog + https://supabase.com/blog + Latest Postgres news from Oliver Rice at Supabase + en + Thu, 15 Dec 2022 03:03:30 +0000 + + + https://supabase.com/blog/pg-graphql-v1 + pg_graphql v1.0 + https://supabase.com/blog/pg-graphql-v1 + Announcing the v1.0 release of pg_graphql + Thu, 15 Dec 2022 03:03:30 +0000 + + + https://supabase.com/blog/audit + Postgres Auditing in 150 lines of SQL + https://supabase.com/blog/audit + PostgreSQL has a robust set of features which we can leverage to create a generic auditing solution in 150 lines of SQL. + Mon, 07 Mar 2022 03:03:30 +0000 + + + + diff --git a/apps/www/public/planetpg-paul_copplestone-rss.xml b/apps/www/public/planetpg-paul_copplestone-rss.xml new file mode 100644 index 00000000000..bc3d31ea3d3 --- /dev/null +++ b/apps/www/public/planetpg-paul_copplestone-rss.xml @@ -0,0 +1,33 @@ + + + + 'Postgres | Supabase Blog + https://supabase.com/blog + Latest Postgres news from Paul Copplestone at Supabase + en + Mon, 27 Jun 2022 02:02:30 +0000 + + + https://supabase.com/blog/partial-postgresql-data-dumps-with-rls + Partial data dumps using Postgres Row Level Security + https://supabase.com/blog/partial-postgresql-data-dumps-with-rls + Using RLS to create seed files for local PostgreSQL testing. + Mon, 27 Jun 2022 02:02:30 +0000 + + + https://supabase.com/blog/postgres-as-a-cron-server + Postgres as a CRON Server + https://supabase.com/blog/postgres-as-a-cron-server + Running repetitive tasks with your Postgres database. + Fri, 05 Mar 2021 03:03:30 +0000 + + + https://supabase.com/blog/postgresql-views + Postgres Views + https://supabase.com/blog/postgresql-views + Creating and using a view in PostgreSQL. + Wed, 18 Nov 2020 03:03:30 +0000 + + + + diff --git a/apps/www/public/planetpg-pavel-rss.xml b/apps/www/public/planetpg-pavel-rss.xml new file mode 100644 index 00000000000..e6dbbb4c1fe --- /dev/null +++ b/apps/www/public/planetpg-pavel-rss.xml @@ -0,0 +1,26 @@ + + + + 'Postgres | Supabase Blog + https://supabase.com/blog + Latest Postgres news from Pavel Borisov at Supabase + en + Thu, 15 Dec 2022 03:03:30 +0000 + + + https://supabase.com/blog/new-in-postgres-15 + What's new in Postgres 15? + https://supabase.com/blog/new-in-postgres-15 + Describes the release of Postgres 15, new features and reasons to use it + Thu, 15 Dec 2022 03:03:30 +0000 + + + https://supabase.com/blog/postgresql-commitfest + What is PostgreSQL commitfest and how to contribute + https://supabase.com/blog/postgresql-commitfest + A time-tested method for contributing to the core Postgres code + Wed, 26 Oct 2022 02:02:30 +0000 + + + + diff --git a/apps/www/public/planetpg-steve_chavez-rss.xml b/apps/www/public/planetpg-steve_chavez-rss.xml new file mode 100644 index 00000000000..575cafb6b06 --- /dev/null +++ b/apps/www/public/planetpg-steve_chavez-rss.xml @@ -0,0 +1,26 @@ + + + + 'Postgres | Supabase Blog + https://supabase.com/blog + Latest Postgres news from Steve Chavez at Supabase + en + Thu, 15 Dec 2022 03:03:30 +0000 + + + https://supabase.com/blog/postgrest-11-prerelease + PostgREST 11 pre-release + https://supabase.com/blog/postgrest-11-prerelease + Describes new features of PostgREST 11 pre-release + Thu, 15 Dec 2022 03:03:30 +0000 + + + https://supabase.com/blog/roles-postgres-hooks + Protecting reserved roles with PostgreSQL Hooks + https://supabase.com/blog/roles-postgres-hooks + Using Postgres Hooks to protect functionality in your Postgres database. + Thu, 01 Jul 2021 02:02:30 +0000 + + + + diff --git a/apps/www/public/planetpg-victor-rss.xml b/apps/www/public/planetpg-victor-rss.xml new file mode 100644 index 00000000000..e742fb6eec8 --- /dev/null +++ b/apps/www/public/planetpg-victor-rss.xml @@ -0,0 +1,33 @@ + + + + 'Postgres | Supabase Blog + https://supabase.com/blog + Latest Postgres news from Victor at Supabase + en + Thu, 13 Oct 2022 02:02:30 +0000 + + + https://supabase.com/blog/postgres-full-text-search-vs-the-rest + Postgres Full Text Search vs the rest + https://supabase.com/blog/postgres-full-text-search-vs-the-rest + Comparing one of the most popular Postgres features against alternatives + Thu, 13 Oct 2022 02:02:30 +0000 + + + https://supabase.com/blog/choosing-a-postgres-primary-key + Choosing a Postgres Primary Key + https://supabase.com/blog/choosing-a-postgres-primary-key + Turns out the question of which identifier to use as a Primary Key is complicated -- we're going to dive into some of the complexity and inherent trade-offs, and figure things out + Wed, 07 Sep 2022 02:02:30 +0000 + + + https://supabase.com/blog/seen-by-in-postgresql + Implementing "seen by" functionality with Postgres + https://supabase.com/blog/seen-by-in-postgresql + Different approaches for tracking visitor counts with PostgreSQL. + Sun, 17 Jul 2022 02:02:30 +0000 + + + + diff --git a/apps/www/public/rss.xml b/apps/www/public/rss.xml index 215ba2e41d8..2e9e12c7b1b 100644 --- a/apps/www/public/rss.xml +++ b/apps/www/public/rss.xml @@ -5,1036 +5,1078 @@ https://supabase.com Latest news from Supabase en - Thu, 15 Dec 2022 16:04:00 +0000 + Wed, 04 Jan 2023 03:03:30 +0000 + https://supabase.com/blog/supabase-beta-december-2022 + Supabase Beta December 2022 + https://supabase.com/blog/supabase-beta-december-2022 + This month the Beta Update is a Launch Week 6 Special, where we review the cascade of announcements. + Wed, 04 Jan 2023 03:03:30 +0000 + + + https://supabase.com/blog/launch-week-6-hackathon-winners + Launch Week 6 Hackathon Winners + https://supabase.com/blog/launch-week-6-hackathon-winners + Announcing the winners of the Launch Week 6 Hackathon! + Mon, 02 Jan 2023 03:03:30 +0000 + + + https://supabase.com/blog/vault-now-in-beta + Supabase Vault is now in Beta + https://supabase.com/blog/vault-now-in-beta + A Postgres extension to store encrypted secrets and encrypt data. + Thu, 15 Dec 2022 03:03:30 +0000 + + https://supabase.com/blog/postgrest-11-prerelease PostgREST 11 pre-release https://supabase.com/blog/postgrest-11-prerelease Describes new features of PostgREST 11 pre-release - Thu, 15 Dec 2022 16:04:00 +0000 + Thu, 15 Dec 2022 03:03:30 +0000 + + + https://supabase.com/blog/postgres-point-in-time-recovery + Point in Time Recovery is now available for Pro projects + https://supabase.com/blog/postgres-point-in-time-recovery + We're making PITR available for more projects, with a new Dashboard UI that makes it simple to use. + Thu, 15 Dec 2022 03:03:30 +0000 + + + https://supabase.com/blog/pg-graphql-v1 + pg_graphql v1.0 + https://supabase.com/blog/pg-graphql-v1 + Announcing the v1.0 release of pg_graphql + Thu, 15 Dec 2022 03:03:30 +0000 https://supabase.com/blog/new-in-postgres-15 What's new in Postgres 15? https://supabase.com/blog/new-in-postgres-15 Describes the release of Postgres 15, new features and reasons to use it - Thu, 15 Dec 2022 16:04:00 +0000 + Thu, 15 Dec 2022 03:03:30 +0000 + + + https://supabase.com/blog/launch-week-6-wrap-up + Launch Week 6: Wrap Up + https://supabase.com/blog/launch-week-6-wrap-up + That's a wrap on Supabase Launch Week Day 6. Here's everything we shipped in one long blog post. + Thu, 15 Dec 2022 03:03:30 +0000 https://supabase.com/blog/launch-week-6-community-day Community Day https://supabase.com/blog/launch-week-6-community-day Wrapping up Launch Week 6 with contributors, partners, and friends. - Thu, 15 Dec 2022 16:04:00 +0000 + Thu, 15 Dec 2022 03:03:30 +0000 + + + https://supabase.com/blog/custom-domain-names + Custom Domain Names + https://supabase.com/blog/custom-domain-names + Change your Supabase project's domain name to your own domain. + Thu, 15 Dec 2022 03:03:30 +0000 https://supabase.com/blog/postgres-foreign-data-wrappers-rust Supabase Wrappers, a Postgres FDW framework written in Rust https://supabase.com/blog/postgres-foreign-data-wrappers-rust A framework for building Postgres Foreign Data Wrappers which connects to Stripe, Firebase, Clickhouse, and more. - Wed, 14 Dec 2022 16:04:00 +0000 + Wed, 14 Dec 2022 03:03:30 +0000 https://supabase.com/blog/mfa-auth-via-rls Multi-factor Authentication via Row Level Security Enforcement https://supabase.com/blog/mfa-auth-via-rls MFA Auth with enforcement via RLS - Tue, 13 Dec 2022 16:04:00 +0000 - - - https://supabase.com/blog/mfa-auth-via-rls - Multi-factor Authentication via Row Level Security Enforcement - https://supabase.com/blog/mfa-auth-via-rls - MFA Auth with enforcement via RLS - Tue, 13 Dec 2022 23:11:00 +0000 + Tue, 13 Dec 2022 03:03:30 +0000 https://supabase.com/blog/storage-image-resizing-smart-cdn Supabase Storage v2: Image resizing and Smart CDN https://supabase.com/blog/storage-image-resizing-smart-cdn We're introducing new features for Supabase Storage: Image resizing and a Smart CDN. - Mon, 12 Dec 2022 05:05:00 +0000 + Mon, 12 Dec 2022 03:03:30 +0000 https://supabase.com/blog/new-supabase-docs-built-with-nextjs New Supabase Docs, built with Next.js https://supabase.com/blog/new-supabase-docs-built-with-nextjs We've redesigned our Docs and migrated to Next.js - Sun, 11 Dec 2022 16:04:00 +0000 + Sun, 11 Dec 2022 03:03:30 +0000 https://supabase.com/blog/postgres-crdt pg_crdt - an experimental CRDT extension for Postgres https://supabase.com/blog/postgres-crdt Embedding Yjs and Automerge into Postgres for collaborative applications. - Fri, 09 Dec 2022 05:05:00 +0000 + Fri, 09 Dec 2022 03:03:30 +0000 https://supabase.com/blog/who-we-hire Who We Hire at Supabase https://supabase.com/blog/who-we-hire Traits we look for to maintain a culture of shipping fast and often - Thu, 08 Dec 2022 05:05:00 +0000 + Thu, 08 Dec 2022 03:03:30 +0000 https://supabase.com/blog/launch-week-6-hackathon Launch Week 6 Hackathon https://supabase.com/blog/launch-week-6-hackathon Build an Open Source Project, Win $1500 and the Supabase Darkmode Keyboard - Thu, 08 Dec 2022 05:05:00 +0000 + Thu, 08 Dec 2022 03:03:30 +0000 https://supabase.com/blog/supabase-beta-november-2022 Supabase Beta November 2022 https://supabase.com/blog/supabase-beta-november-2022 We are preparing everything for Launch Week 6, but we still had time to ship some goodies this month! - Tue, 06 Dec 2022 05:05:00 +0000 + Tue, 06 Dec 2022 03:03:30 +0000 https://supabase.com/blog/the-supabase-content-storm The Supabase Content Storm https://supabase.com/blog/the-supabase-content-storm We worked with +30 content creators to drop a mountain of content simultaneously. - Mon, 05 Dec 2022 05:05:00 +0000 + Mon, 05 Dec 2022 03:03:30 +0000 https://supabase.com/blog/transparent-column-encryption-with-postgres Transparent Column Encryption with Postgres https://supabase.com/blog/transparent-column-encryption-with-postgres Using pgsodium's Transparent Column Encryption to encrypt data and provide your users with row-level encryption. - Wed, 30 Nov 2022 05:05:00 +0000 + Wed, 30 Nov 2022 03:03:30 +0000 https://supabase.com/blog/sql-or-nosql-both-with-postgresql SQL or NoSQL? Why not use both (with PostgreSQL)? https://supabase.com/blog/sql-or-nosql-both-with-postgresql How to turn Postgres into an easy-to-use NoSQL database that retains all the power of SQL - Wed, 23 Nov 2022 05:05:00 +0000 + Wed, 23 Nov 2022 03:03:30 +0000 https://supabase.com/blog/flutter-authentication-and-authorization-with-rls Flutter Authentication and Authorization with RLS https://supabase.com/blog/flutter-authentication-and-authorization-with-rls Learn how you can secure your Flutter app using Supabase Row Level Security. - Mon, 21 Nov 2022 05:05:00 +0000 + Mon, 21 Nov 2022 03:03:30 +0000 https://supabase.com/blog/fetching-and-caching-supabase-data-in-next-js-server-components Fetching and caching Supabase data in Next.js 13 Server Components https://supabase.com/blog/fetching-and-caching-supabase-data-in-next-js-server-components Next.js 13 introduces new data fetching and caching methods to enable React Server Components and Suspense. - Wed, 16 Nov 2022 05:05:00 +0000 + Wed, 16 Nov 2022 03:03:30 +0000 https://supabase.com/blog/authentication-in-ionic-angular Authentication in Ionic Angular with Supabase https://supabase.com/blog/authentication-in-ionic-angular Learn how to build an Ionic Angular app with authentication, Row Level Security, and Magic Link auth. - Mon, 07 Nov 2022 05:05:00 +0000 + Mon, 07 Nov 2022 03:03:30 +0000 https://supabase.com/blog/supabase-beta-update-october-2022 Supabase Beta October 2022 https://supabase.com/blog/supabase-beta-update-october-2022 New SDKs, quickstarts, Functions tricks, and more. But, more importantly, Launch Week 6️ has a date! - Tue, 01 Nov 2022 04:04:00 +0000 + Tue, 01 Nov 2022 02:02:30 +0000 https://supabase.com/blog/postgresql-commitfest What is PostgreSQL commitfest and how to contribute https://supabase.com/blog/postgresql-commitfest A time-tested method for contributing to the core Postgres code - Wed, 26 Oct 2022 04:04:00 +0000 + Wed, 26 Oct 2022 02:02:30 +0000 https://supabase.com/blog/supabase-flutter-sdk-v1-released supabase-flutter v1 Released https://supabase.com/blog/supabase-flutter-sdk-v1-released We've released supabase-flutter v1. More intuitive way of accessing Supabase from your Flutter application. - Thu, 20 Oct 2022 04:04:00 +0000 + Thu, 20 Oct 2022 02:02:30 +0000 https://supabase.com/blog/supabase-js-v2-released supabase-js v2 Released https://supabase.com/blog/supabase-js-v2-released We've released supabase-js v2. Updated examples, quickstarts, and an improved experience. - Wed, 19 Oct 2022 04:04:00 +0000 + Wed, 19 Oct 2022 02:02:30 +0000 https://supabase.com/blog/postgres-full-text-search-vs-the-rest Postgres Full Text Search vs the rest https://supabase.com/blog/postgres-full-text-search-vs-the-rest Comparing one of the most popular Postgres features against alternatives - Thu, 13 Oct 2022 04:04:00 +0000 + Thu, 13 Oct 2022 02:02:30 +0000 https://supabase.com/blog/supabase-beta-update-september-2022 Supabase Beta September 2022 https://supabase.com/blog/supabase-beta-update-september-2022 We were too focused on clearing out the backlog so we didn't ship anything new last month... or did we?! - Tue, 04 Oct 2022 04:04:00 +0000 + Tue, 04 Oct 2022 02:02:30 +0000 https://supabase.com/blog/postgres-wasm Postgres WASM by Snaplet and Supabase https://supabase.com/blog/postgres-wasm We're open sourcing postgres-wasm, a PostgresQL server that runs inside a browser, with our friends at Snaplet. - Sun, 02 Oct 2022 04:04:00 +0000 + Sun, 02 Oct 2022 02:02:30 +0000 https://supabase.com/blog/choosing-a-postgres-primary-key Choosing a Postgres Primary Key https://supabase.com/blog/choosing-a-postgres-primary-key Turns out the question of which identifier to use as a Primary Key is complicated -- we're going to dive into some of the complexity and inherent trade-offs, and figure things out - Wed, 07 Sep 2022 04:04:00 +0000 + Wed, 07 Sep 2022 02:02:30 +0000 https://supabase.com/blog/supabase-beta-update-august-2022 Supabase Beta August 2022 https://supabase.com/blog/supabase-beta-update-august-2022 Launch Week Special. See everything we shipped, plus winners of the Hackathon and the extended Community Highlights - Tue, 06 Sep 2022 04:04:00 +0000 + Tue, 06 Sep 2022 02:02:30 +0000 https://supabase.com/blog/launch-week-5-hackathon-winners Launch Week 5 Hackathon Winners https://supabase.com/blog/launch-week-5-hackathon-winners Announcing the winners of the Launch Week 5 Hackathon! - Wed, 24 Aug 2022 04:04:00 +0000 + Wed, 24 Aug 2022 02:02:30 +0000 https://supabase.com/blog/building-a-realtime-trello-board-with-supabase-and-angular Building a Realtime Trello Board with Supabase and Angular https://supabase.com/blog/building-a-realtime-trello-board-with-supabase-and-angular Go beyond the hello world example with this real world project. - Tue, 23 Aug 2022 04:04:00 +0000 + Tue, 23 Aug 2022 02:02:30 +0000 https://supabase.com/blog/supabase-vault Supabase Vault https://supabase.com/blog/supabase-vault Today we're announcing Vault, a Postgres extension for managing secrets and encryption inside your database. - Thu, 18 Aug 2022 04:04:00 +0000 + Thu, 18 Aug 2022 02:02:30 +0000 https://supabase.com/blog/postgrest-v10 PostgREST v10: EXPLAIN and Improved Relationship Detection https://supabase.com/blog/postgrest-v10 Today, PostgREST 10 was released. Let's take a look at some of the new features that go hand in hand with supabase-js v2. - Thu, 18 Aug 2022 04:04:00 +0000 + Thu, 18 Aug 2022 02:02:30 +0000 https://supabase.com/blog/pg-jsonschema-a-postgres-extension-for-json-validation pg_jsonschema: JSON Schema support for Postgres https://supabase.com/blog/pg-jsonschema-a-postgres-extension-for-json-validation Today we're releasing pg_jsonschema, a Postgres extension for JSON validation. - Thu, 18 Aug 2022 04:04:00 +0000 + Thu, 18 Aug 2022 02:02:30 +0000 https://supabase.com/blog/launch-week-5-one-more-thing One more thing https://supabase.com/blog/launch-week-5-one-more-thing Let's be honest, it's never just one more thing. - Thu, 18 Aug 2022 04:04:00 +0000 + Thu, 18 Aug 2022 02:02:30 +0000 https://supabase.com/blog/launch-week-5-community-day Community Day https://supabase.com/blog/launch-week-5-community-day Wrapping up Launch Week 5 with contributors, partners, and friends. - Thu, 18 Aug 2022 04:04:00 +0000 + Thu, 18 Aug 2022 02:02:30 +0000 https://supabase.com/blog/supabase-realtime-multiplayer-general-availability Realtime: Multiplayer Edition https://supabase.com/blog/supabase-realtime-multiplayer-general-availability Announcing the general availability of Realtime's Broadcast and Presence. - Wed, 17 Aug 2022 04:04:00 +0000 + Wed, 17 Aug 2022 02:02:30 +0000 https://supabase.com/blog/supabase-soc2 Supabase is SOC2 compliant https://supabase.com/blog/supabase-soc2 Supabase is now SOC2 compliant. Learn how we got here and what it means for our customers. - Tue, 16 Aug 2022 04:04:00 +0000 + Tue, 16 Aug 2022 02:02:30 +0000 https://supabase.com/blog/supabase-js-v2 supabase-js v2 https://supabase.com/blog/supabase-js-v2 A look at supabase-js v2, which brings type support and focuses on quality-of-life improvements for developers. - Mon, 15 Aug 2022 04:04:00 +0000 + Mon, 15 Aug 2022 02:02:30 +0000 https://supabase.com/blog/supabase-cli-v1-and-admin-api-beta Supabase CLI v1 and Management API Beta https://supabase.com/blog/supabase-cli-v1-and-admin-api-beta We are moving Supabase CLI v1 out of beta, and releasing Management API beta. - Sun, 14 Aug 2022 04:04:00 +0000 + Sun, 14 Aug 2022 02:02:30 +0000 https://supabase.com/blog/supabase-series-b Supabase Series B https://supabase.com/blog/supabase-series-b Supabase raised $80M in May, bringing our total funding to $116M. - Thu, 11 Aug 2022 04:04:00 +0000 + Thu, 11 Aug 2022 02:02:30 +0000 https://supabase.com/blog/launch-week-5-hackathon Launch Week 5 Hackathon https://supabase.com/blog/launch-week-5-hackathon Build to win $1500 - Friday 12th to Monday 21st August 2022 - Tue, 09 Aug 2022 04:04:00 +0000 + Tue, 09 Aug 2022 02:02:30 +0000 https://supabase.com/blog/slack-consolidate-slackbot-to-consolidate-messages Slack Consolidate: a slackbot built with Python and Supabase https://supabase.com/blog/slack-consolidate-slackbot-to-consolidate-messages A slackbot to consolidate messages from different channels using Supabase, Slack SDK and Python - Mon, 08 Aug 2022 04:04:00 +0000 + Mon, 08 Aug 2022 02:02:30 +0000 https://supabase.com/blog/supabase-beta-update-july-2022 Supabase Beta July 2022 https://supabase.com/blog/supabase-beta-update-july-2022 Launch Week Golden Tickets, Flutter SDK 1.0 Developer Preview and more... - Tue, 02 Aug 2022 04:04:00 +0000 + Tue, 02 Aug 2022 02:02:30 +0000 https://supabase.com/blog/supabase-flutter-sdk-1-developer-preview Supabase Flutter SDK 1.0 Developer Preview https://supabase.com/blog/supabase-flutter-sdk-1-developer-preview Supabase Flutter SDK is getting a major update and we need your help making it better. - Mon, 01 Aug 2022 04:04:00 +0000 + Mon, 01 Aug 2022 02:02:30 +0000 https://supabase.com/blog/seen-by-in-postgresql Implementing "seen by" functionality with Postgres https://supabase.com/blog/seen-by-in-postgresql Different approaches for tracking visitor counts with PostgreSQL. - Sun, 17 Jul 2022 04:04:00 +0000 + Sun, 17 Jul 2022 02:02:30 +0000 https://supabase.com/blog/supabase-auth-helpers-with-sveltekit-support Revamped Auth Helpers for Supabase (with SvelteKit support) https://supabase.com/blog/supabase-auth-helpers-with-sveltekit-support Supabase Auth Helpers now have improved developer experience, Sveltekit support, and more. - Tue, 12 Jul 2022 04:04:00 +0000 + Tue, 12 Jul 2022 02:02:30 +0000 https://supabase.com/blog/beta-update-june-2022 Supabase Beta June 2022 https://supabase.com/blog/beta-update-june-2022 Auth Helpers, Unlimited Free Projects, CLI and more... - Tue, 05 Jul 2022 04:04:00 +0000 + Tue, 05 Jul 2022 02:02:30 +0000 https://supabase.com/blog/flutter-tutorial-building-a-chat-app Flutter Tutorial: building a Flutter chat app https://supabase.com/blog/flutter-tutorial-building-a-chat-app Learn how to build a Flutter chat app with open source and scalable backend (inc. auth, realtime, database, and more). - Wed, 29 Jun 2022 04:04:00 +0000 + Wed, 29 Jun 2022 02:02:30 +0000 https://supabase.com/blog/visualizing-supabase-data-using-metabase Visualizing Supabase Data using Metabase https://supabase.com/blog/visualizing-supabase-data-using-metabase How to create different kinds of charts out of data stored in Supabase using Metabase. - Tue, 28 Jun 2022 04:04:00 +0000 + Tue, 28 Jun 2022 02:02:30 +0000 https://supabase.com/blog/partial-postgresql-data-dumps-with-rls Partial data dumps using Postgres Row Level Security https://supabase.com/blog/partial-postgresql-data-dumps-with-rls Using RLS to create seed files for local PostgreSQL testing. - Mon, 27 Jun 2022 04:04:00 +0000 + Mon, 27 Jun 2022 02:02:30 +0000 https://supabase.com/blog/loading-data-supabase-python Python data loading with Supabase https://supabase.com/blog/loading-data-supabase-python An example of how to load data into Supabase using supabase-py - Thu, 16 Jun 2022 04:04:00 +0000 + Thu, 16 Jun 2022 02:02:30 +0000 https://supabase.com/blog/beta-update-may-2022 Supabase Beta May 2022 https://supabase.com/blog/beta-update-may-2022 Product and community updates including wildcard auth redirects, edge functions with webhooks, and Prometheus endpoints for everybody. - Tue, 31 May 2022 04:04:00 +0000 + Tue, 31 May 2022 02:02:30 +0000 https://supabase.com/blog/how-supabase-accelerates-development-of-all-pull-together How Mike Lyndon is using Supabase to accelerate development of AllPullTogether https://supabase.com/blog/how-supabase-accelerates-development-of-all-pull-together Mike Lyndon is learning web development as he builds AllPullTogether, and Supabase is helping him accelerate what he can accomplish. - Wed, 25 May 2022 04:04:00 +0000 + Wed, 25 May 2022 02:02:30 +0000 https://supabase.com/blog/partner-gallery-works-with-supabase Works With Supabase - announcing our Partner Gallery https://supabase.com/blog/partner-gallery-works-with-supabase Introducing our Partner Gallery - open source and made with Supabase. - Tue, 19 Apr 2022 04:04:00 +0000 + Tue, 19 Apr 2022 02:02:30 +0000 https://supabase.com/blog/bring-the-func-hackathon-winners Bring the Func Hackathon Winners 2022 https://supabase.com/blog/bring-the-func-hackathon-winners Celebrating many amazing OSS Hackathon projects using GraphQL and Edge Functions. - Sun, 17 Apr 2022 04:04:00 +0000 + Sun, 17 Apr 2022 02:02:30 +0000 https://supabase.com/blog/beta-update-march-2022 Supabase Beta March 2022 https://supabase.com/blog/beta-update-march-2022 Functions, GraphQL, and much more. - Thu, 14 Apr 2022 04:04:00 +0000 + Thu, 14 Apr 2022 02:02:30 +0000 https://supabase.com/blog/supabrew Supabrew - Never Code Thirsty https://supabase.com/blog/supabrew A light and refreshing non-alcoholic beer for devs. - Fri, 01 Apr 2022 04:04:00 +0000 + Fri, 01 Apr 2022 02:02:30 +0000 https://supabase.com/blog/supabase-realtime-with-multiplayer-features Supabase Realtime, with Multiplayer Features https://supabase.com/blog/supabase-realtime-with-multiplayer-features Today we're announced Realtime, with multiplayer features. Realtime enables broadcast, presence, and listening to database changes delivered over WebSockets. - Fri, 01 Apr 2022 04:04:00 +0000 + Fri, 01 Apr 2022 02:02:30 +0000 https://supabase.com/blog/hackathon-bring-the-func Hackathon: Bring the Func(🕺) https://supabase.com/blog/hackathon-bring-the-func Build open-source projects with our latest features, win limited edition swag and plant a tree! - Fri, 01 Apr 2022 04:04:00 +0000 + Fri, 01 Apr 2022 02:02:30 +0000 https://supabase.com/blog/supabase-edge-functions Edge Functions are now available in Supabase https://supabase.com/blog/supabase-edge-functions Today we're launching Edge Functions. Edge Functions let you execute Typescript code close to your users, no matter where they're located. - Wed, 30 Mar 2022 04:04:00 +0000 + Wed, 30 Mar 2022 02:02:30 +0000 https://supabase.com/blog/supabase-enterprise Introducing Supabase Enterprise https://supabase.com/blog/supabase-enterprise Today we are releasing Supabase Enterprise, a suite of features to scale your project. - Tue, 29 Mar 2022 04:04:00 +0000 + Tue, 29 Mar 2022 02:02:30 +0000 https://supabase.com/blog/graphql-now-available GraphQL is now available in Supabase https://supabase.com/blog/graphql-now-available GraphQL support is now in general availability on the Supabase platform via our open source PostgreSQL extension, pg_graphql (beta). - Mon, 28 Mar 2022 04:04:00 +0000 + Mon, 28 Mar 2022 02:02:30 +0000 https://supabase.com/blog/community-day-lw4 Community Day https://supabase.com/blog/community-day-lw4 Kicking off Launch Week 4 with contributors, partners, and friends. - Sun, 27 Mar 2022 04:04:00 +0000 + Sun, 27 Mar 2022 02:02:30 +0000 https://supabase.com/blog/supabase-launch-week-four Supabase Launch Week 4 https://supabase.com/blog/supabase-launch-week-four Launch Week 4: One new feature every day for an entire week. Starting Monday 28th March. - Thu, 24 Mar 2022 04:04:00 +0000 + Thu, 24 Mar 2022 02:02:30 +0000 https://supabase.com/blog/should-i-open-source-my-company Should I Open Source my Company? https://supabase.com/blog/should-i-open-source-my-company The unexpected upsides of building in public - Thu, 24 Mar 2022 04:04:00 +0000 + Thu, 24 Mar 2022 02:02:30 +0000 https://supabase.com/blog/audit Postgres Auditing in 150 lines of SQL https://supabase.com/blog/audit PostgreSQL has a robust set of features which we can leverage to create a generic auditing solution in 150 lines of SQL. - Mon, 07 Mar 2022 05:05:00 +0000 + Mon, 07 Mar 2022 03:03:30 +0000 https://supabase.com/blog/supabase-beta-january-2022 Supabase Beta January 2022 https://supabase.com/blog/supabase-beta-january-2022 New auth providers, SMS providers, and new videos. - Mon, 21 Feb 2022 05:05:00 +0000 + Mon, 21 Feb 2022 03:03:30 +0000 https://supabase.com/blog/supabase-beta-december-2021 Supabase Beta December 2021 https://supabase.com/blog/supabase-beta-december-2021 New crypto extension, Postgres videos, and a bunch of cool integrations. - Wed, 19 Jan 2022 05:05:00 +0000 + Wed, 19 Jan 2022 03:03:30 +0000 https://supabase.com/blog/product-hunt-golden-kitty-awards-2021 Golden Kitty Awards Ceremony Watch Party with Supabase https://supabase.com/blog/product-hunt-golden-kitty-awards-2021 Hang out with us while watching the Product Hunt Golden Kitty Awards Ceremony - Wed, 19 Jan 2022 05:05:00 +0000 + Wed, 19 Jan 2022 03:03:30 +0000 https://supabase.com/blog/holiday-hackdays-winners-2021 Holiday Hackdays Winners 2021 https://supabase.com/blog/holiday-hackdays-winners-2021 Celebrating many amazing projects submitted to our Holiday Hackdays Hackathon. - Thu, 16 Dec 2021 05:05:00 +0000 + Thu, 16 Dec 2021 03:03:30 +0000 https://supabase.com/blog/beta-november-2021-launch-week-recap Supabase Beta November 2021: Launch Week Recap https://supabase.com/blog/beta-november-2021-launch-week-recap We wrapped up November with Supabase's third Launch Week. Here's all the awesome stuff that got shipped ... - Tue, 14 Dec 2021 05:05:00 +0000 + Tue, 14 Dec 2021 03:03:30 +0000 https://supabase.com/blog/supabase-holiday-hackdays-hackathon Kicking off the Holiday Hackdays https://supabase.com/blog/supabase-holiday-hackdays-hackathon Build cool stuff and celebrate open-source software with us during the Holiday Hackdays! - Thu, 02 Dec 2021 05:05:00 +0000 + Thu, 02 Dec 2021 03:03:30 +0000 https://supabase.com/blog/pg-graphql pg_graphql: A GraphQL extension for PostgreSQL https://supabase.com/blog/pg-graphql GraphQL support is in development for PostgreSQL + Supabase. - Thu, 02 Dec 2021 05:05:00 +0000 + Thu, 02 Dec 2021 03:03:30 +0000 https://supabase.com/blog/launch-week-three-friday-five-more-things Five more things https://supabase.com/blog/launch-week-three-friday-five-more-things It's never just one more thing! - Thu, 02 Dec 2021 05:05:00 +0000 + Thu, 02 Dec 2021 03:03:30 +0000 https://supabase.com/blog/supabase-acquires-logflare Supabase acquires Logflare https://supabase.com/blog/supabase-acquires-logflare Today, we're ecstatic to announce that Logflare is joining Supabase. - Wed, 01 Dec 2021 05:05:00 +0000 + Wed, 01 Dec 2021 03:03:30 +0000 https://supabase.com/blog/realtime-row-level-security-in-postgresql Realtime Postgres RLS now available on Supabase https://supabase.com/blog/realtime-row-level-security-in-postgresql Realtime database changes are now broadcast to authenticated users, respecting the same PostgreSQL policies that you use for Row Level Security. - Tue, 30 Nov 2021 05:05:00 +0000 + Tue, 30 Nov 2021 03:03:30 +0000 https://supabase.com/blog/supabase-studio Supabase Studio https://supabase.com/blog/supabase-studio The same Dashboard that you're using on our Platform is now available for local development and Self-Hosting. - Mon, 29 Nov 2021 05:05:00 +0000 + Mon, 29 Nov 2021 03:03:30 +0000 https://supabase.com/blog/community-day-lw3 Community Day https://supabase.com/blog/community-day-lw3 Kicking off launch week by highlighting the communities around Supabase. - Sun, 28 Nov 2021 05:05:00 +0000 + Sun, 28 Nov 2021 03:03:30 +0000 https://supabase.com/blog/whats-new-in-postgres-14 New in PostgreSQL 14: What every developer should know https://supabase.com/blog/whats-new-in-postgres-14 A quick look at some new features and functionality in PostgreSQL 14. - Sat, 27 Nov 2021 05:05:00 +0000 + Sat, 27 Nov 2021 03:03:30 +0000 https://supabase.com/blog/postgrest-9 PostgREST 9 https://supabase.com/blog/postgrest-9 New features and updates in PostgREST version 9. - Fri, 26 Nov 2021 05:05:00 +0000 + Fri, 26 Nov 2021 03:03:30 +0000 https://supabase.com/blog/supabase-launch-week-the-trilogy Supabase Launch Week III: Holiday Special https://supabase.com/blog/supabase-launch-week-the-trilogy Tis the season to be shipping. - Thu, 25 Nov 2021 05:05:00 +0000 + Thu, 25 Nov 2021 03:03:30 +0000 https://supabase.com/blog/supabase-how-we-launch How we launch at Supabase https://supabase.com/blog/supabase-how-we-launch The history and methodology of Supabase Launch Week. - Thu, 25 Nov 2021 05:05:00 +0000 + Thu, 25 Nov 2021 03:03:30 +0000 https://supabase.com/blog/supabase-beta-october-2021 Supabase Beta October 2021 https://supabase.com/blog/supabase-beta-october-2021 Three new Auth providers, multi-schema support, and we're gearing up for another Launch Week. - Sat, 06 Nov 2021 04:04:00 +0000 + Sat, 06 Nov 2021 02:02:30 +0000 https://supabase.com/blog/supabase-series-a Supabase $30m Series A https://supabase.com/blog/supabase-series-a Supabase just raised $30M, bringing our total funding to $36M. - Wed, 27 Oct 2021 04:04:00 +0000 + Wed, 27 Oct 2021 02:02:30 +0000 https://supabase.com/blog/replenysh-time-to-value-in-less-than-24-hours Replenysh uses Supabase to implement OTP in less than 24-hours https://supabase.com/blog/replenysh-time-to-value-in-less-than-24-hours Learn how Replenysh uses Supabase to power the circular economy, redefining how brands interact with their customers and products. - Mon, 18 Oct 2021 04:04:00 +0000 + Mon, 18 Oct 2021 02:02:30 +0000 https://supabase.com/blog/hacktoberfest-hackathon-winners-2021 Hacktoberfest Hackathon Winners 2021 https://supabase.com/blog/hacktoberfest-hackathon-winners-2021 Celebrating many amazing projects submitted to our Hacktoberfest Hackathon. - Wed, 13 Oct 2021 04:04:00 +0000 + Wed, 13 Oct 2021 02:02:30 +0000 https://supabase.com/blog/supabase-beta-sept-2021 Supabase Beta Sept 2021 https://supabase.com/blog/supabase-beta-sept-2021 Hackathon, Aborting request, UI updates, and now Hiring. - Sun, 03 Oct 2021 04:04:00 +0000 + Sun, 03 Oct 2021 02:02:30 +0000 https://supabase.com/blog/supabase-hacktoberfest-hackathon-2021 Supabase Hacktoberfest Hackathon 2021 https://supabase.com/blog/supabase-hacktoberfest-hackathon-2021 We're running another Supabase Hackathon during Hacktoberfest! - Mon, 27 Sep 2021 04:04:00 +0000 + Mon, 27 Sep 2021 02:02:30 +0000 https://supabase.com/blog/supabase-beta-august-2021 Supabase Beta August 2021 https://supabase.com/blog/supabase-beta-august-2021 Fundraising, Realtime Security, custom SMS templates, and deployments in South Korea. - Thu, 09 Sep 2021 04:04:00 +0000 + Thu, 09 Sep 2021 02:02:30 +0000 https://supabase.com/blog/supabase-beta-july-2021 Supabase Beta July 2021 https://supabase.com/blog/supabase-beta-july-2021 Discord Logins, Vercel Integration, Full text search, and OAuth guides. - Wed, 11 Aug 2021 04:04:00 +0000 + Wed, 11 Aug 2021 02:02:30 +0000 https://supabase.com/blog/hackathon-winners Open Source Hackathon Winners https://supabase.com/blog/hackathon-winners Let the medal ceremony begin for the best projects submitted during the Supabase Hackathon. - Sun, 08 Aug 2021 04:04:00 +0000 + Sun, 08 Aug 2021 02:02:30 +0000 https://supabase.com/blog/supabase-swag-store Supabase Swag Store https://supabase.com/blog/supabase-swag-store Today we are officially launching the Supabase Swag Store. - Thu, 29 Jul 2021 04:04:00 +0000 + Thu, 29 Jul 2021 02:02:30 +0000 https://supabase.com/blog/supabase-functions-updates Updates for Supabase Functions https://supabase.com/blog/supabase-functions-updates The question on everyone's mind - are we launching Supabase Functions? Well, it's complicated. - Thu, 29 Jul 2021 04:04:00 +0000 + Thu, 29 Jul 2021 02:02:30 +0000 https://supabase.com/blog/1-the-supabase-hackathon The Supabase Hackathon https://supabase.com/blog/1-the-supabase-hackathon A whole week of Hacking for Fun and Prizes. - Thu, 29 Jul 2021 04:04:00 +0000 + Thu, 29 Jul 2021 02:02:30 +0000 https://supabase.com/blog/supabase-reports-and-metrics Supabase Reports and Metrics https://supabase.com/blog/supabase-reports-and-metrics We're exposing a full set of metrics in your projects, so that you can build better (and faster) products for your users. - Wed, 28 Jul 2021 04:04:00 +0000 + Wed, 28 Jul 2021 02:02:30 +0000 https://supabase.com/blog/supabase-auth-passwordless-sms-login Supabase Auth v2: Phone Auth now available https://supabase.com/blog/supabase-auth-passwordless-sms-login Phone Auth is available today on all new and existing Supabase projects. - Tue, 27 Jul 2021 04:04:00 +0000 + Tue, 27 Jul 2021 02:02:30 +0000 https://supabase.com/blog/mobbin-supabase-200000-users Mobbin uses Supabase to authenticate 200,000 users https://supabase.com/blog/mobbin-supabase-200000-users Learn how Mobbin migrated 200,000 users from Firebase for a better authentication experience. - Tue, 27 Jul 2021 04:04:00 +0000 + Tue, 27 Jul 2021 02:02:30 +0000 https://supabase.com/blog/storage-beta Supabase Storage now in Beta https://supabase.com/blog/storage-beta Supabase Storage moves into Beta. - Mon, 26 Jul 2021 04:04:00 +0000 + Mon, 26 Jul 2021 02:02:30 +0000 https://supabase.com/blog/spot-flutter-with-postgres Spot: a video sharing app built with Flutter https://supabase.com/blog/spot-flutter-with-postgres Spot is a geolocation-based video-sharing app with some social networking features. - Mon, 26 Jul 2021 04:04:00 +0000 + Mon, 26 Jul 2021 02:02:30 +0000 https://supabase.com/blog/supabase-postgres-13 Supabase is now on Postgres 13.3 https://supabase.com/blog/supabase-postgres-13 From today, new Supabase projects will be on a version of Supabase Postgres that runs on Postgres 13.3. - Sun, 25 Jul 2021 04:04:00 +0000 + Sun, 25 Jul 2021 02:02:30 +0000 https://supabase.com/blog/supabase-community-day Supabase Community Day https://supabase.com/blog/supabase-community-day Community Day - Sun, 25 Jul 2021 04:04:00 +0000 + Sun, 25 Jul 2021 02:02:30 +0000 https://supabase.com/blog/epsilon3-self-hosting Epsilon3 Self-Host Supabase To Revolutionize Space Operations https://supabase.com/blog/epsilon3-self-hosting Learn how the team at Epsilon3 use Supabase to help teams execute secure and reliable operations in an industry that project spend runs into the billions. - Sun, 25 Jul 2021 04:04:00 +0000 + Sun, 25 Jul 2021 02:02:30 +0000 https://supabase.com/blog/supabase-launch-week-sql Supabase Launch Week II: The SQL https://supabase.com/blog/supabase-launch-week-sql Five days of Supabase. Again. - Wed, 21 Jul 2021 04:04:00 +0000 + Wed, 21 Jul 2021 02:02:30 +0000 https://supabase.com/blog/roles-postgres-hooks Protecting reserved roles with PostgreSQL Hooks https://supabase.com/blog/roles-postgres-hooks Using Postgres Hooks to protect functionality in your Postgres database. - Thu, 01 Jul 2021 04:04:00 +0000 + Thu, 01 Jul 2021 02:02:30 +0000 https://supabase.com/blog/supabase-beta-june-2021 Supabase Beta June 2021 https://supabase.com/blog/supabase-beta-june-2021 Discord Logins, Vercel Integration, Full text search, and OAuth guides. - Tue, 01 Jun 2021 04:04:00 +0000 + Tue, 01 Jun 2021 02:02:30 +0000 https://supabase.com/blog/supabase-beta-may-2021 Supabase Beta May 2021 https://supabase.com/blog/supabase-beta-may-2021 Apple & Twitter Logins, Supabase Grid, Go & Swift Libraries. - Tue, 01 Jun 2021 04:04:00 +0000 + Tue, 01 Jun 2021 02:02:30 +0000 https://supabase.com/blog/supabase-beta-april-2021 Supabase Beta April 2021 https://supabase.com/blog/supabase-beta-april-2021 Supabase "gardening" - stability, security, and community support. - Tue, 04 May 2021 04:04:00 +0000 + Tue, 04 May 2021 02:02:30 +0000 https://supabase.com/blog/supabase-beta-march-2021 Supabase Beta March 2021 https://supabase.com/blog/supabase-beta-march-2021 Launch week, Storage, Supabase CLI, Connection Pooling, Supabase UI, and Pricing. - Mon, 05 Apr 2021 04:04:00 +0000 + Mon, 05 Apr 2021 02:02:30 +0000 https://supabase.com/blog/supabase-workflows Workflows are coming to Supabase https://supabase.com/blog/supabase-workflows Functions are great, but you know what's better? - Thu, 01 Apr 2021 04:04:00 +0000 + Thu, 01 Apr 2021 02:02:30 +0000 https://supabase.com/blog/supabase-pgbouncer PgBouncer is now available in Supabase https://supabase.com/blog/supabase-pgbouncer Better support for Serverless and Postgres. - Thu, 01 Apr 2021 04:04:00 +0000 + Thu, 01 Apr 2021 02:02:30 +0000 https://supabase.com/blog/supabase-dot-com Supabase Dot Com https://supabase.com/blog/supabase-dot-com The Supabase Domain name is changing. - Thu, 01 Apr 2021 04:04:00 +0000 + Thu, 01 Apr 2021 02:02:30 +0000 https://supabase.com/blog/supabase-nft-marketplace Supabase Launches NFT Marketplace https://supabase.com/blog/supabase-nft-marketplace A fully encrypted NFT platform to protect and transact your digital assets - Thu, 01 Apr 2021 04:04:00 +0000 + Thu, 01 Apr 2021 02:02:30 +0000 https://supabase.com/blog/supabase-cli Supabase CLI https://supabase.com/blog/supabase-cli Local development, database migrations, and self-hosting. - Wed, 31 Mar 2021 04:04:00 +0000 + Wed, 31 Mar 2021 02:02:30 +0000 https://supabase.com/blog/supabase-storage Storage is now available in Supabase https://supabase.com/blog/supabase-storage Launching Supabase Storage and how you can use it in your apps - Tue, 30 Mar 2021 04:04:00 +0000 + Tue, 30 Mar 2021 02:02:30 +0000 https://supabase.com/blog/pricing Supabase Beta Pricing https://supabase.com/blog/pricing Supabase launches Beta pricing structure - Mon, 29 Mar 2021 04:04:00 +0000 + Mon, 29 Mar 2021 02:02:30 +0000 https://supabase.com/blog/launch-week Launch week https://supabase.com/blog/launch-week Five days of Supabase. - Wed, 24 Mar 2021 04:04:00 +0000 + Wed, 24 Mar 2021 02:02:30 +0000 https://supabase.com/blog/angels-of-supabase Angels of Supabase https://supabase.com/blog/angels-of-supabase Meet the investors of Supabase. - Wed, 24 Mar 2021 04:04:00 +0000 + Wed, 24 Mar 2021 02:02:30 +0000 https://supabase.com/blog/In-The-Loop Developers stay up to date with intheloop.dev https://supabase.com/blog/In-The-Loop Learn why Kevin is building intheloop.dev with Supabase - Mon, 22 Mar 2021 04:04:00 +0000 + Mon, 22 Mar 2021 02:02:30 +0000 https://supabase.com/blog/using-supabase-replit Using Supabase in Replit https://supabase.com/blog/using-supabase-replit Free hosted relational database from within your node.js repl - Thu, 11 Mar 2021 05:05:00 +0000 + Thu, 11 Mar 2021 03:03:30 +0000 https://supabase.com/blog/toad-a-link-shortener-with-simple-apis-for-low-coders Toad, a link shortener with simple APIs for low-coders https://supabase.com/blog/toad-a-link-shortener-with-simple-apis-for-low-coders An easy-to-use link shortening tool with simple APIs - Mon, 08 Mar 2021 05:05:00 +0000 + Mon, 08 Mar 2021 03:03:30 +0000 https://supabase.com/blog/postgres-as-a-cron-server Postgres as a CRON Server https://supabase.com/blog/postgres-as-a-cron-server Running repetitive tasks with your Postgres database. - Fri, 05 Mar 2021 05:05:00 +0000 + Fri, 05 Mar 2021 03:03:30 +0000 https://supabase.com/blog/supabase-beta-february-2021 Supabase Beta February 2021 https://supabase.com/blog/supabase-beta-february-2021 One year of building. - Tue, 02 Mar 2021 05:05:00 +0000 + Tue, 02 Mar 2021 03:03:30 +0000 https://supabase.com/blog/cracking-postgres-interview Cracking PostgreSQL Interview Questions https://supabase.com/blog/cracking-postgres-interview Understand the top PostgreSQL Interview Questions - Sat, 27 Feb 2021 05:05:00 +0000 + Sat, 27 Feb 2021 03:03:30 +0000 https://supabase.com/blog/case-study-roboflow Roboflow.com choose Supabase to power Paint.wtf leaderboard https://supabase.com/blog/case-study-roboflow Learn how Roboflow.com used Supabase to build their Paint.wtf leaderboard - Tue, 09 Feb 2021 05:05:00 +0000 + Tue, 09 Feb 2021 03:03:30 +0000 https://supabase.com/blog/supabase-beta-january-2021 Supabase Beta January 2021 https://supabase.com/blog/supabase-beta-january-2021 Eleven months of building. - Tue, 02 Feb 2021 05:05:00 +0000 + Tue, 02 Feb 2021 03:03:30 +0000 https://supabase.com/blog/supabase-beta-december-2020 Supabase Beta December 2020 https://supabase.com/blog/supabase-beta-december-2020 Ten months of building. - Sat, 02 Jan 2021 05:05:00 +0000 + Sat, 02 Jan 2021 03:03:30 +0000 https://supabase.com/blog/supabase-dashboard-performance Making the Supabase Dashboard Supa-fast https://supabase.com/blog/supabase-dashboard-performance Improving the performance of the Supabase dashboard - Sun, 13 Dec 2020 05:05:00 +0000 + Sun, 13 Dec 2020 03:03:30 +0000 https://supabase.com/blog/supabase-striveschool Supabase Partners With Strive School To Help Teach Open Source https://supabase.com/blog/supabase-striveschool Supabase Partners With Strive School To Help Teach Open Source To The Next Generation Of Developers - Wed, 02 Dec 2020 05:05:00 +0000 + Wed, 02 Dec 2020 03:03:30 +0000 https://supabase.com/blog/case-study-xendit Xendit Built a Counter-Fraud Watchlist for the Fintech Industry https://supabase.com/blog/case-study-xendit See how Xendit use Supabase to build a full-text search engine. - Wed, 02 Dec 2020 05:05:00 +0000 + Wed, 02 Dec 2020 03:03:30 +0000 https://supabase.com/blog/case-study-tayfa TAYFA Built a No-Code Website Builder in Seven Days https://supabase.com/blog/case-study-tayfa See how Tayfa went from idea to paying customer in less than 30 days. - Wed, 02 Dec 2020 05:05:00 +0000 + Wed, 02 Dec 2020 03:03:30 +0000 https://supabase.com/blog/case-study-monitoro Monitoro Built a Web Crawler Handling Millions of API Requests https://supabase.com/blog/case-study-monitoro See how Monitoro built an automated scraping platform using Supabase. - Wed, 02 Dec 2020 05:05:00 +0000 + Wed, 02 Dec 2020 03:03:30 +0000 https://supabase.com/blog/supabase-alpha-november-2020 Supabase Alpha November 2020 https://supabase.com/blog/supabase-alpha-november-2020 Nine months of building. - Tue, 01 Dec 2020 05:05:00 +0000 + Tue, 01 Dec 2020 03:03:30 +0000 https://supabase.com/blog/postgresql-views Postgres Views https://supabase.com/blog/postgresql-views Creating and using a view in PostgreSQL. - Wed, 18 Nov 2020 05:05:00 +0000 + Wed, 18 Nov 2020 03:03:30 +0000 https://supabase.com/blog/supabase-alpha-october-2020 Supabase Alpha October 2020 https://supabase.com/blog/supabase-alpha-october-2020 Eight months of building. - Mon, 02 Nov 2020 05:05:00 +0000 + Mon, 02 Nov 2020 03:03:30 +0000 https://supabase.com/blog/improved-dx Supabase.js 1.0 https://supabase.com/blog/improved-dx We're releasing a new version of our Supabase client with some awesome new improvements. - Fri, 30 Oct 2020 04:04:00 +0000 + Fri, 30 Oct 2020 02:02:30 +0000 https://supabase.com/blog/supabase-alpha-september-2020 Supabase Alpha September 2020 https://supabase.com/blog/supabase-alpha-september-2020 Seven months of building. - Sat, 03 Oct 2020 04:04:00 +0000 + Sat, 03 Oct 2020 02:02:30 +0000 https://supabase.com/blog/supabase-hacktoberfest-2020 Supabase Hacktoberfest 2020 https://supabase.com/blog/supabase-hacktoberfest-2020 Join us for a celebration of open source software and learn how to contribute to Supabase. - Fri, 11 Sep 2020 04:04:00 +0000 + Fri, 11 Sep 2020 02:02:30 +0000 https://supabase.com/blog/supabase-alpha-august-2020 Supabase Alpha August 2020 https://supabase.com/blog/supabase-alpha-august-2020 Six months of building - Thu, 03 Sep 2020 04:04:00 +0000 + Thu, 03 Sep 2020 02:02:30 +0000 https://supabase.com/blog/supabase-auth Supabase Auth https://supabase.com/blog/supabase-auth Authenticate and authorize your users with Supabase Auth - Wed, 05 Aug 2020 04:04:00 +0000 + Wed, 05 Aug 2020 02:02:30 +0000 https://supabase.com/blog/supabase-alpha-july-2020 Supabase Alpha July 2020 https://supabase.com/blog/supabase-alpha-july-2020 Five months of building - Sun, 02 Aug 2020 04:04:00 +0000 + Sun, 02 Aug 2020 02:02:30 +0000 https://supabase.com/blog/continuous-postgresql-backup-walg Continuous PostgreSQL Backups using WAL-G https://supabase.com/blog/continuous-postgresql-backup-walg Have you ever wanted to restore your database's state to a particular moment in time? This post explains how, using WAL-G. - Sun, 02 Aug 2020 04:04:00 +0000 + Sun, 02 Aug 2020 02:02:30 +0000 https://supabase.com/blog/alpha-launch-postmortem Alpha Launch Postmortem https://supabase.com/blog/alpha-launch-postmortem Everything that went wrong with Supabase's launch - Fri, 10 Jul 2020 04:04:00 +0000 + Fri, 10 Jul 2020 02:02:30 +0000 https://supabase.com/blog/postgresql-templates What are PostgreSQL Templates? https://supabase.com/blog/postgresql-templates What are PostgreSQL templates and what are they used for? - Thu, 09 Jul 2020 04:04:00 +0000 + Thu, 09 Jul 2020 02:02:30 +0000 https://supabase.com/blog/postgresql-physical-logical-backups Physical vs Logical Backups in PostgreSQL https://supabase.com/blog/postgresql-physical-logical-backups What are physical and logical backups in Postgres? - Tue, 07 Jul 2020 04:04:00 +0000 + Tue, 07 Jul 2020 02:02:30 +0000 https://supabase.com/blog/supabase-alpha-june-2020 Supabase Alpha June 2020 https://supabase.com/blog/supabase-alpha-june-2020 Four months of building - Wed, 01 Jul 2020 04:04:00 +0000 + Wed, 01 Jul 2020 02:02:30 +0000 https://supabase.com/blog/supabase-steve-chavez Steve Chavez has joined Supabase https://supabase.com/blog/supabase-steve-chavez Steve joins Supabase to help build Auth. - Mon, 15 Jun 2020 04:04:00 +0000 + Mon, 15 Jun 2020 02:02:30 +0000 https://supabase.com/blog/supabase-alpha-may-2020 Supabase Alpha May 2020 https://supabase.com/blog/supabase-alpha-may-2020 Three months of building - Mon, 01 Jun 2020 04:04:00 +0000 + Mon, 01 Jun 2020 02:02:30 +0000 https://supabase.com/blog/supabase-alpha-april-2020 Supabase Alpha April 2020 https://supabase.com/blog/supabase-alpha-april-2020 Two months of building - Mon, 01 Jun 2020 04:04:00 +0000 + Mon, 01 Jun 2020 02:02:30 +0000 diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 1f70d259a58..db91090408e 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -105,7 +105,7 @@ services: realtime: container_name: realtime-dev.supabase-realtime - image: supabase/realtime:v2.0.2 + image: supabase/realtime:v2.1.0 depends_on: db: # Disable this if you are using an external Postgres database condition: service_healthy diff --git a/package-lock.json b/package-lock.json index 51dde1c8beb..95377a815ed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9706,75 +9706,6 @@ "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1" } }, - "node_modules/@tanstack/match-sorter-utils": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.1.1.tgz", - "integrity": "sha512-IdmEekEYxQsoLOR0XQyw3jD1GujBpRRYaGJYQUw1eOT1eUugWxdc7jomh1VQ1EKHcdwDLpLaCz/8y4KraU4T9A==", - "dependencies": { - "remove-accents": "0.4.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/kentcdodds" - } - }, - "node_modules/@tanstack/query-core": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.15.1.tgz", - "integrity": "sha512-+UfqJsNbPIVo0a9ANW0ZxtjiMfGLaaoIaL9vZeVycvmBuWywJGtSi7fgPVMCPdZQFOzMsaXaOsDtSKQD5xLRVQ==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - } - }, - "node_modules/@tanstack/react-query": { - "version": "4.16.1", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.16.1.tgz", - "integrity": "sha512-PDE9u49wSDykPazlCoLFevUpceLjQ0Mm8i6038HgtTEKb/aoVnUZdlUP7C392ds3Cd75+EGlHU7qpEX06R7d9Q==", - "dependencies": { - "@tanstack/query-core": "4.15.1", - "use-sync-external-store": "^1.2.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-native": "*" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - } - } - }, - "node_modules/@tanstack/react-query-devtools": { - "version": "4.16.1", - "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-4.16.1.tgz", - "integrity": "sha512-VrDYLmG+OOcvGSZL5avG4R8jhqeMFP7pzW2sh2BWEV9UfI+aocG+CW8y8ygacxuKy48m8Tyo/xfe8H1z9BGb+g==", - "dependencies": { - "@tanstack/match-sorter-utils": "8.1.1", - "superjson": "^1.10.0", - "use-sync-external-store": "^1.2.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - }, - "peerDependencies": { - "@tanstack/react-query": "4.16.1", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, "node_modules/@testdeck/core": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/@testdeck/core/-/core-0.2.2.tgz", @@ -37592,8 +37523,8 @@ "@supabase/react-data-grid": "^7.1.0-beta.7", "@supabase/shared-types": "^0.1.29", "@supabase/supabase-js": "^2.1.1", - "@tanstack/react-query": "^4.16.1", - "@tanstack/react-query-devtools": "^4.16.1", + "@tanstack/react-query": "^4.22.0", + "@tanstack/react-query-devtools": "^4.22.0", "@zip.js/zip.js": "^2.6.61", "ajv": "^8.6.3", "analytics-node": "^3.5.0", @@ -37727,6 +37658,75 @@ "tailwindcss": ">=3.0.0 || insiders" } }, + "studio/node_modules/@tanstack/match-sorter-utils": { + "version": "8.7.6", + "resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.7.6.tgz", + "integrity": "sha512-2AMpRiA6QivHOUiBpQAVxjiHAA68Ei23ZUMNaRJrN6omWiSFLoYrxGcT6BXtuzp0Jw4h6HZCmGGIM/gbwebO2A==", + "dependencies": { + "remove-accents": "0.4.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kentcdodds" + } + }, + "studio/node_modules/@tanstack/query-core": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.22.0.tgz", + "integrity": "sha512-OeLyBKBQoT265f5G9biReijeP8mBxNFwY7ZUu1dKL+YzqpG5q5z7J/N1eT8aWyKuhyDTiUHuKm5l+oIVzbtrjw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "studio/node_modules/@tanstack/react-query": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.22.0.tgz", + "integrity": "sha512-P9o+HjG42uB/xHR6dMsJaPhtZydSe4v0xdG5G/cEj1oHZAXelMlm67/rYJNQGKgBamKElKogj+HYGF+NY2yHYg==", + "dependencies": { + "@tanstack/query-core": "4.22.0", + "use-sync-external-store": "^1.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-native": "*" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "studio/node_modules/@tanstack/react-query-devtools": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-4.22.0.tgz", + "integrity": "sha512-YeYFBnfqvb+ZlA0IiJqiHNNSzepNhI1p2o9i8NlhQli9+Zrn230M47OBaBUs8qr3DD1dC2zGB1Dis50Ktz8gAA==", + "dependencies": { + "@tanstack/match-sorter-utils": "^8.7.0", + "superjson": "^1.10.0", + "use-sync-external-store": "^1.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@tanstack/react-query": "4.22.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "studio/node_modules/@types/d3-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-2.0.2.tgz", @@ -44538,38 +44538,6 @@ "mini-svg-data-uri": "^1.2.3" } }, - "@tanstack/match-sorter-utils": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.1.1.tgz", - "integrity": "sha512-IdmEekEYxQsoLOR0XQyw3jD1GujBpRRYaGJYQUw1eOT1eUugWxdc7jomh1VQ1EKHcdwDLpLaCz/8y4KraU4T9A==", - "requires": { - "remove-accents": "0.4.2" - } - }, - "@tanstack/query-core": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.15.1.tgz", - "integrity": "sha512-+UfqJsNbPIVo0a9ANW0ZxtjiMfGLaaoIaL9vZeVycvmBuWywJGtSi7fgPVMCPdZQFOzMsaXaOsDtSKQD5xLRVQ==" - }, - "@tanstack/react-query": { - "version": "4.16.1", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.16.1.tgz", - "integrity": "sha512-PDE9u49wSDykPazlCoLFevUpceLjQ0Mm8i6038HgtTEKb/aoVnUZdlUP7C392ds3Cd75+EGlHU7qpEX06R7d9Q==", - "requires": { - "@tanstack/query-core": "4.15.1", - "use-sync-external-store": "^1.2.0" - } - }, - "@tanstack/react-query-devtools": { - "version": "4.16.1", - "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-4.16.1.tgz", - "integrity": "sha512-VrDYLmG+OOcvGSZL5avG4R8jhqeMFP7pzW2sh2BWEV9UfI+aocG+CW8y8ygacxuKy48m8Tyo/xfe8H1z9BGb+g==", - "requires": { - "@tanstack/match-sorter-utils": "8.1.1", - "superjson": "^1.10.0", - "use-sync-external-store": "^1.2.0" - } - }, "@testdeck/core": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/@testdeck/core/-/core-0.2.2.tgz", @@ -63380,8 +63348,8 @@ "@supabase/shared-types": "^0.1.29", "@supabase/supabase-js": "^2.1.1", "@tailwindcss/typography": "^0.5.2", - "@tanstack/react-query": "^4.16.1", - "@tanstack/react-query-devtools": "^4.16.1", + "@tanstack/react-query": "^4.22.0", + "@tanstack/react-query-devtools": "^4.22.0", "@testing-library/dom": "^8.19.0", "@testing-library/react": "^12.1.5", "@testing-library/react-hooks": "^8.0.1", @@ -63500,6 +63468,38 @@ "postcss-selector-parser": "6.0.10" } }, + "@tanstack/match-sorter-utils": { + "version": "8.7.6", + "resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.7.6.tgz", + "integrity": "sha512-2AMpRiA6QivHOUiBpQAVxjiHAA68Ei23ZUMNaRJrN6omWiSFLoYrxGcT6BXtuzp0Jw4h6HZCmGGIM/gbwebO2A==", + "requires": { + "remove-accents": "0.4.2" + } + }, + "@tanstack/query-core": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.22.0.tgz", + "integrity": "sha512-OeLyBKBQoT265f5G9biReijeP8mBxNFwY7ZUu1dKL+YzqpG5q5z7J/N1eT8aWyKuhyDTiUHuKm5l+oIVzbtrjw==" + }, + "@tanstack/react-query": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.22.0.tgz", + "integrity": "sha512-P9o+HjG42uB/xHR6dMsJaPhtZydSe4v0xdG5G/cEj1oHZAXelMlm67/rYJNQGKgBamKElKogj+HYGF+NY2yHYg==", + "requires": { + "@tanstack/query-core": "4.22.0", + "use-sync-external-store": "^1.2.0" + } + }, + "@tanstack/react-query-devtools": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-4.22.0.tgz", + "integrity": "sha512-YeYFBnfqvb+ZlA0IiJqiHNNSzepNhI1p2o9i8NlhQli9+Zrn230M47OBaBUs8qr3DD1dC2zGB1Dis50Ktz8gAA==", + "requires": { + "@tanstack/match-sorter-utils": "^8.7.0", + "superjson": "^1.10.0", + "use-sync-external-store": "^1.2.0" + } + }, "@types/d3-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-2.0.2.tgz", diff --git a/spec/enrichments/tsdoc_v2/combined.json b/spec/enrichments/tsdoc_v2/combined.json index 0467e800000..0c72523e032 100644 --- a/spec/enrichments/tsdoc_v2/combined.json +++ b/spec/enrichments/tsdoc_v2/combined.json @@ -79271,7 +79271,7 @@ "kindString": "Call signature", "flags": {}, "comment": { - "shortText": "Downloads a file." + "shortText": "Downloads a file from a private bucket. For public buckets, make a request to the URL returned from `getPublicUrl` instead." }, "parameters": [ { diff --git a/spec/enrichments/tsdoc_v2/storage.json b/spec/enrichments/tsdoc_v2/storage.json index d94d2aadde8..475ba699fc3 100644 --- a/spec/enrichments/tsdoc_v2/storage.json +++ b/spec/enrichments/tsdoc_v2/storage.json @@ -5251,7 +5251,7 @@ "kindString": "Call signature", "flags": {}, "comment": { - "shortText": "Downloads a file." + "shortText": "Downloads a file from a private bucket. For public buckets, make a request to the URL returned from `getPublicUrl` instead." }, "parameters": [ { diff --git a/spec/enrichments/tsdoc_v2/storage_dereferenced.json b/spec/enrichments/tsdoc_v2/storage_dereferenced.json index 2074da33442..634dd23b469 100644 --- a/spec/enrichments/tsdoc_v2/storage_dereferenced.json +++ b/spec/enrichments/tsdoc_v2/storage_dereferenced.json @@ -5550,7 +5550,7 @@ "kindString": "Call signature", "flags": {}, "comment": { - "shortText": "Downloads a file." + "shortText": "Downloads a file from a private bucket. For public buckets, make a request to the URL returned from `getPublicUrl` instead." }, "parameters": [ { diff --git a/spec/gotrue_v1_config.yaml b/spec/gotrue_v1_config.yaml index fd7e88431b2..31827802154 100644 --- a/spec/gotrue_v1_config.yaml +++ b/spec/gotrue_v1_config.yaml @@ -40,7 +40,7 @@ parameters: tags: ['general'] # {string[]} These tags are useful for grouping parameters required: true description: | - The base URL of your website. Used as an allow-list for redirects and for constructing URLs used in emails. + The base URL of your website. Used as an allow-list for redirects and for constructing URLs used in emails. - id: 'GOTRUE_URI_ALLOW_LIST' # {string} A unique identifier for this param. title: 'auth.additional_redirect_urls' # {string} Any name. tags: ['general'] # {string[]} These tags are useful for grouping parameters diff --git a/studio/components/interfaces/Database/Wrappers/CreateWrapper.tsx b/studio/components/interfaces/Database/Wrappers/CreateWrapper.tsx new file mode 100644 index 00000000000..dbe8fe10c83 --- /dev/null +++ b/studio/components/interfaces/Database/Wrappers/CreateWrapper.tsx @@ -0,0 +1,294 @@ +import Link from 'next/link' +import { isEmpty } from 'lodash' +import { useState } from 'react' +import { useRouter } from 'next/router' +import { observer } from 'mobx-react-lite' +import { Button, Form, Input, IconArrowLeft, IconExternalLink, IconEdit, IconTrash } from 'ui' +import { PermissionAction } from '@supabase/shared-types/out/constants' + +import { checkPermissions, useParams, useStore } from 'hooks' +import { + FormPanel, + FormActions, + FormSection, + FormsContainer, + FormSectionLabel, + FormSectionContent, +} from 'components/ui/Forms' +import { useFDWCreateMutation } from 'data/fdw/fdw-create-mutation' + +import InputField from './InputField' +import { WRAPPERS } from './Wrappers.constants' +import WrapperTableEditor from './WrapperTableEditor' +import { makeValidateRequired } from './Wrappers.utils' +import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' + +const CreateWrapper = () => { + const formId = 'create-wrapper-form' + const router = useRouter() + const { ui } = useStore() + const { ref, type } = useParams() + const { project } = useProjectContext() + const { mutateAsync: createFDW } = useFDWCreateMutation() + + const [newTables, setNewTables] = useState([]) + const [isEditingTable, setIsEditingTable] = useState(false) + const [selectedTableToEdit, setSelectedTableToEdit] = useState() + const [formErrors, setFormErrors] = useState<{ [k: string]: string }>({}) + + const canCreateWrapper = checkPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'extensions') + + const wrapperMeta = WRAPPERS.find((wrapper) => wrapper.name === type) + const initialValues = + wrapperMeta !== undefined + ? { + wrapper_name: '', + server_name: '', + ...Object.fromEntries( + wrapperMeta.server.options.map((option) => [option.name, option.defaultValue ?? '']) + ), + } + : {} + + if (wrapperMeta === undefined) { + return ( +
+
+

Unsupported wrapper type

+

+ The wrapper type {type} not supported by the dashboard. Head back to create a different + wrapper. +

+
+ + + + + +
+ ) + } + + const onUpdateTable = (values: any) => { + setNewTables((prev) => { + // if the new values have tableIndex, we are editing an existing table + if (values.tableIndex !== undefined) { + const tableIndex = values.tableIndex + const newTables = [...prev] + delete values.tableIndex + newTables[tableIndex] = values + return newTables + } + return [...prev, values] + }) + setIsEditingTable(false) + setSelectedTableToEdit(undefined) + } + + const onSubmit = async (values: any, { setSubmitting }: any) => { + const validate = makeValidateRequired(wrapperMeta.server.options) + const errors: any = validate(values) + + const { wrapper_name } = values + if (wrapper_name.length === 0) errors.name = 'Please provide a name for your wrapper' + if (newTables.length === 0) errors.tables = 'Please add at least one table' + if (!isEmpty(errors)) return setFormErrors(errors) + + setSubmitting(true) + try { + await createFDW({ + projectRef: project?.ref, + connectionString: project?.connectionString, + wrapperMeta, + formState: { ...values, server_name: `${wrapper_name}_server` }, + tables: newTables, + }) + ui.setNotification({ + category: 'success', + message: `Successfully created ${wrapperMeta.label} foreign data wrapper`, + }) + setNewTables([]) + router.push(`/project/${ref}/database/wrappers`) + } catch (error: any) { + ui.setNotification({ + error, + category: 'error', + message: `Failed to create ${wrapperMeta.label} foreign data wrapper: ${error.message}`, + }) + } finally { + setSubmitting(false) + } + } + + return ( + +
+
+ +

Create a {wrapperMeta?.label} Wrapper

+ +
+ +
+ {({ isSubmitting, handleReset, values, initialValues }: any) => { + const hasChanges = JSON.stringify(values) !== JSON.stringify(initialValues) + return ( + + +
+ } + > + Wrapper Configuration}> + + 0 ? ( + <> + Your wrapper's server name will be{' '} + {values.wrapper_name}_server + + ) : ( + '' + ) + } + /> + + + {wrapperMeta.label} Configuration} + > + + {wrapperMeta.server.options.map((option) => ( + + ))} + + + +

Foreign Tables

+

+ You can query your data from these foreign tables after the wrapper is + created +

+ + } + > + + {newTables.length === 0 ? ( +
+ +
+ ) : ( +
+ {newTables.map((table, i) => ( +
+
+

+ {table.schema_name}.{table.table_name} +

+

+ {wrapperMeta.tables[table.index].label}: {table.columns.join(', ')} +

+
+
+
+
+ ))} +
+ )} + {newTables.length > 0 && ( +
+ +
+ )} + {newTables.length === 0 && formErrors.tables && ( +

{formErrors.tables}

+ )} +
+
+ + ) + }} + + + + { + setSelectedTableToEdit(undefined) + setIsEditingTable(false) + }} + onSave={onUpdateTable} + initialData={selectedTableToEdit} + /> +
+ ) +} + +export default observer(CreateWrapper) diff --git a/studio/components/interfaces/Database/Wrappers/EditWrapper.tsx b/studio/components/interfaces/Database/Wrappers/EditWrapper.tsx new file mode 100644 index 00000000000..2286ec04c7f --- /dev/null +++ b/studio/components/interfaces/Database/Wrappers/EditWrapper.tsx @@ -0,0 +1,392 @@ +import Link from 'next/link' +import { isEmpty } from 'lodash' +import { useEffect, useState } from 'react' +import { useRouter } from 'next/router' +import { observer } from 'mobx-react-lite' +import { + Button, + Form, + Input, + IconArrowLeft, + IconExternalLink, + IconEdit, + IconTrash, + IconLoader, +} from 'ui' +import { PermissionAction } from '@supabase/shared-types/out/constants' + +import { VaultSecret } from 'types' +import { checkPermissions, useImmutableValue, useParams, useStore } from 'hooks' +import { useFDWsQuery } from 'data/fdw/fdws-query' +import { useFDWUpdateMutation } from 'data/fdw/fdw-update-mutation' + +import InputField from './InputField' +import { WRAPPERS } from './Wrappers.constants' +import WrapperTableEditor from './WrapperTableEditor' +import { + formatWrapperTables, + makeValidateRequired, + convertKVStringArrayToJson, +} from './Wrappers.utils' +import Loading from 'components/ui/Loading' +import { + FormPanel, + FormActions, + FormSection, + FormSectionLabel, + FormSectionContent, + FormsContainer, +} from 'components/ui/Forms' +import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' + +const EditWrapper = () => { + const formId = 'edit-wrapper-form' + const router = useRouter() + const { ui, vault } = useStore() + const { ref, id } = useParams() + const { project } = useProjectContext() + + const { data, isLoading } = useFDWsQuery({ + projectRef: project?.ref, + connectionString: project?.connectionString, + }) + + const wrappers = data?.result ?? [] + const foundWrapper = wrappers.find((w) => Number(w.id) === Number(id)) + // this call to useImmutableValue should be removed if the redirect after update is also removed + const wrapper = useImmutableValue(foundWrapper) + + const wrapperMeta = WRAPPERS.find((w) => w.handlerName === wrapper?.handler) + + const { mutateAsync: updateFDW, isLoading: isSaving } = useFDWUpdateMutation() + + const [wrapperTables, setWrapperTables] = useState([]) + const [isEditingTable, setIsEditingTable] = useState(false) + const [selectedTableToEdit, setSelectedTableToEdit] = useState() + const [formErrors, setFormErrors] = useState<{ [k: string]: string }>({}) + + const canCreateWrapper = checkPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'extensions') + + const initialValues = + wrapperMeta !== undefined + ? { + wrapper_name: wrapper?.name, + server_name: wrapper?.server_name, + ...convertKVStringArrayToJson(wrapper?.server_options ?? []), + } + : {} + + useEffect(() => { + if (wrapper?.id) { + setWrapperTables(formatWrapperTables(wrapper?.tables ?? [])) + } + }, [wrapper?.id]) + + if (isLoading) { + return ( +
+ +
+ ) + } + + if (wrapper === undefined || wrapperMeta === undefined) { + if (isSaving) { + return ( +
+
+ +

Updating wrapper

+
+
+ ) + } else { + return ( +
+
+

Unknown wrapper

+

+ The wrapper ID {id} cannot be found in your project. Head back to select another + wrapper. +

+
+ + + + + +
+ ) + } + } + + const onUpdateTable = (values: any) => { + setWrapperTables((prev) => { + // if the new values have tableIndex, we are editing an existing table + if (values.tableIndex !== undefined) { + const tableIndex = values.tableIndex + const wrapperTables = [...prev] + delete values.tableIndex + wrapperTables[tableIndex] = values + return wrapperTables + } + return [...prev, values] + }) + setIsEditingTable(false) + setSelectedTableToEdit(undefined) + } + + const onSubmit = async (values: any, { setSubmitting }: any) => { + const validate = makeValidateRequired(wrapperMeta.server.options) + const errors: any = validate(values) + + const { wrapper_name } = values + if (wrapper_name.length === 0) errors.name = 'Please provide a name for your wrapper' + if (wrapperTables.length === 0) errors.tables = 'Please add at least one table' + if (!isEmpty(errors)) return setFormErrors(errors) + + setSubmitting(true) + try { + await updateFDW({ + projectRef: project?.ref, + connectionString: project?.connectionString, + wrapper, + wrapperMeta, + formState: { ...values, server_name: `${wrapper_name}_server` }, + tables: wrapperTables, + }) + + ui.setNotification({ + category: 'success', + message: `Successfully updated ${wrapperMeta.label} foreign data wrapper`, + }) + setWrapperTables([]) + router.push(`/project/${ref}/database/wrappers`) + } catch (error: any) { + ui.setNotification({ + error, + category: 'error', + message: `Failed to create ${wrapperMeta.label} foreign data wrapper: ${error.message}`, + }) + } finally { + setSubmitting(false) + } + } + + return ( + +
+
+ +

Edit wrapper: {wrapper.name}

+ +
+ +
+ {({ isSubmitting, handleReset, values, initialValues, resetForm }: any) => { + const [loadingSecrets, setLoadingSecrets] = useState(false) + + const initialTables = formatWrapperTables(wrapper?.tables ?? []) + const hasFormChanges = JSON.stringify(values) !== JSON.stringify(initialValues) + const hasTableChanges = JSON.stringify(initialTables) !== JSON.stringify(wrapperTables) + const hasChanges = hasFormChanges || hasTableChanges + + const encryptedOptions = wrapperMeta.server.options.filter((option) => option.encrypted) + + useEffect(() => { + const fetchEncryptedValues = async () => { + setLoadingSecrets(true) + const res = await Promise.all( + encryptedOptions.map(async (option) => { + const [secret] = vault.listSecrets( + (secret: VaultSecret) => secret.name === `${wrapper.name}_${option.name}` + ) + if (secret !== undefined) { + const value = await vault.fetchSecretValue(secret.id) + return { [option.name]: value } + } else { + return { [option.name]: '' } + } + }) + ) + const secretValues = res.reduce((a: any, b: any) => { + const [key] = Object.keys(b) + return { ...a, [key]: b[key] } + }, {}) + + resetForm({ + values: { ...values, ...secretValues }, + initialValues: { ...initialValues, ...secretValues }, + }) + setLoadingSecrets(false) + } + + if (encryptedOptions.length > 0) fetchEncryptedValues() + }, []) + + return ( + + { + handleReset() + setWrapperTables(initialTables) + }} + helper={ + !canCreateWrapper + ? 'You need additional permissions to create a foreign data wrapper' + : undefined + } + /> +
+ } + > + Wrapper Configuration}> + + + Your wrapper's server name will be updated to{' '} + {values.wrapper_name}_server + + ) : ( + <> + Your wrapper's server name is{' '} + {values.wrapper_name}_server + + ) + } + /> + + + {wrapperMeta.label} Configuration} + > + + {wrapperMeta.server.options.map((option) => ( + + ))} + + + +

Foreign Tables

+

+ You can query your data from these foreign tables after the wrapper is + created +

+ + } + > + + {wrapperTables.length === 0 ? ( +
+ +
+ ) : ( +
+ {wrapperTables.map((table, i) => ( +
+
+

+ {table.schema_name}.{table.table_name} +

+

+ {wrapperMeta.tables[table.index].label}: {table.columns.join(', ')} +

+
+
+
+
+ ))} +
+ )} + {wrapperTables.length > 0 && ( +
+ +
+ )} + {wrapperTables.length === 0 && formErrors.tables && ( +

{formErrors.tables}

+ )} +
+
+ + ) + }} + + + + { + setSelectedTableToEdit(undefined) + setIsEditingTable(false) + }} + onSave={onUpdateTable} + initialData={selectedTableToEdit} + /> +
+ ) +} + +export default observer(EditWrapper) diff --git a/studio/components/interfaces/Database/Wrappers/InputField.tsx b/studio/components/interfaces/Database/Wrappers/InputField.tsx new file mode 100644 index 00000000000..a3839cc8fad --- /dev/null +++ b/studio/components/interfaces/Database/Wrappers/InputField.tsx @@ -0,0 +1,94 @@ +import Link from 'next/link' +import { FC, useState } from 'react' +import { Button, Input, IconHelpCircle, IconEye, IconEyeOff, IconLoader } from 'ui' +import { ServerOption } from './Wrappers.types' + +interface Props { + option: ServerOption + loading: boolean + error: any +} + +const InputField: FC = ({ option, loading, error }) => { + const [showHidden, setShowHidden] = useState(!option.hidden) + if (option.isTextArea) { + return ( +
+ +

{option.label}

+ {option.urlHelper !== undefined && ( + + + + + + )} +
+ } + value={loading ? 'Fetching value from Vault...' : undefined} + defaultValue={option.defaultValue ?? ''} + required={option.required ?? false} + /> + + ) + } else { + return ( + +

{option.label}

+ {option.urlHelper !== undefined && ( + + + + + + )} + + } + defaultValue={option.defaultValue ?? ''} + error={error} + value={loading ? 'Fetching value from Vault...' : undefined} + type={!option.hidden || loading ? 'text' : showHidden ? 'text' : 'password'} + disabled={loading} + actions={ + loading ? ( +
+
+ ) : option.hidden ? ( +
+
+ ) : null + } + /> + ) + } +} + +export default InputField diff --git a/studio/components/interfaces/Database/Wrappers/WrapperRow.tsx b/studio/components/interfaces/Database/Wrappers/WrapperRow.tsx index 75368404eef..35b7efbed34 100644 --- a/studio/components/interfaces/Database/Wrappers/WrapperRow.tsx +++ b/studio/components/interfaces/Database/Wrappers/WrapperRow.tsx @@ -1,222 +1,50 @@ import Link from 'next/link' import Image from 'next/image' -import { isEmpty } from 'lodash' -import { FC, useState } from 'react' -import { - Collapsible, - Input, - IconCheck, - IconChevronUp, - Button, - IconEdit, - IconTrash, - IconEye, - IconEyeOff, - IconCheckCircle, - IconExternalLink, - IconLoader, - IconHelpCircle, -} from 'ui' +import { FC } from 'react' +import { Collapsible, IconChevronUp, Button, IconExternalLink, IconTrash, IconEdit } from 'ui' +import { partition } from 'lodash' import { useParams, useStore } from 'hooks' -import { ServerOption, Wrapper } from './Wrappers.types' -import { makeValidateRequired } from './Wrappers.utils' -import { useFDWCreateMutation } from 'data/fdw/fdw-create-mutation' +import { WrapperMeta } from './Wrappers.types' +import { FDW } from 'data/fdw/fdws-query' import { useFDWDeleteMutation } from 'data/fdw/fdw-delete-mutation' import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' -import WrapperTableEditor from './WrapperTableEditor' -import InformationBox from 'components/ui/InformationBox' import { confirmAlert } from 'components/to-be-cleaned/ModalsDeprecated/ConfirmModal' interface Props { - wrapper: Wrapper - tables: any[] - isLoading: boolean - isEnabled: boolean + wrappers: FDW[] + wrapperMeta: WrapperMeta isOpen: boolean onOpen: (wrapper: string) => void } -const InputField: FC<{ - option: ServerOption - value: any - error: any - onChange: (e: any) => void -}> = ({ option, value, error, onChange }) => { - const [showHidden, setShowHidden] = useState(!option.hidden) - - if (option.isTextArea) { - return ( -
- -

{option.label}

- {option.urlHelper !== undefined && ( - - - - - - )} -
- } - defaultValue={option.defaultValue ?? ''} - required={option.required ?? false} - value={value} - onChange={onChange} - error={error} - className="input-mono" - rows={6} - /> - - ) - } else { - return ( - -

{option.label}

- {option.urlHelper !== undefined && ( - - - - - - )} - - } - defaultValue={option.defaultValue ?? ''} - required={option.required ?? false} - value={value} - onChange={onChange} - error={error} - className="input-mono" - type={!option.hidden ? 'text' : showHidden ? 'text' : 'password'} - actions={ - option.hidden ? ( -
-
- ) : null - } - /> - ) - } -} - -const WrapperRow: FC = ({ wrapper, tables, isLoading, isEnabled, isOpen, onOpen }) => { - const getInitialFormState = () => - Object.fromEntries( - wrapper.server.options.map((option) => [option.name, option.defaultValue ?? '']) - ) - +const WrapperRow: FC = ({ wrappers = [], wrapperMeta, isOpen, onOpen }) => { const { ui } = useStore() const { ref } = useParams() const { project } = useProjectContext() - const { mutateAsync: createFDW } = useFDWCreateMutation() const { mutateAsync: deleteFDW } = useFDWDeleteMutation() - const [isSubmitting, setIsSubmitting] = useState(false) - const [isEditingTable, setIsEditingTable] = useState(false) - const [selectedTableToEdit, setSelectedTableToEdit] = useState() - - const [newTables, setNewTables] = useState([]) - const [formState, setFormState] = useState(getInitialFormState) - const [formErrors, setFormErrors] = useState<{ [k: string]: string }>({}) - - const noChanges = JSON.stringify(getInitialFormState()) === JSON.stringify(formState) - - const onUpdateTable = (values: any) => { - setNewTables((prev) => { - // if the new values have tableIndex, we are editing an existing table - if (values.tableIndex !== undefined) { - const tableIndex = values.tableIndex - const newTables = [...prev] - delete values.tableIndex - newTables[tableIndex] = values - return newTables - } - return [...prev, values] - }) - setIsEditingTable(false) - setSelectedTableToEdit(undefined) - } - - const onSaveWrapper = async () => { - const validate = makeValidateRequired(wrapper.server.options) - const errors: any = validate(formState) - if (newTables.length === 0) errors.tables = 'Please add at least one table' - - if (!isEmpty(errors)) { - setFormErrors(errors) - return - } - - setIsSubmitting(true) - try { - await createFDW({ - projectRef: project?.ref, - connectionString: project?.connectionString, - wrapper, - formState, - newTables, - }) - ui.setNotification({ - category: 'success', - message: `Successfully created ${wrapper.label} foreign data wrapper`, - }) - setNewTables([]) - setFormState(getInitialFormState) - } catch (error: any) { - ui.setNotification({ - error, - category: 'error', - message: `Failed to create ${wrapper.label} foreign data wrapper: ${error.message}`, - }) - } finally { - setIsSubmitting(false) - } - } - - const onDeleteWrapper = () => { + const onDeleteWrapper = (wrapper: any) => { confirmAlert({ - title: `Confirm to disable ${wrapper.label} wrapper`, - message: `Are you sure you want to disable the ${wrapper.label} wrapper? This will also remove all tables created with this wrapper.`, + title: `Confirm to disable ${wrapper.name}`, + message: `Are you sure you want to disable the ${wrapper.name} wrapper? This will also remove all tables created with this wrapper.`, onAsyncConfirm: async () => { try { await deleteFDW({ projectRef: project?.ref, connectionString: project?.connectionString, wrapper, + wrapperMeta, }) ui.setNotification({ category: 'success', - message: `Successfully removed ${wrapper.label} foreign data wrapper`, + message: `Successfully disabled ${wrapper.name} foreign data wrapper`, }) } catch (error: any) { ui.setNotification({ error, category: 'error', - message: `Disabling ${wrapper.name} failed: ${error.message}`, + message: `Failed to disable ${wrapper.name}: ${error.message}`, }) } }, @@ -227,7 +55,7 @@ const WrapperRow: FC = ({ wrapper, tables, isLoading, isEnabled, isOpen, <> onOpen(wrapper.name)} + onOpenChange={() => onOpen(wrapperMeta.name)} className={[ 'bg-scale-100 dark:bg-scale-300 ', 'hover:bg-scale-200 dark:hover:bg-scale-500', @@ -235,11 +63,11 @@ const WrapperRow: FC = ({ wrapper, tables, isLoading, isEnabled, isOpen, 'border-scale-300', 'dark:border-scale-500 hover:border-scale-500', 'dark:hover:border-scale-700 data-open:border-scale-700', - 'data-open:pb-px col-span-12 mx-auto', + 'col-span-12 mx-auto', '-space-y-px overflow-hidden', 'transition border shadow hover:z-50', - 'first:rounded-tl first:rounded-tr', - 'last:rounded-bl last:rounded-br', + 'first:rounded-tl first:rounded-tr first:!border-t', + 'last:rounded-bl last:rounded-br last:border-t-0', ].join(' ')} > @@ -254,209 +82,102 @@ const WrapperRow: FC = ({ wrapper, tables, isLoading, isEnabled, isOpen, width={14} /> {`${wrapper.name} - {wrapper.label} + {wrapperMeta.label}
- {isLoading ? ( -
- -
- ) : isEnabled ? ( -
- - - - Enabled -
- ) : ( -
- Disabled -
- )} +
+ {wrappers.length} wrapper{wrappers.length > 1 ? 's' : ''} +
- {!isLoading && ( - <> - {isEnabled ? ( - -
-
- - } - title={`${wrapper.label} foreign data wrapper is currently enabled`} - description="If you'd like to edit this wrapper, you'll need to disable the wrapper first and create it again with any updated configuration." - /> -
-

Foreign Tables

-
- {tables.map((table) => ( -
-
-

- {table.schema}.{table.name} -

-

- Columns: {table.columns.map((c: any) => c.name).join(', ')} -

-
- - - - - -
+ +
+ {wrappers.map((wrapper) => { + const serverOptions = Object.fromEntries( + wrapper.server_options.map((option: any) => option.split('=')) + ) + const [encryptedMetadata, visibleMetadata] = partition( + wrapperMeta.server.options, + 'hidden' + ) + + return ( +
+
+
+

{wrapper.name}

+
+ {visibleMetadata.map((metadata) => ( +
+

{metadata.label}:

+

{serverOptions[metadata.name]}

+
+ ))} + {encryptedMetadata.map((metadata) => ( +
+

{metadata.label}:

+ + + Encrypted in Vault + + + +
+ ))} +
+

+ Foreign tables: ({wrapper.tables.length}) +

+
+ {wrapper.tables.map((table: any) => ( + + +
+ {table.name} +
+
+ ))}
- + icon={} + className="py-2" + /> + + +
- - ) : ( - -
-
- {wrapper.server.options.map((option) => ( - { - setFormState((prev) => ({ ...prev, [option.name]: e.target.value })) - setFormErrors((prev) => ({ ...prev, [option.name]: '' })) - }} - /> - ))} -
-
-
-

Foreign Tables

- -
-
-
- {newTables.length === 0 && ( -
-

- Add foreign tables to query from after the wrapper is enabled -

-
- )} - {newTables.map((table, i) => ( -
-
-

- {table.schema_name}.{table.table_name} -

-

- {wrapper.tables[table.index].label}: {table.columns.join(', ')} -

-
-
-
-
- ))} - {newTables.length === 0 && formErrors.tables && ( -

{formErrors.tables}

- )} -
-
- - - - - -
- - -
-
-
-
-
-
- )} - - )} + ) + })} +
+
- { - setSelectedTableToEdit(undefined) - setIsEditingTable(false) - }} - onSave={onUpdateTable} - initialData={selectedTableToEdit} - /> ) } diff --git a/studio/components/interfaces/Database/Wrappers/Wrappers.constants.ts b/studio/components/interfaces/Database/Wrappers/Wrappers.constants.ts index a987a4533af..cbb51c09408 100644 --- a/studio/components/interfaces/Database/Wrappers/Wrappers.constants.ts +++ b/studio/components/interfaces/Database/Wrappers/Wrappers.constants.ts @@ -1,6 +1,6 @@ -import { Wrapper } from './Wrappers.types' +import { WrapperMeta } from './Wrappers.types' -export const WRAPPERS: Wrapper[] = [ +export const WRAPPERS: WrapperMeta[] = [ { name: 'stripe_wrapper', handlerName: 'stripe_fdw_handler', @@ -10,7 +10,6 @@ export const WRAPPERS: Wrapper[] = [ label: 'Stripe', docsUrl: 'https://supabase.com/docs/guides/database/wrappers/stripe', server: { - name: 'stripe_server', options: [ { name: 'api_key_id', @@ -415,7 +414,6 @@ export const WRAPPERS: Wrapper[] = [ label: 'Firebase', docsUrl: 'https://supabase.com/docs/guides/database/wrappers/firebase', server: { - name: 'firebase_server', options: [ { name: 'project_id', diff --git a/studio/components/interfaces/Database/Wrappers/Wrappers.tsx b/studio/components/interfaces/Database/Wrappers/Wrappers.tsx index bc6abe43bab..d05146b786d 100644 --- a/studio/components/interfaces/Database/Wrappers/Wrappers.tsx +++ b/studio/components/interfaces/Database/Wrappers/Wrappers.tsx @@ -2,34 +2,32 @@ import Link from 'next/link' import { useState } from 'react' import { Button, IconExternalLink } from 'ui' import { observer } from 'mobx-react-lite' -import * as Tooltip from '@radix-ui/react-tooltip' -import { PermissionAction } from '@supabase/shared-types/out/constants' -import { checkPermissions, useParams, useStore } from 'hooks' +import { useStore } from 'hooks' import { useFDWsQuery } from 'data/fdw/fdws-query' import { WRAPPERS } from './Wrappers.constants' import WrapperRow from './WrapperRow' import ShimmeringLoader from 'components/ui/ShimmeringLoader' +import WrappersDropdown from './WrappersDropdown' +import WrappersDisabledState from './WrappersDisabledState' import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' +import { groupBy } from 'lodash' const Wrappers = () => { - const { ref } = useParams() - const { ui, meta } = useStore() + const { meta } = useStore() const { project } = useProjectContext() const { data, isLoading } = useFDWsQuery({ projectRef: project?.ref, connectionString: project?.connectionString, }) - const wrappers = data?.result ?? [] - const enabledWrapperNamesSet = new Set(data?.result.map((fdw) => fdw.name)) - const [open, setOpen] = useState('') - const [isEnabling, setIsEnabling] = useState(false) + + const wrappers = data?.result ?? [] + const groupedWrappers = groupBy(wrappers, 'handler') const wrappersExtension = meta.extensions.byId('wrappers') const vaultExtension = meta.extensions.byId('supabase_vault') const isLoadingExtensions = meta.extensions.isLoading - const isNotAvailable = wrappersExtension === undefined || vaultExtension === undefined const isWrappersEnabled = wrappersExtension !== undefined && @@ -37,48 +35,6 @@ const Wrappers = () => { vaultExtension !== undefined && vaultExtension?.installed_version !== null - const canToggleWrappers = checkPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'extensions') - - const onEnableWrappers = async () => { - if (wrappersExtension === undefined || vaultExtension === undefined) return - setIsEnabling(true) - - const requiredExtensions = await Promise.all([ - await meta.extensions.create({ - schema: wrappersExtension.schema ?? 'extensions', - name: wrappersExtension.name, - version: wrappersExtension.default_version, - cascade: true, - }), - await meta.extensions.create({ - schema: vaultExtension.schema ?? 'vault', - name: vaultExtension.name, - version: vaultExtension.default_version, - cascade: true, - }), - ]) - const errors = requiredExtensions.filter( - (res) => res.error && !res.error.message.includes('already exists') - ) - - if (errors.length > 0) { - ui.setNotification({ - error: errors, - category: 'error', - message: `Failed to enable Wrappers for your project: ${errors - .map((x) => x.message) - .join(', ')}`, - }) - } else { - ui.setNotification({ - category: 'success', - message: 'Wrappers is now enabled for your project!', - }) - } - - setIsEnabling(false) - } - return (
@@ -88,159 +44,63 @@ const Wrappers = () => { Query your data warehouse directly from your database, or third-party APIs using SQL.
- {/* [Joshen TODO] For when we support multiple instances per wrapper */} - {/*
- - {WRAPPERS.map((wrapper, idx) => ( - <> - - } - onClick={() => {}} - > - {wrapper.label} - - {idx !== WRAPPERS.length - 1 && } - - ))} - - } - > - - -
*/} +
+ + + + + + {isWrappersEnabled && } +
- {isLoadingExtensions ? ( -
+ {isLoadingExtensions || isLoading ? ( +
) : isWrappersEnabled ? (
- {WRAPPERS.map((wrapper, i) => { - return ( - w.name === wrapper.server.name)?.tables ?? []} - isLoading={isLoading} - isEnabled={enabledWrapperNamesSet.has(wrapper.server.name)} - isOpen={open === wrapper.name} - onOpen={(wrapperName) => { - if (open !== wrapperName) setOpen(wrapperName) - else setOpen('') - }} - /> - ) - })} + {wrappers.length === 0 ? ( +
+

No wrappers created yet

+ +
+ ) : ( + <> + {/* [Joshen] This probably needs to change anyways so dont get too stuck with this */} + {WRAPPERS.map((wrapper, i) => { + const createdWrappers = groupedWrappers[wrapper.handlerName] ?? [] + if (createdWrappers.length > 0) { + return ( + { + if (open !== wrapperName) setOpen(wrapperName) + else setOpen('') + }} + /> + ) + } else { + return null + } + })} + + )}
) : ( -
-
-
-
-

Supabase Wrappers

-

- Supabase Wrappers is a framework for building Postgres Foreign Data Wrappers (FDW) - which connect Postgres to external systems. Query your data warehouse or - third-party APIs directly from your database. -

-
- {isNotAvailable ? ( -
-
-
-

- Wrappers is not available for this project yet. -

-

- Do reach out to us if you're interested! -

-
- -
- -
- ) : ( -
- - - - - - - - - - {!canToggleWrappers && ( - - -
- - You need additional permissions to enable Wrappers for this project - -
-
- )} -
-
- )} -
-
-
+ )}
) diff --git a/studio/components/interfaces/Database/Wrappers/Wrappers.types.ts b/studio/components/interfaces/Database/Wrappers/Wrappers.types.ts index 25ac12d203b..c0da532e9f1 100644 --- a/studio/components/interfaces/Database/Wrappers/Wrappers.types.ts +++ b/studio/components/interfaces/Database/Wrappers/Wrappers.types.ts @@ -1,4 +1,4 @@ -export type Wrapper = { +export type WrapperMeta = { name: string handlerName: string validatorName: string @@ -22,7 +22,6 @@ export type ServerOption = { } export type Server = { - name: string options: ServerOption[] } diff --git a/studio/components/interfaces/Database/Wrappers/Wrappers.utils.ts b/studio/components/interfaces/Database/Wrappers/Wrappers.utils.ts index 19b48c207f1..97545babb4f 100644 --- a/studio/components/interfaces/Database/Wrappers/Wrappers.utils.ts +++ b/studio/components/interfaces/Database/Wrappers/Wrappers.utils.ts @@ -19,3 +19,24 @@ export const makeValidateRequired = return errors } + +export const formatWrapperTables = (tables: any[]) => { + return tables.map((table, index: number) => { + const object = table.options.find((option: string) => option.startsWith('object=')) + const objectValue = object !== undefined ? object.split('=')[1] : undefined + + return { + index, + columns: table.columns.map((column: any) => column.name), + is_new_schema: false, + object: objectValue, + schema: table.schema, + schema_name: table.schema, + table_name: table.name, + } + }) +} + +export const convertKVStringArrayToJson = (values: string[]) => { + return Object.fromEntries(values.map((value) => value.split('='))) +} diff --git a/studio/components/interfaces/Database/Wrappers/WrappersDisabledState.tsx b/studio/components/interfaces/Database/Wrappers/WrappersDisabledState.tsx new file mode 100644 index 00000000000..54e4fa6a8b1 --- /dev/null +++ b/studio/components/interfaces/Database/Wrappers/WrappersDisabledState.tsx @@ -0,0 +1,156 @@ +import Link from 'next/link' +import { useState } from 'react' +import { Button, IconExternalLink } from 'ui' +import { observer } from 'mobx-react-lite' +import * as Tooltip from '@radix-ui/react-tooltip' +import { PermissionAction } from '@supabase/shared-types/out/constants' + +import { checkPermissions, useParams, useStore } from 'hooks' + +const WrappersDisabledState = () => { + const { ui, meta } = useStore() + const { ref } = useParams() + const wrappersExtension = meta.extensions.byId('wrappers') + const vaultExtension = meta.extensions.byId('supabase_vault') + const isNotAvailable = wrappersExtension === undefined || vaultExtension === undefined + + const [isEnabling, setIsEnabling] = useState(false) + const canToggleWrappers = checkPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'extensions') + + const onEnableWrappers = async () => { + if (wrappersExtension === undefined || vaultExtension === undefined) return + setIsEnabling(true) + + const requiredExtensions = await Promise.all([ + await meta.extensions.create({ + schema: wrappersExtension.schema ?? 'extensions', + name: wrappersExtension.name, + version: wrappersExtension.default_version, + cascade: true, + }), + await meta.extensions.create({ + schema: vaultExtension.schema ?? 'vault', + name: vaultExtension.name, + version: vaultExtension.default_version, + cascade: true, + }), + ]) + const errors = requiredExtensions.filter( + (res) => res.error && !res.error.message.includes('already exists') + ) + + if (errors.length > 0) { + ui.setNotification({ + error: errors, + category: 'error', + message: `Failed to enable Wrappers for your project: ${errors + .map((x) => x.message) + .join(', ')}`, + }) + } else { + ui.setNotification({ + category: 'success', + message: 'Wrappers is now enabled for your project!', + }) + } + + setIsEnabling(false) + } + + return ( +
+
+
+
+

Supabase Wrappers

+

+ Supabase Wrappers is a framework for building Postgres Foreign Data Wrappers (FDW) + which connect Postgres to external systems. Query your data warehouse or third-party + APIs directly from your database. +

+
+ {isNotAvailable ? ( +
+
+
+

+ Wrappers is not available for this project yet. +

+

+ Do reach out to us if you're interested! +

+
+ +
+ +
+ ) : ( +
+ + + + + + + + + + {!canToggleWrappers && ( + + +
+ + You need additional permissions to enable Wrappers for this project + +
+
+ )} +
+
+ )} +
+
+
+ ) +} + +export default observer(WrappersDisabledState) diff --git a/studio/components/interfaces/Database/Wrappers/WrappersDropdown.tsx b/studio/components/interfaces/Database/Wrappers/WrappersDropdown.tsx new file mode 100644 index 00000000000..b74c9a9fffb --- /dev/null +++ b/studio/components/interfaces/Database/Wrappers/WrappersDropdown.tsx @@ -0,0 +1,59 @@ +import Link from 'next/link' +import Image from 'next/image' +import { FC, Fragment } from 'react' +import { observer } from 'mobx-react-lite' +import { Button, Dropdown, IconPlus } from 'ui' + +import { useParams } from 'hooks' +import { WRAPPERS } from './Wrappers.constants' + +interface Props { + buttonText?: string + align?: 'center' | 'end' +} + +const WrapperDropdown: FC = ({ buttonText = 'Add wrapper', align = 'end' }) => { + const { ref } = useParams() + + return ( + + {WRAPPERS.map((wrapper, idx) => ( + + + + + } + > + {wrapper.label} + + + + {idx !== WRAPPERS.length - 1 && } + + ))} + + } + > + + + ) +} + +export default observer(WrapperDropdown) diff --git a/studio/components/interfaces/Database/index.ts b/studio/components/interfaces/Database/index.ts index b294434c18e..d866b7d8c8c 100644 --- a/studio/components/interfaces/Database/index.ts +++ b/studio/components/interfaces/Database/index.ts @@ -7,6 +7,8 @@ import RolesSettings from './Roles/RolesSettings' import Extensions from './Extensions/Extensions' import Wrappers from './Wrappers/Wrappers' +import CreateWrapper from './Wrappers/CreateWrapper' +import EditWrapper from './Wrappers/EditWrapper' import PublicationsList from './Publications/PublicationsList' import PublicationsTables from './Publications/PublicationsTables' @@ -28,6 +30,8 @@ export { RolesSettings, Extensions, Wrappers, + CreateWrapper, + EditWrapper, PublicationsList, PublicationsTables, BackupsList, diff --git a/studio/components/interfaces/Organization/TeamSettings/RolesHelperModal/RolesHelperModal.tsx b/studio/components/interfaces/Organization/TeamSettings/RolesHelperModal/RolesHelperModal.tsx index c448c0334f3..6c322bf60f3 100644 --- a/studio/components/interfaces/Organization/TeamSettings/RolesHelperModal/RolesHelperModal.tsx +++ b/studio/components/interfaces/Organization/TeamSettings/RolesHelperModal/RolesHelperModal.tsx @@ -1,22 +1,35 @@ import { FC, Fragment, useState } from 'react' -import { IconCheck, IconHelpCircle, Modal } from 'ui' -import { useFlag } from 'hooks' +import { IconCheck, IconHelpCircle, IconInfo, Modal } from 'ui' import { PERMISSIONS_MAPPING } from './RolesHelperModal.constants' +import * as Tooltip from '@radix-ui/react-tooltip' interface Props {} const RolesHelperModal: FC = ({}) => { const [showModal, setShowModal] = useState(false) - const enableBillingOnlyReadOnlyRoles = useFlag('enableBillingOnlyReadOnlyRoles') - const permissionColumnClassName = [ - `${enableBillingOnlyReadOnlyRoles ? 'w-[40%]' : 'w-[49%]'}`, - 'text-sm pl-4 font-bold', - ].join(' ') - const roleColumnClassName = [ - `${enableBillingOnlyReadOnlyRoles ? 'w-[12%]' : 'w-[17%]'}`, - 'text-sm h-8 flex items-center justify-center border-l border-scale-600 font-bold', - ].join(' ') + const permissionColumnClassName = 'w-[40%] text-sm pl-4 font-bold' + const roleColumnClassName = + 'w-[12%] text-sm h-8 flex items-center justify-center border-l border-scale-600 font-bold' + + const accessTooltip = ( + + + + + + +
+ Only available in Team/Enterprise plan. +
+
+
+ ) return ( <> @@ -30,7 +43,7 @@ const RolesHelperModal: FC = ({}) => { closable hideFooter visible={showModal} - size={enableBillingOnlyReadOnlyRoles ? 'xxlarge' : 'xlarge'} + size={'xxlarge'} header="Permissions for each role" onCancel={() => setShowModal(!showModal)} > @@ -48,12 +61,8 @@ const RolesHelperModal: FC = ({}) => {
Owner
Adminstrator
Developer
- {enableBillingOnlyReadOnlyRoles && ( -
Read-only
- )} - {enableBillingOnlyReadOnlyRoles && ( -
Billing-only
- )} +
Read-only {accessTooltip}
+
Billing-only {accessTooltip}
@@ -77,20 +86,14 @@ const RolesHelperModal: FC = ({}) => {
{action.permissions.developer && }
- {enableBillingOnlyReadOnlyRoles && ( -
- {action.permissions.read_only && ( - - )} -
- )} - {enableBillingOnlyReadOnlyRoles && ( -
- {action.permissions.billing_only && ( - - )} -
- )} +
+ {action.permissions.read_only && } +
+
+ {action.permissions.billing_only && ( + + )} +
))} diff --git a/studio/components/interfaces/Organization/TeamSettings/TeamSettings.tsx b/studio/components/interfaces/Organization/TeamSettings/TeamSettings.tsx index 409c80f35de..e3df3e859c9 100644 --- a/studio/components/interfaces/Organization/TeamSettings/TeamSettings.tsx +++ b/studio/components/interfaces/Organization/TeamSettings/TeamSettings.tsx @@ -19,12 +19,8 @@ const TeamSettings = () => { const isOwner = ui.selectedOrganization?.is_owner const { members } = useOrganizationDetail(slug || '') - const { roles: allRoles } = useOrganizationRoles(slug) + const { roles } = useOrganizationRoles(slug) - const enableBillingOnlyReadOnly = useFlag('enableBillingOnlyReadOnlyRoles') - const roles = enableBillingOnlyReadOnly - ? allRoles - : (allRoles ?? []).filter((role) => ['Owner', 'Administrator', 'Developer'].includes(role.name)) const { rolesAddable } = getRolesManagementPermissions(roles) const [isLeaving, setIsLeaving] = useState(false) diff --git a/studio/components/interfaces/Settings/Vault/Secrets/SecretsManagement.tsx b/studio/components/interfaces/Settings/Vault/Secrets/SecretsManagement.tsx index 38d514c6c11..468773ef358 100644 --- a/studio/components/interfaces/Settings/Vault/Secrets/SecretsManagement.tsx +++ b/studio/components/interfaces/Settings/Vault/Secrets/SecretsManagement.tsx @@ -1,25 +1,31 @@ import Link from 'next/link' import { observer } from 'mobx-react-lite' -import { FC, Fragment, useState } from 'react' -import { IconSearch, Input, Button, Listbox, IconLoader, IconExternalLink } from 'ui' +import { FC, Fragment, useState, useEffect } from 'react' +import { IconSearch, Input, Button, Listbox, IconLoader, IconExternalLink, IconX } from 'ui' +import { useStore, useParams } from 'hooks' import SecretRow from './SecretRow' import EditSecretModal from './EditSecretModal' import DeleteSecretModal from './DeleteSecretModal' import AddNewSecretModal from './AddNewSecretModal' import Divider from 'components/ui/Divider' -import { useStore } from 'hooks' interface Props {} const SecretsManagement: FC = ({}) => { const { vault } = useStore() + const { search } = useParams() + const [searchValue, setSearchValue] = useState('') const [selectedSort, setSelectedSort] = useState<'updated_at' | 'name'>('updated_at') const [showAddSecretModal, setShowAddSecretModal] = useState(false) const [selectedSecretToEdit, setSelectedSecretToEdit] = useState() const [selectedSecretToRemove, setSelectedSecretToRemove] = useState() + useEffect(() => { + if (search !== undefined) setSearchValue(search) + }, [search]) + const secrets = ( searchValue.length > 0 ? vault.listSecrets( @@ -48,6 +54,19 @@ const SecretsManagement: FC = ({}) => { value={searchValue} onChange={(event) => setSearchValue(event.target.value)} icon={} + actions={ + searchValue.length > 0 + ? [ +