From f67d531ba67c8bfecb3ee55808b93078dba7f7ec Mon Sep 17 00:00:00 2001 From: supastore <111777088+supastore@users.noreply.github.com> Date: Fri, 4 Nov 2022 12:28:40 -0400 Subject: [PATCH 001/261] Update README.md Removed comment about api folder --- examples/caching/with-nextjs-13/README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/examples/caching/with-nextjs-13/README.md b/examples/caching/with-nextjs-13/README.md index a71d2e3b114..25fa94bde48 100644 --- a/examples/caching/with-nextjs-13/README.md +++ b/examples/caching/with-nextjs-13/README.md @@ -30,10 +30,6 @@ Open [http://localhost:3000](http://localhost:3000) with your browser to see the You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. -[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`. - -The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. - ## Learn More To learn more about Next.js, take a look at the following resources: From 6ea892db7a959067453b0bc286b211e9988dc353 Mon Sep 17 00:00:00 2001 From: supastore Date: Thu, 22 Dec 2022 23:29:54 -0500 Subject: [PATCH 002/261] added hello.ts api --- examples/caching/with-nextjs-13/README.md | 4 ++++ examples/caching/with-nextjs-13/pages/api/hello.ts | 13 +++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 examples/caching/with-nextjs-13/pages/api/hello.ts diff --git a/examples/caching/with-nextjs-13/README.md b/examples/caching/with-nextjs-13/README.md index 25fa94bde48..a71d2e3b114 100644 --- a/examples/caching/with-nextjs-13/README.md +++ b/examples/caching/with-nextjs-13/README.md @@ -30,6 +30,10 @@ Open [http://localhost:3000](http://localhost:3000) with your browser to see the You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. +[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`. + +The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. + ## Learn More To learn more about Next.js, take a look at the following resources: diff --git a/examples/caching/with-nextjs-13/pages/api/hello.ts b/examples/caching/with-nextjs-13/pages/api/hello.ts new file mode 100644 index 00000000000..f8bcc7e5cae --- /dev/null +++ b/examples/caching/with-nextjs-13/pages/api/hello.ts @@ -0,0 +1,13 @@ +// Next.js API route support: https://nextjs.org/docs/api-routes/introduction +import type { NextApiRequest, NextApiResponse } from 'next' + +type Data = { + name: string +} + +export default function handler( + req: NextApiRequest, + res: NextApiResponse +) { + res.status(200).json({ name: 'John Doe' }) +} From 318fee66f77691a174a5ce6fd721b2d2bd7dc1a9 Mon Sep 17 00:00:00 2001 From: Thiago Resende Date: Sun, 25 Dec 2022 16:50:13 -0300 Subject: [PATCH 003/261] Update README.pt-br.md --- i18n/README.pt-br.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/README.pt-br.md b/i18n/README.pt-br.md index 9d22a6c7282..7101f9d2fb3 100644 --- a/i18n/README.pt-br.md +++ b/i18n/README.pt-br.md @@ -142,7 +142,7 @@ Nossa biblioteca de cliente é modular. Cada sub-biblioteca é uma implementaç - gotrue-java - - - + storage-java - From 1185405146393ad29892a3a669a589b8f8ed473c Mon Sep 17 00:00:00 2001 From: Long Hoang <1732217+loong@users.noreply.github.com> Date: Tue, 27 Dec 2022 17:29:05 +0800 Subject: [PATCH 004/261] Add user survey fields to org creation --- studio/pages/new/index.tsx | 75 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 72 insertions(+), 3 deletions(-) diff --git a/studio/pages/new/index.tsx b/studio/pages/new/index.tsx index 290d515599b..4e66c177e03 100644 --- a/studio/pages/new/index.tsx +++ b/studio/pages/new/index.tsx @@ -1,6 +1,6 @@ import { useState } from 'react' import { observer } from 'mobx-react-lite' -import { Button, Input } from 'ui' +import { Button, Input, Listbox } from 'ui' import { useRouter } from 'next/router' import { API_URL } from 'lib/constants' @@ -10,6 +10,25 @@ import { WizardLayout } from 'components/layouts' import Panel from 'components/ui/Panel' import { NextPageWithLayout } from 'types' +const ORG_KIND_TYPES = { + PERSONAL: 'Personal', + EDUCATIONAL: 'Educational', + STARTUP: 'Startup', + AGENCY: 'Agency', + COMPANY: 'Company', + UNDISCLOSED: 'Don’t know', +} +const ORG_KIND_DEFAULT = 'PERSONAL' + +const ORG_SIZE_TYPES = { + '1': '1 - 10', + '10': '10 - 49', + '50': '50 - 99', + '100': '100 - 299', + '300': 'More than 300', +} +const ORG_SIZE_DEFAULT = '1' + /** * No org selected yet, create a new one */ @@ -18,6 +37,8 @@ const Wizard: NextPageWithLayout = () => { const router = useRouter() const [orgName, setOrgName] = useState('') + const [orgKind, setOrgKind] = useState(ORG_KIND_DEFAULT) + const [orgSize, setOrgSize] = useState(ORG_SIZE_DEFAULT) const [newOrgLoading, setNewOrgLoading] = useState(false) function validateOrgName(name: any) { @@ -29,6 +50,14 @@ const Wizard: NextPageWithLayout = () => { setOrgName(e.target.value) } + function onOrgKindChange(value: any) { + setOrgKind(value) + } + + function onOrgSizeChange(value: any) { + setOrgSize(value) + } + async function onClickSubmit(e: any) { e.preventDefault() const isOrgNameValid = validateOrgName(orgName) @@ -40,6 +69,8 @@ const Wizard: NextPageWithLayout = () => { setNewOrgLoading(true) const response = await post(`${API_URL}/organizations`, { name: orgName, + kind: orgKind, + ...(orgKind == 'COMPANY' ? { size: orgSize } : {}), }) if (response.error) { @@ -78,9 +109,9 @@ const Wizard: NextPageWithLayout = () => { } > -

This is your organization's name within Supabase.

+

This is your organization within Supabase.

- For example, you can use the name of your company or department + For example, you can use the name of your company or department.

@@ -95,6 +126,44 @@ const Wizard: NextPageWithLayout = () => { onChange={onOrgNameChange} /> + + + {Object.entries(ORG_KIND_TYPES).map(([k, v]) => { + return ( + + {v} + + ) + })} + + + {orgKind == 'COMPANY' ? ( + + + {Object.entries(ORG_SIZE_TYPES).map(([k, v]) => { + return ( + + {v} + + ) + })} + + + ) : ( + <> + )} ) } From 45550be4e0784a9aece37e72ed068748c506f3fa Mon Sep 17 00:00:00 2001 From: dannykng Date: Wed, 4 Jan 2023 15:50:54 -0800 Subject: [PATCH 005/261] Delete old mdx files --- apps/docs/pages/getting-started.mdx | 161 ------ apps/docs/pages/guides/tutorials.mdx | 159 ------ apps/docs/pages/new/auth.mdx | 77 --- apps/docs/pages/ref-pages.mdx | 0 apps/docs/pages/tutorials/nextjs.mdx | 724 --------------------------- 5 files changed, 1121 deletions(-) delete mode 100644 apps/docs/pages/getting-started.mdx delete mode 100644 apps/docs/pages/guides/tutorials.mdx delete mode 100644 apps/docs/pages/new/auth.mdx delete mode 100644 apps/docs/pages/ref-pages.mdx delete mode 100644 apps/docs/pages/tutorials/nextjs.mdx diff --git a/apps/docs/pages/getting-started.mdx b/apps/docs/pages/getting-started.mdx deleted file mode 100644 index ccb7790f15e..00000000000 --- a/apps/docs/pages/getting-started.mdx +++ /dev/null @@ -1,161 +0,0 @@ -import Layout from '~/layouts/DefaultGuideLayout' - -export const meta = { - title: 'Overview', - description: 'An overview of Supabase\'s architecture and product principles.', -} - -Supabase is open source. We choose open source tools which are scalable and make them simple to use. - -Supabase is not a 1-to-1 mapping of Firebase. While we are building many of the features that Firebase offers, we are not going about it the same way: -our technological choices are quite different; everything we use is open source; and wherever possible, we use and support existing tools rather than developing from scratch. - -Most notably, we use Postgres rather than a NoSQL store. This choice was deliberate. We believe that no other database offers the functionality required to compete with Firebase, -while maintaining the scalability required to go beyond it. - -## Architecture - -Each Supabase project consists of several tools: - -![Supabase Architecture](/docs/img/supabase-architecture.png) - -### PostgreSQL (Database) - -PostgreSQL is the core of Supabase. We do not abstract the PostgreSQL database — you can access it and use it with full privileges. We simply provide tools which makes PostgreSQL as easy to use as Firebase. - -- Official Docs: [postgresql.org/docs](https://www.postgresql.org/docs/current/index.html) -- Source code: [github.com/postgres/postgres](https://github.com/postgres/postgres) (mirror) -- License: [PostgreSQL Licence](https://www.postgresql.org/about/licence/) -- Language: C - -### Studio (Dashboard) - -An open source Dashboard for managing your database and services. - -- Official Docs: [Supabase docs](/docs) -- Source code: [github.com/supabase/supabase](https://github.com/supabase/supabase/tree/master/studio) -- License: [Apache 2](https://github.com/supabase/supabase/blob/master/LICENSE) -- Language: TypeScript - -### GoTrue (Auth) - -A JWT-based API for managing users and issuing access tokens. This integrates with PostgreSQL's Row Level Security and the API servers. - -- Official Docs: [Supabase Auth reference docs](/docs/reference/auth) -- Source code: [github.com/supabase/gotrue](https://github.com/supabase/gotrue) -- License: [MIT](https://github.com/supabase/gotrue/blob/master/LICENSE) -- Language: Go - -### PostgREST (API) - -A standalone web server that turns your PostgreSQL database directly into a RESTful API. -We use this with our [pg_graphql](https://github.com/supabase/pg_graphql) extension to provide a GraphQL API. - -- Official Docs: [postgrest.org](https://postgrest.org/) -- Source code: [github.com/PostgREST/postgrest](https://github.com/PostgREST/postgrest) -- License: [MIT](https://github.com/PostgREST/postgrest/blob/main/LICENSE) -- Language: Haskel - -### Realtime (API & multiplayer) - -A scalable websocket engine for managing user Presence, broadcasting messages, and streaming database changes. - -- Official Docs: [Supabase Realtime docs](/docs/guides/realtime) -- Source code: [github.com/supabase/realtime](https://github.com/supabase/realtime) -- License: [Apache 2](https://github.com/supabase/realtime/blob/main/LICENSE) -- Language: Elixir - -### Storage API (large file storage) - -An S3-compatible object storage service that stores metadata in Postgres. - -- Official Docs: [Supabase Storage reference docs](/docs/reference/storage) -- Source code: [github.com/supabase/storage-api](https://github.com/supabase/storage-api) -- License: [Apache 2.0](https://github.com/supabase/storage-api/blob/master/LICENSE) -- Language: NodeJS / TypeScript - -### Deno (Edge Functions) - -A modern runtime for JavaScript and TypeScript. - -- Official Docs: [deno.land](https://deno.land/) -- Source code: [github.com/denoland/deno](https://github.com/denoland/deno) -- License: [MIT](https://github.com/denoland/deno/blob/main/LICENSE.md) -- Language: TypeScript / Rust - -### postgres-meta (Database management) - -A RESTful API for managing your Postgres. Fetch tables, add roles, and run queries. - -- Official Docs: [supabase.github.io/postgres-meta](https://supabase.github.io/postgres-meta/) -- Source code: [github.com/supabase/postgres-meta](https://github.com/supabase/postgres-meta) -- License: [Apache 2.0](https://github.com/supabase/postgres-meta/blob/master/LICENSE) -- Language: NodeJS / TypeScript - -### PgBouncer - -A lightweight connection pooler for PostgreSQL. This is useful for connecting to Postgres when using Serverless functions. - -- Official Docs: [pgbouncer.org](http://www.pgbouncer.org/) -- Source code: [pgbouncer/pgbouncer](https://github.com/pgbouncer/pgbouncer) -- License: [ISC](https://github.com/pgbouncer/pgbouncer/blob/master/COPYRIGHT) -- Language: C - -### Kong (API Gateway) - -A cloud-native API gateway, built on top of Nginx. - -- Official Docs: [docs.konghq.com](https://docs.konghq.com/) -- Source code: [github.com/kong/kong](https://github.com/kong/kong) -- License: [Apache 2.0](https://github.com/Kong/kong/blob/master/LICENSE) -- Language: Lua - -## Product Principles - -It is our goal to provide an architecture that any large-scale company would design for themselves, -and then provide tooling around that architecture that is easy-to-use for indie-developers and small teams. - -We use a series of principles to ensure that scalability and usability are never mutually exclusive: - -### Everything works in isolation - -Each system must work as a standalone tool with as few moving parts as possible. -The litmus test for this is: "Can a user run this product with nothing but a Postgres database?" - -### Everything is integrated - -Supabase is composable. Even though every product works in isolation, each product on the platform needs to 10x the other products. -For integration, each tool should expose an API and Webhooks. - -### Everything is extensible - -We're deliberate about adding a new tool, and prefer instead to extend an existing one. -This is the opposite of many cloud providers whose product offering expands into niche use-cases. We provide _primitives_ for developers, which allow them to achieve any goal. -Less, but better. - -### Everything is portable - -To avoid lock-in, we make it easy to migrate in and out. Our cloud offering is compatible with our self-hosted product. -We use existing standards to increase portability (like pg_dump an CSV files). If a new standard emerges which competes with a "Supabase" approach, we will deprecate the approach in favor of the standard. -This forces us compete on experience. We aim to be the best Postgres hosting service. - -### Play the long game - -We sacrifice short-term wins for long-term gains. For example, it is tempting to run a fork of Postgres with additional functionality which only our customers need. -Instead, we prefer to support efforts to upstream missing functionality so that the entire community benefits. This has the additional benefit of ensuring portability and longevity. - -### Build for developers - -"Developers" are a specific profile of user: they are _builders_. -When assessing impact as a function of effort, developers have a large efficiency due to the type of products and systems they can build. -As the profile of a developer changes over time, Supabase will continue to evolve the product to fit this evolving profile. - -### Support existing tools - -Supabase supports existing tools and communities wherever possible. Supabase is more like a "community of communities" - each tool typically has its own community -which we work with. -Open source is something we approach [collaboratively](https://supabase.com/blog/supabase-series-b#giving-back): we employ maintainers, sponsor projects, invest in businesses, and develop our own open source tools. - -export const Page = ({ children }) => - -export default Page diff --git a/apps/docs/pages/guides/tutorials.mdx b/apps/docs/pages/guides/tutorials.mdx deleted file mode 100644 index 39f553feea8..00000000000 --- a/apps/docs/pages/guides/tutorials.mdx +++ /dev/null @@ -1,159 +0,0 @@ -import Layout from '~/layouts/DefaultLayout' -import Link from 'next/link' -import { GlassPanel } from 'ui' - -export const meta = { - title: 'Tutorials', -} - -### Webapps - -
- {webapps.map((item) => { - return ( - - - - {item.description} - - - - ) - })} -
- -### Mobile - -
- {mobile.map((item) => { - return ( - - - - {item.description} - - - - ) - })} -
- -### Low code - -
- {lowCode.map((item) => { - return ( - - - - {item.description} - - - - ) - })} -
- -export const webapps = [ - { - title: 'NextJS', - href: '/guides/tutorials/with-nextjs', - description: - 'Learn how to build a user management app with NextJS and Supabase Database, Auth, and Storage functionality.', - icon: '/docs/img/icons/nextjs-icon', - }, - { - title: 'React', - href: '/guides/tutorials/with-react', - description: - 'Learn how to build a user management app with React and Supabase Database, Auth, and Storage functionality.', - icon: '/docs/img/icons/react-icon', - }, - { - title: 'Vue 3', - href: '/guides/tutorials/with-vue-3', - description: - 'Learn how to build a user management app with Vue 3 and Supabase Database, Auth, and Storage functionality.', - icon: '/docs/img/icons/vuejs-icon', - }, - { - title: 'Nuxt 3', - href: '/guides/tutorials/with-nuxt-3', - description: - 'Learn how to build a user management app with Nuxt 3 and Supabase Database, Auth, and Storage functionality.', - icon: '/docs/img/icons/nuxt-icon', - }, - { - title: 'Anglular', - href: '/guides/tutorials/with-angular', - description: - 'Learn how to build a user management app with Nuxt 3 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.', - 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.', - 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.', - icon: '/docs/img/icons/svelte-icon', - }, -] - -export const mobile = [ - { - title: 'Flutter', - href: '/guides/tutorials/with-flutter', - description: - 'Learn how to build a user management app with Flutter and Supabase Database, Auth, and Storage functionality.', - icon: '/docs/img/icons/flutter-icon', - }, - { - 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.', - icon: '/docs/img/icons/expo-icon', - }, -] - -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.', - 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.', - 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.', - icon: '/docs/img/icons/ionic-icon', - }, -] - -export const Page = ({ children }) => - -export default Page diff --git a/apps/docs/pages/new/auth.mdx b/apps/docs/pages/new/auth.mdx deleted file mode 100644 index 5228f5129da..00000000000 --- a/apps/docs/pages/new/auth.mdx +++ /dev/null @@ -1,77 +0,0 @@ -import Layout from '~/layouts/DefaultLayout' -import Link from 'next/link' -// import { useTheme } from 'common/Providers' -import { GlassPanel } from 'ui' - -{/* // export const { isDarkMode } = useTheme() */} - -export const meta = { - title: 'Auth', -} - -## Add and manage email and password, passwordless, OAuth, and mobile logins to your project through a suite of identity providers and APIs. - -### Popular Guides - -
- {topArticles.map((item) => { - return ( - - - - {item.description} - - - - )})} - -
- -export const topArticles = [ - { - title: 'Enable Captcha Protection', - header: '/docs/img/cards/sample-card-header-4.svg', - href: '/guides/auth/auth-captcha', - description: 'Keep your platform safe from bots and malicious scripts by implementing hCaptcha.', - span: 'col-span-6', - }, - { - title: 'Auth Helpers', - header: '/docs/img/cards/sample-card-header-4.svg', - href: '/guides/auth/auth-helpers', - description: 'A collection of framework-specific utilities for implementing Supabase Auth.', - span: 'col-span-6', - }, - { - title: 'Email Login', - header: '/docs/img/cards/sample-card-header-4.svg', - href: '/guides/auth/auth-email', - description: 'Learn how to add an email and password login flow to your platform.', - }, - { - title: 'Phone Login', - header: '/docs/img/cards/sample-card-header-4.svg', - href: '/guides/auth/phone-login', - description: 'Learn how to add a login flow so your users can access your platform through one-time passwords via SMS.', - }, - { - title: 'Social Login', - header: '/docs/img/cards/sample-card-header-4.svg', - href: '/guides/auth/social-login', - description: 'Learn how to add a login flow so your users can access your platform through social providers like Apple, Facebook, or Google.', - }, -] - -export const Page = ({ children }) => - -export default Page diff --git a/apps/docs/pages/ref-pages.mdx b/apps/docs/pages/ref-pages.mdx deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/apps/docs/pages/tutorials/nextjs.mdx b/apps/docs/pages/tutorials/nextjs.mdx deleted file mode 100644 index 5e22b391296..00000000000 --- a/apps/docs/pages/tutorials/nextjs.mdx +++ /dev/null @@ -1,724 +0,0 @@ -import Layout from '~/layouts/tutorials/TutorialLayout' - -export const meta = { - title: 'Creating a user management dashboard with NextJS and Supabase', - author: 'Rich Haines', - video: 'https://www.youtube-nocookie.com/embed/0Fs96oZ4se0', -} - -In this 25-minute guide, we’ll be building a collaborative user managment app using NextJS and Supabase. -As users add and move rectangles in a canvas, changes will be automatically synced and persisted, allowing for a canvas that updates in real-time across clients. -Users will also be able to see other users selections, and undo and redo actions. - -![Supabase User Management example](/docs/img/user-management-demo.png) - -### GitHub - -Should you get stuck while working through the guide, refer to [this repo](https://github.com/supabase/supabase/tree/master/examples/user-management/nextjs-ts-user-management). - -## Building the App - -Let's start building the Next.js app from scratch. - -### Initialize a Next.js app - -We can use [`create-next-app`](https://nextjs.org/docs/getting-started) to initialize -an app called `supabase-nextjs`: - - - - -```bash -npx create-next-app@latest --use-npm supabase-nextjs -cd supabase-nextjs -``` - - - - -```bash -npx create-next-app@latest --ts --use-npm supabase-nextjs -cd supabase-nextjs -``` - - - - -Then install the Supabase client library: [supabase-js](https://github.com/supabase/supabase-js) - -```bash -npm install @supabase/supabase-js -``` - -And finally we want to save the environment variables in a `.env.local`. -All we need are the API URL and the `anon` key that you copied [earlier](#get-the-api-keys). - -```bash title=.env.local -NEXT_PUBLIC_SUPABASE_URL=YOUR_SUPABASE_URL -NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY -``` - -And one optional step is to update the CSS file `styles/globals.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/nextjs-ts-user-management/styles/globals.css). - -### Set up a Login component - -#### Supabase Auth Helpers - -Next.js is a highly versatile framework offering pre-rendering at build time (SSG), server-side rendering at request time (SSR), API routes, and middleware edge-functions. - -It can be challenging to authenticate your users in all these different environments, that's why we've created the [Supabase Auth Helpers](https://supabase.com/docs/guides/auth/auth-helpers/nextjs) to make user management and data fetching within Next.js as easy as possible. - -Install the auth helpers for React and Next.js - -```bash -npm install @supabase/auth-helpers-react @supabase/auth-helpers-nextjs -``` - - - - -Wrap your `pages/_app.js` component with the `SessionContextProvider` component: - -```jsx title=pages/_app.js -import '../styles/globals.css' -import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs' -import { SessionContextProvider } from '@supabase/auth-helpers-react' - -function MyApp({ Component, pageProps }) { - const [supabaseClient] = useState(() => createBrowserSupabaseClient()) - - return ( - - - - ) -} -export default MyApp -``` - - - - -Wrap your `pages/_app.tsx` component with the `SessionContextProvider` component: - -```jsx lines=2,8 title=pages/_app.tsx -import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs' -import { SessionContextProvider, Session } from '@supabase/auth-helpers-react' - -function MyApp({ - Component, - pageProps, -}: AppProps<{ - initialSession: Session, -}>) { - const [supabaseClient] = useState(() => createBrowserSupabaseClient()) - - return ( - - - - ) -} -export default MyApp -``` - -See the [Auth Helpers docs](/docs/guides/auth/auth-helpers/nextjs#usage-with-typescript) for more details on usage with TypeScript. - - - - -#### Supabase Auth UI - -We can use the [Supabase Auth UI](https://supabase.com/docs/guides/auth/auth-helpers/auth-ui) a pre-built React component for authenticating users via OAuth, email, and magic links. - -Install the Supabase Auth UI for React - -```bash -npm install @supabase/auth-ui-react -``` - -Add the `Auth` component to your home page - -```jsx title=pages/index.js -import { Auth, ThemeSupa } from '@supabase/auth-ui-react' -import { useSession, useSupabaseClient } from '@supabase/auth-helpers-react' - -const Home = () => { - const session = useSession() - const supabase = useSupabaseClient() - - return ( -
- {!session ? ( - - ) : ( -

Account page will go here.

- )} -
- ) -} - -export default Home -``` - -### Account page - -After a user is signed in we can allow them to edit their profile details and manage their account. - - - - -Let's create a new component for that called `Account.js` within a `components` folder. - -```tsx title=components/Account.js -import { useState, useEffect } from 'react' -import { useUser, useSupabaseClient } from '@supabase/auth-helpers-react' - -export default function Account({ session }) { - const supabase = useSupabaseClient() - const user = useUser() - const [loading, setLoading] = useState(true) - const [username, setUsername] = useState(null) - const [website, setWebsite] = useState(null) - const [avatar_url, setAvatarUrl] = useState(null) - - useEffect(() => { - getProfile() - }, [session]) - - async function getProfile() { - try { - setLoading(true) - - let { data, error, status } = await supabase - .from('profiles') - .select(`username, website, avatar_url`) - .eq('id', user.id) - .single() - - if (error && status !== 406) { - throw error - } - - if (data) { - setUsername(data.username) - setWebsite(data.website) - setAvatarUrl(data.avatar_url) - } - } catch (error) { - alert('Error loading user data!') - console.log(error) - } finally { - setLoading(false) - } - } - - async function updateProfile({ username, website, avatar_url }) { - try { - setLoading(true) - - const updates = { - id: user.id, - username, - website, - avatar_url, - updated_at: new Date().toISOString(), - } - - let { error } = await supabase.from('profiles').upsert(updates) - if (error) throw error - alert('Profile updated!') - } catch (error) { - alert('Error updating the data!') - console.log(error) - } finally { - setLoading(false) - } - } - - return ( -
-
- - -
-
- - setUsername(e.target.value)} - /> -
-
- - setWebsite(e.target.value)} - /> -
- -
- -
- -
- -
-
- ) -} -``` - -
- - -Let's create a new component for that called `Account.tsx` within a `components` folder. - -```tsx title=components/Account.tsx -import { useState, useEffect } from 'react' -import { useUser, useSupabaseClient, Session } from '@supabase/auth-helpers-react' -import { Database } from '../utils/database.types' -type Profiles = Database['public']['Tables']['profiles']['Row'] - -export default function Account({ session }: { session: Session }) { - const supabase = useSupabaseClient() - const user = useUser() - const [loading, setLoading] = useState(true) - const [username, setUsername] = useState(null) - const [website, setWebsite] = useState(null) - const [avatar_url, setAvatarUrl] = useState(null) - - useEffect(() => { - getProfile() - }, [session]) - - async function getProfile() { - try { - setLoading(true) - if (!user) throw new Error('No user') - - let { data, error, status } = await supabase - .from('profiles') - .select(`username, website, avatar_url`) - .eq('id', user.id) - .single() - - if (error && status !== 406) { - throw error - } - - if (data) { - setUsername(data.username) - setWebsite(data.website) - setAvatarUrl(data.avatar_url) - } - } catch (error) { - alert('Error loading user data!') - console.log(error) - } finally { - setLoading(false) - } - } - - async function updateProfile({ - username, - website, - avatar_url, - }: { - username: Profiles['username'] - website: Profiles['website'] - avatar_url: Profiles['avatar_url'] - }) { - try { - setLoading(true) - if (!user) throw new Error('No user') - - const updates = { - id: user.id, - username, - website, - avatar_url, - updated_at: new Date().toISOString(), - } - - let { error } = await supabase.from('profiles').upsert(updates) - if (error) throw error - alert('Profile updated!') - } catch (error) { - alert('Error updating the data!') - console.log(error) - } finally { - setLoading(false) - } - } - - return ( -
-
- - -
-
- - setUsername(e.target.value)} - /> -
-
- - setWebsite(e.target.value)} - /> -
- -
- -
- -
- -
-
- ) -} -``` - -
-
- -### Launch! - -Now that we have all the components in place, let's update `pages/index.js`: - -```jsx lines=3,14 title=pages/index.js -import { Auth, ThemeSupa } from '@supabase/auth-ui-react' -import { useSession, useSupabaseClient } from '@supabase/auth-helpers-react' -import Account from '../components/Account' - -const Home = () => { - const session = useSession() - const supabase = useSupabaseClient() - - return ( -
- {!session ? ( - - ) : ( - - )} -
- ) -} - -export default Home -``` - -Once that's done, run this in a terminal window: - -```bash -npm run dev -``` - -And then open the browser to [localhost:3000](http://localhost:3000) and you should see the completed app. - -## Bonus: Profile photos - -Every Supabase project is configured with [Storage](/docs/guides/storage) for managing large files like -photos and videos. - -### Create an upload widget - -Let's create an avatar widget for the user so that they can upload a profile photo. We can start by creating a new component: - - - - -```jsx title=components/Avatar.js -import React, { useEffect, useState } from 'react' -import { useSupabaseClient } from '@supabase/auth-helpers-react' - -export default function Avatar({ uid, url, size, onUpload }) { - const supabase = useSupabaseClient() - const [avatarUrl, setAvatarUrl] = useState(null) - const [uploading, setUploading] = useState(false) - - useEffect(() => { - if (url) downloadImage(url) - }, [url]) - - async function downloadImage(path) { - try { - const { data, error } = await supabase.storage.from('avatars').download(path) - if (error) { - throw error - } - const url = URL.createObjectURL(data) - setAvatarUrl(url) - } catch (error) { - console.log('Error downloading image: ', error) - } - } - - const uploadAvatar = async (event) => { - try { - setUploading(true) - - if (!event.target.files || event.target.files.length === 0) { - throw new Error('You must select an image to upload.') - } - - const file = event.target.files[0] - const fileExt = file.name.split('.').pop() - const fileName = `${uid}.${fileExt}` - const filePath = `${fileName}` - - let { error: uploadError } = await supabase.storage - .from('avatars') - .upload(filePath, file, { upsert: true }) - - if (uploadError) { - throw uploadError - } - - onUpload(filePath) - } catch (error) { - alert('Error uploading avatar!') - console.log(error) - } finally { - setUploading(false) - } - } - - return ( -
- {avatarUrl ? ( - Avatar - ) : ( -
- )} -
- - -
-
- ) -} -``` - - - - -```tsx title=components/Avatar.tsx -import React, { useEffect, useState } from 'react' -import { useSupabaseClient } from '@supabase/auth-helpers-react' -import { Database } from '../utils/database.types' -type Profiles = Database['public']['Tables']['profiles']['Row'] - -export default function Avatar({ - uid, - url, - size, - onUpload, -}: { - uid: string - url: Profiles['avatar_url'] - size: number - onUpload: (url: string) => void -}) { - const supabase = useSupabaseClient() - const [avatarUrl, setAvatarUrl] = useState(null) - const [uploading, setUploading] = useState(false) - - useEffect(() => { - if (url) downloadImage(url) - }, [url]) - - async function downloadImage(path: string) { - try { - const { data, error } = await supabase.storage.from('avatars').download(path) - if (error) { - throw error - } - const url = URL.createObjectURL(data) - setAvatarUrl(url) - } catch (error) { - console.log('Error downloading image: ', error) - } - } - - const uploadAvatar: React.ChangeEventHandler = async (event) => { - try { - setUploading(true) - - if (!event.target.files || event.target.files.length === 0) { - throw new Error('You must select an image to upload.') - } - - const file = event.target.files[0] - const fileExt = file.name.split('.').pop() - const fileName = `${uid}.${fileExt}` - const filePath = `${fileName}` - - let { error: uploadError } = await supabase.storage - .from('avatars') - .upload(filePath, file, { upsert: true }) - - if (uploadError) { - throw uploadError - } - - onUpload(filePath) - } catch (error) { - alert('Error uploading avatar!') - console.log(error) - } finally { - setUploading(false) - } - } - - return ( -
- {avatarUrl ? ( - Avatar - ) : ( -
- )} -
- - -
-
- ) -} -``` - - - - -### Add the new widget - -And then we can add the widget to the Account page: - -```jsx title=components/Account.js -// Import the new component -import Avatar from './Avatar' - -// ... - -return ( -
- {/* Add to the body */} - { - setAvatarUrl(url) - updateProfile({ username, website, avatar_url: url }) - }} - /> - {/* ... */} -
-) -``` - -## Next steps - -At this stage you have a fully functional application! - -- See the complete [example on GitHub](https://github.com/supabase/supabase/tree/master/examples/user-management/nextjs-ts-user-management) and deploy it to Vercel. -- Explore the [pre-built Auth UI for React](https://supabase.com/docs/guides/auth/auth-helpers/auth-ui). -- Explore the [Auth Helpers for Next.js](https://supabase.com/docs/guides/auth/auth-helpers/nextjs). -- Explore the [Supabase Cache Helpers](https://github.com/psteinroe/supabase-cache-helpers). -- See the [Next.js Subscription Payments Starter](https://github.com/vercel/nextjs-subscription-payments) template on GitHub. -- Got a question? [Ask here](https://github.com/supabase/supabase/discussions). -- Sign in: [app.supabase.com](https://app.supabase.com) - -export default ({ children }) => From 6af3acdc046e6b616a7c745461cabd9bae7ddb37 Mon Sep 17 00:00:00 2001 From: dannykng Date: Wed, 4 Jan 2023 16:09:51 -0800 Subject: [PATCH 006/261] Update sitemap --- apps/docs/public/sitemap.xml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/apps/docs/public/sitemap.xml b/apps/docs/public/sitemap.xml index 38b0e06220a..9cc0078e8dd 100644 --- a/apps/docs/public/sitemap.xml +++ b/apps/docs/public/sitemap.xml @@ -6,12 +6,6 @@ 0.5 - - https://supabase.com/docs/getting-started - weekly - 0.5 - - https://supabase.com/docs/index weekly @@ -84,12 +78,6 @@ 0.5 - - https://supabase.com/docs/guides/tutorials - weekly - 0.5 - - https://supabase.com/docs/handbook/contributing weekly From fcfc1886d0c1a5a7f260039fdd15fed3279c8a98 Mon Sep 17 00:00:00 2001 From: Ollie Halliday <22655069+Hallidayo@users.noreply.github.com> Date: Mon, 9 Jan 2023 14:51:15 +0000 Subject: [PATCH 007/261] added link for docs --- .../Auth/EmailTemplates/EmailTemplates.tsx | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/studio/components/interfaces/Auth/EmailTemplates/EmailTemplates.tsx b/studio/components/interfaces/Auth/EmailTemplates/EmailTemplates.tsx index 0e86b1247a5..a982880a845 100644 --- a/studio/components/interfaces/Auth/EmailTemplates/EmailTemplates.tsx +++ b/studio/components/interfaces/Auth/EmailTemplates/EmailTemplates.tsx @@ -1,4 +1,4 @@ -import { Tabs } from 'ui' +import { Button, IconExternalLink, Tabs } from 'ui' import { observer } from 'mobx-react-lite' import { TEMPLATES_SCHEMAS } from 'stores/authConfig/schema' @@ -8,10 +8,20 @@ import TemplateEditor from './TemplateEditor' const EmailTemplates = observer(() => { return (
- + Date: Mon, 9 Jan 2023 13:54:43 -0800 Subject: [PATCH 008/261] Changes from PR #10796 --- .../auth-helpers/nextjs-server-components.mdx | 625 +++++++++--------- 1 file changed, 306 insertions(+), 319 deletions(-) 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 35861d98c86..614f31625c1 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 @@ -10,6 +10,8 @@ export const meta = { This submodule provides experimental convenience helpers for implementing user authentication in Next.js Server Components - the `app` directory. For examples using the `pages` directory check out [Auth Helpers in Next.js](/docs/guides/auth/auth-helpers/nextjs). +> For a complete implementation example, check out [this repo](https://github.com/supabase/auth-helpers/tree/main/examples/nextjs-server-components). + > To learn more about fetching and caching Supabase data with Next.js 13 Server Components, check out our [blog](/blog/fetching-and-caching-supabase-data-in-next-js-server-components) or [live stream](https://www.youtube.com/watch?v=QH0P5xZt5wY). ## Install the Next.js helper library @@ -52,6 +54,8 @@ NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY ## Creating a Supabase Client +### Server-side + -Create a new file at `/utils/supabase-browser.js` and populate with the following: - -```js title="/utils/supabase-browser.js" -import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs' - -export default createBrowserSupabaseClient() -``` - -This will be used any time we need to create a Supabase client _client-side_ - in `useEffect`, for example. - Create a new file at `/utils/supabase-server.js` and populate with the following: ```js title="/utils/supabase-server.js" import { headers, cookies } from 'next/headers' import { createServerComponentSupabaseClient } from '@supabase/auth-helpers-nextjs' -export default () => +export const createClient = () => createServerComponentSupabaseClient({ headers, cookies, @@ -85,33 +79,19 @@ export default () => > This needs to export a function, as the headers and cookies are not populated with values until the Server Component is requesting data. -This will be used any time we need to create a Supabase client _server-side_ - in a Server Component, for example. - -Create a new file at `/utils/supabase-browser.ts` and populate with the following: - -```ts -import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs' -import { Database } from '../lib/database.types' - -export default createBrowserSupabaseClient() -``` - -> TypeScript types can be [generated with the Supabase CLI](https://supabase.com/docs/reference/javascript/typescript-support) and passed to `createBrowserSupabaseClient` to add type support to the Supabase client. - -This will be used any time we need to create a Supabase client _client-side_ - in `useEffect`, for example. - Create a new file at `/utils/supabase-server.ts` and populate with the following: ```ts title="/utils/supabase-server.ts" import { headers, cookies } from 'next/headers' import { createServerComponentSupabaseClient } from '@supabase/auth-helpers-nextjs' -import { Database } from '../lib/database.types' -export default () => +import type { Database } from '../db_types' + +export const createClient = () => createServerComponentSupabaseClient({ headers, cookies, @@ -122,14 +102,12 @@ export default () => > This needs to export a function, as the headers and cookies are not populated with values until the Server Component is requesting data. -This will be used any time we need to create a Supabase client _server-side_ - in a Server Component, for example. - -## Middleware +This will be used any time we need to create a Supabase client _server-side_ - in a Server Component, for example. -Middleware runs before every route declared in the `matcher` array. Since we don't have access to set cookies or headers from Server Components, we need to create a Middleware Supabase client and refresh the user's session by calling `getSession()`. +Next, we need a middleware file to refresh the user's session on navigation. -Create a new file at `/app/middleware.js` and populate with the following: - ```jsx title="middleware.js" import { createMiddlewareSupabaseClient } from '@supabase/auth-helpers-nextjs' import { NextResponse } from 'next/server' @@ -156,27 +132,22 @@ export async function middleware(req) { return res } - -export const config = { - matcher: ['/optional-session', '/required-session', '/realtime'], -} ``` -Create a new file at `/app/middleware.ts` and populate with the following: - ```tsx title="middleware.ts" import { createMiddlewareSupabaseClient } from '@supabase/auth-helpers-nextjs' -import { NextResponse } from 'next/server' -import type { NextRequest } from 'next/server' + +import type { NextRequest, NextResponse } from 'next/server' +import type { Database } from 'db_types' export async function middleware(req: NextRequest) { const res = NextResponse.next() - const supabase = createMiddlewareSupabaseClient({ req, res }) + const supabase = createMiddlewareSupabaseClient({ req, res }) const { data: { session }, @@ -184,20 +155,34 @@ export async function middleware(req: NextRequest) { return res } - -export const config = { - matcher: ['/optional-session', '/required-session', '/realtime'], -} ``` +> TypeScript types can be [generated with the Supabase CLI](https://supabase.com/docs/reference/javascript/typescript-support) and passed to `createMiddlewareSupabaseClient` to add type support to the Supabase client. + -> Any Server Component route that uses a Supabase client must be added to this middleware's `matcher` array. Without this, the Server Component may try to make a request to Supabase with an expired `access_token`. +We can now use our server-side Supabase client to fetch data in Server Components. -## Supabase Listener +```jsx +import 'server-only' -We need to set up a listener to fetch fresh data whenever our user logs in or out. +import { createClient } from '../../utils/supabase-server' + +// do not cache this page +export const revalidate = 0 + +export default async function ServerComponent() { + const supabase = createClient() + const { data } = await supabase.from('posts').select('*') + + return
{JSON.stringify({ data }, null, 2)}
+} +``` + +### Client-side + +We still need a Supabase instance client-side for authentication and realtime subscriptions. It is important, when using Supabase client-side, to have a single instance of a client. We can share this singleton instance across our components using providers and React context. -Create a `/components/supabase-listener.jsx` file and add the following: +Create a new file at `/utils/supabase-browser.js` and populate with the following: + +```js title="/utils/supabase-browser.js" +import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs' +export const createClient = () => createBrowserSupabaseClient() +``` + + + + + +Create a new file at `/utils/supabase-browser.ts` and populate with the following: + +```ts +import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs' +import { Database } from '../db_types' + +export const createClient = () => createBrowserSupabaseClient() +``` + +> TypeScript types can be [generated with the Supabase CLI](https://supabase.com/docs/reference/javascript/typescript-support) and passed to `createBrowserSupabaseClient` to add type support to the Supabase client. + + + + +Next, we need to create a single instance of Supabase to use client-side. Let's create a new Provider for Supabase. + + + + +```jsx title=components/supabase-provider.jsx +'use client' + +import { createContext, useContext, useState } from 'react' +import { createClient } from '../utils/supabase-browser' + +const Context = createContext() + +export default function SupabaseProvider({ children }) { + const [supabase] = useState(() => createClient()) + + return ( + + <>{children} + + ) +} + +export const useSupabase = () => useContext(Context) +``` + + + + + +```tsx title=components/supabase-provider.tsx +'use client' + +import { createContext, useContext, useState } from 'react' +import { createClient } from '../utils/supabase-browser' + +import type { SupabaseClient } from '@supabase/auth-helpers-nextjs' +import type { Database } from '../db_types' + +type SupabaseContext = { + supabase: SupabaseClient +} + +const Context = createContext() + +export default function SupabaseProvider({ children }: { children: React.ReactNode }) { + const [supabase] = useState(() => createClient()) + + return ( + + <>{children} + + ) +} + +export const useSupabase = () => useContext(Context) +``` + +> TypeScript types can be [generated with the Supabase CLI](https://supabase.com/docs/reference/javascript/typescript-support) and passed to `createBrowserSupabaseClient` to add type support to the Supabase client. + + + + +We need to set up a listener to fetch fresh data whenever our user logs in or out. For this we need to check whether our client and server sessions match. Let's start by installing the `server-only` package. + +```bash +npm install server-only +``` + +This will ensure that any component that imports this package will be a Server Component, and excluded from the browser bundle. + +Next, let's modify our root layout to fetch the user's session, wrap our application in our Supabase Provider, and pass the server access token as a prop to the `` component (we will create this next). + + + + +```jsx title=app/layout.jsx +import 'server-only' + +import SupabaseListener from '../components/supabase-listener' +import SupabaseProvider from '../components/supabase-provider' +import './globals.css' +import { createClient } from '../utils/supabase-server' + +// do not cache this layout +export const revalidate = 0 + +export default async function RootLayout({ children }) { + const supabase = createClient() + + const { + data: { session }, + } = await supabase.auth.getSession() + + return ( + + {/* + will contain the components returned by the nearest parent + head.tsx. Find out more at https://beta.nextjs.org/docs/api-reference/file-conventions/head + */} + + + + + {children} + + + + ) +} +``` + + + + + +```tsx title=app/layout.tsx +import 'server-only' + +import SupabaseListener from '../components/supabase-listener' +import SupabaseProvider from '../components/supabase-provider' +import './globals.css' +import { createClient } from '../utils/supabase-server' + +// do not cache this layout +export const revalidate = 0 + +export default async function RootLayout({ children }: { children: React.ReactNode }) { + const supabase = createClient() + + const { + data: { session }, + } = await supabase.auth.getSession() + + return ( + + {/* + will contain the components returned by the nearest parent + head.tsx. Find out more at https://beta.nextjs.org/docs/api-reference/file-conventions/head + */} + + + + + {children} + + + + ) +} +``` + + + + +And now create our Supabase listener component that uses the singleton Supabase instance to listen for auth changes. + + + ```jsx title="/components/supabase-listener.jsx" 'use client' import { useRouter } from 'next/navigation' import { useEffect } from 'react' -import supabase from '../utils/supabase-browser' +import { useSupabase } from './supabase-provider' -export default function SupabaseListener({ accessToken }) { +export default function SupabaseListener({ serverAccessToken }) { + const { supabase } = useSupabase() const router = useRouter() useEffect(() => { - supabase.auth.onAuthStateChange((event, session) => { - if (session?.access_token !== accessToken) { + const { + data: { subscription }, + } = supabase.auth.onAuthStateChange((event, session) => { + if (session?.access_token !== serverAccessToken) { router.refresh() } }) - }, [accessToken]) + + return () => { + subscription.unsubscribe() + } + }, [serverAccessToken, router, supabase]) return null } ``` -> `use client` tells Next.js that this is a [Client Component](https://beta.nextjs.org/docs/rendering/server-and-client-components#client-components). Only Client Components can use hooks like `useEffect` and `useRouter`. - -The function we pass to `onAuthStateChange` is automatically called by Supabase whenever a user's session changes. This component takes an `accessToken` prop, which will be the server's state for our user (we'll set this up next). If the `accessToken` from the server and the new `access_token` do not match then the client and server are out of sync, therefore, we want to reload the active route. - -Lastly, fetch the server-side session in the `RootLayout` and pass it to our new `` component. - -```jsx title="/app/layout.jsx" -import SupabaseListener from '../components/supabase-listener' -import createClient from '../utils/supabase-server' - -export default async function RootLayout({ children }) { - const supabase = createClient() - - const { - data: { session }, - } = await supabase.auth.getSession() - - return ( - // html and head section omitted - - - {children} - - ) -} -``` - -We don't want Next.js to cache this `session` value, so we need to export a `revalidate` value of `0` in the layout file. - -```jsx -export const revalidate = 0 -``` - -We also want to tell Next.js to explicitly exclude this component's code from the client bundle by installing the `server-only` package: - -```bash -npm install server-only -``` - -And importing it at the top of our component. - -```jsx -import 'server-only' -``` - -The entire Layout component should look something like this: - -```jsx title="/app/layout.jsx" -import 'server-only' - -import SupabaseListener from '../components/supabase-listener' -import './globals.css' -import createClient from '../utils/supabase-server' - -// do not cache this layout -export const revalidate = 0 - -export default async function RootLayout({ children }) { - const supabase = createClient() - - const { - data: { session }, - } = await supabase.auth.getSession() - - return ( - - {/* - will contain the components returned by the nearest parent - head.jsx. Find out more at https://beta.nextjs.org/docs/api-reference/file-conventions/head - */} - - - - {children} - - - ) -} -``` - -Create a `/components/supabase-listener.tsx` file and add the following: - ```tsx title="/components/supabase-listener.tsx" 'use client' import { useRouter } from 'next/navigation' import { useEffect } from 'react' -import supabase from '../utils/supabase-browser' +import { useSupabase } from './supabase-provider' -export default function SupabaseListener({ accessToken }: { accessToken?: string }) { +export default function SupabaseListener({ serverAccessToken }: { serverAccessToken?: string }) { + const { supabase } = useSupabase() const router = useRouter() useEffect(() => { - supabase.auth.onAuthStateChange((event, session) => { - if (session?.access_token !== accessToken) { + const { + data: { subscription }, + } = supabase.auth.onAuthStateChange((event, session) => { + if (session?.access_token !== serverAccessToken) { router.refresh() } }) - }, [accessToken]) + + return () => { + subscription.unsubscribe() + } + }, [serverAccessToken, router, supabase]) return null } ``` + + + > `use client` tells Next.js that this is a [Client Component](https://beta.nextjs.org/docs/rendering/server-and-client-components#client-components). Only Client Components can use hooks like `useEffect` and `useRouter`. -The function we pass to `onAuthStateChange` is automatically called by Supabase whenever a user's session changes. This component takes an `accessToken` prop, which will be the server's state for our user (we'll set this up next). If the `accessToken` from the server and the new `access_token` do not match then the client and server are out of sync, therefore, we want to reload the active route. +The function we pass to `onAuthStateChange` is automatically called by Supabase whenever a user's session changes. This component takes an `serverAccessToken` prop, which is the server's state for our user. If the `serverAccessToken` and the new session's `access_token` do not match then the client and server are out of sync, therefore, we want to reload the active route. -Lastly, fetch the server-side session in the `RootLayout` and pass it to our new `` component. - -```tsx title="/app/layout.tsx" -import SupabaseListener from '../components/supabase-listener' -import createClient from '../utils/supabase-server' - -export default async function RootLayout({ children }: { children: React.ReactNode }) { - const supabase = createClient() - - const { - data: { session }, - } = await supabase.auth.getSession() - - return ( - // html and head section omitted - - - {children} - - ) -} -``` - -> Again, TypeScript types can be [generated with the Supabase CLI](https://supabase.com/docs/reference/javascript/typescript-support) and passed to `createServerComponentSupabaseClient` to add type support to the Supabase client. - -We don't want Next.js to cache this `session` value, so we need to export a `revalidate` value of `0`. - -```tsx -export const revalidate = 0 -``` - -We also want to tell Next.js to explicitly exclude this component's code from the client bundle by installing the `server-only` package: - -```bash -npm install server-only -``` - -And importing it at the top of our component. - -```tsx -import 'server-only' -``` - -The entire Layout component should look something like this: - -```tsx title="/app/layout.tsx" -import 'server-only' - -import SupabaseListener from '../components/supabase-listener' -import './globals.css' -import createClient from '../utils/supabase-server' - -// do not cache this layout -export const revalidate = 0 - -export default async function RootLayout({ children }: { children: React.ReactNode }) { - const supabase = createClient() - - const { - data: { session }, - } = await supabase.auth.getSession() - - return ( - - {/* - will contain the components returned by the nearest parent - head.tsx. Find out more at https://beta.nextjs.org/docs/api-reference/file-conventions/head - */} - - - - {children} - - - ) -} -``` - - -
- -Supabase can now be used in any Client or Server component. - -## Server Components - -Next.js recommends fetching data in Server Components whenever possible. - - - - -```jsx -import 'server-only' - -import createClient from '../../utils/supabase-server' - -// do not cache this page -export const revalidate = 0 - -export default async function ServerComponent() { - const supabase = createClient() - const { data } = await supabase.from('posts').select('*') - - return
{JSON.stringify({ data }, null, 2)}
-} -``` - -
- - - -```tsx -import 'server-only' - -import createClient from '../../utils/supabase-server' - -// do not cache this page -export const revalidate = 0 - -export default async function ServerComponent() { - const supabase = createClient() - const { data } = await supabase.from('posts').select('*') - - return
{JSON.stringify({ data }, null, 2)}
-} -``` - -
-
- -## Client Components - -While Next.js recommend doing all data fetching in Server Components, we still need Supabase client-side for things like authentication and subscribing to realtime updates. +Now we can use our `useSupabase` hook throughout our client-side components. ### Authentication -We can call any of [Supabase's authentication methods](https://supabase.com/docs/reference/javascript/v1/auth-signin) - such as `supabase.auth.signInWithOAuth` - from a client component. - { - const { error } = await supabase.auth.signInWithOAuth({ + const { supabase, session } = useSupabase() + + const handleEmailLogin = async () => { + await supabase.auth.signInWithPassword({ + email: 'jon@supabase.com', + password: 'password', + }) + } + + const handleGitHubLogin = async () => { + await supabase.auth.signInWithOAuth({ provider: 'github', }) - - if (error) { - console.log({ error }) - } } const handleLogout = async () => { - const { error } = await supabase.auth.signOut() - - if (error) { - console.log({ error }) - } + await supabase.auth.signOut() } return ( <> - + + ) @@ -533,30 +516,33 @@ export default function Login() { ```tsx title="/components/login.tsx" 'use client' -import supabase from '../utils/supabase-browser' +import { useSupabase } from './supabase-provider' +// Supabase auth needs to be triggered client-side export default function Login() { - const handleLogin = async () => { - const { error } = await supabase.auth.signInWithOAuth({ + const { supabase, session } = useSupabase() + + const handleEmailLogin = async () => { + await supabase.auth.signInWithPassword({ + email: 'jon@supabase.com', + password: 'password', + }) + } + + const handleGitHubLogin = async () => { + await supabase.auth.signInWithOAuth({ provider: 'github', }) - - if (error) { - console.log({ error }) - } } const handleLogout = async () => { - const { error } = await supabase.auth.signOut() - - if (error) { - console.log({ error }) - } + await supabase.auth.signOut() } return ( <> - + + ) @@ -586,10 +572,11 @@ Create a new file at `/app/realtime/posts.jsx` and populate with the following: 'use client' import { useEffect, useState } from 'react' -import supabase from '../../utils/supabase-browser' +import { useSupabase } from '../../components/supabase-provider' export default function Posts({ serverPosts }) { const [posts, setPosts] = useState(serverPosts) + const { supabase } = useSupabase() useEffect(() => { setPosts(serverPosts) @@ -606,14 +593,12 @@ export default function Posts({ serverPosts }) { return () => { supabase.removeChannel(channel) } - }, [serverPosts]) + }, [supabase, setPosts, posts]) return
{JSON.stringify(posts, null, 2)}
} ``` -> The first `useEffect` is required to overwrite posts if the `serverPosts` prop changes. - This can now be used in a Server Component to subscribe to realtime updates. Create a new file at `/app/realtime/page.jsx` and populate with the following: @@ -621,7 +606,7 @@ Create a new file at `/app/realtime/page.jsx` and populate with the following: ```jsx title="/app/realtime/page.jsx" import 'server-only' -import createClient from '../../utils/supabase-server' +import { createClient } from '../../utils/supabase-server' import Posts from './posts' // do not cache this page @@ -645,13 +630,15 @@ Create a new file at `/app/realtime/posts.tsx` and populate with the following: 'use client' import { useEffect, useState } from 'react' -import { Database } from '../lib/database.types' -import supabase from '../../utils/supabase-browser' +import { useSupabase } from '../../components/supabase-provider' + +import type { Database } from '../../db_types' type Post = Database['public']['Tables']['posts']['Row'] export default function Posts({ serverPosts }: { serverPosts: Post[] }) { const [posts, setPosts] = useState(serverPosts) + const { supabase } = useSupabase() useEffect(() => { setPosts(serverPosts) @@ -668,13 +655,13 @@ export default function Posts({ serverPosts }: { serverPosts: Post[] }) { return () => { supabase.removeChannel(channel) } - }, [serverPosts]) + }, [supabase, setPosts, posts]) return
{JSON.stringify(posts, null, 2)}
} ``` -> The first `useEffect` is required to overwrite posts if the `serverPosts` prop changes. +> TypeScript types can be [generated with the Supabase CLI](https://supabase.com/docs/reference/javascript/typescript-support) and passed to `createServerSupabaseClient` to add type support to the Supabase client. This can now be used in a Server Component to subscribe to realtime updates. @@ -683,7 +670,7 @@ Create a new file at `/app/realtime/page.tsx` and populate with the following: ```tsx title="/app/realtime/page.tsx" import 'server-only' -import createClient from '../../utils/supabase-server' +import { createClient } from '../../utils/supabase-server' import Posts from './posts' // do not cache this page From 54b2b1c506a2d9089ae6a66a32df779e74ef6312 Mon Sep 17 00:00:00 2001 From: dannykng Date: Mon, 9 Jan 2023 13:54:56 -0800 Subject: [PATCH 009/261] Add doc to nav menu --- .../Navigation/NavigationMenu/NavigationMenu.constants.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index 7abe1bea978..d7a62e152c0 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -320,6 +320,11 @@ export const auth = { { name: 'Overview', url: '/guides/auth/auth-helpers', items: [] }, { name: 'Auth UI', url: '/guides/auth/auth-helpers/auth-ui', items: [] }, { name: 'Next.js', url: '/guides/auth/auth-helpers/nextjs', items: [] }, + { + name: 'Next.js Server Components', + url: '/guides/auth/auth-helpers/nextjs-server-components', + items: [], + }, { name: 'Remix', url: '/guides/auth/auth-helpers/remix', items: [] }, { name: 'SvelteKit', url: '/guides/auth/auth-helpers/sveltekit', items: [] }, ], From 8015db8894b2be4df0d8f83d9a46752beef16255 Mon Sep 17 00:00:00 2001 From: dannykng Date: Mon, 9 Jan 2023 14:15:09 -0800 Subject: [PATCH 010/261] Updates --- .../auth/auth-helpers/nextjs-server-components.mdx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) 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 614f31625c1..da7987bdf14 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 @@ -89,7 +89,7 @@ Create a new file at `/utils/supabase-server.ts` and populate with the following import { headers, cookies } from 'next/headers' import { createServerComponentSupabaseClient } from '@supabase/auth-helpers-nextjs' -import type { Database } from '../db_types' +import type { Database } from '../lib/database.types' export const createClient = () => createServerComponentSupabaseClient({ @@ -117,6 +117,8 @@ Next, we need a middleware file to refresh the user's session on navigation. > +Create a new `/app/middleware.js` file and populate with the following: + ```jsx title="middleware.js" import { createMiddlewareSupabaseClient } from '@supabase/auth-helpers-nextjs' import { NextResponse } from 'next/server' @@ -138,11 +140,13 @@ export async function middleware(req) { +Create a new `/app/middleware.js` file and populate with the following: + ```tsx title="middleware.ts" import { createMiddlewareSupabaseClient } from '@supabase/auth-helpers-nextjs' import type { NextRequest, NextResponse } from 'next/server' -import type { Database } from 'db_types' +import type { Database } from '../lib/database.types' export async function middleware(req: NextRequest) { const res = NextResponse.next() @@ -207,7 +211,7 @@ Create a new file at `/utils/supabase-browser.ts` and populate with the followin ```ts import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs' -import { Database } from '../db_types' +import { Database } from '../lib/database.types' export const createClient = () => createBrowserSupabaseClient() ``` @@ -259,7 +263,7 @@ import { createContext, useContext, useState } from 'react' import { createClient } from '../utils/supabase-browser' import type { SupabaseClient } from '@supabase/auth-helpers-nextjs' -import type { Database } from '../db_types' +import type { Database } from '../lib/database.types' type SupabaseContext = { supabase: SupabaseClient @@ -632,7 +636,7 @@ Create a new file at `/app/realtime/posts.tsx` and populate with the following: import { useEffect, useState } from 'react' import { useSupabase } from '../../components/supabase-provider' -import type { Database } from '../../db_types' +import type { Database } from '../../lib/database.types' type Post = Database['public']['Tables']['posts']['Row'] From ebd559d9c9a7d2a722b7ca5b39195d3057e01bc9 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 10 Jan 2023 16:07:08 +0800 Subject: [PATCH 011/261] Update text for RLS in table editor --- .../SidePanelEditor/TableEditor/TableEditor.tsx | 15 +++++++-------- studio/pages/project/[ref]/auth/policies.tsx | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx index 09928532ab9..6329a56ae4c 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx @@ -17,6 +17,7 @@ import { generateTableFieldFromPostgresTable, formatImportedContentToColumnFields, } from './TableEditor.utils' +import InformationBox from 'components/ui/InformationBox' interface Props { table?: PostgresTable @@ -215,9 +216,9 @@ const TableEditor: FC = ({

Restrict access to your table by enabling RLS and writing Postgres policies.

-

- If RLS is not enabled, anyone with the anon key can modify and delete your data. + RLS is secure by default - all normal access to this table must be allowed by a + policy.

{!tableFields.isRLSEnabled && ( = ({ variant="warning" className="!px-4 !py-3 mt-3" // @ts-ignore - title={ - - Turning off RLS means that you are allowing anonymous access to your table - - } - /> + title="Turning off RLS means that you are allowing anonymous access to your table" + > + As such, anyone with the anonymous key can modify or delete your data. + )} } diff --git a/studio/pages/project/[ref]/auth/policies.tsx b/studio/pages/project/[ref]/auth/policies.tsx index 14fc0d2a805..26a0ceb74ea 100644 --- a/studio/pages/project/[ref]/auth/policies.tsx +++ b/studio/pages/project/[ref]/auth/policies.tsx @@ -242,7 +242,7 @@ const AuthPoliciesTables: FC = observer(({ tables, hasP

This is also known as Row-Level Security (RLS). Each policy is attached to a - table, and the policy is executed everytime a time is accessed. + table, and the policy is executed each time its accessed.

} From a258844ba622a50db6ea2c48a013028f754a444d Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 10 Jan 2023 16:26:33 +0800 Subject: [PATCH 012/261] Refactor policies page --- .../interfaces/Auth/Policies/Policies.tsx | 215 ++++++++++++++++ .../interfaces/Auth/Policies/index.tsx | 3 +- .../TableEditor/TableEditor.tsx | 1 - .../TableEditorLayout/TableEditorMenu.tsx | 28 +-- studio/pages/project/[ref]/auth/policies.tsx | 236 ++---------------- 5 files changed, 251 insertions(+), 232 deletions(-) create mode 100644 studio/components/interfaces/Auth/Policies/Policies.tsx diff --git a/studio/components/interfaces/Auth/Policies/Policies.tsx b/studio/components/interfaces/Auth/Policies/Policies.tsx new file mode 100644 index 00000000000..d734e40500d --- /dev/null +++ b/studio/components/interfaces/Auth/Policies/Policies.tsx @@ -0,0 +1,215 @@ +import { useState, FC } from 'react' +import { isEmpty } from 'lodash' +import { IconHelpCircle } from 'ui' +import { useRouter } from 'next/router' +import { observer } from 'mobx-react-lite' +import { useParams, useStore } from 'hooks' +import { PolicyEditorModal, PolicyTableRow } from 'components/interfaces/Auth/Policies' +import { PostgresRole } from '@supabase/postgres-meta' + +import ConfirmModal from 'components/ui/Dialogs/ConfirmDialog' +import NoTableState from 'components/ui/States/NoTableState' +import NoSearchResults from 'components/to-be-cleaned/NoSearchResults' +import ProductEmptyState from 'components/to-be-cleaned/ProductEmptyState' +import InformationBox from 'components/ui/InformationBox' + +interface Props { + tables: any[] + hasPublicTables: boolean +} + +const Policies: FC = ({ tables, hasPublicTables }) => { + const router = useRouter() + const { ref } = useParams() + + const { ui, meta } = useStore() + const roles = meta.roles.list((role: PostgresRole) => !meta.roles.systemRoles.includes(role.name)) + + const [selectedSchemaAndTable, setSelectedSchemaAndTable] = useState({}) + const [selectedTableToToggleRLS, setSelectedTableToToggleRLS] = useState({}) + const [selectedPolicyToEdit, setSelectedPolicyToEdit] = useState({}) + const [selectedPolicyToDelete, setSelectedPolicyToDelete] = useState({}) + + const closePolicyEditorModal = () => { + setSelectedPolicyToEdit({}) + setSelectedSchemaAndTable({}) + } + + const closeConfirmModal = () => { + setSelectedPolicyToDelete({}) + setSelectedTableToToggleRLS({}) + } + + const onSelectToggleRLS = (table: any) => { + setSelectedTableToToggleRLS(table) + } + + const onSelectCreatePolicy = (table: any) => { + setSelectedSchemaAndTable({ schema: table.schema, table: table.name }) + } + + const onSelectEditPolicy = (policy: any) => { + setSelectedPolicyToEdit(policy) + setSelectedSchemaAndTable({ schema: policy.schema, table: policy.table }) + } + + const onSelectDeletePolicy = (policy: any) => { + setSelectedPolicyToDelete(policy) + } + + const onSavePolicySuccess = async () => { + ui.setNotification({ category: 'success', message: 'Policy successfully saved!' }) + closePolicyEditorModal() + } + + // Methods that involve some API + const onToggleRLS = async () => { + const payload = { + id: selectedTableToToggleRLS.id, + rls_enabled: !selectedTableToToggleRLS.rls_enabled, + } + + const res: any = await meta.tables.update(payload.id, payload) + if (res.error) { + ui.setNotification({ + category: 'error', + message: `Failed to toggle RLS: ${res.error.message}`, + }) + } + closeConfirmModal() + } + + const onCreatePolicy = async (payload: any) => { + const res = await meta.policies.create(payload) + if (res.error) { + ui.setNotification({ + category: 'error', + message: `Error adding policy: ${res.error.message}`, + }) + return true + } + return false + } + + const onUpdatePolicy = async (payload: any) => { + const res = await meta.policies.update(payload.id, payload) + if (res.error) { + ui.setNotification({ + category: 'error', + message: `Error updating policy: ${res.error.message}`, + }) + return true + } + return false + } + + const onDeletePolicy = async () => { + const res = await meta.policies.del(selectedPolicyToDelete.id) + if (typeof res !== 'boolean' && res.error) { + ui.setNotification({ + category: 'error', + message: `Error deleting policy: ${res.error.message}`, + }) + } else { + ui.setNotification({ category: 'success', message: 'Successfully deleted policy!' }) + } + closeConfirmModal() + } + + if (!hasPublicTables) + return + + return ( + <> + {tables.length > 0 ? ( + tables.map((table: any) => ( +
+ +
+ )) + ) : hasPublicTables ? ( + + ) : ( +
+ router.push(`/project/${ref}/editor`)} + > +
+ } + description={ +
+

+ Policies restrict, on a per-user basis, which rows can be returned by normal + queries, or inserted, updated, or deleted by data modification commands. +

+

+ This is also known as Row-Level Security (RLS). Each policy is attached to a + table, and the policy is executed each time its accessed. +

+
+ } + /> +

+ A table within the public schema is required before you can create a Row-Level + Security policy. +

+
+
+
+ )} + + + + + + + + ) +} + +export default observer(Policies) diff --git a/studio/components/interfaces/Auth/Policies/index.tsx b/studio/components/interfaces/Auth/Policies/index.tsx index f1cd35fac60..129075d7344 100644 --- a/studio/components/interfaces/Auth/Policies/index.tsx +++ b/studio/components/interfaces/Auth/Policies/index.tsx @@ -1,7 +1,8 @@ +import Policies from './Policies' import PolicyEditorModal from './PolicyEditorModal' import PolicyTableRow from './PolicyTableRow' import PolicySelection from './PolicySelection' import PolicyTemplates from './PolicyTemplates' export * from './Policies.constants' -export { PolicyEditorModal, PolicyTableRow, PolicySelection, PolicyTemplates } +export { Policies, PolicyEditorModal, PolicyTableRow, PolicySelection, PolicyTemplates } diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx index 6329a56ae4c..b9a68c8aa27 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx @@ -225,7 +225,6 @@ const TableEditor: FC = ({ withIcon variant="warning" className="!px-4 !py-3 mt-3" - // @ts-ignore title="Turning off RLS means that you are allowing anonymous access to your table" > As such, anyone with the anonymous key can modify or delete your data. diff --git a/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx b/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx index 1bc51c408ae..65ca74e668a 100644 --- a/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx +++ b/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx @@ -1,5 +1,4 @@ import { FC, useEffect, useState } from 'react' -import { useRouter } from 'next/router' import Link from 'next/link' import { partition } from 'lodash' import { observer } from 'mobx-react-lite' @@ -10,6 +9,7 @@ import { IconCopy, IconEdit, IconLoader, + IconLock, IconRefreshCw, IconSearch, IconTrash, @@ -23,7 +23,7 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' import { PostgresSchema, PostgresTable } from '@supabase/postgres-meta' import Base64 from 'lib/base64' -import { checkPermissions, useStore } from 'hooks' +import { checkPermissions, useStore, useParams } from 'hooks' import { SchemaView } from './TableEditorLayout.types' import ProductMenuItem from 'components/ui/ProductMenu/ProductMenuItem' @@ -44,11 +44,9 @@ const TableEditorMenu: FC = ({ onDeleteTable = () => {}, onDuplicateTable = () => {}, }) => { - const { meta, ui } = useStore() - const router = useRouter() - const { id } = router.query + const { meta } = useStore() + const { id, ref } = useParams() - const projectRef = ui.selectedProject?.ref const schemas: PostgresSchema[] = meta.schemas.list() const tables: PostgresTable[] = meta.tables.list( (table: PostgresTable) => table.schema === selectedSchema @@ -67,15 +65,10 @@ const TableEditorMenu: FC = ({ // We may need to shift this to the schema store and do something like meta.schema.loadViews() // I don't need we need a separate store for views useEffect(() => { - let cancel = false const fetchViews = async (selectedSchema: string) => { await meta.schemas.loadViews(selectedSchema) } if (selectedSchema) fetchViews(selectedSchema) - - return () => { - cancel = true - } }, [selectedSchema]) const refreshTables = async () => { @@ -258,7 +251,7 @@ const TableEditorMenu: FC = ({ return ( = ({ > Duplicate Table , + + + }> + View Policies + + + , , = ({ const viewId = Base64.encode(JSON.stringify(view)) const isActive = id === viewId return ( - +
@@ -356,7 +356,7 @@ const TableEditorMenu: FC = ({ {filteredForeignTables.map((table: Partial) => { const isActive = Number(id) === table.id return ( - +
diff --git a/studio/pages/project/[ref]/auth/policies.tsx b/studio/pages/project/[ref]/auth/policies.tsx index 26a0ceb74ea..9d52be5eee7 100644 --- a/studio/pages/project/[ref]/auth/policies.tsx +++ b/studio/pages/project/[ref]/auth/policies.tsx @@ -1,22 +1,14 @@ -import React, { useState, FC } from 'react' -import { isEmpty } from 'lodash' -import { Button, IconSearch, Input, IconHelpCircle, IconExternalLink } from 'ui' -import { useRouter } from 'next/router' +import { useState, useEffect } from 'react' +import { Button, IconSearch, Input, IconExternalLink } from 'ui' import { observer } from 'mobx-react-lite' -import { checkPermissions, useStore } from 'hooks' -import { AuthLayout } from 'components/layouts' -import { NextPageWithLayout } from 'types' -import { PolicyEditorModal, PolicyTableRow } from 'components/interfaces/Auth/Policies' -import { PostgresRole } from '@supabase/postgres-meta' import { PostgresTable, PostgresPolicy } from '@supabase/postgres-meta' - -import ConfirmModal from 'components/ui/Dialogs/ConfirmDialog' -import NoTableState from 'components/ui/States/NoTableState' -import NoSearchResults from 'components/to-be-cleaned/NoSearchResults' import { PermissionAction } from '@supabase/shared-types/out/constants' + +import { NextPageWithLayout } from 'types' +import { checkPermissions, useParams, useStore } from 'hooks' +import { AuthLayout } from 'components/layouts' +import { Policies } from 'components/interfaces/Auth/Policies' import NoPermission from 'components/ui/NoPermission' -import ProductEmptyState from 'components/to-be-cleaned/ProductEmptyState' -import InformationBox from 'components/ui/InformationBox' /** * Filter tables by table name and policy name @@ -39,13 +31,15 @@ const onFilterTables = ( const findSearchString = (s: string) => s.toLowerCase().includes(filter) // @ts-ignore Type instantiation is excessively deep and possibly infinite const filteredPolicies = policies.filter((p: PostgresPolicy) => findSearchString(p.name)) + return tables .slice() .filter((x: PostgresTable) => { - const searchTableName = findSearchString(x.name) - if (searchTableName) return true - const searchPolicyName = filteredPolicies.some((p: PostgresPolicy) => p.table === x.name) - return searchPolicyName + return ( + x.name.toLowerCase().includes(filter) || + x.id.toString() === filter || + filteredPolicies.some((p: PostgresPolicy) => p.table === x.name) + ) }) .sort((a: PostgresTable, b: PostgresTable) => a.name.localeCompare(b.name)) } @@ -53,8 +47,13 @@ const onFilterTables = ( const AuthPoliciesPage: NextPageWithLayout = () => { const { meta } = useStore() + const { search } = useParams() + const [policiesFilter, setPoliciesFilter] = useState('') + + useEffect(() => { + if (search) setPoliciesFilter(search) + }, [search]) - const [policiesFilter, setPoliciesFilter] = useState(undefined) const publicTables = meta.tables.list((table: { schema: string }) => table.schema === 'public') const policies = meta.policies.list() const filteredTables = onFilterTables(publicTables, policies, policiesFilter) @@ -89,7 +88,7 @@ const AuthPoliciesPage: NextPageWithLayout = () => {
)} - 0} tables={filteredTables} /> + 0} tables={filteredTables} />
) } @@ -101,198 +100,3 @@ AuthPoliciesPage.getLayout = (page) => ( ) export default observer(AuthPoliciesPage) - -interface AuthPoliciesTablesProps { - hasPublicTables: boolean - tables: any[] -} -const AuthPoliciesTables: FC = observer(({ tables, hasPublicTables }) => { - const router = useRouter() - const { ref } = router.query - - const { ui, meta } = useStore() - const roles = meta.roles.list((role: PostgresRole) => !meta.roles.systemRoles.includes(role.name)) - - const [selectedSchemaAndTable, setSelectedSchemaAndTable] = useState({}) - const [selectedTableToToggleRLS, setSelectedTableToToggleRLS] = useState({}) - const [selectedPolicyToEdit, setSelectedPolicyToEdit] = useState({}) - const [selectedPolicyToDelete, setSelectedPolicyToDelete] = useState({}) - - const closePolicyEditorModal = () => { - setSelectedPolicyToEdit({}) - setSelectedSchemaAndTable({}) - } - - const closeConfirmModal = () => { - setSelectedPolicyToDelete({}) - setSelectedTableToToggleRLS({}) - } - - const onSelectToggleRLS = (table: any) => { - setSelectedTableToToggleRLS(table) - } - - const onSelectCreatePolicy = (table: any) => { - setSelectedSchemaAndTable({ schema: table.schema, table: table.name }) - } - - const onSelectEditPolicy = (policy: any) => { - setSelectedPolicyToEdit(policy) - setSelectedSchemaAndTable({ schema: policy.schema, table: policy.table }) - } - - const onSelectDeletePolicy = (policy: any) => { - setSelectedPolicyToDelete(policy) - } - - const onSavePolicySuccess = async () => { - ui.setNotification({ category: 'success', message: 'Policy successfully saved!' }) - closePolicyEditorModal() - } - - // Methods that involve some API - const onToggleRLS = async () => { - const payload = { - id: selectedTableToToggleRLS.id, - rls_enabled: !selectedTableToToggleRLS.rls_enabled, - } - - const res: any = await meta.tables.update(payload.id, payload) - if (res.error) { - ui.setNotification({ - category: 'error', - message: `Failed to toggle RLS: ${res.error.message}`, - }) - } - closeConfirmModal() - } - - const onCreatePolicy = async (payload: any) => { - const res = await meta.policies.create(payload) - if (res.error) { - ui.setNotification({ - category: 'error', - message: `Error adding policy: ${res.error.message}`, - }) - return true - } - return false - } - - const onUpdatePolicy = async (payload: any) => { - const res = await meta.policies.update(payload.id, payload) - if (res.error) { - ui.setNotification({ - category: 'error', - message: `Error updating policy: ${res.error.message}`, - }) - return true - } - return false - } - - const onDeletePolicy = async () => { - const res = await meta.policies.del(selectedPolicyToDelete.id) - if (typeof res !== 'boolean' && res.error) { - ui.setNotification({ - category: 'error', - message: `Error deleting policy: ${res.error.message}`, - }) - } else { - ui.setNotification({ category: 'success', message: 'Successfully deleted policy!' }) - } - closeConfirmModal() - } - - return ( - <> - {tables.length > 0 ? ( - tables.map((table: any) => ( -
- -
- )) - ) : hasPublicTables ? ( - - ) : ( -
- router.push(`/project/${ref}/editor`)} - > -
- } - description={ -
-

- Policies restrict, on a per-user basis, which rows can be returned by normal - queries, or inserted, updated, or deleted by data modification commands. -

-

- This is also known as Row-Level Security (RLS). Each policy is attached to a - table, and the policy is executed each time its accessed. -

-
- } - /> -

- A table within the public schema is required before you can create a Row-Level - Security policy. -

-
-
-
- )} - - - - - - - - ) -}) From 64fd4034b33f99b7dcbe439c23bfb8e7af5d4d8e Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 10 Jan 2023 17:13:21 +0800 Subject: [PATCH 013/261] Allow selection of schema in auth policies page --- .../interfaces/Auth/Policies/Policies.tsx | 20 ++-- .../PolicyTableRow/PolicyTableRowHeader.tsx | 112 +++++++++--------- .../Auth/Policies/PolicyTableRow/index.tsx | 80 +++++++------ .../interfaces/Database/Tables/TableList.tsx | 2 +- .../layouts/AuthLayout/AuthLayout.tsx | 1 + studio/pages/project/[ref]/auth/policies.tsx | 100 ++++++++++++---- 6 files changed, 190 insertions(+), 125 deletions(-) diff --git a/studio/components/interfaces/Auth/Policies/Policies.tsx b/studio/components/interfaces/Auth/Policies/Policies.tsx index d734e40500d..81669a1a6f5 100644 --- a/studio/components/interfaces/Auth/Policies/Policies.tsx +++ b/studio/components/interfaces/Auth/Policies/Policies.tsx @@ -5,20 +5,20 @@ import { useRouter } from 'next/router' import { observer } from 'mobx-react-lite' import { useParams, useStore } from 'hooks' import { PolicyEditorModal, PolicyTableRow } from 'components/interfaces/Auth/Policies' -import { PostgresRole } from '@supabase/postgres-meta' +import { PostgresRole, PostgresTable } from '@supabase/postgres-meta' import ConfirmModal from 'components/ui/Dialogs/ConfirmDialog' -import NoTableState from 'components/ui/States/NoTableState' import NoSearchResults from 'components/to-be-cleaned/NoSearchResults' import ProductEmptyState from 'components/to-be-cleaned/ProductEmptyState' import InformationBox from 'components/ui/InformationBox' interface Props { - tables: any[] - hasPublicTables: boolean + tables: PostgresTable[] + hasTables: boolean + isLocked: boolean } -const Policies: FC = ({ tables, hasPublicTables }) => { +const Policies: FC = ({ tables, hasTables, isLocked }) => { const router = useRouter() const { ref } = useParams() @@ -116,9 +116,6 @@ const Policies: FC = ({ tables, hasPublicTables }) => { closeConfirmModal() } - if (!hasPublicTables) - return - return ( <> {tables.length > 0 ? ( @@ -126,6 +123,7 @@ const Policies: FC = ({ tables, hasPublicTables }) => {
= ({ tables, hasPublicTables }) => { />
)) - ) : hasPublicTables ? ( + ) : hasTables ? ( ) : (
@@ -163,8 +161,8 @@ const Policies: FC = ({ tables, hasPublicTables }) => { } />

- A table within the public schema is required before you can create a Row-Level - Security policy. + A table within this schema is required before you can create a Row-Level Security + policy.

diff --git a/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyTableRowHeader.tsx b/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyTableRowHeader.tsx index e985b6af5db..1e2d975e7a4 100644 --- a/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyTableRowHeader.tsx +++ b/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyTableRowHeader.tsx @@ -9,12 +9,14 @@ import { checkPermissions } from 'hooks' interface Props { table: PostgresTable + isLocked: boolean onSelectToggleRLS: (table: PostgresTable) => void onSelectCreatePolicy: (table: PostgresTable) => void } const PolicyTableRowHeader: FC = ({ table, + isLocked, onSelectToggleRLS = () => {}, onSelectCreatePolicy = () => {}, }) => { @@ -35,63 +37,65 @@ const PolicyTableRowHeader: FC = ({ {table.rls_enabled ? 'RLS enabled' : 'RLS disabled'}
-
-
- - - - - {!canCreatePolicies && ( - - -
+
+ + +
- - )} - - - - - - {!canToggleRLS && ( - - -
+ + {!canCreatePolicies && ( + + +
+ + You need additional permissions to create RLS policies + +
+
+ )} + + + +
-
- )} -
+ {table.rls_enabled ? 'Disable RLS' : 'Enable RLS'} + + + {!canToggleRLS && ( + + +
+ + You need additional permissions to toggle RLS + +
+
+ )} + +
-
+ )} ) } diff --git a/studio/components/interfaces/Auth/Policies/PolicyTableRow/index.tsx b/studio/components/interfaces/Auth/Policies/PolicyTableRow/index.tsx index d181c0d6463..b3f53cd0df6 100644 --- a/studio/components/interfaces/Auth/Policies/PolicyTableRow/index.tsx +++ b/studio/components/interfaces/Auth/Policies/PolicyTableRow/index.tsx @@ -9,50 +9,52 @@ import Panel from 'components/ui/Panel' interface Props { table: PostgresTable + isLocked: boolean onSelectToggleRLS: (table: PostgresTable) => void onSelectCreatePolicy: (table: PostgresTable) => void onSelectEditPolicy: (policy: PostgresPolicy) => void onSelectDeletePolicy: (policy: PostgresPolicy) => void } -const PolicyTableRow: FC = observer( - ({ - table, - onSelectToggleRLS = () => {}, - onSelectCreatePolicy = () => {}, - onSelectEditPolicy = () => {}, - onSelectDeletePolicy = () => {}, - }) => { - const { meta } = useStore() - const policies = meta.policies.list((x: PostgresPolicy) => x.table === table.name) - return ( - - } - > - {policies.length === 0 && ( -
-

No policies created yet

-
- )} +const PolicyTableRow: FC = ({ + table, + isLocked, + onSelectToggleRLS = () => {}, + onSelectCreatePolicy = () => {}, + onSelectEditPolicy = () => {}, + onSelectDeletePolicy = () => {}, +}) => { + const { meta } = useStore() + const policies = meta.policies.list((x: PostgresPolicy) => x.table === table.name) - {/* @ts-ignore */} - {policies.map((policy) => ( - - ))} -
- ) - } -) + return ( + + } + > + {policies.length === 0 && ( +
+

No policies created yet

+
+ )} -export default PolicyTableRow + {/* @ts-ignore */} + {policies.map((policy) => ( + + ))} +
+ ) +} + +export default observer(PolicyTableRow) diff --git a/studio/components/interfaces/Database/Tables/TableList.tsx b/studio/components/interfaces/Database/Tables/TableList.tsx index 2c4b60c1acb..002c788aa95 100644 --- a/studio/components/interfaces/Database/Tables/TableList.tsx +++ b/studio/components/interfaces/Database/Tables/TableList.tsx @@ -67,7 +67,7 @@ const TableList: FC = ({ size="small" value={selectedSchema} onChange={onSelectSchema} - icon={isLocked && } + icon={isLocked && } >

Schemas

diff --git a/studio/components/layouts/AuthLayout/AuthLayout.tsx b/studio/components/layouts/AuthLayout/AuthLayout.tsx index 8fa69472d88..f747c9adf22 100644 --- a/studio/components/layouts/AuthLayout/AuthLayout.tsx +++ b/studio/components/layouts/AuthLayout/AuthLayout.tsx @@ -28,6 +28,7 @@ const AuthLayout: FC = ({ title, children }) => { meta.policies.load() meta.tables.load() meta.roles.load() + meta.schemas.load() } }, [ui.selectedProject?.ref]) diff --git a/studio/pages/project/[ref]/auth/policies.tsx b/studio/pages/project/[ref]/auth/policies.tsx index 9d52be5eee7..42562e4bc55 100644 --- a/studio/pages/project/[ref]/auth/policies.tsx +++ b/studio/pages/project/[ref]/auth/policies.tsx @@ -1,5 +1,6 @@ import { useState, useEffect } from 'react' -import { Button, IconSearch, Input, IconExternalLink } from 'ui' +import { partition } from 'lodash' +import { Button, Listbox, IconSearch, Input, IconExternalLink, IconLock } from 'ui' import { observer } from 'mobx-react-lite' import { PostgresTable, PostgresPolicy } from '@supabase/postgres-meta' import { PermissionAction } from '@supabase/shared-types/out/constants' @@ -48,15 +49,25 @@ const onFilterTables = ( const AuthPoliciesPage: NextPageWithLayout = () => { const { meta } = useStore() const { search } = useParams() - const [policiesFilter, setPoliciesFilter] = useState('') + const [selectedSchema, setSelectedSchema] = useState('public') + const [searchString, setSearchString] = useState('') useEffect(() => { - if (search) setPoliciesFilter(search) + if (search) setSearchString(search) }, [search]) - const publicTables = meta.tables.list((table: { schema: string }) => table.schema === 'public') + const schemas = meta.schemas.list() + const [protectedSchemas, openSchemas] = partition(schemas, (schema) => + meta.excludedSchemas.includes(schema?.name ?? '') + ) + // @ts-ignore + const schema = schemas.find((schema) => schema.name === selectedSchema) + const isLocked = protectedSchemas.some((s) => s.id === schema?.id) + const policies = meta.policies.list() - const filteredTables = onFilterTables(publicTables, policies, policiesFilter) + + const tables = meta.tables.list((table: { schema: string }) => table.schema === selectedSchema) + const filteredTables = onFilterTables(tables, policies, searchString) const canReadPolicies = checkPermissions(PermissionAction.TENANT_SQL_ADMIN_READ, 'policies') @@ -66,29 +77,78 @@ const AuthPoliciesPage: NextPageWithLayout = () => { return (
- {(publicTables || []).length > 0 && ( -
- ) } From 455e2684f40aa9b3b8d6b568b7435e6f2605b15b Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 10 Jan 2023 17:18:21 +0800 Subject: [PATCH 014/261] Improve clarity of RLS in auth policies page --- .../interfaces/Auth/Policies/PolicyTableRow/index.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/studio/components/interfaces/Auth/Policies/PolicyTableRow/index.tsx b/studio/components/interfaces/Auth/Policies/PolicyTableRow/index.tsx index b3f53cd0df6..5caf05d60fe 100644 --- a/studio/components/interfaces/Auth/Policies/PolicyTableRow/index.tsx +++ b/studio/components/interfaces/Auth/Policies/PolicyTableRow/index.tsx @@ -39,8 +39,13 @@ const PolicyTableRow: FC = ({ } > {policies.length === 0 && ( -
-

No policies created yet

+
+

No policies created yet

+ {table.rls_enabled && ( +

+ As RLS has been enabled, all normal access to this table must be allowed by a policy. +

+ )}
)} From fad66aed8ee31d019390a26d38b8878671f7d71e Mon Sep 17 00:00:00 2001 From: Ollie Halliday <22655069+Hallidayo@users.noreply.github.com> Date: Tue, 10 Jan 2023 15:16:19 +0000 Subject: [PATCH 015/261] changed verifyOtp code snippet --- studio/components/to-be-cleaned/Docs/Snippets.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/studio/components/to-be-cleaned/Docs/Snippets.js b/studio/components/to-be-cleaned/Docs/Snippets.js index 955409e15bb..3345f1e2759 100644 --- a/studio/components/to-be-cleaned/Docs/Snippets.js +++ b/studio/components/to-be-cleaned/Docs/Snippets.js @@ -604,9 +604,10 @@ curl -X POST '${endpoint}/auth/v1/verify' \\ js: { language: 'js', code: ` -let { data, error } = await supabase.auth.verifyOTP({ +let { data, error } = await supabase.auth.verifyOtp({ phone: '+13334445555', - token: '123456' + token: '123456', + type: 'sms' }) `, }, From 74d71c96093722b64ff989891771105aa75417f6 Mon Sep 17 00:00:00 2001 From: Ollie Halliday <22655069+Hallidayo@users.noreply.github.com> Date: Tue, 10 Jan 2023 15:17:45 +0000 Subject: [PATCH 016/261] added third party title --- studio/components/to-be-cleaned/Docs/Snippets.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/components/to-be-cleaned/Docs/Snippets.js b/studio/components/to-be-cleaned/Docs/Snippets.js index 3345f1e2759..c4ed7a96a6e 100644 --- a/studio/components/to-be-cleaned/Docs/Snippets.js +++ b/studio/components/to-be-cleaned/Docs/Snippets.js @@ -634,7 +634,7 @@ let { data, error } = await supabase.auth.api.inviteUserByEmail('someone@email.c }, }), authThirdPartyLogin: (endpoint, apiKey) => ({ - title: '', + title: 'Third Party Login', bash: { language: 'bash', code: ``, From 6ed5bd22b7ab81b5c7c1b88fa534647b512e10ae Mon Sep 17 00:00:00 2001 From: Rodrigo Mansueli Nunes Date: Tue, 10 Jan 2023 15:09:14 -0300 Subject: [PATCH 017/261] Update PolicyEditorModal.constants.ts --- .../PolicyEditorModal/PolicyEditorModal.constants.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/studio/components/interfaces/Auth/Policies/PolicyEditorModal/PolicyEditorModal.constants.ts b/studio/components/interfaces/Auth/Policies/PolicyEditorModal/PolicyEditorModal.constants.ts index e6487f59eec..a24711e35e9 100644 --- a/studio/components/interfaces/Auth/Policies/PolicyEditorModal/PolicyEditorModal.constants.ts +++ b/studio/components/interfaces/Auth/Policies/PolicyEditorModal/PolicyEditorModal.constants.ts @@ -57,13 +57,13 @@ WITH CHECK (true);`.trim(), CREATE POLICY "policy_name" ON ${schema}.${table} FOR UPDATE USING ( - auth.email() = email + auth.jwt() ->> 'email' = email ) WITH CHECK ( - auth.email() = email + auth.jwt() ->> 'email' = email );`.trim(), name: 'Enable update for users based on email', - definition: 'auth.email() = email', - check: 'auth.email() = email', + definition: `auth.jwt() ->> 'email' = email`, + check: `auth.jwt() ->> 'email' = email`, command: 'UPDATE', roles: [], }, From ffb42ba1b1d39f3dec68072875de28de689497de Mon Sep 17 00:00:00 2001 From: dannykng Date: Tue, 10 Jan 2023 15:20:26 -0800 Subject: [PATCH 018/261] Revert "Update sitemap" This reverts commit 6af3acdc046e6b616a7c745461cabd9bae7ddb37. --- apps/docs/public/sitemap.xml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/apps/docs/public/sitemap.xml b/apps/docs/public/sitemap.xml index 9cc0078e8dd..38b0e06220a 100644 --- a/apps/docs/public/sitemap.xml +++ b/apps/docs/public/sitemap.xml @@ -6,6 +6,12 @@ 0.5 + + https://supabase.com/docs/getting-started + weekly + 0.5 + + https://supabase.com/docs/index weekly @@ -78,6 +84,12 @@ 0.5 + + https://supabase.com/docs/guides/tutorials + weekly + 0.5 + + https://supabase.com/docs/handbook/contributing weekly From 9be65118433f35eb67234fc41ad4f507fd4b206b Mon Sep 17 00:00:00 2001 From: dannykng Date: Tue, 10 Jan 2023 16:59:59 -0800 Subject: [PATCH 019/261] Swap titles and summaries --- spec/cli_v1_commands.yaml | 242 +++++++++++++++++++------------------- 1 file changed, 121 insertions(+), 121 deletions(-) diff --git a/spec/cli_v1_commands.yaml b/spec/cli_v1_commands.yaml index fabffeb3733..0358d8e04f9 100644 --- a/spec/cli_v1_commands.yaml +++ b/spec/cli_v1_commands.yaml @@ -2,7 +2,7 @@ clispec: '001' info: id: cli version: 1.27.0 - title: Supabase CLI + summary: supabase CLI language: sh source: https://github.com/supabase/cli bugs: https://github.com/supabase/cli/issues @@ -35,8 +35,8 @@ flags: default_value: '' commands: - id: supabase-vanity-subdomains - title: supabase vanity-subdomains - summary: Manage vanity subdomains for Supabase projects + summary: supabase vanity-subdomains + title: Manage vanity subdomains for Supabase projects description: |- Manage vanity subdomains for Supabase projects. @@ -51,8 +51,8 @@ commands: - supabase-vanity-subdomains-get flags: [] - id: supabase-vanity-subdomains-get - title: supabase vanity-subdomains get - summary: Get the current vanity subdomain + summary: supabase vanity-subdomains get + title: Get the current vanity subdomain tags: [] links: [] usage: supabase vanity-subdomains get @@ -68,8 +68,8 @@ commands: description: Project ref of the Supabase project. default_value: '' - id: supabase-vanity-subdomains-delete - title: supabase vanity-subdomains delete - summary: Deletes a project's vanity subdomain + summary: supabase vanity-subdomains delete + title: Deletes a project's vanity subdomain description: | Deletes the vanity subdomain for a project, and reverts to using the project ref for routing. tags: [] @@ -87,8 +87,8 @@ commands: description: Project ref of the Supabase project. default_value: '' - id: supabase-vanity-subdomains-check-availability - title: supabase vanity-subdomains check-availability - summary: Checks if a desired subdomain is available for use + summary: supabase vanity-subdomains check-availability + title: Checks if a desired subdomain is available for use tags: [] links: [] usage: supabase vanity-subdomains check-availability [flags] @@ -109,8 +109,8 @@ commands: description: Project ref of the Supabase project. default_value: '' - id: supabase-vanity-subdomains-activate - title: supabase vanity-subdomains activate - summary: Activate a vanity subdomain + summary: supabase vanity-subdomains activate + title: Activate a vanity subdomain description: | Activate a vanity subdomain for your Supabase project. @@ -136,8 +136,8 @@ commands: description: Project ref of the Supabase project. default_value: '' - id: supabase-test - title: supabase test - summary: Run tests on local Supabase containers + summary: supabase test + title: Run tests on local Supabase containers tags: - local-dev links: [] @@ -145,16 +145,16 @@ commands: - supabase-test-db flags: [] - id: supabase-test-db - title: supabase test db - summary: Tests local database with pgTAP + summary: supabase test db + title: Tests local database with pgTAP tags: [] links: [] usage: supabase test db subcommands: [] flags: [] - id: supabase-stop - title: supabase stop - summary: Stop all local Supabase containers + summary: supabase stop + title: Stop all local Supabase containers tags: - local-dev links: [] @@ -166,8 +166,8 @@ commands: description: Backs up the current database before stopping. default_value: 'false' - id: supabase-status - title: supabase status - summary: Show status of local Supabase containers + summary: supabase status + title: Show status of local Supabase containers tags: - local-dev links: [] @@ -199,8 +199,8 @@ commands: description: Override specific variable names. default_value: '[]' - id: supabase-start - title: supabase start - summary: Start containers for Supabase local development + summary: supabase start + title: Start containers for Supabase local development tags: - local-dev links: [] @@ -213,8 +213,8 @@ commands: Names of containers to not start. [gotrue, realtime, storage-api, imgproxy, kong, inbucket, postgrest, pgadmin-schema-diff, migra, postgres-meta, studio, deno-relay] default_value: '[]' - id: supabase-secrets - title: supabase secrets - summary: Manage Supabase secrets + summary: supabase secrets + title: Manage Supabase secrets tags: - management-api links: [] @@ -224,8 +224,8 @@ commands: - supabase-secrets-unset flags: [] - id: supabase-secrets-unset - title: supabase secrets unset - summary: Unset a secret(s) on Supabase + summary: supabase secrets unset + title: Unset a secret(s) on Supabase description: Unset a secret(s) from the linked Supabase project. tags: [] links: [] @@ -233,8 +233,8 @@ commands: subcommands: [] flags: [] - id: supabase-secrets-set - title: supabase secrets set - summary: Set a secret(s) on Supabase + summary: supabase secrets set + title: Set a secret(s) on Supabase description: Set a secret(s) to the linked Supabase project. tags: [] links: [] @@ -246,8 +246,8 @@ commands: description: Read secrets from a .env file. default_value: '' - id: supabase-secrets-list - title: supabase secrets list - summary: List all secrets on Supabase + summary: supabase secrets list + title: List all secrets on Supabase description: List all secrets in the linked project. tags: [] links: [] @@ -255,8 +255,8 @@ commands: subcommands: [] flags: [] - id: supabase-projects - title: supabase projects - summary: Manage Supabase projects + summary: supabase projects + title: Manage Supabase projects tags: - management-api links: [] @@ -265,8 +265,8 @@ commands: - supabase-projects-list flags: [] - id: supabase-projects-list - title: supabase projects list - summary: List all Supabase projects + summary: supabase projects list + title: List all Supabase projects description: List all Supabase projects the logged-in user can access. tags: [] links: [] @@ -274,8 +274,8 @@ commands: subcommands: [] flags: [] - id: supabase-projects-create - title: supabase projects create - summary: Create a project on Supabase + summary: supabase projects create + title: Create a project on Supabase tags: [] links: [] usage: supabase projects create [flags] @@ -349,8 +349,8 @@ commands: name: us-west-1 type: string - id: supabase-orgs - title: supabase orgs - summary: Manage Supabase organizations + summary: supabase orgs + title: Manage Supabase organizations tags: - management-api links: [] @@ -358,8 +358,8 @@ commands: - supabase-orgs-list flags: [] - id: supabase-orgs-list - title: supabase orgs list - summary: List all organizations + summary: supabase orgs list + title: List all organizations description: List all organizations the logged-in user belongs. tags: [] links: [] @@ -367,8 +367,8 @@ commands: subcommands: [] flags: [] - id: supabase-network-restrictions - title: supabase network-restrictions - summary: Manage network restrictions + summary: supabase network-restrictions + title: Manage network restrictions tags: - management-api links: [] @@ -377,8 +377,8 @@ commands: - supabase-network-restrictions-update flags: [] - id: supabase-network-restrictions-update - title: supabase network-restrictions update - summary: Update network restrictions + summary: supabase network-restrictions update + title: Update network restrictions tags: [] links: [] usage: supabase network-restrictions update [flags] @@ -402,8 +402,8 @@ commands: description: Project ref of the Supabase project. default_value: '' - id: supabase-network-restrictions-get - title: supabase network-restrictions get - summary: Get the current network restrictions + summary: supabase network-restrictions get + title: Get the current network restrictions tags: [] links: [] usage: supabase network-restrictions get @@ -419,8 +419,8 @@ commands: description: Project ref of the Supabase project. default_value: '' - id: supabase-network-bans - title: supabase network-bans - summary: Manage network bans + summary: supabase network-bans + title: Manage network bans description: |- Network bans are IPs that get temporarily blocked if their traffic pattern looks abusive (e.g. multiple failed auth attempts). @@ -433,8 +433,8 @@ commands: - supabase-network-bans-remove flags: [] - id: supabase-network-bans-remove - title: supabase network-bans remove - summary: Remove a network ban + summary: supabase network-bans remove + title: Remove a network ban tags: [] links: [] usage: supabase network-bans remove [flags] @@ -454,8 +454,8 @@ commands: description: Project ref of the Supabase project. default_value: '' - id: supabase-network-bans-get - title: supabase network-bans get - summary: Get the current network bans + summary: supabase network-bans get + title: Get the current network bans tags: [] links: [] usage: supabase network-bans get @@ -471,8 +471,8 @@ commands: description: Project ref of the Supabase project. default_value: '' - id: supabase-migration - title: supabase migration - summary: Manage database migration scripts + summary: supabase migration + title: Manage database migration scripts tags: - local-dev links: [] @@ -482,8 +482,8 @@ commands: - supabase-migration-repair flags: [] - id: supabase-migration-repair - title: supabase migration repair - summary: Repairs the migration history table + summary: supabase migration repair + title: Repairs the migration history table tags: [] links: [] usage: supabase migration repair [flags] @@ -506,16 +506,16 @@ commands: name: reverted type: '[ applied | reverted ]' - id: supabase-migration-new - title: supabase migration new - summary: Create an empty migration script + summary: supabase migration new + title: Create an empty migration script tags: [] links: [] usage: supabase migration new subcommands: [] flags: [] - id: supabase-migration-list - title: supabase migration list - summary: List local and remote migrations + summary: supabase migration list + title: List local and remote migrations tags: [] links: [] usage: supabase migration list [flags] @@ -526,8 +526,8 @@ commands: description: Password to your remote Postgres database. default_value: '' - id: supabase-login - title: supabase login - summary: Authenticate using an access token + summary: supabase login + title: Authenticate using an access token tags: - local-dev links: [] @@ -535,8 +535,8 @@ commands: subcommands: [] flags: [] - id: supabase-link - title: supabase link - summary: Link to a Supabase project + summary: supabase link + title: Link to a Supabase project tags: - local-dev links: [] @@ -553,8 +553,8 @@ commands: required: true default_value: '' - id: supabase-init - title: supabase init - summary: Initialize a local project + summary: supabase init + title: Initialize a local project tags: - local-dev links: [] @@ -562,8 +562,8 @@ commands: subcommands: [] flags: [] - id: supabase-gen - title: supabase gen - summary: Run code generation tools + summary: supabase gen + title: Run code generation tools tags: - local-dev links: [] @@ -571,16 +571,16 @@ commands: - supabase-gen-types flags: [] - id: supabase-gen-types - title: supabase gen types - summary: Generate types from Postgres schema + summary: supabase gen types + title: Generate types from Postgres schema tags: [] links: [] subcommands: - supabase-gen-types-typescript flags: [] - id: supabase-gen-types-typescript - title: supabase gen types typescript - summary: Generate types for TypeScript + summary: supabase gen types typescript + title: Generate types for TypeScript description: | Generate types for TypeScript. Must specify one of --local, --linked, --project-id, or --db-url tags: [] @@ -609,8 +609,8 @@ commands: description: Schemas to generate types for. default_value: '[]' - id: supabase-functions - title: supabase functions - summary: Manage Supabase Edge functions + summary: supabase functions + title: Manage Supabase Edge functions tags: - management-api links: [] @@ -621,8 +621,8 @@ commands: - supabase-functions-serve flags: [] - id: supabase-functions-serve - title: supabase functions serve - summary: Serve a Function locally + summary: supabase functions serve + title: Serve a Function locally tags: [] links: [] usage: supabase functions serve [flags] @@ -642,16 +642,16 @@ commands: description: Disable JWT verification for the Function. default_value: 'false' - id: supabase-functions-new - title: supabase functions new - summary: Create a new Function locally + summary: supabase functions new + title: Create a new Function locally tags: [] links: [] usage: supabase functions new subcommands: [] flags: [] - id: supabase-functions-deploy - title: supabase functions deploy - summary: Deploy a Function to Supabase + summary: supabase functions deploy + title: Deploy a Function to Supabase description: Deploy a Function to the linked Supabase project. tags: [] links: [] @@ -675,8 +675,8 @@ commands: description: Project ref of the Supabase project. default_value: '' - id: supabase-functions-delete - title: supabase functions delete - summary: Delete a Function from Supabase + summary: supabase functions delete + title: Delete a Function from Supabase description: | Delete a Function from the linked Supabase project. This does NOT remove the Function locally. tags: [] @@ -689,8 +689,8 @@ commands: description: Project ref of the Supabase project. default_value: '' - id: supabase-domains - title: supabase domains - summary: Manage custom domain names for Supabase projects + summary: supabase domains + title: Manage custom domain names for Supabase projects description: | Manage custom domain names for Supabase projects. @@ -706,8 +706,8 @@ commands: - supabase-domains-reverify flags: [] - id: supabase-domains-reverify - title: supabase domains reverify - summary: Re-verify the custom hostname config for your project + summary: supabase domains reverify + title: Re-verify the custom hostname config for your project tags: [] links: [] usage: supabase domains reverify @@ -722,8 +722,8 @@ commands: description: Project ref of the Supabase project. default_value: '' - id: supabase-domains-get - title: supabase domains get - summary: Get the current custom hostname config + summary: supabase domains get + title: Get the current custom hostname config description: | Retrieve the custom hostname config for your project, as stored in the Supabase platform. tags: [] @@ -740,8 +740,8 @@ commands: description: Project ref of the Supabase project. default_value: '' - id: supabase-domains-delete - title: supabase domains delete - summary: Deletes the custom hostname config for your project + summary: supabase domains delete + title: Deletes the custom hostname config for your project tags: [] links: [] usage: supabase domains delete @@ -756,8 +756,8 @@ commands: description: Project ref of the Supabase project. default_value: '' - id: supabase-domains-create - title: supabase domains create - summary: Create a custom hostname + summary: supabase domains create + title: Create a custom hostname description: |- Create a custom hostname for your Supabase project. @@ -780,8 +780,8 @@ commands: description: Project ref of the Supabase project. default_value: '' - id: supabase-domains-activate - title: supabase domains activate - summary: Activate the custom hostname for a project + summary: supabase domains activate + title: Activate the custom hostname for a project description: |- Activates the custom hostname configuration for a project. @@ -801,8 +801,8 @@ commands: description: Project ref of the Supabase project. default_value: '' - id: supabase-db - title: supabase db - summary: Manage local Postgres databases + summary: supabase db + title: Manage local Postgres databases tags: - local-dev links: [] @@ -816,32 +816,32 @@ commands: - supabase-db-start flags: [] - id: supabase-db-start - title: supabase db start - summary: Starts local Postgres database + summary: supabase db start + title: Starts local Postgres database tags: [] links: [] usage: supabase db start subcommands: [] flags: [] - id: supabase-db-reset - title: supabase db reset - summary: Resets the local database to current migrations + summary: supabase db reset + title: Resets the local database to current migrations tags: [] links: [] usage: supabase db reset subcommands: [] flags: [] - id: supabase-db-remote - title: supabase db remote - summary: Manage remote databases + summary: supabase db remote + title: Manage remote databases tags: [] links: [] subcommands: - supabase-db-remote-commit flags: [] - id: supabase-db-remote-commit - title: supabase db remote commit - summary: Commit remote changes as a new migration + summary: supabase db remote commit + title: Commit remote changes as a new migration tags: [] links: [] usage: supabase db remote commit @@ -852,8 +852,8 @@ commands: description: Password to your remote Postgres database. default_value: '' - id: supabase-db-push - title: supabase db push - summary: Push new migrations to the remote database + summary: supabase db push + title: Push new migrations to the remote database tags: [] links: [] usage: supabase db push [flags] @@ -869,8 +869,8 @@ commands: description: Password to your remote Postgres database. default_value: '' - id: supabase-db-lint - title: supabase db lint - summary: Checks local database for typing error + summary: supabase db lint + title: Checks local database for typing error tags: [] links: [] usage: supabase db lint [flags] @@ -892,8 +892,8 @@ commands: description: List of schema to include. default_value: '[public]' - id: supabase-db-dump - title: supabase db dump - summary: Dumps schemas from the remote database + summary: supabase db dump + title: Dumps schemas from the remote database tags: [] links: [] usage: supabase db dump [flags] @@ -908,8 +908,8 @@ commands: description: Password to your remote Postgres database. default_value: '' - id: supabase-db-diff - title: supabase db diff - summary: Diffs the local database for schema changes + summary: supabase db diff + title: Diffs the local database for schema changes tags: [] links: [] usage: supabase db diff [flags] @@ -932,8 +932,8 @@ commands: description: Use migra to generate schema diff. default_value: 'false' - id: supabase-completion - title: supabase completion - summary: Generate the autocompletion script for the specified shell + summary: supabase completion + title: Generate the autocompletion script for the specified shell description: | Generate the autocompletion script for supabase for the specified shell. See each sub-command's help for details on how to use the generated script. @@ -947,8 +947,8 @@ commands: - supabase-completion-zsh flags: [] - id: supabase-completion-zsh - title: supabase completion zsh - summary: Generate the autocompletion script for zsh + summary: supabase completion zsh + title: Generate the autocompletion script for zsh description: | Generate the autocompletion script for the zsh shell. @@ -982,8 +982,8 @@ commands: description: disable completion descriptions default_value: 'false' - id: supabase-completion-powershell - title: supabase completion powershell - summary: Generate the autocompletion script for powershell + summary: supabase completion powershell + title: Generate the autocompletion script for powershell description: | Generate the autocompletion script for powershell. @@ -1003,8 +1003,8 @@ commands: description: disable completion descriptions default_value: 'false' - id: supabase-completion-fish - title: supabase completion fish - summary: Generate the autocompletion script for fish + summary: supabase completion fish + title: Generate the autocompletion script for fish description: | Generate the autocompletion script for the fish shell. @@ -1027,8 +1027,8 @@ commands: description: disable completion descriptions default_value: 'false' - id: supabase-completion-bash - title: supabase completion bash - summary: Generate the autocompletion script for bash + summary: supabase completion bash + title: Generate the autocompletion script for bash description: | Generate the autocompletion script for the bash shell. From 49684030714403f4067457dca86af6da4fb2a897 Mon Sep 17 00:00:00 2001 From: dannykng Date: Tue, 10 Jan 2023 17:43:38 -0800 Subject: [PATCH 020/261] Update nav titles --- spec/common-cli-sections.json | 137 ++++++++++++++++++---------------- 1 file changed, 71 insertions(+), 66 deletions(-) diff --git a/spec/common-cli-sections.json b/spec/common-cli-sections.json index 4481a05bacf..c4e21a5bb66 100644 --- a/spec/common-cli-sections.json +++ b/spec/common-cli-sections.json @@ -14,71 +14,71 @@ "type": "markdown" }, { - "title": "general", + "title": "General", "items": [ { "id": "supabase-init", - "title": "supabase init", + "title": "Initialize a local project", "slug": "supabase-init", "type": "cli-command" }, { "id": "supabase-login", - "title": "supabase login", + "title": "Log in to a Supabase account", "slug": "supabase-login", "type": "cli-command" }, { "id": "supabase-link", - "title": "supabase link", + "title": "Link to a Supabase project", "slug": "supabase-link", "type": "cli-command" }, { "id": "supabase-start", - "title": "supabase start", + "title": "Start a container", "slug": "supabase-start", "type": "cli-command" }, { "id": "supabase-stop", - "title": "supabase stop", + "title": "Stop all containers", "slug": "supabase-stop", "type": "cli-command" }, { "id": "supabase-status", - "title": "supabase status", + "title": "Retrieve container status", "slug": "supabase-status", "type": "cli-command" }, { "id": "supabase-test", - "title": "supabase test", + "title": "Run tests", "slug": "supabase-test", "type": "cli-command" }, { "id": "supabase-test-db", - "title": "supabase test db", + "title": "Run tests (pgTAP)", "slug": "supabase-test-db", "type": "cli-command" }, { "id": "supabase-gen", - "title": "supabase gen", + "title": "Run code generation tools", "slug": "supabase-gen", "type": "cli-command" }, { "id": "supabase-gen-types", - "title": "supabase gen types", + "title": "Generate types (Postgres schema)", "slug": "supabase-gen-types", "type": "cli-command" }, { "id": "supabase-gen-types-typescript", - "title": "supabase gen types typescript", + "title": "Generate types (TypeScript)", "slug": "supabase-gen-types-typescript", "type": "cli-command" } @@ -87,115 +87,120 @@ { "title": "Database", "items": [ - { "id": "supabase-db", "title": "supabase db", "slug": "supabase-db", "type": "cli-command" }, + { + "id": "supabase-db", + "title": "Manage local Postgres databases", + "slug": "supabase-db", + "type": "cli-command" + }, { "id": "supabase-db-dump", - "title": "supabase db dump", + "title": "Dump schema from remote database", "slug": "supabase-db-dump", "type": "cli-command" }, { "id": "supabase-db-push", - "title": "supabase db push", + "title": "Push migration to remote database", "slug": "supabase-db-push", "type": "cli-command" }, { "id": "supabase-db-reset", - "title": "supabase db reset", + "title": "Reset local database", "slug": "supabase-db-reset", "type": "cli-command" }, { "id": "supabase-db-diff", - "title": "supabase db diff", + "title": "Diff local database", "slug": "supabase-db-diff", "type": "cli-command" }, { "id": "supabase-db-lint", - "title": "supabase db lint", + "title": "Lint local database", "slug": "supabase-db-lint", "type": "cli-command" }, { "id": "supabase-db-remote", - "title": "supabase db remote", + "title": "Manage remote databases", "slug": "supabase-db-remote", "type": "cli-command" }, { "id": "supabase-db-remote-commit", - "title": "supabase db remote commit", + "title": "Commit remote database changes", "slug": "supabase-db-remote-commit", "type": "cli-command" } ] }, { - "title": "migrations", + "title": "Migrations", "items": [ { "id": "supabase-migration", - "title": "supabase migration", + "title": "Manage database migrations", "slug": "supabase-migration", "type": "cli-command" }, { "id": "supabase-migration-new", - "title": "supabase migration new", + "title": "Create a migration", "slug": "supabase-migration-new", "type": "cli-command" }, { "id": "supabase-migration-list", - "title": "supabase migration list", + "title": "List all migrations", "slug": "supabase-migration-list", "type": "cli-command" }, { "id": "supabase-migration-repair", - "title": "supabase migration repair", + "title": "Repair migration history table", "slug": "supabase-migration-repair", "type": "cli-command" } ] }, { - "title": "projects", + "title": "Projects", "items": [ { "id": "supabase-projects", - "title": "supabase projects", + "title": "Manage projects", "slug": "supabase-projects", "type": "cli-command" }, { "id": "supabase-projects-create", - "title": "supabase projects create", + "title": "Create a project", "slug": "supabase-projects-create", "type": "cli-command" }, { "id": "supabase-projects-list", - "title": "supabase projects list", + "title": "List all projects", "slug": "supabase-projects-list", "type": "cli-command" } ] }, { - "title": "organizations", + "title": "Organizations", "items": [ { "id": "supabase-orgs", - "title": "supabase orgs", + "title": "Manage organizations", "slug": "supabase-orgs", "type": "cli-command" }, { "id": "supabase-orgs-list", - "title": "supabase orgs list", + "title": "List all organizations", "slug": "supabase-orgs-list", "type": "cli-command" } @@ -206,101 +211,101 @@ "items": [ { "id": "supabase-functions", - "title": "supabase functions", + "title": "Manage edge functions", "slug": "supabase-functions", "type": "cli-command" }, { "id": "supabase-functions-new", - "title": "supabase functions new", + "title": "Create a function", "slug": "supabase-functions-new", "type": "cli-command" }, { "id": "supabase-functions-serve", - "title": "supabase functions serve", + "title": "Serve a function", "slug": "supabase-functions-serve", "type": "cli-command" }, { "id": "supabase-functions-deploy", - "title": "supabase functions deploy", + "title": "Deploy a function", "slug": "supabase-functions-deploy", "type": "cli-command" }, { "id": "supabase-functions-delete", - "title": "supabase functions delete", + "title": "Delete a function", "slug": "supabase-functions-delete", "type": "cli-command" } ] }, { - "title": "secrets", + "title": "Secrets", "items": [ { "id": "supabase-secrets", - "title": "supabase secrets", + "title": "Manage secrets", "slug": "supabase-secrets", "type": "cli-command" }, { "id": "supabase-secrets-set", - "title": "supabase secrets set", + "title": "Set a secret", "slug": "supabase-secrets-set", "type": "cli-command" }, { "id": "supabase-secrets-list", - "title": "supabase secrets list", + "title": "List all secrets", "slug": "supabase-secrets-list", "type": "cli-command" }, { "id": "supabase-secrets-unset", - "title": "supabase secrets unset", + "title": "Unset a secret", "slug": "supabase-secrets-unset", "type": "cli-command" } ] }, { - "title": "Domains", + "title": "Custom Domains", "items": [ { "id": "supabase-domains", - "title": "supabase domains", + "title": "Manage custom domains", "slug": "supabase-domains", "type": "cli-command" }, { "id": "supabase-domains-activate", - "title": "supabase domains activate", + "title": "Activate custom hostname", "slug": "supabase-domains-activate", "type": "cli-command" }, { "id": "supabase-domains-create", - "title": "supabase domains create", + "title": "Create a custom hostname", "slug": "supabase-domains-create", "type": "cli-command" }, { "id": "supabase-domains-get", - "title": "supabase domains get", + "title": "Retrieve custom hostname config", "slug": "supabase-domains-get", "type": "cli-command" }, { "id": "supabase-domains-reverify", - "title": "supabase domains reverify", + "title": "Re-verify custom hostname config", "slug": "supabase-domains-reverify", "type": "cli-command" }, { "id": "supabase-domains-delete", - "title": "supabase domains delete", + "title": "Delete custom hostname config", "slug": "supabase-domains-delete", "type": "cli-command" } @@ -311,31 +316,31 @@ "items": [ { "id": "supabase-vanity-subdomains", - "title": "supabase vanity-subdomains", + "title": "Manage vanity subdomains", "slug": "supabase-vanity-subdomains", "type": "cli-command" }, { "id": "supabase-vanity-subdomains-activate", - "title": "supabase vanity-subdomains activate", + "title": "Activate a vanity subdomain", "slug": "supabase-vanity-subdomains-activate", "type": "cli-command" }, { "id": "supabase-vanity-subdomains-get", - "title": "supabase vanity-subdomains get", + "title": "Retrieve vanity subdomain", "slug": "supabase-vanity-subdomains-get", "type": "cli-command" }, { "id": "supabase-vanity-subdomains-check-availability", - "title": "supabase vanity-subdomains check-availability", + "title": "Check subdomain availability", "slug": "supabase-vanity-subdomains-check-availability", "type": "cli-command" }, { "id": "supabase-vanity-subdomains-delete", - "title": "supabase vanity-subdomains delete", + "title": "Delete vanity subdomain", "slug": "supabase-vanity-subdomains-delete", "type": "cli-command" } @@ -346,19 +351,19 @@ "items": [ { "id": "supabase-network-bans", - "title": "supabase network-bans", + "title": "Manage network bans", "slug": "supabase-network-bans", "type": "cli-command" }, { "id": "supabase-network-bans-get", - "title": "supabase network-bans get", + "title": "Retrieve network bans", "slug": "supabase-network-bans-get", "type": "cli-command" }, { "id": "supabase-network-bans-remove", - "title": "supabase network-bans remove", + "title": "Remove a network ban", "slug": "supabase-network-bans-remove", "type": "cli-command" } @@ -369,54 +374,54 @@ "items": [ { "id": "supabase-network-restrictions", - "title": "supabase network-restrictions", + "title": "Manage network restrictions", "slug": "supabase-network-restrictions", "type": "cli-command" }, { "id": "supabase-network-restrictions-get", - "title": "supabase network-restrictions get", + "title": "Retrieve network restrictions", "slug": "supabase-network-restrictions-get", "type": "cli-command" }, { "id": "supabase-network-restrictions-update", - "title": "supabase network-restrictions update", + "title": "Update network restrictions", "slug": "supabase-network-restrictions-update", "type": "cli-command" } ] }, { - "title": "Completion", + "title": "Autocompletion Scripts", "items": [ { "id": "supabase-completion", - "title": "supabase completion", + "title": "Generate autocompletion scripts", "slug": "supabase-completion", "type": "cli-command" }, { "id": "supabase-completion-zsh", - "title": "supabase completion zsh", + "title": "Generate zsh script", "slug": "supabase-completion-zsh", "type": "cli-command" }, { "id": "supabase-completion-powershell", - "title": "supabase completion powershell", + "title": "Generate powershell script", "slug": "supabase-completion-powershell", "type": "cli-command" }, { "id": "supabase-completion-fish", - "title": "supabase completion fish", + "title": "Generate fish script", "slug": "supabase-completion-fish", "type": "cli-command" }, { "id": "supabase-completion-bash", - "title": "supabase completion bash", + "title": "Generate bash script", "slug": "supabase-completion-bash", "type": "cli-command" } From 518a89b930daa3a938a8edb138483692b1ac9d81 Mon Sep 17 00:00:00 2001 From: dannykng Date: Tue, 10 Jan 2023 17:46:54 -0800 Subject: [PATCH 021/261] Fix title --- spec/cli_v1_commands.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/cli_v1_commands.yaml b/spec/cli_v1_commands.yaml index 0358d8e04f9..8f0a2945ff3 100644 --- a/spec/cli_v1_commands.yaml +++ b/spec/cli_v1_commands.yaml @@ -2,7 +2,7 @@ clispec: '001' info: id: cli version: 1.27.0 - summary: supabase CLI + title: Supabase CLI language: sh source: https://github.com/supabase/cli bugs: https://github.com/supabase/cli/issues From 58f4a51eb9e8b888f83cf350035a81d541a645d8 Mon Sep 17 00:00:00 2001 From: dannykng Date: Tue, 10 Jan 2023 17:57:45 -0800 Subject: [PATCH 022/261] Revert "Swap titles and summaries" This reverts commit 9be65118433f35eb67234fc41ad4f507fd4b206b. --- spec/cli_v1_commands.yaml | 240 +++++++++++++++++++------------------- 1 file changed, 120 insertions(+), 120 deletions(-) diff --git a/spec/cli_v1_commands.yaml b/spec/cli_v1_commands.yaml index 8f0a2945ff3..fabffeb3733 100644 --- a/spec/cli_v1_commands.yaml +++ b/spec/cli_v1_commands.yaml @@ -35,8 +35,8 @@ flags: default_value: '' commands: - id: supabase-vanity-subdomains - summary: supabase vanity-subdomains - title: Manage vanity subdomains for Supabase projects + title: supabase vanity-subdomains + summary: Manage vanity subdomains for Supabase projects description: |- Manage vanity subdomains for Supabase projects. @@ -51,8 +51,8 @@ commands: - supabase-vanity-subdomains-get flags: [] - id: supabase-vanity-subdomains-get - summary: supabase vanity-subdomains get - title: Get the current vanity subdomain + title: supabase vanity-subdomains get + summary: Get the current vanity subdomain tags: [] links: [] usage: supabase vanity-subdomains get @@ -68,8 +68,8 @@ commands: description: Project ref of the Supabase project. default_value: '' - id: supabase-vanity-subdomains-delete - summary: supabase vanity-subdomains delete - title: Deletes a project's vanity subdomain + title: supabase vanity-subdomains delete + summary: Deletes a project's vanity subdomain description: | Deletes the vanity subdomain for a project, and reverts to using the project ref for routing. tags: [] @@ -87,8 +87,8 @@ commands: description: Project ref of the Supabase project. default_value: '' - id: supabase-vanity-subdomains-check-availability - summary: supabase vanity-subdomains check-availability - title: Checks if a desired subdomain is available for use + title: supabase vanity-subdomains check-availability + summary: Checks if a desired subdomain is available for use tags: [] links: [] usage: supabase vanity-subdomains check-availability [flags] @@ -109,8 +109,8 @@ commands: description: Project ref of the Supabase project. default_value: '' - id: supabase-vanity-subdomains-activate - summary: supabase vanity-subdomains activate - title: Activate a vanity subdomain + title: supabase vanity-subdomains activate + summary: Activate a vanity subdomain description: | Activate a vanity subdomain for your Supabase project. @@ -136,8 +136,8 @@ commands: description: Project ref of the Supabase project. default_value: '' - id: supabase-test - summary: supabase test - title: Run tests on local Supabase containers + title: supabase test + summary: Run tests on local Supabase containers tags: - local-dev links: [] @@ -145,16 +145,16 @@ commands: - supabase-test-db flags: [] - id: supabase-test-db - summary: supabase test db - title: Tests local database with pgTAP + title: supabase test db + summary: Tests local database with pgTAP tags: [] links: [] usage: supabase test db subcommands: [] flags: [] - id: supabase-stop - summary: supabase stop - title: Stop all local Supabase containers + title: supabase stop + summary: Stop all local Supabase containers tags: - local-dev links: [] @@ -166,8 +166,8 @@ commands: description: Backs up the current database before stopping. default_value: 'false' - id: supabase-status - summary: supabase status - title: Show status of local Supabase containers + title: supabase status + summary: Show status of local Supabase containers tags: - local-dev links: [] @@ -199,8 +199,8 @@ commands: description: Override specific variable names. default_value: '[]' - id: supabase-start - summary: supabase start - title: Start containers for Supabase local development + title: supabase start + summary: Start containers for Supabase local development tags: - local-dev links: [] @@ -213,8 +213,8 @@ commands: Names of containers to not start. [gotrue, realtime, storage-api, imgproxy, kong, inbucket, postgrest, pgadmin-schema-diff, migra, postgres-meta, studio, deno-relay] default_value: '[]' - id: supabase-secrets - summary: supabase secrets - title: Manage Supabase secrets + title: supabase secrets + summary: Manage Supabase secrets tags: - management-api links: [] @@ -224,8 +224,8 @@ commands: - supabase-secrets-unset flags: [] - id: supabase-secrets-unset - summary: supabase secrets unset - title: Unset a secret(s) on Supabase + title: supabase secrets unset + summary: Unset a secret(s) on Supabase description: Unset a secret(s) from the linked Supabase project. tags: [] links: [] @@ -233,8 +233,8 @@ commands: subcommands: [] flags: [] - id: supabase-secrets-set - summary: supabase secrets set - title: Set a secret(s) on Supabase + title: supabase secrets set + summary: Set a secret(s) on Supabase description: Set a secret(s) to the linked Supabase project. tags: [] links: [] @@ -246,8 +246,8 @@ commands: description: Read secrets from a .env file. default_value: '' - id: supabase-secrets-list - summary: supabase secrets list - title: List all secrets on Supabase + title: supabase secrets list + summary: List all secrets on Supabase description: List all secrets in the linked project. tags: [] links: [] @@ -255,8 +255,8 @@ commands: subcommands: [] flags: [] - id: supabase-projects - summary: supabase projects - title: Manage Supabase projects + title: supabase projects + summary: Manage Supabase projects tags: - management-api links: [] @@ -265,8 +265,8 @@ commands: - supabase-projects-list flags: [] - id: supabase-projects-list - summary: supabase projects list - title: List all Supabase projects + title: supabase projects list + summary: List all Supabase projects description: List all Supabase projects the logged-in user can access. tags: [] links: [] @@ -274,8 +274,8 @@ commands: subcommands: [] flags: [] - id: supabase-projects-create - summary: supabase projects create - title: Create a project on Supabase + title: supabase projects create + summary: Create a project on Supabase tags: [] links: [] usage: supabase projects create [flags] @@ -349,8 +349,8 @@ commands: name: us-west-1 type: string - id: supabase-orgs - summary: supabase orgs - title: Manage Supabase organizations + title: supabase orgs + summary: Manage Supabase organizations tags: - management-api links: [] @@ -358,8 +358,8 @@ commands: - supabase-orgs-list flags: [] - id: supabase-orgs-list - summary: supabase orgs list - title: List all organizations + title: supabase orgs list + summary: List all organizations description: List all organizations the logged-in user belongs. tags: [] links: [] @@ -367,8 +367,8 @@ commands: subcommands: [] flags: [] - id: supabase-network-restrictions - summary: supabase network-restrictions - title: Manage network restrictions + title: supabase network-restrictions + summary: Manage network restrictions tags: - management-api links: [] @@ -377,8 +377,8 @@ commands: - supabase-network-restrictions-update flags: [] - id: supabase-network-restrictions-update - summary: supabase network-restrictions update - title: Update network restrictions + title: supabase network-restrictions update + summary: Update network restrictions tags: [] links: [] usage: supabase network-restrictions update [flags] @@ -402,8 +402,8 @@ commands: description: Project ref of the Supabase project. default_value: '' - id: supabase-network-restrictions-get - summary: supabase network-restrictions get - title: Get the current network restrictions + title: supabase network-restrictions get + summary: Get the current network restrictions tags: [] links: [] usage: supabase network-restrictions get @@ -419,8 +419,8 @@ commands: description: Project ref of the Supabase project. default_value: '' - id: supabase-network-bans - summary: supabase network-bans - title: Manage network bans + title: supabase network-bans + summary: Manage network bans description: |- Network bans are IPs that get temporarily blocked if their traffic pattern looks abusive (e.g. multiple failed auth attempts). @@ -433,8 +433,8 @@ commands: - supabase-network-bans-remove flags: [] - id: supabase-network-bans-remove - summary: supabase network-bans remove - title: Remove a network ban + title: supabase network-bans remove + summary: Remove a network ban tags: [] links: [] usage: supabase network-bans remove [flags] @@ -454,8 +454,8 @@ commands: description: Project ref of the Supabase project. default_value: '' - id: supabase-network-bans-get - summary: supabase network-bans get - title: Get the current network bans + title: supabase network-bans get + summary: Get the current network bans tags: [] links: [] usage: supabase network-bans get @@ -471,8 +471,8 @@ commands: description: Project ref of the Supabase project. default_value: '' - id: supabase-migration - summary: supabase migration - title: Manage database migration scripts + title: supabase migration + summary: Manage database migration scripts tags: - local-dev links: [] @@ -482,8 +482,8 @@ commands: - supabase-migration-repair flags: [] - id: supabase-migration-repair - summary: supabase migration repair - title: Repairs the migration history table + title: supabase migration repair + summary: Repairs the migration history table tags: [] links: [] usage: supabase migration repair [flags] @@ -506,16 +506,16 @@ commands: name: reverted type: '[ applied | reverted ]' - id: supabase-migration-new - summary: supabase migration new - title: Create an empty migration script + title: supabase migration new + summary: Create an empty migration script tags: [] links: [] usage: supabase migration new subcommands: [] flags: [] - id: supabase-migration-list - summary: supabase migration list - title: List local and remote migrations + title: supabase migration list + summary: List local and remote migrations tags: [] links: [] usage: supabase migration list [flags] @@ -526,8 +526,8 @@ commands: description: Password to your remote Postgres database. default_value: '' - id: supabase-login - summary: supabase login - title: Authenticate using an access token + title: supabase login + summary: Authenticate using an access token tags: - local-dev links: [] @@ -535,8 +535,8 @@ commands: subcommands: [] flags: [] - id: supabase-link - summary: supabase link - title: Link to a Supabase project + title: supabase link + summary: Link to a Supabase project tags: - local-dev links: [] @@ -553,8 +553,8 @@ commands: required: true default_value: '' - id: supabase-init - summary: supabase init - title: Initialize a local project + title: supabase init + summary: Initialize a local project tags: - local-dev links: [] @@ -562,8 +562,8 @@ commands: subcommands: [] flags: [] - id: supabase-gen - summary: supabase gen - title: Run code generation tools + title: supabase gen + summary: Run code generation tools tags: - local-dev links: [] @@ -571,16 +571,16 @@ commands: - supabase-gen-types flags: [] - id: supabase-gen-types - summary: supabase gen types - title: Generate types from Postgres schema + title: supabase gen types + summary: Generate types from Postgres schema tags: [] links: [] subcommands: - supabase-gen-types-typescript flags: [] - id: supabase-gen-types-typescript - summary: supabase gen types typescript - title: Generate types for TypeScript + title: supabase gen types typescript + summary: Generate types for TypeScript description: | Generate types for TypeScript. Must specify one of --local, --linked, --project-id, or --db-url tags: [] @@ -609,8 +609,8 @@ commands: description: Schemas to generate types for. default_value: '[]' - id: supabase-functions - summary: supabase functions - title: Manage Supabase Edge functions + title: supabase functions + summary: Manage Supabase Edge functions tags: - management-api links: [] @@ -621,8 +621,8 @@ commands: - supabase-functions-serve flags: [] - id: supabase-functions-serve - summary: supabase functions serve - title: Serve a Function locally + title: supabase functions serve + summary: Serve a Function locally tags: [] links: [] usage: supabase functions serve [flags] @@ -642,16 +642,16 @@ commands: description: Disable JWT verification for the Function. default_value: 'false' - id: supabase-functions-new - summary: supabase functions new - title: Create a new Function locally + title: supabase functions new + summary: Create a new Function locally tags: [] links: [] usage: supabase functions new subcommands: [] flags: [] - id: supabase-functions-deploy - summary: supabase functions deploy - title: Deploy a Function to Supabase + title: supabase functions deploy + summary: Deploy a Function to Supabase description: Deploy a Function to the linked Supabase project. tags: [] links: [] @@ -675,8 +675,8 @@ commands: description: Project ref of the Supabase project. default_value: '' - id: supabase-functions-delete - summary: supabase functions delete - title: Delete a Function from Supabase + title: supabase functions delete + summary: Delete a Function from Supabase description: | Delete a Function from the linked Supabase project. This does NOT remove the Function locally. tags: [] @@ -689,8 +689,8 @@ commands: description: Project ref of the Supabase project. default_value: '' - id: supabase-domains - summary: supabase domains - title: Manage custom domain names for Supabase projects + title: supabase domains + summary: Manage custom domain names for Supabase projects description: | Manage custom domain names for Supabase projects. @@ -706,8 +706,8 @@ commands: - supabase-domains-reverify flags: [] - id: supabase-domains-reverify - summary: supabase domains reverify - title: Re-verify the custom hostname config for your project + title: supabase domains reverify + summary: Re-verify the custom hostname config for your project tags: [] links: [] usage: supabase domains reverify @@ -722,8 +722,8 @@ commands: description: Project ref of the Supabase project. default_value: '' - id: supabase-domains-get - summary: supabase domains get - title: Get the current custom hostname config + title: supabase domains get + summary: Get the current custom hostname config description: | Retrieve the custom hostname config for your project, as stored in the Supabase platform. tags: [] @@ -740,8 +740,8 @@ commands: description: Project ref of the Supabase project. default_value: '' - id: supabase-domains-delete - summary: supabase domains delete - title: Deletes the custom hostname config for your project + title: supabase domains delete + summary: Deletes the custom hostname config for your project tags: [] links: [] usage: supabase domains delete @@ -756,8 +756,8 @@ commands: description: Project ref of the Supabase project. default_value: '' - id: supabase-domains-create - summary: supabase domains create - title: Create a custom hostname + title: supabase domains create + summary: Create a custom hostname description: |- Create a custom hostname for your Supabase project. @@ -780,8 +780,8 @@ commands: description: Project ref of the Supabase project. default_value: '' - id: supabase-domains-activate - summary: supabase domains activate - title: Activate the custom hostname for a project + title: supabase domains activate + summary: Activate the custom hostname for a project description: |- Activates the custom hostname configuration for a project. @@ -801,8 +801,8 @@ commands: description: Project ref of the Supabase project. default_value: '' - id: supabase-db - summary: supabase db - title: Manage local Postgres databases + title: supabase db + summary: Manage local Postgres databases tags: - local-dev links: [] @@ -816,32 +816,32 @@ commands: - supabase-db-start flags: [] - id: supabase-db-start - summary: supabase db start - title: Starts local Postgres database + title: supabase db start + summary: Starts local Postgres database tags: [] links: [] usage: supabase db start subcommands: [] flags: [] - id: supabase-db-reset - summary: supabase db reset - title: Resets the local database to current migrations + title: supabase db reset + summary: Resets the local database to current migrations tags: [] links: [] usage: supabase db reset subcommands: [] flags: [] - id: supabase-db-remote - summary: supabase db remote - title: Manage remote databases + title: supabase db remote + summary: Manage remote databases tags: [] links: [] subcommands: - supabase-db-remote-commit flags: [] - id: supabase-db-remote-commit - summary: supabase db remote commit - title: Commit remote changes as a new migration + title: supabase db remote commit + summary: Commit remote changes as a new migration tags: [] links: [] usage: supabase db remote commit @@ -852,8 +852,8 @@ commands: description: Password to your remote Postgres database. default_value: '' - id: supabase-db-push - summary: supabase db push - title: Push new migrations to the remote database + title: supabase db push + summary: Push new migrations to the remote database tags: [] links: [] usage: supabase db push [flags] @@ -869,8 +869,8 @@ commands: description: Password to your remote Postgres database. default_value: '' - id: supabase-db-lint - summary: supabase db lint - title: Checks local database for typing error + title: supabase db lint + summary: Checks local database for typing error tags: [] links: [] usage: supabase db lint [flags] @@ -892,8 +892,8 @@ commands: description: List of schema to include. default_value: '[public]' - id: supabase-db-dump - summary: supabase db dump - title: Dumps schemas from the remote database + title: supabase db dump + summary: Dumps schemas from the remote database tags: [] links: [] usage: supabase db dump [flags] @@ -908,8 +908,8 @@ commands: description: Password to your remote Postgres database. default_value: '' - id: supabase-db-diff - summary: supabase db diff - title: Diffs the local database for schema changes + title: supabase db diff + summary: Diffs the local database for schema changes tags: [] links: [] usage: supabase db diff [flags] @@ -932,8 +932,8 @@ commands: description: Use migra to generate schema diff. default_value: 'false' - id: supabase-completion - summary: supabase completion - title: Generate the autocompletion script for the specified shell + title: supabase completion + summary: Generate the autocompletion script for the specified shell description: | Generate the autocompletion script for supabase for the specified shell. See each sub-command's help for details on how to use the generated script. @@ -947,8 +947,8 @@ commands: - supabase-completion-zsh flags: [] - id: supabase-completion-zsh - summary: supabase completion zsh - title: Generate the autocompletion script for zsh + title: supabase completion zsh + summary: Generate the autocompletion script for zsh description: | Generate the autocompletion script for the zsh shell. @@ -982,8 +982,8 @@ commands: description: disable completion descriptions default_value: 'false' - id: supabase-completion-powershell - summary: supabase completion powershell - title: Generate the autocompletion script for powershell + title: supabase completion powershell + summary: Generate the autocompletion script for powershell description: | Generate the autocompletion script for powershell. @@ -1003,8 +1003,8 @@ commands: description: disable completion descriptions default_value: 'false' - id: supabase-completion-fish - summary: supabase completion fish - title: Generate the autocompletion script for fish + title: supabase completion fish + summary: Generate the autocompletion script for fish description: | Generate the autocompletion script for the fish shell. @@ -1027,8 +1027,8 @@ commands: description: disable completion descriptions default_value: 'false' - id: supabase-completion-bash - summary: supabase completion bash - title: Generate the autocompletion script for bash + title: supabase completion bash + summary: Generate the autocompletion script for bash description: | Generate the autocompletion script for the bash shell. From 338d527189208c7ec52b705090cfd6db4a902d09 Mon Sep 17 00:00:00 2001 From: dannykng Date: Tue, 10 Jan 2023 18:02:59 -0800 Subject: [PATCH 023/261] Update title --- spec/common-cli-sections.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/common-cli-sections.json b/spec/common-cli-sections.json index c4e21a5bb66..392a585f520 100644 --- a/spec/common-cli-sections.json +++ b/spec/common-cli-sections.json @@ -89,7 +89,7 @@ "items": [ { "id": "supabase-db", - "title": "Manage local Postgres databases", + "title": "Manage local databases", "slug": "supabase-db", "type": "cli-command" }, From e53e74d68c7d6bd18ebb3f31b6059bd0e6d86f4e Mon Sep 17 00:00:00 2001 From: dannykng Date: Tue, 10 Jan 2023 18:20:43 -0800 Subject: [PATCH 024/261] Build --- spec/cli_v1_commands.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/cli_v1_commands.yaml b/spec/cli_v1_commands.yaml index fabffeb3733..711f7822cd4 100644 --- a/spec/cli_v1_commands.yaml +++ b/spec/cli_v1_commands.yaml @@ -8,7 +8,7 @@ info: bugs: https://github.com/supabase/cli/issues spec: https://github.com/supabase/supabase.tools/cli_spec/lib.yaml description: | - Supabase CLI provides you with tools to develop your application locally, and deploy your application to the Supabase platform. + Supabase CLI provides you with tools to develop your application locally, and deploy your application to the Supabase Platform. tags: - id: local-dev title: Local Development From c5614d9a19068ceb57d240a667062e12ed75ddf0 Mon Sep 17 00:00:00 2001 From: dannykng Date: Tue, 10 Jan 2023 18:25:48 -0800 Subject: [PATCH 025/261] Build --- apps/docs/pages/guides/resources/supabase-cli.mdx | 2 -- spec/cli_v1_commands.yaml | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/docs/pages/guides/resources/supabase-cli.mdx b/apps/docs/pages/guides/resources/supabase-cli.mdx index ef861654950..394766e483f 100644 --- a/apps/docs/pages/guides/resources/supabase-cli.mdx +++ b/apps/docs/pages/guides/resources/supabase-cli.mdx @@ -5,8 +5,6 @@ export const meta = { title: 'Supabase CLI', description: 'The Supabase CLI provides tools to develop your project locally and deploy to the Supabase Platform.', - sidebar_label: 'Overview', - toc_max_heading_level: 2, } The Supabase CLI provides tools to develop your project locally and deploy to the Supabase Platform. diff --git a/spec/cli_v1_commands.yaml b/spec/cli_v1_commands.yaml index 711f7822cd4..fabffeb3733 100644 --- a/spec/cli_v1_commands.yaml +++ b/spec/cli_v1_commands.yaml @@ -8,7 +8,7 @@ info: bugs: https://github.com/supabase/cli/issues spec: https://github.com/supabase/supabase.tools/cli_spec/lib.yaml description: | - Supabase CLI provides you with tools to develop your application locally, and deploy your application to the Supabase Platform. + Supabase CLI provides you with tools to develop your application locally, and deploy your application to the Supabase platform. tags: - id: local-dev title: Local Development From de37a51ad2d00d70191fa663a1336fd9d48f8649 Mon Sep 17 00:00:00 2001 From: Ollie Halliday <22655069+Hallidayo@users.noreply.github.com> Date: Wed, 11 Jan 2023 15:24:35 +0000 Subject: [PATCH 026/261] change styling and flow of tabs --- .../reference/RefFunctionSection.tsx | 55 ++++++++++--------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/apps/docs/components/reference/RefFunctionSection.tsx b/apps/docs/components/reference/RefFunctionSection.tsx index 5cdacd0d458..0b50bb219be 100644 --- a/apps/docs/components/reference/RefFunctionSection.tsx +++ b/apps/docs/components/reference/RefFunctionSection.tsx @@ -158,18 +158,24 @@ const RefFunctionSection: React.FC = (props) => { label={example.name} className="flex flex-col gap-3" > - {example.description && ( -
- - {example.description} - -
- )} + + {exampleString + + (example.code && + example.code + .replace(/```/g, '') + .replace('js', '') + .replace('ts', '') + .replace('dart', ''))} + {((tables && tables.length > 0) || sql) && ( <> @@ -204,29 +210,14 @@ const RefFunctionSection: React.FC = (props) => { )} - - - {exampleString + - (example.code && - example.code - .replace(/```/g, '') - .replace('js', '') - .replace('ts', '') - .replace('dart', ''))} - - {response && ( @@ -234,6 +225,20 @@ const RefFunctionSection: React.FC = (props) => { )} + + {example.description && ( + +
+ + {example.description} + +
+
+ )} ) })} From 4fad729f9704deaab1eb36570285434a3d838541 Mon Sep 17 00:00:00 2001 From: Ollie Halliday <22655069+Hallidayo@users.noreply.github.com> Date: Wed, 11 Jan 2023 15:24:52 +0000 Subject: [PATCH 027/261] ran prettier --- apps/docs/components/reference/RefFunctionSection.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/docs/components/reference/RefFunctionSection.tsx b/apps/docs/components/reference/RefFunctionSection.tsx index 0b50bb219be..1c4e9668a23 100644 --- a/apps/docs/components/reference/RefFunctionSection.tsx +++ b/apps/docs/components/reference/RefFunctionSection.tsx @@ -232,10 +232,10 @@ const RefFunctionSection: React.FC = (props) => { label="Notes" defaultOpen={false} > -
- - {example.description} - +
+ + {example.description} +
)} From cdd4b9cd4d148ae8a56e915d28c3d032921eb04d Mon Sep 17 00:00:00 2001 From: dannykng Date: Wed, 11 Jan 2023 17:35:45 -0800 Subject: [PATCH 028/261] Add Testing and Types sections --- spec/common-cli-sections.json | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/spec/common-cli-sections.json b/spec/common-cli-sections.json index 392a585f520..1407e6fa344 100644 --- a/spec/common-cli-sections.json +++ b/spec/common-cli-sections.json @@ -51,7 +51,12 @@ "title": "Retrieve container status", "slug": "supabase-status", "type": "cli-command" - }, + } + ] + }, + { + "title": "Testing", + "items": [ { "id": "supabase-test", "title": "Run tests", @@ -63,7 +68,12 @@ "title": "Run tests (pgTAP)", "slug": "supabase-test-db", "type": "cli-command" - }, + } + ] + }, + { + "title": "Generate Types", + "items": [ { "id": "supabase-gen", "title": "Run code generation tools", From 36c366b5641a200a4757fad156c1c6821639c9ad Mon Sep 17 00:00:00 2001 From: thorwebdev Date: Thu, 12 Jan 2023 16:05:08 +0800 Subject: [PATCH 029/261] feat: reorg function docs and add videos. --- .../NavigationMenu.constants.ts | 20 +- .../pages/guides/functions/cicd-workflow.mdx | 9 + apps/docs/pages/guides/functions/cors.mdx | 59 +++++ .../examples/cloudflare-turnstile.mdx | 96 ++++++++ .../functions/examples/github-actions.mdx | 49 ++++ .../guides/functions/examples/og-image.mdx | 6 + .../functions/examples/storage-caching.mdx | 22 ++ .../functions/examples/stripe-webhooks.mdx | 22 ++ .../functions/examples/telegram-bot.mdx | 22 ++ apps/docs/pages/guides/functions/overview.mdx | 84 +++++++ .../pages/guides/functions/quickstart.mdx | 210 ------------------ apps/docs/pages/guides/functions/secrets.mdx | 89 ++++++++ .../guides/functions/testing-debugging.mdx | 55 +++++ .../{best-practices.mdx => tips.mdx} | 26 +-- 14 files changed, 533 insertions(+), 236 deletions(-) create mode 100644 apps/docs/pages/guides/functions/cors.mdx create mode 100644 apps/docs/pages/guides/functions/examples/cloudflare-turnstile.mdx create mode 100644 apps/docs/pages/guides/functions/examples/github-actions.mdx create mode 100644 apps/docs/pages/guides/functions/examples/storage-caching.mdx create mode 100644 apps/docs/pages/guides/functions/examples/stripe-webhooks.mdx create mode 100644 apps/docs/pages/guides/functions/examples/telegram-bot.mdx create mode 100644 apps/docs/pages/guides/functions/overview.mdx delete mode 100644 apps/docs/pages/guides/functions/quickstart.mdx create mode 100644 apps/docs/pages/guides/functions/secrets.mdx create mode 100644 apps/docs/pages/guides/functions/testing-debugging.mdx rename apps/docs/pages/guides/functions/{best-practices.mdx => tips.mdx} (71%) diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index 7abe1bea978..75d54fb2afb 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -421,14 +421,28 @@ export const functions = { label: 'Edge Functions', url: '/guides/functions', items: [ + { name: 'Overview', url: '/guides/functions/overview', items: [] }, { name: 'Auth', url: '/guides/functions/auth', items: [] }, + { name: 'CORS', url: '/guides/functions/cors', items: [] }, { name: 'CI/CD Workflow', url: '/guides/functions/cicd-workflow', items: [] }, - { name: 'Best Practices', url: '/guides/functions/best-practices', items: [] }, - { name: 'Quickstart', url: '/guides/functions/quickstart', items: [] }, + { name: 'Managing Secrets', url: '/guides/functions/secrets', items: [] }, + { name: 'Testing & Debugging', url: '/guides/functions/testing-debugging', items: [] }, + { name: 'Tips', url: '/guides/functions/tips', items: [] }, { name: 'Examples', url: '/guides/functions/examples', - items: [{ name: 'OG Image', url: '/guides/functions/examples/og-image', items: [] }], + items: [ + { + name: 'Cloudflare Turnstile', + url: '/guides/functions/examples/cloudflare-turnstile', + items: [], + }, + { name: 'GitHub Actions', url: '/guides/functions/examples/github-actions', items: [] }, + { name: 'OG Image', url: '/guides/functions/examples/og-image', items: [] }, + { name: 'Storage Caching', url: '/guides/functions/examples/storage-caching', items: [] }, + { name: 'Stripe Webhooks', url: '/guides/functions/examples/stripe-webhooks', items: [] }, + { name: 'Telegram Bot', url: '/guides/functions/examples/telegram-bot', items: [] }, + ], }, ], } diff --git a/apps/docs/pages/guides/functions/cicd-workflow.mdx b/apps/docs/pages/guides/functions/cicd-workflow.mdx index e902f140370..fd7826ddf0e 100644 --- a/apps/docs/pages/guides/functions/cicd-workflow.mdx +++ b/apps/docs/pages/guides/functions/cicd-workflow.mdx @@ -35,6 +35,15 @@ jobs: - run: supabase functions deploy your-function-name --project-ref $PROJECT_ID ``` +
+ +
+ See the [example on GitHub](https://github.com/supabase/supabase/blob/master/examples/edge-functions/.github/workflows/deploy.yaml). export const Page = ({ children }) => diff --git a/apps/docs/pages/guides/functions/cors.mdx b/apps/docs/pages/guides/functions/cors.mdx new file mode 100644 index 00000000000..fca25fa4933 --- /dev/null +++ b/apps/docs/pages/guides/functions/cors.mdx @@ -0,0 +1,59 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'functions-cors', + title: 'CORS (Cross-Origin Resource Sharing)', + description: 'Add CORS headers to invoke functions from the browser.', +} + +To invoke the functions from the browser, you need to handle [CORS Preflight](https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request) requests. + +See the [example on GitHub](https://github.com/supabase/supabase/blob/master/examples/edge-functions/supabase/functions/browser-with-cors/index.ts). + +### Recommended setup + +We recommend adding a `corst.ts` file within a `_shared` folder which makes it easy to reuse the CORS headers across functions: + +```ts cors.ts +export const corsHeaders = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', +} +``` + +You can then import and use the CORS headers within your functions: + +```ts index.ts +import { serve } from 'https://deno.land/std@0.131.0/http/server.ts' +import { corsHeaders } from '../_shared/cors.ts' + +console.log(`Function "browser-with-cors" up and running!`) + +serve(async (req) => { + // This is needed if you're planning to invoke your function from a browser. + if (req.method === 'OPTIONS') { + return new Response('ok', { headers: corsHeaders }) + } + + try { + const { name } = await req.json() + const data = { + message: `Hello ${name}!`, + } + + return new Response(JSON.stringify(data), { + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + status: 200, + }) + } catch (error) { + return new Response(JSON.stringify({ error: error.message }), { + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + status: 400, + }) + } +}) +``` + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/functions/examples/cloudflare-turnstile.mdx b/apps/docs/pages/guides/functions/examples/cloudflare-turnstile.mdx new file mode 100644 index 00000000000..560b3d9e454 --- /dev/null +++ b/apps/docs/pages/guides/functions/examples/cloudflare-turnstile.mdx @@ -0,0 +1,96 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'examples-cloudflare-turnstile', + title: 'Cloudflare Turnstile', + description: 'Protecting Forms with Cloudflare Turnstile.', +} + +
+ +
+ +[Clouflare Turnstile](https://www.cloudflare.com/products/turnstile/) is a friendly, free CAPTCHA replacement, and it works seamlessly with Supabase Edge Functions to protect your forms. [View on GitHub](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/cloudflare-turnstile). + +## Setup + +- Follow these steps to set up a new site: https://developers.cloudflare.com/turnstile/get-started/ +- Add the Cloudflare Turnstile widget to your site: https://developers.cloudflare.com/turnstile/get-started/client-side-rendering/ + +## Code + +Create a new function in your project: + +```bash +supabase functions new cloudflare-turnstile +``` + +And add the code to the `index.ts` file: + +```ts index.ts +import { serve } from 'https://deno.land/std@0.131.0/http/server.ts' +import { corsHeaders } from '../_shared/cors.ts' + +console.log('Hello from Cloudflare Trunstile!') + +function ips(req: Request) { + return req.headers.get('x-forwarded-for')?.split(/\s*,\s*/) +} + +serve(async (req) => { + // This is needed if you're planning to invoke your function from a browser. + if (req.method === 'OPTIONS') { + return new Response('ok', { headers: corsHeaders }) + } + + const { token } = await req.json() + const clientIps = ips(req) || [''] + const ip = clientIps[0] + + // Validate the token by calling the + // "/siteverify" API endpoint. + let formData = new FormData() + formData.append('secret', Deno.env.get('CLOUDFLARE_SECRET_KEY') ?? '') + formData.append('response', token) + formData.append('remoteip', ip) + + const url = 'https://challenges.cloudflare.com/turnstile/v0/siteverify' + const result = await fetch(url, { + body: formData, + method: 'POST', + }) + + const outcome = await result.json() + console.log(outcome) + if (outcome.success) { + return new Response('success', { headers: corsHeaders }) + } + return new Response('failure', { headers: corsHeaders }) +}) +``` + +## Deploy the server-side validation Edge Functions + +- https://developers.cloudflare.com/turnstile/get-started/server-side-validation/ + +```bash +supabase functions deploy cloudflare-turnstile +supabase secrets set CLOUDFLARE_TURNSTILE_SECRET_KEY=your_secret_key +``` + +## Invoke the function from your site + +```js +const { data, error } = await supabase.functions.invoke('cloudflare-turnstile', { + body: { token }, +}) +``` + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/functions/examples/github-actions.mdx b/apps/docs/pages/guides/functions/examples/github-actions.mdx new file mode 100644 index 00000000000..97456f33767 --- /dev/null +++ b/apps/docs/pages/guides/functions/examples/github-actions.mdx @@ -0,0 +1,49 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'examples-github-actions', + title: 'GitHub Actions', + description: 'Deploying Edge Functions with GitHub Actions.', +} + +
+ +
+ +Use the Supabase CLI together with GitHub Actions to automatically deploy our Supabase Edge Functions. [View on GitHub](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/github-action-deploy). + +```yaml deploy.yaml +name: Deploy Function + +on: + push: + branches: + - main + workflow_dispatch: + +jobs: + deploy: + runs-on: ubuntu-latest + + env: + SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }} + PROJECT_ID: zdtdtxajzydjqzuktnqx + + steps: + - uses: actions/checkout@v3 + + - uses: supabase/setup-cli@v1 + with: + version: 1.0.0 + + - run: supabase functions deploy github-action-deploy --project-ref $PROJECT_ID +``` + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/functions/examples/og-image.mdx b/apps/docs/pages/guides/functions/examples/og-image.mdx index 2974d5ef853..e4be6c85796 100644 --- a/apps/docs/pages/guides/functions/examples/og-image.mdx +++ b/apps/docs/pages/guides/functions/examples/og-image.mdx @@ -17,6 +17,10 @@ export const meta = { Generate Open Graph images with Deno and Supabase Edge Functions. [View on GitHub](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/opengraph). +## Code + +Create a `handler.tsx` file to construct the OG image in React: + ```tsx handler.tsx import React from 'https://esm.sh/react@18.2.0' import { ImageResponse } from 'https://deno.land/x/og_edge@0.0.4/mod.ts' @@ -42,6 +46,8 @@ export default function handler(req: Request) { } ``` +Create an `index.ts` file to execute the handler on incoming requests: + ```ts index.ts import { serve } from 'https://deno.land/std@0.131.0/http/server.ts' import handler from './handler.tsx' diff --git a/apps/docs/pages/guides/functions/examples/storage-caching.mdx b/apps/docs/pages/guides/functions/examples/storage-caching.mdx new file mode 100644 index 00000000000..6e4b6ff604e --- /dev/null +++ b/apps/docs/pages/guides/functions/examples/storage-caching.mdx @@ -0,0 +1,22 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'examples-storage-caching', + title: 'Caching Images with Supabase Storage CDN', + description: 'Integrate Edge Functions with Supabase Storage to cache images on the Edge (CDN).', +} + +
+ +
+ +Integrate Edge Functions with Supabase Storage to cache images on the Edge (CDN). [View on GitHub](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/og-image-with-storage-cdn). + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/functions/examples/stripe-webhooks.mdx b/apps/docs/pages/guides/functions/examples/stripe-webhooks.mdx new file mode 100644 index 00000000000..5b75bb48878 --- /dev/null +++ b/apps/docs/pages/guides/functions/examples/stripe-webhooks.mdx @@ -0,0 +1,22 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'examples-stripe-webhooks', + title: 'Handling Stripe Webhooks', + description: 'Handling signed Stripe Webhooks with Edge Functions.', +} + +
+ +
+ +Handling signed Stripe Webhooks with Edge Functions. [View on GitHub](https://github.com/supabase/supabase/blob/master/examples/edge-functions/supabase/functions/stripe-webhooks/index.ts). + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/functions/examples/telegram-bot.mdx b/apps/docs/pages/guides/functions/examples/telegram-bot.mdx new file mode 100644 index 00000000000..bac6a45eba2 --- /dev/null +++ b/apps/docs/pages/guides/functions/examples/telegram-bot.mdx @@ -0,0 +1,22 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'examples-telegram-bot', + title: 'Telegram Bot', + description: 'Building a Telegram Bot with Edge Functions.', +} + +
+ +
+ +Handle Telegram Bot Webhooks with the [grammY framework](https://grammy.dev/). grammY is an open source Telegram Bot Framework which makes it easy to handle and respond to incoming messages. [View on GitHub](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/telegram-bot). + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/functions/overview.mdx b/apps/docs/pages/guides/functions/overview.mdx new file mode 100644 index 00000000000..49935c280bf --- /dev/null +++ b/apps/docs/pages/guides/functions/overview.mdx @@ -0,0 +1,84 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'functions-overview', + title: 'Edge Functions Overview', + description: 'Globally distributed TypeScript Functions.', + sidebar_label: 'Overview', +} + +Learn how to build an Edge Function locally and deploy it to the Supabase Platform in less than 7 minutes. + +
+ +
+ +## Prerequisites + +Follow the steps to prepare your Supabase project on your local machine. + +- Install the Supabase CLI. [Docs](/docs/guides/cli). +- Login to the CLI using the command: `supabase login`. [Docs](/docs/reference/cli/usage#supabase-login). +- Initialize Supabase inside your project using the command: `supabase init`. [Docs](/docs/guides/cli/local-development#getting-started). +- Link to your Remote Project using the command `supabase link --project-ref your-project-ref`. [Docs](/docs/reference/cli/usage#supabase-link). +- Optional: Setup your environment: Follow [this setup guide](https://deno.land/manual/getting_started/setup_your_environment) to integrate the Deno language server with your editor. + +## Create a function + +Let's create a new Edge Function called `hello-world` inside your project: + +```bash +supabase functions new hello-world +``` + +This creates a function stub in your `supabase` folder at `./functions/hello-world/index.ts`. + +## Deploy to production + +```bash +supabase functions deploy hello-world +``` + +This command bundles your Edge Function from `./functions/hello-world/index.ts` and deploys it to the Supabase platform. +The command outputs a URL to the Supabase Dashboard which you can open to find view more details. Let's open the link to find the execution command. + + + +By default, Edge Functions require a valid JWT in the authorization header. This header is automatically set when invoking your function via a Supabase client library. + +If you want to use Edge Functions to handle webhooks (e.g. [Stripe payment webhooks](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/stripe-webhooks) etc.), you need to pass the `--no-verify-jwt` flag when deploying your function. + + + +## Execute remote functions + +You can execute Edge Functions using curl. Copy the curl command from the Dashboard. It should look like this: + +```bash +curl --request POST 'https://.functions.supabase.co/hello-world' \ + --header 'Authorization: Bearer ANON_KEY' \ + --header 'Content-Type: application/json' \ + --data '{ "name":"Functions" }' +``` + +If you receive an error `Invalid JWT`, find the `ANON_KEY` of your project in the Dashboard under `Settings > API`. + +After invoking your Edge Function you should see the response `{ "message":"Hello Functions!" }`. + +## Limitations + +- Deno Deploy limitations + - Deno does not support outgoing connections to ports `25`, `465`, and `587`. + - Cannot write to File System +- Edge Functions + - Local development - only one function at a time + - Serving of HTML content is not supported (`GET` requests that return `text/html` will be rewritten to `text/plain`). + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/functions/quickstart.mdx b/apps/docs/pages/guides/functions/quickstart.mdx deleted file mode 100644 index 55b9ed617c6..00000000000 --- a/apps/docs/pages/guides/functions/quickstart.mdx +++ /dev/null @@ -1,210 +0,0 @@ -import Layout from '~/layouts/DefaultGuideLayout' - -export const meta = { - id: 'functions-quickstart', - title: 'Edge Functions Quickstart', - description: 'Globally distributed TypeScript functions.', - sidebar_label: 'Quickstart', -} - -Learn how to build an Edge Function locally and deploy it to the Supabase Platform in less than 7 minutes. - -
- -
- -## Prerequisites - -Follow the steps to prepare your Supabase project on your local machine. - -- Install the Supabase CLI. [Docs](/docs/guides/cli). -- Login to the CLI using the command: `supabase login`. [Docs](/docs/reference/cli/usage#supabase-login). -- Initialize Supabase inside your project using the command: `supabase init`. [Docs](/docs/guides/cli/local-development#getting-started). -- Link to your Remote Project using the command `supabase link --project-ref your-project-ref`. [Docs](/docs/reference/cli/usage#supabase-link). -- Optional: Setup your environment: Follow [this setup guide](https://deno.land/manual/getting_started/setup_your_environment) to integrate the Deno language server with your editor. - -## Create a function - -Let's create a new Edge Function called `hello-world` inside your project: - -```bash -supabase functions new hello-world -``` - -This creates a function stub in your `supabase` folder at `./functions/hello-world/index.ts`. - -## Deploy to production - -```bash -supabase functions deploy hello-world -``` - -This command bundles your Edge Function from `./functions/hello-world/index.ts` and deploys it to the Supabase platform. -The command outputs a URL to the Supabase Dashboard which you can open to find view more details. Let's open the link to find the execution command. - - - -By default, Edge Functions require a valid JWT in the authorization header. This header is automatically set when invoking your function via a Supabase client library. - -If you want to use Edge Functions to handle webhooks (e.g. [Stripe payment webhooks](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/stripe-webhooks) etc.), you need to pass the `--no-verify-jwt` flag when deploying your function. - - - -## Execute remote functions - -You can execute Edge Functions using curl. Copy the curl command from the Dashboard. It should look like this: - -```bash -curl --request POST 'https://.functions.supabase.co/hello-world' \ - --header 'Authorization: Bearer ANON_KEY' \ - --header 'Content-Type: application/json' \ - --data '{ "name":"Functions" }' -``` - -If you receive an error `Invalid JWT`, find the `ANON_KEY` of your project in the Dashboard under `Settings > API`. - -After invoking your Edge Function you should see the response `{ "message":"Hello Functions!" }`. - -## Debug functions - -You can debug your deployed Edge Functions using the "Functions" section of the Dashboard. There are two types debugging tools available: - -- Invocations: shows the Request and Response for each execution. -- Logs: shows any platform events, including deployments and errors. - -![Function invocations.](/docs/img/guides/functions/function-logs.png) - -## Develop locally - -You can run your Edge Function locally using [`supabase functions serve`](/docs/reference/cli/usage#supabase-functions-serve): - -```bash -supabase start # start the supabase stack -supabase functions serve hello-world # start the Function watcher -``` - -The `functions serve` command has hot-reloading capabilities. It will watch for any changes to your files and restart the Deno server. - -### Invoke functions locally - -While serving your local Function, you can execute it using curl: - -```bash -curl --request POST 'http://localhost:54321/functions/v1/hello-world' \ - --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24ifQ.625_WdcF3KHqz5amU0x2X5WWHP-OEs_4qj0ssLNHzTs' \ - --header 'Content-Type: application/json' \ - --data '{ "name":"Functions" }' -``` - -You should see the response `{ "message":"Hello Functions!" }`. - -
-Implementation details - -- Edge Functions don't serve HTML content (`GET` requests that return `text/html` are rewritten to `text/plain`). -- The `Authorization` header is required. You can use either the `ANON` key, the `SERVICE_ROLE` key, or a logged-in user's JWT. -- The Function is proxied through the local API (`http://localhost:54321`) - -
-
- -If you execute Function with a different payload the response will change.
-Modify the `--data '{"name":"Functions"}'` line to `--data '{"name":"World"}'` and try invoking the command again! - -## Secrets and Environment Variables - -It's common that you will need to use sensitive information or environment-specific variables inside your Edge Functions. You can access these using Deno's built-in handler - -```js -Deno.env.get(MY_SECRET_NAME) -``` - -### Default secrets - -By default, Edge Functions have access to these secrets: - -- `SUPABASE_URL`: The API gateway for your Supabase project. -- `SUPABASE_ANON_KEY`: The `anon` key for your Supabase API. This is safe to use in a browser when you have [Row Level Security](/docs/guides/auth/row-level-security) enabled. -- `SUPABASE_SERVICE_ROLE_KEY`: The `service_role` key for your Supabase API. This is safe to use in Edge Functions, but it should NEVER be used in a browser. This key will bypass [Row Level Security](/docs/guides/auth/row-level-security). -- `SUPABASE_DB_URL`: The URL for your [PostgreSQL database](/docs/guides/database). You can use this to connect directly to your database. - -### Local secrets - -Let's create a local file for storing our secrets, and inside it we can store a secret `MY_NAME`: - -```jsx -echo "MY_NAME=Yoda" >> ./supabase/.env.local -``` - -This creates a new file `./supabase/.env.local` for storing your local development secrets. - - - -Never check your .env files into Git! - - - -Now let's access this environment variable `MY_NAME` inside our Function. Anywhere in your function, add this line: - -```jsx -console.log(Deno.env.get('MY_NAME')) -``` - -Now we can invoke our function locally, by serving it with our new `.env.local` file: - -```bash -supabase functions serve hello-world --env-file ./supabase/.env.local -``` - -When the function starts you should see the name “Yoda” output to the terminal. - -### Production secrets - -Let's create a `.env` for production. In this case we'll just use the same as our local secrets: - -```bash -cp ./supabase/.env.local ./supabase/.env -``` - -This creates a new file `./supabase/.env` for storing your production secrets. - - - -Never check your `.env` files into Git! - - - -Let's push all the secrets from the `.env` file to our remote project using [`supabase secrets set`](/docs/reference/cli/usage#supabase-secrets-set): - -```bash -supabase secrets set --env-file ./supabase/.env - -# You can also set secrets individually using: -supabase secrets set MY_NAME=Chewbacca -``` - -You don't need to re-deploy after setting your secrets. - -To see all the secrets which you have set remotely, use [`supabase secrets list`](/docs/reference/cli/usage#supabase-secrets-list): - -```bash -supabase secrets list -``` - -## Limitations - -- Deno Deploy limitations - - Deno does not support outgoing connections to ports `25`, `465`, and `587`. - - Cannot write to File System -- Edge Functions - - Local development - only one function at a time - - Serving of HTML content is not supported (`GET` requests that return `text/html` will be rewritten to `text/plain`). - -export const Page = ({ children }) => - -export default Page diff --git a/apps/docs/pages/guides/functions/secrets.mdx b/apps/docs/pages/guides/functions/secrets.mdx new file mode 100644 index 00000000000..dd51094d374 --- /dev/null +++ b/apps/docs/pages/guides/functions/secrets.mdx @@ -0,0 +1,89 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'functions-secrets', + title: 'Secrets and Environment Variables', + description: 'Managing secrets and environment variables.', +} + +It's common that you will need to use sensitive information or environment-specific variables inside your Edge Functions. You can access these using Deno's built-in handler + +```js +Deno.env.get(MY_SECRET_NAME) +``` + +### Default secrets + +By default, Edge Functions have access to these secrets: + +- `SUPABASE_URL`: The API gateway for your Supabase project. +- `SUPABASE_ANON_KEY`: The `anon` key for your Supabase API. This is safe to use in a browser when you have [Row Level Security](/docs/guides/auth/row-level-security) enabled. +- `SUPABASE_SERVICE_ROLE_KEY`: The `service_role` key for your Supabase API. This is safe to use in Edge Functions, but it should NEVER be used in a browser. This key will bypass [Row Level Security](/docs/guides/auth/row-level-security). +- `SUPABASE_DB_URL`: The URL for your [PostgreSQL database](/docs/guides/database). You can use this to connect directly to your database. + +### Local secrets + +Let's create a local file for storing our secrets, and inside it we can store a secret `MY_NAME`: + +```jsx +echo "MY_NAME=Yoda" >> ./supabase/.env.local +``` + +This creates a new file `./supabase/.env.local` for storing your local development secrets. + + + +Never check your .env files into Git! + + + +Now let's access this environment variable `MY_NAME` inside our Function. Anywhere in your function, add this line: + +```jsx +console.log(Deno.env.get('MY_NAME')) +``` + +Now we can invoke our function locally, by serving it with our new `.env.local` file: + +```bash +supabase functions serve hello-world --env-file ./supabase/.env.local +``` + +When the function starts you should see the name “Yoda” output to the terminal. + +### Production secrets + +Let's create a `.env` for production. In this case we'll just use the same as our local secrets: + +```bash +cp ./supabase/.env.local ./supabase/.env +``` + +This creates a new file `./supabase/.env` for storing your production secrets. + + + +Never check your `.env` files into Git! + + + +Let's push all the secrets from the `.env` file to our remote project using [`supabase secrets set`](/docs/reference/cli/usage#supabase-secrets-set): + +```bash +supabase secrets set --env-file ./supabase/.env + +# You can also set secrets individually using: +supabase secrets set MY_NAME=Chewbacca +``` + +You don't need to re-deploy after setting your secrets. + +To see all the secrets which you have set remotely, use [`supabase secrets list`](/docs/reference/cli/usage#supabase-secrets-list): + +```bash +supabase secrets list +``` + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/functions/testing-debugging.mdx b/apps/docs/pages/guides/functions/testing-debugging.mdx new file mode 100644 index 00000000000..b0b4cc468dd --- /dev/null +++ b/apps/docs/pages/guides/functions/testing-debugging.mdx @@ -0,0 +1,55 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'functions-testing-debugging', + title: 'Testing and Debugging Functions', + description: 'Test and debug functions locally and in production.', +} + +You can debug your deployed Edge Functions using the "Functions" section of the Dashboard. There are two types debugging tools available: + +- Invocations: shows the Request and Response for each execution. +- Logs: shows any platform events, including deployments and errors. + +![Function invocations.](/docs/img/guides/functions/function-logs.png) + +## Develop locally + +You can run your Edge Function locally using [`supabase functions serve`](/docs/reference/cli/usage#supabase-functions-serve): + +```bash +supabase start # start the supabase stack +supabase functions serve hello-world # start the Function watcher +``` + +The `functions serve` command has hot-reloading capabilities. It will watch for any changes to your files and restart the Deno server. + +### Invoke functions locally + +While serving your local Function, you can execute it using curl: + +```bash +curl --request POST 'http://localhost:54321/functions/v1/hello-world' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24ifQ.625_WdcF3KHqz5amU0x2X5WWHP-OEs_4qj0ssLNHzTs' \ + --header 'Content-Type: application/json' \ + --data '{ "name":"Functions" }' +``` + +You should see the response `{ "message":"Hello Functions!" }`. + +
+Implementation details + +- Edge Functions don't serve HTML content (`GET` requests that return `text/html` are rewritten to `text/plain`). +- The `Authorization` header is required. You can use either the `ANON` key, the `SERVICE_ROLE` key, or a logged-in user's JWT. +- The Function is proxied through the local API (`http://localhost:54321`) + +
+
+ +If you execute Function with a different payload the response will change.
+Modify the `--data '{"name":"Functions"}'` line to `--data '{"name":"World"}'` and try invoking the command again! + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/functions/best-practices.mdx b/apps/docs/pages/guides/functions/tips.mdx similarity index 71% rename from apps/docs/pages/guides/functions/best-practices.mdx rename to apps/docs/pages/guides/functions/tips.mdx index b670cd73a05..8d11169805a 100644 --- a/apps/docs/pages/guides/functions/best-practices.mdx +++ b/apps/docs/pages/guides/functions/tips.mdx @@ -1,9 +1,9 @@ import Layout from '~/layouts/DefaultGuideLayout' export const meta = { - id: 'functions-best-practices', - title: 'Edge Functions Best Practices', - description: 'Globally distributed TypeScript functions.', + id: 'functions-tips', + title: 'Edge Functions Tips', + description: 'Misc Tips for Supabase Edge Functions.', } ## Database Functions vs Edge Functions @@ -35,26 +35,6 @@ We recommend developing “fat functions”. This means that you should develop We recommend using hyphens to name functions because hyphens are the most URL-friendly of all the naming conventions (snake_case, camelCase, PascalCase). -## CORS (Cross-Origin Resource Sharing) - -We recommend adding a check to handle [CORS Preflight](https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request) requests in your edge function to be able to invoke the function from browsers. - -See the [example on GitHub](https://github.com/supabase/supabase/blob/master/examples/edge-functions/supabase/functions/browser-with-cors/index.ts). - -```ts -export const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey', -} - -serve(async (req) => { - if (req.method === 'OPTIONS') { - return new Response('ok', { headers: corsHeaders }) - } - ... -}) -``` - ## Using HTTP Methods Edge Functions supports `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, and `OPTIONS`. A function can be designed to perform different actions based on a request's HTTP method. See the [example on building a RESTful service](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/restful-tasks) to learn how to handle different HTTP methods in your function. From 6a09b850a799e8a14f7fd3fe0d95032fc6608f23 Mon Sep 17 00:00:00 2001 From: thorwebdev Date: Thu, 12 Jan 2023 16:59:39 +0800 Subject: [PATCH 030/261] fix: get started link --- apps/docs/pages/guides/functions.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/pages/guides/functions.mdx b/apps/docs/pages/guides/functions.mdx index 9c970cb6bfe..792972fe630 100644 --- a/apps/docs/pages/guides/functions.mdx +++ b/apps/docs/pages/guides/functions.mdx @@ -18,7 +18,7 @@ Edge Functions are server-side TypeScript functions, distributed globally at the - Edge Functions are globally distributed for low-latency.
-
From d4a38f7feb414facb2db19fed2fbe08c8f7b4aac Mon Sep 17 00:00:00 2001 From: thorwebdev Date: Fri, 13 Jan 2023 10:56:19 +0800 Subject: [PATCH 031/261] fix: functions invoke reference slug. --- spec/common-client-libs-sections.json | 2 +- spec/common-client-libs.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/common-client-libs-sections.json b/spec/common-client-libs-sections.json index 14606af13be..c97a764bc2a 100644 --- a/spec/common-client-libs-sections.json +++ b/spec/common-client-libs-sections.json @@ -730,7 +730,7 @@ { "id": "invoke", "title": "Invoke a function", - "slug": "auth-admin-invoke", + "slug": "functions-invoke", "product": "functions", "type": "function", "libs": ["js", "js_v1", "dart", "dart_v0"] diff --git a/spec/common-client-libs.yml b/spec/common-client-libs.yml index 1d2a2b713c6..d7f01a169c9 100644 --- a/spec/common-client-libs.yml +++ b/spec/common-client-libs.yml @@ -361,7 +361,7 @@ functions: - id: invoke title: invoke() - slug: auth-admin-invoke + slug: functions-invoke product: 'functions' - id: stream From 5037ee95e5e476d93c0077c2921dc4d1efdb2c97 Mon Sep 17 00:00:00 2001 From: thorwebdev Date: Fri, 13 Jan 2023 10:57:35 +0800 Subject: [PATCH 032/261] fix: review changes. --- .../NavigationMenu.constants.ts | 5 ++- apps/docs/pages/guides/functions.mdx | 2 +- apps/docs/pages/guides/functions/cors.mdx | 2 +- .../docs/pages/guides/functions/debugging.mdx | 20 +++++++++ ...ng-debugging.mdx => local-development.mdx} | 45 ++++++++++--------- .../{overview.mdx => quickstart.mdx} | 6 +-- apps/docs/pages/guides/functions/tips.mdx | 11 ++--- 7 files changed, 59 insertions(+), 32 deletions(-) create mode 100644 apps/docs/pages/guides/functions/debugging.mdx rename apps/docs/pages/guides/functions/{testing-debugging.mdx => local-development.mdx} (64%) rename apps/docs/pages/guides/functions/{overview.mdx => quickstart.mdx} (97%) diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index 75d54fb2afb..e46dafbf7df 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -421,12 +421,13 @@ export const functions = { label: 'Edge Functions', url: '/guides/functions', items: [ - { name: 'Overview', url: '/guides/functions/overview', items: [] }, + { name: 'Quickstart', url: '/guides/functions/quickstart', items: [] }, { name: 'Auth', url: '/guides/functions/auth', items: [] }, { name: 'CORS', url: '/guides/functions/cors', items: [] }, { name: 'CI/CD Workflow', url: '/guides/functions/cicd-workflow', items: [] }, + { name: 'Debugging', url: '/guides/functions/debugging', items: [] }, + { name: 'Local Development', url: '/guides/functions/local-development', items: [] }, { name: 'Managing Secrets', url: '/guides/functions/secrets', items: [] }, - { name: 'Testing & Debugging', url: '/guides/functions/testing-debugging', items: [] }, { name: 'Tips', url: '/guides/functions/tips', items: [] }, { name: 'Examples', diff --git a/apps/docs/pages/guides/functions.mdx b/apps/docs/pages/guides/functions.mdx index 792972fe630..9c970cb6bfe 100644 --- a/apps/docs/pages/guides/functions.mdx +++ b/apps/docs/pages/guides/functions.mdx @@ -18,7 +18,7 @@ Edge Functions are server-side TypeScript functions, distributed globally at the - Edge Functions are globally distributed for low-latency.
-
diff --git a/apps/docs/pages/guides/functions/cors.mdx b/apps/docs/pages/guides/functions/cors.mdx index fca25fa4933..e991ad181a9 100644 --- a/apps/docs/pages/guides/functions/cors.mdx +++ b/apps/docs/pages/guides/functions/cors.mdx @@ -12,7 +12,7 @@ See the [example on GitHub](https://github.com/supabase/supabase/blob/master/exa ### Recommended setup -We recommend adding a `corst.ts` file within a `_shared` folder which makes it easy to reuse the CORS headers across functions: +We recommend adding a `corst.ts` file within a [`_shared` folder](/docs/guides/functions/tips#organizing-your-edge-functions) which makes it easy to reuse the CORS headers across functions: ```ts cors.ts export const corsHeaders = { diff --git a/apps/docs/pages/guides/functions/debugging.mdx b/apps/docs/pages/guides/functions/debugging.mdx new file mode 100644 index 00000000000..62c93035b2a --- /dev/null +++ b/apps/docs/pages/guides/functions/debugging.mdx @@ -0,0 +1,20 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'functions-debugging', + title: 'Debugging Functions', + description: 'Debug functions in production.', +} + +You can debug your deployed Edge Functions using the ["Functions" section](https://app.supabase.com/project/_/functions) of the Dashboard. There are two debugging tools available: + +- Invocations: shows the Request and Response for each execution. +- Logs: shows any platform events, including deployments and errors. + +![Function invocations.](/docs/img/guides/functions/function-logs.png) + +When [developing locally](/docs/guides/functions/local-development) you will see error messages and console log statements printed to your local terminal window. + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/functions/testing-debugging.mdx b/apps/docs/pages/guides/functions/local-development.mdx similarity index 64% rename from apps/docs/pages/guides/functions/testing-debugging.mdx rename to apps/docs/pages/guides/functions/local-development.mdx index b0b4cc468dd..01bd0f88a4d 100644 --- a/apps/docs/pages/guides/functions/testing-debugging.mdx +++ b/apps/docs/pages/guides/functions/local-development.mdx @@ -1,20 +1,11 @@ import Layout from '~/layouts/DefaultGuideLayout' export const meta = { - id: 'functions-testing-debugging', - title: 'Testing and Debugging Functions', - description: 'Test and debug functions locally and in production.', + id: 'functions-local-development', + title: 'Local Development', + description: 'Run your functions locally.', } -You can debug your deployed Edge Functions using the "Functions" section of the Dashboard. There are two types debugging tools available: - -- Invocations: shows the Request and Response for each execution. -- Logs: shows any platform events, including deployments and errors. - -![Function invocations.](/docs/img/guides/functions/function-logs.png) - -## Develop locally - You can run your Edge Function locally using [`supabase functions serve`](/docs/reference/cli/usage#supabase-functions-serve): ```bash @@ -26,7 +17,7 @@ The `functions serve` command has hot-reloading capabilities. It will watch for ### Invoke functions locally -While serving your local Function, you can execute it using curl: +While serving your local Function, you can invoke it using curl: ```bash curl --request POST 'http://localhost:54321/functions/v1/hello-world' \ @@ -35,20 +26,34 @@ curl --request POST 'http://localhost:54321/functions/v1/hello-world' \ --data '{ "name":"Functions" }' ``` +or using one of the [client libraries](/docs#reference-documentation), e.g. using [supabase-js](/docs/reference/javascript/functions-invoke): + +```js +import { createClient } from '@supabase/supabase-js' + +// Use the credentials outputted in your terminal when running `supabase start` +const supabase = createClient( + 'http://localhost:54321', + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0' +) + +const { data, error } = await supabase.functions.invoke('hello-world', { + body: { name: 'Functions' }, +}) +``` + You should see the response `{ "message":"Hello Functions!" }`. -
-Implementation details +If you execute Function with a different payload the response will change.
+Modify the `--data '{"name":"Functions"}'` line to `--data '{"name":"World"}'` and try invoking the command again! + + - Edge Functions don't serve HTML content (`GET` requests that return `text/html` are rewritten to `text/plain`). - The `Authorization` header is required. You can use either the `ANON` key, the `SERVICE_ROLE` key, or a logged-in user's JWT. - The Function is proxied through the local API (`http://localhost:54321`) -
-
- -If you execute Function with a different payload the response will change.
-Modify the `--data '{"name":"Functions"}'` line to `--data '{"name":"World"}'` and try invoking the command again! + export const Page = ({ children }) => diff --git a/apps/docs/pages/guides/functions/overview.mdx b/apps/docs/pages/guides/functions/quickstart.mdx similarity index 97% rename from apps/docs/pages/guides/functions/overview.mdx rename to apps/docs/pages/guides/functions/quickstart.mdx index 49935c280bf..7b489f2009e 100644 --- a/apps/docs/pages/guides/functions/overview.mdx +++ b/apps/docs/pages/guides/functions/quickstart.mdx @@ -1,10 +1,10 @@ import Layout from '~/layouts/DefaultGuideLayout' export const meta = { - id: 'functions-overview', - title: 'Edge Functions Overview', + id: 'functions-quickstart', + title: 'Edge Functions Quickstart', description: 'Globally distributed TypeScript Functions.', - sidebar_label: 'Overview', + sidebar_label: 'Quickstart', } Learn how to build an Edge Function locally and deploy it to the Supabase Platform in less than 7 minutes. diff --git a/apps/docs/pages/guides/functions/tips.mdx b/apps/docs/pages/guides/functions/tips.mdx index 8d11169805a..9c96998db4a 100644 --- a/apps/docs/pages/guides/functions/tips.mdx +++ b/apps/docs/pages/guides/functions/tips.mdx @@ -21,11 +21,12 @@ We recommend developing “fat functions”. This means that you should develop └── supabase ├── functions │ ├── _shared - │ | ├── supabaseAdmin.ts # Supabase client with SERVICE_ROLE key - │ │ └── supabaseClient.ts # Supabase client with ANON key - │ ├── function-one # use hyphens to name functions - │ │ └── index.ts - │ └── function-two + │ │ ├── supabaseAdmin.ts # Supabase client with SERVICE_ROLE key + │ │ └── supabaseClient.ts # Supabase client with ANON key + │ │ └── cors.ts # Reusable CORS headers + │ ├── function-one # use hyphens to name functions + │ │ └── index.ts + │ └── function-two │ └── index.ts ├── migrations └── config.toml From 17aa997ffe60dc85e21cc1874202678a7e4d32d1 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Fri, 13 Jan 2023 14:04:06 +0800 Subject: [PATCH 033/261] Updates based as per PR comments --- studio/components/interfaces/Auth/Policies/Policies.tsx | 5 ++--- .../interfaces/Auth/Policies/PolicyTableRow/index.tsx | 8 ++++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/studio/components/interfaces/Auth/Policies/Policies.tsx b/studio/components/interfaces/Auth/Policies/Policies.tsx index 81669a1a6f5..550bd59f740 100644 --- a/studio/components/interfaces/Auth/Policies/Policies.tsx +++ b/studio/components/interfaces/Auth/Policies/Policies.tsx @@ -137,7 +137,7 @@ const Policies: FC = ({ tables, hasTables, isLocked }) => {
= ({ tables, hasTables, isLocked }) => { } />

- A table within this schema is required before you can create a Row-Level Security - policy. + Create a table in this schema first before creating a policy.

diff --git a/studio/components/interfaces/Auth/Policies/PolicyTableRow/index.tsx b/studio/components/interfaces/Auth/Policies/PolicyTableRow/index.tsx index 5caf05d60fe..b9eafb7e119 100644 --- a/studio/components/interfaces/Auth/Policies/PolicyTableRow/index.tsx +++ b/studio/components/interfaces/Auth/Policies/PolicyTableRow/index.tsx @@ -41,9 +41,13 @@ const PolicyTableRow: FC = ({ {policies.length === 0 && (

No policies created yet

- {table.rls_enabled && ( + {table.rls_enabled ? (

- As RLS has been enabled, all normal access to this table must be allowed by a policy. + RLS is enabled - create a policy to allow access to this table. +

+ ) : ( +

+ Warning: RLS is disabled - anonymous access is allowed to this table

)}
From 8dc866897e870f76040bfcab799c56c3d4842fce Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 11 Jan 2023 15:57:21 +0800 Subject: [PATCH 034/261] Support loadBySchema in meta store and loading tables in table editor based on selected schema --- .../TableEditorLayout/TableEditorLayout.tsx | 7 +- .../TableEditorLayout/TableEditorMenu.tsx | 300 +++++++++--------- studio/stores/common/PostgresMetaInterface.ts | 28 ++ studio/stores/pgmeta/ExtensionsStore.ts | 5 + studio/stores/pgmeta/ForeignTableStore.ts | 5 + studio/stores/pgmeta/MetaStore.ts | 2 +- studio/stores/pgmeta/PublicationStore.ts | 5 + studio/stores/pgmeta/RolesStore.ts | 5 + studio/stores/pgmeta/SchemaStore.ts | 5 + 9 files changed, 215 insertions(+), 147 deletions(-) diff --git a/studio/components/layouts/TableEditorLayout/TableEditorLayout.tsx b/studio/components/layouts/TableEditorLayout/TableEditorLayout.tsx index af9ac5edeec..2f44c5c7870 100644 --- a/studio/components/layouts/TableEditorLayout/TableEditorLayout.tsx +++ b/studio/components/layouts/TableEditorLayout/TableEditorLayout.tsx @@ -41,7 +41,6 @@ const TableEditorLayout: FC = ({ useEffect(() => { if (ui.selectedProject?.ref) { meta.schemas.load() - meta.tables.load() meta.types.load() meta.policies.load() meta.publications.load() @@ -50,6 +49,12 @@ const TableEditorLayout: FC = ({ } }, [ui.selectedProject?.ref]) + useEffect(() => { + if (selectedSchema && ui.selectedProject?.ref) { + meta.tables.loadBySchema(selectedSchema) + } + }, [ui.selectedProject?.ref, selectedSchema]) + useEffect(() => { if (isEnabled) { vault.load() diff --git a/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx b/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx index 1bc51c408ae..d016fbc6816 100644 --- a/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx +++ b/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx @@ -58,6 +58,9 @@ const TableEditorMenu: FC = ({ ) // @ts-ignore + const isFetchingTables = + tables.filter((t) => t.schema === selectedSchema).length === 0 && meta.tables.isLoading + const schema = schemas.find((schema) => schema.name === selectedSchema) const canCreateTables = checkPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables') @@ -79,10 +82,12 @@ const TableEditorMenu: FC = ({ }, [selectedSchema]) const refreshTables = async () => { - setIsRefreshing(true) - await meta.tables.load() - if (selectedSchema) await meta.schemas.loadViews(selectedSchema) - setIsRefreshing(false) + if (selectedSchema) { + setIsRefreshing(true) + await meta.tables.loadBySchema(selectedSchema) + await meta.schemas.loadViews(selectedSchema) + setIsRefreshing(false) + } } const filteredTables = @@ -229,147 +234,159 @@ const TableEditorMenu: FC = ({
-
- {/* List of tables belonging to selected schema */} - {filteredTables.length > 0 && ( - - -
-
-

Tables

-

- ({filteredTables.length}) -

+ {isFetchingTables ? ( +
+ +

Loading tables...

+
+ ) : searchText.length === 0 && filteredTables.length === 0 && filteredViews.length === 0 ? ( +
+

No tables available

+

This schema has no tables available yet

+
+ ) : ( +
+ {/* List of tables belonging to selected schema */} + {filteredTables.length > 0 && ( + + +
+
+

Tables

+

+ ({filteredTables.length}) +

+
+
- -
- - } - /> + + } + /> -
- {filteredTables.map((table) => { - const isActive = Number(id) === table.id +
+ {filteredTables.map((table) => { + const isActive = Number(id) === table.id + return ( + } + onClick={() => onEditTable(table)} + > + Edit Table + , + } + onClick={() => onDuplicateTable(table)} + > + Duplicate Table + , + , + } + onClick={() => onDeleteTable(table)} + > + Delete Table + , + ]} + > +
+ +
+ + ) + } + /> + ) + })} +
+
+ )} + + {/* List of views belonging to selected schema */} + {filteredViews.length > 0 && ( + + +

Views

+

({filteredViews.length})

+
+ } + /> + + {filteredViews.map((view: SchemaView) => { + const viewId = Base64.encode(JSON.stringify(view)) + const isActive = id === viewId return ( - } - onClick={() => onEditTable(table)} - > - Edit Table - , - } - onClick={() => onDuplicateTable(table)} - > - Duplicate Table - , - , - } - onClick={() => onDeleteTable(table)} - > - Delete Table - , - ]} - > -
- -
- - ) - } - /> + + + +
+

{view.name}

+
+
+
+ ) })} -
- - )} + + )} - {/* List of views belonging to selected schema */} - {filteredViews.length > 0 && ( - - -

Views

-

({filteredViews.length})

-
- } - /> + {/* List of foreign tables belonging to selected schema */} + {filteredForeignTables.length > 0 && ( + + +

Foreign Tables

+

+ ({filteredForeignTables.length}) +

+ + } + /> - {filteredViews.map((view: SchemaView) => { - const viewId = Base64.encode(JSON.stringify(view)) - const isActive = id === viewId - return ( - - - -
-

{view.name}

-
-
-
- - ) - })} -
- )} - - {/* List of foreign tables belonging to selected schema */} - {filteredForeignTables.length > 0 && ( - - -

Foreign Tables

-

- ({filteredForeignTables.length}) -

- - } - /> - - {filteredForeignTables.map((table: Partial) => { - const isActive = Number(id) === table.id - return ( - - - -
-

{table.name}

-
-
-
- - ) - })} -
- )} - + {filteredForeignTables.map((table: Partial) => { + const isActive = Number(id) === table.id + return ( + + + +
+

{table.name}

+
+
+
+ + ) + })} + + )} + + )} {searchText.length > 0 && filteredTables.length === 0 && filteredViews.length === 0 && (
@@ -379,13 +396,6 @@ const TableEditorMenu: FC = ({

)} - - {searchText.length === 0 && filteredTables.length === 0 && filteredViews.length === 0 && ( -
-

No tables available

-

This schema has no tables available yet

-
- )} ) } diff --git a/studio/stores/common/PostgresMetaInterface.ts b/studio/stores/common/PostgresMetaInterface.ts index 5345022f277..07445b75e46 100644 --- a/studio/stores/common/PostgresMetaInterface.ts +++ b/studio/stores/common/PostgresMetaInterface.ts @@ -15,6 +15,7 @@ export interface IPostgresMetaInterface { isInitialized: boolean load: () => void + loadBySchema: (schema: string) => Promise create: (payload: any) => Promise update: (id: number | string, updates: any) => Promise del: (id: number | string, cascade?: boolean) => Promise @@ -116,6 +117,33 @@ export default class PostgresMetaInterface implements IPostgresMetaInterface< } } + async loadBySchema(schema: string) { + let { LOADING, ERROR, LOADED } = this.STATES + try { + this.setError(null) + this.setState(LOADING) + + const url = this.url.includes('?') + ? `${this.url}&included_schemas=${schema}` + : `${this.url}?included_schemas=${schema}` + const response = await get(url, { headers: this.headers }) + if (response.error) throw response.error + + const data = response as T[] + const formattedData = keyBy(data, this.identifier) + + this.data = { ...this.data, ...formattedData } + this.setState(LOADED) + + return data + } catch (error: any) { + console.error('Error in loadBySchema:', error.message) + this.setError(error) + this.setState(ERROR) + return { error } + } + } + initialDataArray(value: T[]) { if (this.state === this.STATES.INITIAL) { this.data = keyBy(value, this.identifier) diff --git a/studio/stores/pgmeta/ExtensionsStore.ts b/studio/stores/pgmeta/ExtensionsStore.ts index 9df35a1ab4e..fbb26f7d5e8 100644 --- a/studio/stores/pgmeta/ExtensionsStore.ts +++ b/studio/stores/pgmeta/ExtensionsStore.ts @@ -13,4 +13,9 @@ export default class ExtensionsStore extends PostgresMetaInterface { ) { super(rootStore, dataUrl, headers, options) } + + // loadBySchema is not supported in this store + async loadBySchema(schema: string) { + return [] + } } diff --git a/studio/stores/pgmeta/RolesStore.ts b/studio/stores/pgmeta/RolesStore.ts index 5bd5d59fc1b..b4bb4d0c055 100644 --- a/studio/stores/pgmeta/RolesStore.ts +++ b/studio/stores/pgmeta/RolesStore.ts @@ -28,4 +28,9 @@ export default class RolesStore extends PostgresMetaInterface { 'pg_read_all_data', 'pg_write_all_data', ] + + // loadBySchema is not supported in this store + async loadBySchema(schema: string) { + return [] + } } diff --git a/studio/stores/pgmeta/SchemaStore.ts b/studio/stores/pgmeta/SchemaStore.ts index 1fd1ff89ac5..ffa9554e200 100644 --- a/studio/stores/pgmeta/SchemaStore.ts +++ b/studio/stores/pgmeta/SchemaStore.ts @@ -45,4 +45,9 @@ export default class SchemaStore extends PostgresMetaInterface { this.views = views return views } + + // loadBySchema is not supported in this store + async loadBySchema(schema: string) { + return [] + } } From e34989cbf2d66b0c763d7970091af08b9244de5a Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 11 Jan 2023 17:00:21 +0800 Subject: [PATCH 035/261] Add ViewStore --- .../SidePanelEditor/SidePanelEditor.tsx | 4 +-- .../TableGridEditor/TableGridEditor.tsx | 4 +-- .../TableEditorLayout/TableEditorLayout.tsx | 1 + .../TableEditorLayout.types.ts | 4 --- .../TableEditorLayout/TableEditorMenu.tsx | 31 ++++++------------- studio/pages/project/[ref]/editor/[id].tsx | 4 +-- studio/pages/project/[ref]/editor/index.tsx | 2 +- studio/stores/pgmeta/MetaStore.ts | 14 +++++++-- studio/stores/pgmeta/SchemaStore.ts | 31 +------------------ studio/stores/pgmeta/ViewStore.ts | 16 ++++++++++ studio/stores/project/VaultStore.ts | 8 ++--- studio/types/ui.ts | 6 ++++ 12 files changed, 55 insertions(+), 70 deletions(-) delete mode 100644 studio/components/layouts/TableEditorLayout/TableEditorLayout.types.ts create mode 100644 studio/stores/pgmeta/ViewStore.ts diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx index 7e410f7d2ab..1c4f3fa1a55 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx @@ -144,8 +144,8 @@ const SidePanelEditor: FC = ({ closePanel() } - if (configuration.isEncrypted) { - await meta.schemas.loadViews(selectedTable?.schema ?? '') + if (configuration.isEncrypted && selectedTable?.schema) { + await meta.views.loadBySchema(selectedTable.schema) } resolve() diff --git a/studio/components/interfaces/TableGridEditor/TableGridEditor.tsx b/studio/components/interfaces/TableGridEditor/TableGridEditor.tsx index 58e2adba72d..36798d285ea 100644 --- a/studio/components/interfaces/TableGridEditor/TableGridEditor.tsx +++ b/studio/components/interfaces/TableGridEditor/TableGridEditor.tsx @@ -5,11 +5,11 @@ import { find, isUndefined } from 'lodash' import { PostgresColumn, PostgresTable } from '@supabase/postgres-meta' import { PermissionAction } from '@supabase/shared-types/out/constants' +import { SchemaView } from 'types' import { checkPermissions, useFlag, useStore } from 'hooks' import GridHeaderActions from './GridHeaderActions' import NotFoundState from './NotFoundState' import SidePanelEditor from './SidePanelEditor' -import { SchemaView } from 'components/layouts/TableEditorLayout/TableEditorLayout.types' import { Dictionary, parseSupaTable, SupabaseGrid, SupabaseGridRef } from 'components/grid' interface Props { @@ -81,7 +81,7 @@ const TableGridEditor: FC = ({ // @ts-ignore const schema = meta.schemas.list().find((schema) => schema.name === selectedSchema) - const isViewSelected = Object.keys(selectedTable).length === 2 + const isViewSelected = !Object.keys(selectedTable).includes('rls_enabled') const isForeignTableSelected = meta.foreignTables.byId(selectedTable.id) !== undefined const isLocked = meta.excludedSchemas.includes(schema?.name ?? '') const canUpdateTables = checkPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables') diff --git a/studio/components/layouts/TableEditorLayout/TableEditorLayout.tsx b/studio/components/layouts/TableEditorLayout/TableEditorLayout.tsx index 2f44c5c7870..9791b01dfa1 100644 --- a/studio/components/layouts/TableEditorLayout/TableEditorLayout.tsx +++ b/studio/components/layouts/TableEditorLayout/TableEditorLayout.tsx @@ -52,6 +52,7 @@ const TableEditorLayout: FC = ({ useEffect(() => { if (selectedSchema && ui.selectedProject?.ref) { meta.tables.loadBySchema(selectedSchema) + meta.views.loadBySchema(selectedSchema) } }, [ui.selectedProject?.ref, selectedSchema]) diff --git a/studio/components/layouts/TableEditorLayout/TableEditorLayout.types.ts b/studio/components/layouts/TableEditorLayout/TableEditorLayout.types.ts deleted file mode 100644 index 636a06056a8..00000000000 --- a/studio/components/layouts/TableEditorLayout/TableEditorLayout.types.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface SchemaView { - schema: string - name: string -} diff --git a/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx b/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx index d016fbc6816..a832e6643f4 100644 --- a/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx +++ b/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx @@ -1,4 +1,4 @@ -import { FC, useEffect, useState } from 'react' +import { FC, useState } from 'react' import { useRouter } from 'next/router' import Link from 'next/link' import { partition } from 'lodash' @@ -23,8 +23,8 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' import { PostgresSchema, PostgresTable } from '@supabase/postgres-meta' import Base64 from 'lib/base64' +import { SchemaView } from 'types' import { checkPermissions, useStore } from 'hooks' -import { SchemaView } from './TableEditorLayout.types' import ProductMenuItem from 'components/ui/ProductMenu/ProductMenuItem' interface Props { @@ -53,12 +53,13 @@ const TableEditorMenu: FC = ({ const tables: PostgresTable[] = meta.tables.list( (table: PostgresTable) => table.schema === selectedSchema ) + const views: SchemaView[] = meta.views.list((view: SchemaView) => view.schema === selectedSchema) const foreignTables: Partial[] = meta.foreignTables.list( - (table: PostgresTable) => table.schema === selectedSchema + (table: Partial) => table.schema === selectedSchema ) - // @ts-ignore const isFetchingTables = + // @ts-ignore tables.filter((t) => t.schema === selectedSchema).length === 0 && meta.tables.isLoading const schema = schemas.find((schema) => schema.name === selectedSchema) @@ -67,25 +68,11 @@ const TableEditorMenu: FC = ({ const [searchText, setSearchText] = useState('') const [isRefreshing, setIsRefreshing] = useState(false) - // We may need to shift this to the schema store and do something like meta.schema.loadViews() - // I don't need we need a separate store for views - useEffect(() => { - let cancel = false - const fetchViews = async (selectedSchema: string) => { - await meta.schemas.loadViews(selectedSchema) - } - if (selectedSchema) fetchViews(selectedSchema) - - return () => { - cancel = true - } - }, [selectedSchema]) - const refreshTables = async () => { if (selectedSchema) { setIsRefreshing(true) await meta.tables.loadBySchema(selectedSchema) - await meta.schemas.loadViews(selectedSchema) + await meta.views.loadBySchema(selectedSchema) setIsRefreshing(false) } } @@ -98,9 +85,9 @@ const TableEditorMenu: FC = ({ const filteredViews = searchText.length === 0 - ? meta.schemas.views || [] - : (meta.schemas.views || []).filter((view) => - view.name.toLowerCase().includes(searchText.toLowerCase()) + ? views || [] + : (views || []).filter((view: Partial) => + (view?.name ?? '').toLowerCase().includes(searchText.toLowerCase()) ) const filteredForeignTables = diff --git a/studio/pages/project/[ref]/editor/[id].tsx b/studio/pages/project/[ref]/editor/[id].tsx index e273343bcd9..2b191faf79a 100644 --- a/studio/pages/project/[ref]/editor/[id].tsx +++ b/studio/pages/project/[ref]/editor/[id].tsx @@ -140,7 +140,7 @@ const TableEditorPage: NextPage = () => { }) await meta.tables.loadById(selectedColumnToDelete!.table_id) - if (selectedSchema) await meta.schemas.loadViews(selectedSchema) + if (selectedSchema) await meta.views.loadBySchema(selectedSchema) } catch (error: any) { ui.setNotification({ category: 'error', @@ -171,7 +171,7 @@ const TableEditorPage: NextPage = () => { category: 'success', message: `Successfully deleted table "${selectedTableToDelete.name}"`, }) - if (selectedSchema) await meta.schemas.loadViews(selectedSchema) + if (selectedSchema) await meta.views.loadBySchema(selectedSchema) } catch (error: any) { ui.setNotification({ error, diff --git a/studio/pages/project/[ref]/editor/index.tsx b/studio/pages/project/[ref]/editor/index.tsx index 7532dd7fc3c..ee5e4ffc1cf 100644 --- a/studio/pages/project/[ref]/editor/index.tsx +++ b/studio/pages/project/[ref]/editor/index.tsx @@ -55,7 +55,7 @@ const Editor: NextPage = () => { category: 'success', message: `Successfully deleted ${selectedTableToDelete!.name}`, }) - await meta.schemas.loadViews(selectedSchema) + await meta.views.loadBySchema(selectedSchema) } catch (error: any) { ui.setNotification({ category: 'error', diff --git a/studio/stores/pgmeta/MetaStore.ts b/studio/stores/pgmeta/MetaStore.ts index 282732007a4..dc599292ab5 100644 --- a/studio/stores/pgmeta/MetaStore.ts +++ b/studio/stores/pgmeta/MetaStore.ts @@ -8,16 +8,17 @@ import { PostgresTable, PostgresRelationship, PostgresPrimaryKey, + PostgresSchema, } from '@supabase/postgres-meta' import { IS_PLATFORM, API_URL } from 'lib/constants' import { post } from 'lib/common/fetch' import { timeout } from 'lib/helpers' -import { ResponseError } from 'types' +import { ResponseError, SchemaView } from 'types' import { IRootStore } from '../RootStore' import ColumnStore from './ColumnStore' -import SchemaStore, { ISchemaStore } from './SchemaStore' +import SchemaStore from './SchemaStore' import TableStore, { ITableStore } from './TableStore' import OpenApiStore, { IOpenApiStore } from './OpenApiStore' import { IPostgresMetaInterface } from '../common/PostgresMetaInterface' @@ -41,6 +42,7 @@ import HooksStore from './HooksStore' import ExtensionsStore from './ExtensionsStore' import TypesStore from './TypesStore' import ForeignTableStore from './ForeignTableStore' +import ViewStore from './ViewStore' const BATCH_SIZE = 1000 const CHUNK_SIZE = 1024 * 1024 * 0.1 // 0.1MB @@ -51,7 +53,8 @@ export interface IMetaStore { openApi: IOpenApiStore tables: ITableStore columns: IPostgresMetaInterface - schemas: ISchemaStore + schemas: IPostgresMetaInterface + views: IPostgresMetaInterface foreignTables: IPostgresMetaInterface> hooks: IPostgresMetaInterface @@ -132,6 +135,7 @@ export default class MetaStore implements IMetaStore { tables: TableStore columns: ColumnStore schemas: SchemaStore + views: ViewStore foreignTables: ForeignTableStore hooks: HooksStore @@ -184,6 +188,7 @@ export default class MetaStore implements IMetaStore { this.tables = new TableStore(this.rootStore, `${this.baseUrl}/tables`, this.headers) this.columns = new ColumnStore(this.rootStore, `${this.baseUrl}/columns`, this.headers) this.schemas = new SchemaStore(this.rootStore, `${this.baseUrl}/schemas`, this.headers) + this.views = new ViewStore(this.rootStore, `${this.baseUrl}/views`, this.headers) this.foreignTables = new ForeignTableStore( this.rootStore, `${this.baseUrl}/foreign-tables`, @@ -903,6 +908,9 @@ export default class MetaStore implements IMetaStore { this.schemas.setUrl(`${this.baseUrl}/schemas`) this.schemas.setHeaders(this.headers) + this.views.setUrl(`${this.baseUrl}/views`) + this.views.setHeaders(this.headers) + this.foreignTables.setUrl(`${this.baseUrl}/foreign-tables`) this.foreignTables.setHeaders(this.headers) diff --git a/studio/stores/pgmeta/SchemaStore.ts b/studio/stores/pgmeta/SchemaStore.ts index ffa9554e200..09236d13166 100644 --- a/studio/stores/pgmeta/SchemaStore.ts +++ b/studio/stores/pgmeta/SchemaStore.ts @@ -1,17 +1,8 @@ -import { observable, makeObservable } from 'mobx' import { PostgresSchema } from '@supabase/postgres-meta' - -import PostgresMetaInterface, { IPostgresMetaInterface } from '../common/PostgresMetaInterface' +import PostgresMetaInterface from '../common/PostgresMetaInterface' import { IRootStore } from '../RootStore' -import { SchemaView, ResponseError } from 'types' -export interface ISchemaStore extends IPostgresMetaInterface { - views: SchemaView[] - loadViews: (schema: string) => Promise -} export default class SchemaStore extends PostgresMetaInterface { - views: SchemaView[] = [] - constructor( rootStore: IRootStore, dataUrl: string, @@ -21,10 +12,6 @@ export default class SchemaStore extends PostgresMetaInterface { options?: { identifier: string } ) { super(rootStore, dataUrl, headers, options) - makeObservable(this, { - views: observable, - }) - this.views = [] } // Dashboard to hide schemas with pg_ prefix @@ -34,20 +21,4 @@ export default class SchemaStore extends PostgresMetaInterface { return super.list(schemasFilter) } - - async loadViews(schema: string) { - const query = ` - select schemaname as schema, viewname as name from pg_catalog.pg_views - where schemaname = '${schema}' - order by schemaname, viewname; - ` - const views = await this.rootStore.meta.query(query) - this.views = views - return views - } - - // loadBySchema is not supported in this store - async loadBySchema(schema: string) { - return [] - } } diff --git a/studio/stores/pgmeta/ViewStore.ts b/studio/stores/pgmeta/ViewStore.ts new file mode 100644 index 00000000000..63b74701e42 --- /dev/null +++ b/studio/stores/pgmeta/ViewStore.ts @@ -0,0 +1,16 @@ +import { SchemaView } from 'types' +import PostgresMetaInterface from '../common/PostgresMetaInterface' +import { IRootStore } from '../RootStore' + +export default class ViewStore extends PostgresMetaInterface { + constructor( + rootStore: IRootStore, + dataUrl: string, + headers?: { + [prop: string]: any + }, + options?: { identifier: string } + ) { + super(rootStore, dataUrl, headers, options) + } +} diff --git a/studio/stores/project/VaultStore.ts b/studio/stores/project/VaultStore.ts index a470a864470..166f59bf7fe 100644 --- a/studio/stores/project/VaultStore.ts +++ b/studio/stores/project/VaultStore.ts @@ -1,7 +1,7 @@ import { PostgresColumn } from '@supabase/postgres-meta' import { Query } from 'components/grid' import { makeAutoObservable } from 'mobx' -import { VaultSecret } from 'types' +import { SchemaView, VaultSecret } from 'types' import { IRootStore } from '../RootStore' export interface IVaultStore { @@ -211,9 +211,9 @@ export default class VaultStore implements IVaultStore { async listEncryptedColumns(schema: string, table: string) { if (!table) return [] - await this.rootStore.meta.schemas.loadViews(schema) - const decryptedView = this.rootStore.meta.schemas.views.find( - (view) => view.name === `decrypted_${table}` + await this.rootStore.meta.views.loadBySchema(schema) + const decryptedView = this.rootStore.meta.views.find( + (view: SchemaView) => view.name === `decrypted_${table}` ) if (!decryptedView) return [] diff --git a/studio/types/ui.ts b/studio/types/ui.ts index f8d5308ac25..edeb42ec9ac 100644 --- a/studio/types/ui.ts +++ b/studio/types/ui.ts @@ -1,3 +1,5 @@ +import { PostgresColumn } from '@supabase/postgres-meta' + export interface Notification { category: 'info' | 'error' | 'success' | 'loading' message: string // Readable message for users to understand @@ -33,6 +35,10 @@ export interface VaultSecret { } export interface SchemaView { + id: number name: string schema: string + is_updatable: boolean + comment?: string + columns: PostgresColumn[] } From 633f296eeeef3fe9798be9c7276d5fc6313e54b7 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 11 Jan 2023 22:23:17 +0800 Subject: [PATCH 036/261] Fix tables not loading in table editor on [id] route after new refactor --- .../TableEditorLayout/TableEditorLayout.tsx | 25 ++++++++++++++++--- .../TableEditorLayout/TableEditorMenu.tsx | 19 +++++++------- studio/pages/project/[ref]/editor/[id].tsx | 17 +++++++------ 3 files changed, 40 insertions(+), 21 deletions(-) diff --git a/studio/components/layouts/TableEditorLayout/TableEditorLayout.tsx b/studio/components/layouts/TableEditorLayout/TableEditorLayout.tsx index 9791b01dfa1..19178db155f 100644 --- a/studio/components/layouts/TableEditorLayout/TableEditorLayout.tsx +++ b/studio/components/layouts/TableEditorLayout/TableEditorLayout.tsx @@ -4,7 +4,7 @@ import { observer } from 'mobx-react-lite' import { PostgresTable } from '@supabase/postgres-meta' import { PermissionAction } from '@supabase/shared-types/out/constants' -import { checkPermissions, useStore } from 'hooks' +import { checkPermissions, useParams, useStore } from 'hooks' import Error from 'components/ui/Error' import ProjectLayout from '../ProjectLayout/ProjectLayout' import TableEditorMenu from './TableEditorMenu' @@ -30,13 +30,14 @@ const TableEditorLayout: FC = ({ children, }) => { const { vault, meta, ui } = useStore() + const { id, type } = useParams() const { isInitialized, isLoading, error } = meta.tables const [loaded, setLoaded] = useState(isInitialized) const canReadTables = checkPermissions(PermissionAction.TENANT_SQL_ADMIN_READ, 'tables') const vaultExtension = meta.extensions.byId('supabase_vault') - const isEnabled = vaultExtension !== undefined && vaultExtension?.installed_version !== null + const isVaultEnabled = vaultExtension !== undefined && vaultExtension?.installed_version !== null useEffect(() => { if (ui.selectedProject?.ref) { @@ -45,6 +46,12 @@ const TableEditorLayout: FC = ({ meta.policies.load() meta.publications.load() meta.extensions.load() + + // [Joshen] pg-meta doesn't support loading views nor foreign tables by a specific ID yet + // Separately, Alaister and I chatted that perhaps we leverage on pg-catalog's pg-class + // table directly, so we can fetch the schema of tables/views/foreign-tables in one call + // rather than trying to discern if the ID is a view, or foreign table (refer to below) + meta.views.load() meta.foreignTables.load() } }, [ui.selectedProject?.ref]) @@ -57,10 +64,20 @@ const TableEditorLayout: FC = ({ }, [ui.selectedProject?.ref, selectedSchema]) useEffect(() => { - if (isEnabled) { + if (ui.selectedProject?.ref && id) { + // [Joshen] This is a little silly, but because fetching tables/views/foreign-tables + // are all through different endpoints, we need to discern them + if (type !== 'view' && type !== 'foreign') { + meta.tables.loadById(Number(id)) + } + } + }, [ui.selectedProject?.ref, id]) + + useEffect(() => { + if (isVaultEnabled) { vault.load() } - }, [ui.selectedProject?.ref, isEnabled]) + }, [ui.selectedProject?.ref, isVaultEnabled]) useEffect(() => { let cancel = false diff --git a/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx b/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx index a832e6643f4..be30f4d81f6 100644 --- a/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx +++ b/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx @@ -1,5 +1,4 @@ import { FC, useState } from 'react' -import { useRouter } from 'next/router' import Link from 'next/link' import { partition } from 'lodash' import { observer } from 'mobx-react-lite' @@ -22,9 +21,8 @@ import * as Tooltip from '@radix-ui/react-tooltip' import { PermissionAction } from '@supabase/shared-types/out/constants' import { PostgresSchema, PostgresTable } from '@supabase/postgres-meta' -import Base64 from 'lib/base64' import { SchemaView } from 'types' -import { checkPermissions, useStore } from 'hooks' +import { checkPermissions, useParams, useStore } from 'hooks' import ProductMenuItem from 'components/ui/ProductMenu/ProductMenuItem' interface Props { @@ -45,8 +43,7 @@ const TableEditorMenu: FC = ({ onDuplicateTable = () => {}, }) => { const { meta, ui } = useStore() - const router = useRouter() - const { id } = router.query + const { id } = useParams() const projectRef = ui.selectedProject?.ref const schemas: PostgresSchema[] = meta.schemas.list() @@ -324,12 +321,11 @@ const TableEditorMenu: FC = ({ /> {filteredViews.map((view: SchemaView) => { - const viewId = Base64.encode(JSON.stringify(view)) - const isActive = id === viewId + const isActive = Number(id) === view.id return ( - + - +

{view.name}

@@ -359,7 +355,10 @@ const TableEditorMenu: FC = ({ {filteredForeignTables.map((table: Partial) => { const isActive = Number(id) === table.id return ( - +
diff --git a/studio/pages/project/[ref]/editor/[id].tsx b/studio/pages/project/[ref]/editor/[id].tsx index 2b191faf79a..32893d76bec 100644 --- a/studio/pages/project/[ref]/editor/[id].tsx +++ b/studio/pages/project/[ref]/editor/[id].tsx @@ -7,17 +7,16 @@ import { isUndefined, isNaN } from 'lodash' import { Alert, Button, Checkbox, IconExternalLink, Modal } from 'ui' import { PostgresTable, PostgresColumn } from '@supabase/postgres-meta' -import Base64 from 'lib/base64' -import { tryParseJson } from 'lib/helpers' -import { useStore, withAuth, useUrlState } from 'hooks' +import { useStore, withAuth, useUrlState, useParams } from 'hooks' import { Dictionary } from 'components/grid' import { TableEditorLayout } from 'components/layouts' import { TableGridEditor } from 'components/interfaces' import ConfirmationModal from 'components/ui/ConfirmationModal' +import { SchemaView } from 'types' const TableEditorPage: NextPage = () => { const router = useRouter() - const { id }: any = router.query + const { id } = useParams() const [_, setParams] = useUrlState({ arrayKeys: ['filter', 'sort'] }) const { meta, ui } = useStore() @@ -37,13 +36,17 @@ const TableEditorPage: NextPage = () => { const projectRef = ui.selectedProject?.ref const tables: PostgresTable[] = meta.tables.list() + const views: SchemaView[] = meta.views.list() const foreignTables: Partial[] = meta.foreignTables.list() const selectedTable = !isNaN(Number(id)) ? // @ts-ignore - tables.concat(foreignTables).find((table) => table.id === Number(id)) - : id !== undefined - ? tryParseJson(Base64.decode(id)) + tables + // @ts-ignore + .concat(views) + // @ts-ignore + .concat(foreignTables) + .find((table) => table.id === Number(id)) : undefined useEffect(() => { From d739a011f62cfaddafbf2b53490873a4c3659b69 Mon Sep 17 00:00:00 2001 From: thorwebdev Date: Fri, 13 Jan 2023 16:52:44 +0800 Subject: [PATCH 037/261] feat: add import maps guide. --- .../NavigationMenu.constants.ts | 4 +- apps/docs/pages/guides/functions/cors.mdx | 2 +- .../pages/guides/functions/import-maps.mdx | 53 ++++++++++++++++++ .../guides/functions/local-development.mdx | 2 +- .../pages/guides/functions/quickstart.mdx | 56 ++++++++++++++++++- apps/docs/pages/guides/functions/tips.mdx | 45 --------------- apps/www/lib/redirects.js | 5 ++ 7 files changed, 116 insertions(+), 51 deletions(-) create mode 100644 apps/docs/pages/guides/functions/import-maps.mdx delete mode 100644 apps/docs/pages/guides/functions/tips.mdx diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index e46dafbf7df..8fa5333d8d0 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -423,12 +423,12 @@ export const functions = { items: [ { name: 'Quickstart', url: '/guides/functions/quickstart', items: [] }, { name: 'Auth', url: '/guides/functions/auth', items: [] }, - { name: 'CORS', url: '/guides/functions/cors', items: [] }, { name: 'CI/CD Workflow', url: '/guides/functions/cicd-workflow', items: [] }, + { name: 'CORS', url: '/guides/functions/cors', items: [] }, { name: 'Debugging', url: '/guides/functions/debugging', items: [] }, + { name: 'Import Maps', url: '/guides/functions/import-maps', items: [] }, { name: 'Local Development', url: '/guides/functions/local-development', items: [] }, { name: 'Managing Secrets', url: '/guides/functions/secrets', items: [] }, - { name: 'Tips', url: '/guides/functions/tips', items: [] }, { name: 'Examples', url: '/guides/functions/examples', diff --git a/apps/docs/pages/guides/functions/cors.mdx b/apps/docs/pages/guides/functions/cors.mdx index e991ad181a9..14f1a1208d9 100644 --- a/apps/docs/pages/guides/functions/cors.mdx +++ b/apps/docs/pages/guides/functions/cors.mdx @@ -12,7 +12,7 @@ See the [example on GitHub](https://github.com/supabase/supabase/blob/master/exa ### Recommended setup -We recommend adding a `corst.ts` file within a [`_shared` folder](/docs/guides/functions/tips#organizing-your-edge-functions) which makes it easy to reuse the CORS headers across functions: +We recommend adding a `corst.ts` file within a [`_shared` folder](/docs/guides/functions/quickstart#organizing-your-edge-functions) which makes it easy to reuse the CORS headers across functions: ```ts cors.ts export const corsHeaders = { diff --git a/apps/docs/pages/guides/functions/import-maps.mdx b/apps/docs/pages/guides/functions/import-maps.mdx new file mode 100644 index 00000000000..1509a9cd8a9 --- /dev/null +++ b/apps/docs/pages/guides/functions/import-maps.mdx @@ -0,0 +1,53 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'functions-import-maps', + title: 'Import Maps', + description: 'Using Import Maps.', +} + +Since [Supabase CLI](https://supabase.com/docs/guides/resources/supabase-cli) version `1.33.0` [import maps](https://deno.land/manual@v1.29.2/node/import_maps) can be used with Supabase Edge Functions. + +[Import maps](https://github.com/WICG/import-maps#the-import-map) is a web-platform standard that allows you to use bare specifiers with Deno without having to install the Node.js package locally. + +So if we want to do the following in our code: + +```ts, ignore +import lodash from "lodash"; +``` + +We can accomplish this using an import map, and we don't even have to install the `lodash` package locally. We would want to create a JSON file (for example **import_map.json**) with the following: + +```json +{ + "imports": { + "lodash": "https://cdn.skypack.dev/lodash" + } +} +``` + +## Import Map Placement + +We recommend creating one `import_map.json` within the `/supabase/functions` folder (see [Organizing your Edge Functions](/docs/guides/functions/quickstart#organizing-your-edge-functions)), similar to a `package.json` file, to define imports that can be used across all of your project's functions. + +Alternatively, you can create one `import_map.json` file in each function folder, which will take priority over a top-level file. + +Lastly, you can override this default behaviour by providing the `--import-map ` flag to the `serve` and `deploy` commands. + +## Visual Studio Code Configuration + +In order for vscode to understand the imports correctly, you need to specify the `deno.importMap` flag in your `.vscode/settings.json` file: + +```json settings.json +{ + "deno.enable": true, + "deno.unstable": true, + "deno.importMap": "./supabase/functions/import_map.json" +} +``` + +For a full guide on developing with Deno in Visual Studio Code, see [this guide](https://deno.land/manual@v1.25.4/vscode_deno). + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/functions/local-development.mdx b/apps/docs/pages/guides/functions/local-development.mdx index 01bd0f88a4d..fffd6ac7072 100644 --- a/apps/docs/pages/guides/functions/local-development.mdx +++ b/apps/docs/pages/guides/functions/local-development.mdx @@ -15,7 +15,7 @@ supabase functions serve hello-world # start the Function watcher The `functions serve` command has hot-reloading capabilities. It will watch for any changes to your files and restart the Deno server. -### Invoke functions locally +### Invoking functions locally While serving your local Function, you can invoke it using curl: diff --git a/apps/docs/pages/guides/functions/quickstart.mdx b/apps/docs/pages/guides/functions/quickstart.mdx index 7b489f2009e..e4e17a3a708 100644 --- a/apps/docs/pages/guides/functions/quickstart.mdx +++ b/apps/docs/pages/guides/functions/quickstart.mdx @@ -55,9 +55,9 @@ If you want to use Edge Functions to handle webhooks (e.g. [Stripe payment webho -## Execute remote functions +## Invoking remote functions -You can execute Edge Functions using curl. Copy the curl command from the Dashboard. It should look like this: +You can invoke Edge Functions using curl: ```bash curl --request POST 'https://.functions.supabase.co/hello-world' \ @@ -66,10 +66,62 @@ curl --request POST 'https://.functions.supabase.co/hello-world' \ --data '{ "name":"Functions" }' ``` + + If you receive an error `Invalid JWT`, find the `ANON_KEY` of your project in the Dashboard under `Settings > API`. + + +or using one of the [client libraries](/docs#reference-documentation), e.g. using [supabase-js](/docs/reference/javascript/functions-invoke): + +```js +import { createClient } from '@supabase/supabase-js' + +// Create a single supabase client for interacting with your database +const supabase = createClient('https://xyzcompany.supabase.co', 'public-anon-key') + +const { data, error } = await supabase.functions.invoke('hello-world', { + body: { name: 'Functions' }, +}) +``` + After invoking your Edge Function you should see the response `{ "message":"Hello Functions!" }`. +## Database Functions vs Edge Functions + +For data-intensive operations we recommend using [Database Functions](/docs/guides/database/functions), which are executed within your database +and can be called remotely using the [REST and GraphQL API](/docs/guides/api). + +For use-cases which require low-latency we recommend [Edge Functions](/docs/guides/functions), which are globally-distributed and can be written in TypeScript. + +## Organizing your Edge Functions + +We recommend developing “fat functions”. This means that you should develop few large functions, rather than many small functions. One common pattern when developing Functions is that you need to share code between two or more Functions. To do this, you can store any shared code in a folder prefixed with an underscore (`_`). We recommend this folder structure: + +```bash +└── supabase + ├── functions + │ ├── import_map.json # A top-level import map to use across functions. + │ ├── _shared + │ │ ├── supabaseAdmin.ts # Supabase client with SERVICE_ROLE key. + │ │ └── supabaseClient.ts # Supabase client with ANON key. + │ │ └── cors.ts # Reusable CORS headers. + │ ├── function-one # Use hyphens to name functions. + │ │ └── index.ts + │ └── function-two + │ └── index.ts + ├── migrations + └── config.toml +``` + +## Naming Edge Functions + +We recommend using hyphens to name functions because hyphens are the most URL-friendly of all the naming conventions (snake_case, camelCase, PascalCase). + +## Using HTTP Methods + +Edge Functions supports `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, and `OPTIONS`. A function can be designed to perform different actions based on a request's HTTP method. See the [example on building a RESTful service](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/restful-tasks) to learn how to handle different HTTP methods in your function. + ## Limitations - Deno Deploy limitations diff --git a/apps/docs/pages/guides/functions/tips.mdx b/apps/docs/pages/guides/functions/tips.mdx deleted file mode 100644 index 9c96998db4a..00000000000 --- a/apps/docs/pages/guides/functions/tips.mdx +++ /dev/null @@ -1,45 +0,0 @@ -import Layout from '~/layouts/DefaultGuideLayout' - -export const meta = { - id: 'functions-tips', - title: 'Edge Functions Tips', - description: 'Misc Tips for Supabase Edge Functions.', -} - -## Database Functions vs Edge Functions - -For data-intensive operations we recommend using [Database Functions](/docs/guides/database/functions), which are executed within your database -and can be called remotely using the [REST and GraphQL API](/docs/guides/api). - -For use-cases which require low-latency we recommend [Edge Functions](/docs/guides/functions), which are globally-distributed and can be written in TypeScript. - -## Organizing your Edge Functions - -We recommend developing “fat functions”. This means that you should develop few large functions, rather than many small functions. One common pattern when developing Functions is that you need to share code between two or more Functions. To do this, you can store any shared code in a folder prefixed with an underscore (`_`). We recommend this folder structure: - -```bash -└── supabase - ├── functions - │ ├── _shared - │ │ ├── supabaseAdmin.ts # Supabase client with SERVICE_ROLE key - │ │ └── supabaseClient.ts # Supabase client with ANON key - │ │ └── cors.ts # Reusable CORS headers - │ ├── function-one # use hyphens to name functions - │ │ └── index.ts - │ └── function-two - │ └── index.ts - ├── migrations - └── config.toml -``` - -## Naming Edge Functions - -We recommend using hyphens to name functions because hyphens are the most URL-friendly of all the naming conventions (snake_case, camelCase, PascalCase). - -## Using HTTP Methods - -Edge Functions supports `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, and `OPTIONS`. A function can be designed to perform different actions based on a request's HTTP method. See the [example on building a RESTful service](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/restful-tasks) to learn how to handle different HTTP methods in your function. - -export const Page = ({ children }) => - -export default Page diff --git a/apps/www/lib/redirects.js b/apps/www/lib/redirects.js index 1e992b67d9e..108782dcb29 100644 --- a/apps/www/lib/redirects.js +++ b/apps/www/lib/redirects.js @@ -1554,6 +1554,11 @@ module.exports = [ source: '/docs/guides/functions/examples', destination: '/docs/guides/functions', }, + { + permanent: true, + source: '/docs/guides/functions/best-practices', + destination: '/docs/guides/functions/quickstart', + }, { permanent: true, source: '/projects', From 25fb445fb96ecbf57bf012ce4d7566e2ebb0421a Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Fri, 13 Jan 2023 17:00:20 +0800 Subject: [PATCH 038/261] Storage preview pane support getting signed url of files from private buckets with custom expiry --- .../Storage/StorageExplorer/PreviewPane.tsx | 263 ++++++++++++------ .../storageExplorer/StorageExplorerStore.js | 12 +- 2 files changed, 190 insertions(+), 85 deletions(-) diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx b/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx index 49066467eae..855bd029827 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx @@ -1,12 +1,18 @@ import { isEmpty } from 'lodash' +import { useState } from 'react' import { Button, + Dropdown, + Form, IconX, IconLoader, IconClipboard, IconDownload, IconTrash2, IconAlertCircle, + IconChevronDown, + Modal, + Input, } from 'ui' import SVG from 'react-inlinesvg' import { formatBytes } from 'lib/helpers' @@ -90,12 +96,15 @@ const PreviewFile = ({ mimeType, previewUrl }: { mimeType: string; previewUrl: s const PreviewPane = () => { const storageExplorerStore = useStorageStore() const { + selectedBucket, selectedFilePreview: file, copyFileURLToClipboard, closeFilePreview, setSelectedItemsToDelete, } = storageExplorerStore + const [showExpiryInput, setShowExpiryInput] = useState(false) + const width = 450 const isOpen = !isEmpty(file) const size = file.metadata ? formatBytes(file.metadata.size) : null @@ -104,104 +113,200 @@ const PreviewPane = () => { const updatedAt = file.updated_at ? new Date(file.updated_at).toLocaleString() : 'Unknown' return ( - -
+
- {/* Preview Header */} -
- closeFilePreview()} - /> -
- - {/* Preview Thumbnail*/} -
-
- + style={{ width }} + > + {/* Preview Header */} +
+ closeFilePreview()} + />
-
-
- {/* Preview Information */} -
-
{file.name}
- {file.isCorrupted && ( -
- + {/* Preview Thumbnail*/} +
+
+ +
+
+ +
+ {/* Preview Information */} +
+
{file.name}
+ {file.isCorrupted && ( +
+ +

+ File is corrupted, please delete and reupload this file again +

+
+ )} + {mimeType && (

- File is corrupted, please delete and reupload this file again + {mimeType} + {size && - {size}}

+ )} +
+ + {/* Preview Metadata */} +
+
+ +

{createdAt}

+
+
+ +

{updatedAt}

- )} - {mimeType && ( -

- {mimeType} - {size && - {size}} -

- )} -
- - {/* Preview Metadata */} -
-
- -

{createdAt}

-
- -

{updatedAt}

-
-
- {/* Actions */} -
- - + {/* Actions */} +
+ + + + + + {selectedBucket.public ? ( - - + ) : ( + await copyFileURLToClipboard(file, 60 * 60 * 24 * 7)} + > + Expire in 1 week + , + await copyFileURLToClipboard(file, 60 * 60 * 24 * 30)} + > + Expire in 1 month + , + await copyFileURLToClipboard(file, 60 * 60 * 24 * 365)} + > + Expire in 1 year + , + setShowExpiryInput(true)}> + Custom expiry + , + ]} + > + + + )} +
-
-
- + + setShowExpiryInput(false)} + > +
{ + setSubmitting(true) + await copyFileURLToClipboard(file, values.expiresIn) + setSubmitting(false) + setShowExpiryInput(false) + }} + validate={(values: any) => { + const errors: any = {} + if (values.expiresIn !== '' && values.expiresIn <= 0) + errors.expiresIn = 'Expiry duration cannot be less than 0' + return errors + }} + > + {({ values, isSubmitting }: { values: any; isSubmitting: boolean }) => ( + <> +
+ + seconds

} + /> +
+
+ + +
+ + +
+
+ + )} + +
+ ) } diff --git a/studio/localStores/storageExplorer/StorageExplorerStore.js b/studio/localStores/storageExplorer/StorageExplorerStore.js index 5e11d3c234c..323a51d10ca 100644 --- a/studio/localStores/storageExplorer/StorageExplorerStore.js +++ b/studio/localStores/storageExplorer/StorageExplorerStore.js @@ -37,7 +37,7 @@ export const useStorageExplorerStore = () => { const CORRUPTED_THRESHOLD_MS = 15 * 60 * 1000 // 15 minutes const LIMIT = 200 const OFFSET = 0 -const DEFAULT_EXPIRY = 10 * 365 * 24 * 60 * 60 // in seconds, default to 1 year +const DEFAULT_EXPIRY = 10 * 365 * 24 * 60 * 60 // in seconds, default to 10 years const PREVIEW_SIZE_LIMIT = 10000000 // 10MB const BATCH_SIZE = 2 const EMPTY_FOLDER_PLACEHOLDER_FILE_NAME = '.emptyFolderPlaceholder' @@ -397,9 +397,9 @@ class StorageExplorerStore { this.selectedFilePreview = {} } - copyFileURLToClipboard = async (file) => { + copyFileURLToClipboard = async (file, expiresIn = 0) => { const filePreview = find(this.filePreviewCache, { id: file.id }) - if (filePreview) { + if (filePreview && expiresIn === 0) { // Already generated signed URL copyToClipboard(filePreview.url, () => { this.ui.setNotification({ @@ -410,7 +410,7 @@ class StorageExplorerStore { }) } else { // Need to generate signed URL, and might as well save it to cache as well - const signedUrl = await this.fetchFilePreview(file.name) + const signedUrl = await this.fetchFilePreview(file.name, expiresIn) try { let formattedUrl = new URL(signedUrl) @@ -840,7 +840,7 @@ class StorageExplorerStore { this.clearSelectedItemsToMove() } - fetchFilePreview = async (fileName) => { + fetchFilePreview = async (fileName, expiresIn = 0) => { const includeBucket = false const pathToFile = this.getPathAlongOpenedFolders(includeBucket) const formattedPathToFile = pathToFile.length > 0 ? `${pathToFile}/${fileName}` : fileName @@ -857,7 +857,7 @@ class StorageExplorerStore { const { data, error } = await this.supabaseClient.storage .from(this.selectedBucket.name) - .createSignedUrl(formattedPathToFile, DEFAULT_EXPIRY) + .createSignedUrl(formattedPathToFile, expiresIn || DEFAULT_EXPIRY) if (!error) { return data.signedUrl From 210f7b7e1891909d9627bfd7eaf0227c8ad852e9 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Fri, 13 Jan 2023 17:04:39 +0800 Subject: [PATCH 039/261] Ensure that payment summary panel is loading when confirming payment --- .../interfaces/Billing/EnterpriseUpdate.tsx | 12 ------------ studio/components/interfaces/Billing/ProUpgrade.tsx | 1 + studio/components/interfaces/Billing/TeamUpgrade.tsx | 1 + 3 files changed, 2 insertions(+), 12 deletions(-) diff --git a/studio/components/interfaces/Billing/EnterpriseUpdate.tsx b/studio/components/interfaces/Billing/EnterpriseUpdate.tsx index 969b5d58993..2c234c62fae 100644 --- a/studio/components/interfaces/Billing/EnterpriseUpdate.tsx +++ b/studio/components/interfaces/Billing/EnterpriseUpdate.tsx @@ -158,18 +158,6 @@ const EnterpriseUpdate: FC = ({ // Last todo to support enterprise billing on dashboard + E2E test const onConfirmPayment = async () => { setIsSubmitting(true) - let token = captchaToken - - try { - if (!token) { - const captchaResponse = await captchaRef.current?.execute({ async: true }) - token = captchaResponse?.response ?? null - } - } catch (error) { - setIsSubmitting(false) - return - } - const payload = { ...formSubscriptionUpdatePayload( currentSubscription, diff --git a/studio/components/interfaces/Billing/ProUpgrade.tsx b/studio/components/interfaces/Billing/ProUpgrade.tsx index b4cf2635051..81d991fe5a0 100644 --- a/studio/components/interfaces/Billing/ProUpgrade.tsx +++ b/studio/components/interfaces/Billing/ProUpgrade.tsx @@ -177,6 +177,7 @@ const ProUpgrade: FC = ({ } const onConfirmPayment = async () => { + setIsSubmitting(true) const payload = formSubscriptionUpdatePayload( currentSubscription, selectedTier, diff --git a/studio/components/interfaces/Billing/TeamUpgrade.tsx b/studio/components/interfaces/Billing/TeamUpgrade.tsx index 7cc18e7847a..fcfef76cd0d 100644 --- a/studio/components/interfaces/Billing/TeamUpgrade.tsx +++ b/studio/components/interfaces/Billing/TeamUpgrade.tsx @@ -169,6 +169,7 @@ const TeamUpgrade: FC = ({ message: 'Team Plan is not enabled yet.', }) } + setIsSubmitting(true) const payload = formSubscriptionUpdatePayload( currentSubscription, selectedTier, From 22be1fe49310e1f9f31c0eaec3dfbc9f8c69af0e Mon Sep 17 00:00:00 2001 From: Ollie Halliday <22655069+Hallidayo@users.noreply.github.com> Date: Fri, 13 Jan 2023 17:20:35 +0000 Subject: [PATCH 040/261] added in realtime column on table list --- .../interfaces/Database/Tables/TableList.tsx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/studio/components/interfaces/Database/Tables/TableList.tsx b/studio/components/interfaces/Database/Tables/TableList.tsx index 2c4b60c1acb..850c720fe03 100644 --- a/studio/components/interfaces/Database/Tables/TableList.tsx +++ b/studio/components/interfaces/Database/Tables/TableList.tsx @@ -10,6 +10,7 @@ import { IconColumns, Listbox, IconLock, + IconCheck, } from 'ui' import { partition } from 'lodash' @@ -54,6 +55,10 @@ const TableList: FC = ({ : // @ts-ignore allTables.filter((table: PostgresTable) => table.name.includes(filterString)) + const publications = meta.publications.list() + const realtimePublication = publications.find( + (publication) => publication.name === 'supabase_realtime' + ) // @ts-ignore const schema = schemas.find((schema) => schema.name === selectedSchema) const isLocked = protectedSchemas.some((s) => s.id === schema?.id) @@ -137,6 +142,9 @@ const TableList: FC = ({ Size (Estimated) , + + Realtime Enabled + , , ]} body={tables.map((x: any, i: any) => ( @@ -157,6 +165,13 @@ const TableList: FC = ({ {x.size} + + {realtimePublication.tables.find((table: any) => table.id === x.id) && ( +
+ +
+ )} +
- This reference documents every object and method available in Supabase's Flutter - library, [supabase-flutter](https://pub.dev/packages/supabase_flutter). You can - use supabase-flutter to interact with your Postgres database, listen to database changes, invoke - Deno Edge Functions, build login and user management functionality, and manage large files. -
-
- We also provide [supabase](https://pub.dev/packages/supabase) package for non-Flutter projects. + +This reference documents every object and method available in Supabase's Flutter +library, [supabase-flutter](https://pub.dev/packages/supabase_flutter). You can +use supabase-flutter to interact with your Postgres database, listen to database changes, invoke +Deno Edge Functions, build login and user management functionality, and manage large files. + +We also provide a [supabase](https://pub.dev/packages/supabase) package for non-Flutter projects. +
diff --git a/apps/docs/docs/ref/dart/v0/introduction.mdx b/apps/docs/docs/ref/dart/v0/introduction.mdx new file mode 100644 index 00000000000..fdbb9d766a5 --- /dev/null +++ b/apps/docs/docs/ref/dart/v0/introduction.mdx @@ -0,0 +1,28 @@ +--- +id: introduction +title: Introduction +hideTitle: true +--- + +
+ +
+

Flutter Client Library

+

supabase-flutter

+
+
+ +
+ + + You're viewing the docs for an older version of the `supabase-flutter` library. Learn how to + [upgrade to the latest version](/docs/reference/dart/v0/upgrade-guide). + + +This reference documents every object and method available in Supabase's Flutter library, [supabase-flutter](https://pub.dev/packages/supabase_flutter). +You can use supabase-flutter to interact with your Postgres database, listen to database changes, invoke +Deno Edge Functions, build login and user management functionality, and manage large files. + +We also provide a [supabase](https://pub.dev/packages/supabase) package for non-Flutter projects. + +
diff --git a/apps/docs/docs/ref/javascript/v1/introduction.mdx b/apps/docs/docs/ref/javascript/v1/introduction.mdx index 3e1af1ff9dd..6d050a1f668 100644 --- a/apps/docs/docs/ref/javascript/v1/introduction.mdx +++ b/apps/docs/docs/ref/javascript/v1/introduction.mdx @@ -13,8 +13,16 @@ hideTitle: true
- This reference documents every object and method available in Supabase's isomorphic JavaScript - library, supabase-js. You can use supabase-js to interact with your Postgres database, listen to - database changes, invoke Deno Edge Functions, build login and user management functionality, and - manage large files. + + + You're viewing the docs for an older version of the `supabase-js` library. View the [latest + version](/docs/reference/javascript/introduction) or follow the [supabase-js v2 + guide](/docs/reference/javascript/v1/upgrade-guide) to upgrade to the latest version. + + +This reference documents every object and method available in Supabase's isomorphic JavaScript +library, supabase-js. You can use supabase-js to interact with your Postgres database, listen to +database changes, invoke Deno Edge Functions, build login and user management functionality, and +manage large files. +
From 679de08aa14b8d60cec0a5a8d34e3549aef0340e Mon Sep 17 00:00:00 2001 From: dannykng Date: Fri, 13 Jan 2023 17:14:05 -0800 Subject: [PATCH 044/261] Move upgrade guides --- .../dart => ref/dart/v0}/upgrade-guide.mdx | 177 ++++++- .../ref/javascript/{ => v1}/upgrade-guide.mdx | 0 .../reference/javascript/upgrade-guide.mdx | 439 ------------------ 3 files changed, 167 insertions(+), 449 deletions(-) rename apps/docs/docs/{reference/dart => ref/dart/v0}/upgrade-guide.mdx (68%) rename apps/docs/docs/ref/javascript/{ => v1}/upgrade-guide.mdx (100%) delete mode 100644 apps/docs/docs/reference/javascript/upgrade-guide.mdx diff --git a/apps/docs/docs/reference/dart/upgrade-guide.mdx b/apps/docs/docs/ref/dart/v0/upgrade-guide.mdx similarity index 68% rename from apps/docs/docs/reference/dart/upgrade-guide.mdx rename to apps/docs/docs/ref/dart/v0/upgrade-guide.mdx index 707cdd06129..6da7b0c0fe5 100644 --- a/apps/docs/docs/reference/dart/upgrade-guide.mdx +++ b/apps/docs/docs/ref/dart/v0/upgrade-guide.mdx @@ -4,23 +4,35 @@ title: Upgrade to supabase-flutter v1 description: 'Learn how to upgrade to supabase-flutter v1.' --- -import Tabs from '@theme/Tabs' -import TabItem from '@theme/TabItem' - -supabase-flutter focuses on improving the developer experience and making it easier to use. This guide will help you upgrade from supabase-flutter v0 to v1. +supabase-flutter focuses on improving the developer experience and making it easier to use. This guide demonstrates how to upgrade from supabase-flutter v0 to v1. ## Upgrade the client library + + + Update the package in your pubspec.yaml file. + + + ```yaml supabase_flutter: ^1.0.0 ``` + + + ## Error handling + + + The way supabase-flutter throws error has changed in v1. In v0, errors were returned as a response. In v1, errors are thrown as exceptions. This makes it more intuitive as a Flutter developer to handle errors. + + + -## Auth classes / methods + + + +## Auth classes and methods ### Usage of `SupabaseAuthState` and `SupabaseAuthRequiredState` classes -In v0, `SupabaseAuthState` and `SupabaseAuthRequiredState` were required to handle automatic token refresh and to listen to auth state change. In v1, `SupabaseAuthState` and `SupabaseAuthRequiredState` are deprecated, and token refresh will happen automatically just by initializing Supabase. [`onAuthStateChange`](/docs/reference/dart/upgrade-guide#listening-to-auth-state-change) can be used to action on auth state change. + + + +In v0, `SupabaseAuthState` and `SupabaseAuthRequiredState` were required to handle automatic token refresh and to listen to auth state change. In v1, `SupabaseAuthState` and `SupabaseAuthRequiredState` are deprecated, and token refresh will happen automatically just by initializing Supabase. [`onAuthStateChange`](#listening-to-auth-state-change) can be used to action on auth state change. + + + + + + ### Listening to auth state change + + + `onAuthStateChange` now returns a `Stream`. + + + + + + ### Sign in with email and password -The signIn() method has been deprecated in favor of more explicit method signatures to help with type hinting. Previously it was difficult for developers to know what they were missing (e.g., a lot of developers didn't realize they could use passwordless magic links). + + + +`signIn()` has been deprecated in favor of more explicit method signatures to help with type hinting. Previously it was difficult for developers to know what they were missing (e.g., a lot of developers didn't realize they could use passwordless magic links). + + + + + + + + + ### Sign in with magic link + + + + + + + + + ### Sign in with a third-party OAuth provider + + + + + + + + + ### Sign in with phone + + + + + + + + + ### Sign in with phone using OTP + + + + + + + + + ### Reset password for email + + + + + + + + + ### Get the user's current session + + + + + + + + + ### Get the logged-in user + + + + + + + + + ### Update user data for a logged-in user + + + + + + ## Data methods -`.insert()` / `.upsert()` / `.update()` / `.delete()` don't return rows by default. +`.insert()` / `.upsert()` / `.update()` / `.delete()` no longer return rows by default. Previously, these methods return inserted/updated/deleted rows by default (which caused [some confusion](https://github.com/supabase/supabase/discussions/1548)), and you can opt to not return it by specifying `returning: 'minimal'`. Now the default behavior is to not return rows. To return inserted/updated/deleted rows, add a `.select()` call at the end. -Previously, these methods return inserted/updated/deleted rows by default (which caused [some confusion](https://github.com/supabase/supabase/discussions/1548)), and you can opt to not return it by specifying `returning: 'minimal'`. Now the default behavior is to not return rows. To return inserted/updated/deleted rows, add a `.select()` call at the end. +Also, calling `.execute()` at the end of the query was a requirement in v0, but deprecated in v1. -Also, calling `.execute()` at the end of the query was a requirement in v0, but in v1 `.execute` is deperecated. + + ### Insert without returning inserted data + + + + + + + + + ### Insert with returning inserted data + + + + + + ## Realtime methods + + + ### Stream `.stream()` no longer needs the `.execute()` at the end. Also, filtering by `eq` is a lot easier now. `primaryKey` is now a named parameter to make it more obvious what to pass. + + + + + + + + + ### Subscribe + + + + + + + + + ### Unsubscribe + + + + + + diff --git a/apps/docs/docs/ref/javascript/upgrade-guide.mdx b/apps/docs/docs/ref/javascript/v1/upgrade-guide.mdx similarity index 100% rename from apps/docs/docs/ref/javascript/upgrade-guide.mdx rename to apps/docs/docs/ref/javascript/v1/upgrade-guide.mdx diff --git a/apps/docs/docs/reference/javascript/upgrade-guide.mdx b/apps/docs/docs/reference/javascript/upgrade-guide.mdx deleted file mode 100644 index 282d66d4966..00000000000 --- a/apps/docs/docs/reference/javascript/upgrade-guide.mdx +++ /dev/null @@ -1,439 +0,0 @@ ---- -id: upgrade-guide -title: Upgrade to supabase-js v2 -description: 'Learn how to upgrade to supabase-js v2.' ---- - -import Tabs from '@theme/Tabs' -import TabItem from '@theme/TabItem' - -supabase-js v2 focuses on "quality-of-life" improvements for developers and addresses some of the largest pain points in v1. v2 includes type support, a rebuilt Auth library with async methods, improved errors, and more. - -No new features will be added to supabase-js v1 , but we'll continuing merging security fixes to v1, with maintenance patches for the next 3 months. - -## Upgrade the client library - -```sh -npm install @supabase/supabase-js@2 -``` - -_Optionally_ if you are using custom configuration with `createClient` then follow below: - - - - -```ts title=src/supabaseClient.ts -const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, { - schema: 'custom', - persistSession: false, -}) -``` - - - - -```ts title=src/supabaseClient.ts -const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, { - db: { - schema: 'custom', - }, - auth: { - persistSession: true, - }, -}) -``` - - - - -Read more about the [constructor options](/docs/reference/javascript/release-notes#explicit-constructor-options). - -### Auth methods - -The signIn() method has been deprecated in favor of more explicit method signatures to help with type hinting. Previously it was difficult for developers to know what they were missing (e.g., a lot of developers didn't realize they could use passwordless magic links). - -#### Sign in with email and password - - - - -```ts -const { user, error } = await supabase.auth.signIn({ email, password }) -``` - - - - -```ts -const { - data: { user }, - error, -} = await supabase.auth.signInWithPassword({ email, password }) -``` - - - - -#### Sign in with magic link - - - - -```ts -const { error } = await supabase.auth.signIn({ email }) -``` - - - - -```ts -const { error } = await supabase.auth.signInWithOtp({ email }) -``` - - - - -#### Sign in with a third-party provider - - - - -```ts -const { error } = await supabase.auth.signIn({ provider }) -``` - - - - -```ts -const { error } = await supabase.auth.signInWithOAuth({ provider }) -``` - - - - -#### Sign in with phone - - - - -```ts -const { error } = await supabase.auth.signIn({ phone, password }) -``` - - - - -```ts -const { error } = await supabase.auth.signInWithPassword({ phone, password }) -``` - - - - -#### Sign in with phone using OTP - - - - -```ts -const { error } = await supabase.auth.api.sendMobileOTP(phone) -``` - - - - -```ts -const { data, error } = await supabase.auth.signInWithOtp({ phone }) - -// After receiving a SMS with a OTP. -const { data, error } = await supabase.auth.verifyOtp({ phone, token }) -``` - - - - -#### Reset password for email - - - - -```ts -const { data, error } = await supabase.auth.api.resetPasswordForEmail(email) -``` - - - - -```ts -const { data, error } = await supabase.auth.resetPasswordForEmail(email) -``` - - - - -#### Get the user's current session - - - - -```ts -const session = supabase.auth.session() -``` - - - - -```ts -const { - data: { session }, -} = await supabase.auth.getSession() -``` - - - - -#### Get the logged-in user - - - - -```ts -const user = supabase.auth.user() -``` - - - - -```ts -const { - data: { session }, -} = await supabase.auth.getSession() -const { user } = session -``` - - - - -#### Update user data for a logged-in user - - - - -```ts -const { user, error } = await supabase.auth.update({ attributes }) -``` - - - - -```ts -const { - data: { user }, - error, -} = await supabase.auth.updateUser({ attributes }) -``` - - - - -#### Use a custom `access_token` JWT with Supabase - - - - -```ts -const { user, error } = supabase.auth.setAuth(access_token) -``` - - - - -```ts -const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, { - global: { - headers: { - Authorization: `Bearer ${access_token}`, - }, - }, -}) -``` - - - - -#### Cookie methods - -The cookie-related methods like `setAuthCookie` and `getUserByCookie` have been removed. - -For Next.js you can use the [Auth Helpers](https://supabase.com/docs/guides/auth/auth-helpers/nextjs) to help you manage cookies. -If you can't use the Auth Helpers, you can use [server-side rendering](https://supabase.com/docs/guides/auth/server-side-rendering). - -Some the [PR](https://github.com/supabase/gotrue-js/pull/340) for additional background information. - -### Data methods - -`.insert()` / `.upsert()` / `.update()` / `.delete()` don't return rows by default: [PR](https://github.com/supabase/postgrest-js/pull/276). - -Previously, these methods return inserted/updated/deleted rows by default (which caused [some confusion](https://github.com/supabase/supabase/discussions/1548)), and you can opt to not return it by specifying `returning: 'minimal'`. Now the default behavior is to not return rows. To return inserted/updated/deleted rows, add a `.select()` call at the end. - -#### Insert and return data - - - - -```ts -const { data, error } = await supabase.auth.insert({ new_data }) -``` - - - - -```ts -const { data, error } = await supabase.auth.insert({ new_data }).select() -``` - - - - -#### Update and return data - - - - -```ts -const { data, error } = await supabase.auth.update({ new_data }).eq('id', id) -``` - - - - -```ts -const { data, error } = await supabase.auth.update({ new_data }).eq('id', id).select() -``` - - - - -### Realtime methods - -#### Subscribe - - - - -```ts -const userListener = supabase - .from('users') - .on('*', (payload) => handleAllEventsPayload(payload.new)) - .subscribe() -``` - - - - -```ts -const userListener = supabase - .channel('public:user') - .on('postgres_changes', { event: '*', schema: 'public', table: 'user' }, (payload) => - handleAllEventsPayload() - ) - .subscribe() -``` - - - - -#### Unsubscribe - - - - -```ts -userListener.unsubscribe() -``` - - - - -```ts -supabase.removeChannel(userListener) -``` - - - From 0c853ca4b04aa68ca3c773aad0419893f7213853 Mon Sep 17 00:00:00 2001 From: dannykng Date: Fri, 13 Jan 2023 17:14:27 -0800 Subject: [PATCH 045/261] Add redirects --- apps/www/lib/redirects.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apps/www/lib/redirects.js b/apps/www/lib/redirects.js index 108782dcb29..420340205b9 100644 --- a/apps/www/lib/redirects.js +++ b/apps/www/lib/redirects.js @@ -1891,4 +1891,14 @@ module.exports = [ source: '/legal/soc2', destination: 'https://forms.supabase.com/soc2', }, + { + permanent: true, + source: '/docs/reference/javascript/upgrade-guide', + destination: '/docs/reference/javascript/v1/upgrade-guide', + }, + { + permanent: true, + source: '/docs/reference/dart/upgrade-guide', + destination: '/docs/reference/dart/v0/upgrade-guide', + }, ] From 9c46b931111631aa319408908a257223e94918cd Mon Sep 17 00:00:00 2001 From: dannykng Date: Fri, 13 Jan 2023 17:20:16 -0800 Subject: [PATCH 046/261] Update links --- apps/docs/data/nonGeneratedReferencePages.ts | 4 ++-- apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx | 2 +- apps/www/_blog/2022-08-16-supabase-js-v2.mdx | 6 +++--- .../www/_blog/2022-10-20-supabase-js-v2-released.mdx | 12 ++++++------ .../2022-10-21-supabase-flutter-sdk-v1-released.mdx | 6 +++--- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/apps/docs/data/nonGeneratedReferencePages.ts b/apps/docs/data/nonGeneratedReferencePages.ts index fd5ba84d1f5..01b060055c7 100644 --- a/apps/docs/data/nonGeneratedReferencePages.ts +++ b/apps/docs/data/nonGeneratedReferencePages.ts @@ -2,7 +2,7 @@ const nonGeneratedReferencePages = [ 'docs/reference/javascript', 'docs/reference/javascript/installing', 'docs/reference/javascript/release-notes', - 'docs/reference/javascript/upgrade-guide', + 'docs/reference/javascript/v1/upgrade-guide', 'docs/reference/javascript/typescript-support', 'docs/reference/javascript/v1', 'docs/reference/javascript/v1/installing', @@ -11,7 +11,7 @@ const nonGeneratedReferencePages = [ 'docs/reference/dart', 'docs/reference/dart/installing', 'docs/reference/dart/initializing', - 'docs/reference/dart/upgrade-guide', + 'docs/reference/dart/v0/upgrade-guide', 'docs/reference/dart/v0', 'docs/reference/api', 'docs/reference/api/release-notes', diff --git a/apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx b/apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx index 76e169c2271..8f5d318c151 100644 --- a/apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx +++ b/apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx @@ -782,7 +782,7 @@ export const config = { ### Migrating to v0.4.X and supabase-js v2 -With the update to `supabase-js` v2 the `auth` API routes are no longer required, therefore you can go ahead and delete your `auth` directory under the `/pages/api/` directory. Please refer to the [v2 migration guide](/docs/reference/javascript/upgrade-guide) for the full set of changes within supabase-js. +With the update to `supabase-js` v2 the `auth` API routes are no longer required, therefore you can go ahead and delete your `auth` directory under the `/pages/api/` directory. Please refer to the [v2 migration guide](/docs/reference/javascript/v1/upgrade-guide) for the full set of changes within supabase-js. The `/api/auth/logout` API route has been removed, please use the `signout` method instead: diff --git a/apps/www/_blog/2022-08-16-supabase-js-v2.mdx b/apps/www/_blog/2022-08-16-supabase-js-v2.mdx index d7986434a97..07cf3fd54dc 100644 --- a/apps/www/_blog/2022-08-16-supabase-js-v2.mdx +++ b/apps/www/_blog/2022-08-16-supabase-js-v2.mdx @@ -13,7 +13,7 @@ toc_depth: 3

⚠️ UPDATED 20/10: supabase-js v2 is fully released 🥳
-Check the updated docs and migration guide. +Check the updated docs and migration guide.
@@ -309,7 +309,7 @@ Update today by running: npm i @supabase/supabase-js@2 ``` -[Migration guide](https://supabase.com/docs/reference/javascript/upgrade-guide) +[Migration guide](https://supabase.com/docs/reference/javascript/v1/upgrade-guide) We'll continuing merging security fixes to v1, with maintenance patches for the next three months. @@ -329,6 +329,6 @@ We'll continuing merging security fixes to v1, with maintenance patches for the ## supabase-js v2 resources - [v2 Documentation](https://supabase.com/docs/reference/javascript) -- [Migration guide](https://supabase.com/docs/reference/javascript/upgrade-guide) +- [Migration guide](https://supabase.com/docs/reference/javascript/v1/upgrade-guide) - [Next.js quickstart guide](/docs/guides/with-nextjs) - [Examples](https://github.com/supabase/supabase/tree/master/examples) diff --git a/apps/www/_blog/2022-10-20-supabase-js-v2-released.mdx b/apps/www/_blog/2022-10-20-supabase-js-v2-released.mdx index 6e17afd4d14..6c2fca19cd8 100644 --- a/apps/www/_blog/2022-10-20-supabase-js-v2-released.mdx +++ b/apps/www/_blog/2022-10-20-supabase-js-v2-released.mdx @@ -2,7 +2,7 @@ title: 'supabase-js v2 Released' description: We've released supabase-js v2. Updated examples, quickstarts, and an improved experience. author: thor_schaeff -image: supabase-js-v2-release/supabase-js.jpg +image: supabase-js-v2-release/supabase-js.jpg thumb: supabase-js-v2-release/supabase-js.jpg tags: - launch-week @@ -10,17 +10,17 @@ date: '2022-10-20' toc_depth: 3 --- -During our [last Launch Week](/launch-week) we presented the [release candidate for supabase-js v2](/blog/supabase-js-v2). Since then we've been busy incorporating your feedback, [updating the docs](/docs/reference/javascript/), [examples](https://github.com/supabase/supabase/tree/master/examples), [quickstart guides](/docs/guides/with-nextjs), and putting a [migration guide](/docs/reference/javascript/upgrade-guide) together. +During our [last Launch Week](/launch-week) we presented the [release candidate for supabase-js v2](/blog/supabase-js-v2). Since then we've been busy incorporating your feedback, [updating the docs](/docs/reference/javascript/), [examples](https://github.com/supabase/supabase/tree/master/examples), [quickstart guides](/docs/guides/with-nextjs), and putting a [migration guide](/docs/reference/javascript/v1/upgrade-guide) together. ## What is new in v2? -Enhanced TypeScript support built right in was the big one! Now you can use the CLI to generate types, directly from your database. +Enhanced TypeScript support built right in was the big one! Now you can use the CLI to generate types, directly from your database. -Plus v2 comes with lots of improvements that solved some of the largest pain-points highlighted by our users. Your feedback help us improve: you speak, we listen. +Plus v2 comes with lots of improvements that solved some of the largest pain-points highlighted by our users. Your feedback help us improve: you speak, we listen. Read all the updates and the differences with v1 on the [announcement blog post](/blog/supabase-js-v2) -## Acknowledgements +## Acknowledgements It truly takes a village, well, in this case an entire community, and we want to thank you all for your feedback, and contributions! @@ -40,7 +40,7 @@ Special shout outs to: [@vejja](https://github.com/vejja), [@pixtron](https://gi ## Links - [Documentation](/docs/reference/javascript) -- [Migration Guide](/docs/reference/javascript/upgrade-guide) +- [Migration Guide](/docs/reference/javascript/v1/upgrade-guide) - [Quickstart Guides](/docs/guides/with-nextjs) - [Examples (GitHub)](https://github.com/supabase/supabase/tree/master/examples) - [Release Notes](/docs/reference/javascript/release-notes) diff --git a/apps/www/_blog/2022-10-21-supabase-flutter-sdk-v1-released.mdx b/apps/www/_blog/2022-10-21-supabase-flutter-sdk-v1-released.mdx index e7fdabdc869..92aed5cbb3b 100644 --- a/apps/www/_blog/2022-10-21-supabase-flutter-sdk-v1-released.mdx +++ b/apps/www/_blog/2022-10-21-supabase-flutter-sdk-v1-released.mdx @@ -2,7 +2,7 @@ title: 'supabase-flutter v1 Released' description: We've released supabase-flutter v1. More intuitive way of accessing Supabase from your Flutter application. author: tyler_shukert -image: flutter-v1-release/flutter_v1_official_release.jpeg +image: flutter-v1-release/flutter_v1_official_release.jpeg thumb: flutter-v1-release/flutter_v1_official_release.jpeg tags: - flutter @@ -11,7 +11,7 @@ date: '2022-10-21' toc_depth: 3 --- -A few months ago, we announced a [developer preview version of supabase-flutter SDK](https://supabase.com/blog/supabase-flutter-sdk-1-developer-preview). Since then, we have heard a lot of amazing feedback from the community, and have been improving it. Today, we are happy to announce the stable v1 of [supabase-flutter](https://pub.dev/packages/supabase_flutter). You can also find the updated [quick start guide](https://supabase.com/docs/guides/with-flutter), [documentation](https://supabase.com/docs/reference/dart) and a [migration guide from v0](https://supabase.com/docs/reference/dart/upgrade-guide). +A few months ago, we announced a [developer preview version of supabase-flutter SDK](https://supabase.com/blog/supabase-flutter-sdk-1-developer-preview). Since then, we have heard a lot of amazing feedback from the community, and have been improving it. Today, we are happy to announce the stable v1 of [supabase-flutter](https://pub.dev/packages/supabase_flutter). You can also find the updated [quick start guide](https://supabase.com/docs/guides/with-flutter), [documentation](https://supabase.com/docs/reference/dart) and a [migration guide from v0](https://supabase.com/docs/reference/dart/v0/upgrade-guide). ## What is new in v1? @@ -97,7 +97,7 @@ It required massive support from the community to bring the supabase-flutter to - [Install supabase-flutter v1.0](https://pub.dev/packages/supabase_flutter) - [supabase-flutter documentation](https://supabase.com/docs/reference/dart/) -- [v0 to v1 migration guide](https://supabase.com/docs/reference/dart/upgrade-guide) +- [v0 to v1 migration guide](https://supabase.com/docs/reference/dart/v0/upgrade-guide) - [Flutter Tutorial: building a Flutter chat app](https://supabase.com/blog/flutter-tutorial-building-a-chat-app) - [Flutter Tutorial - Part 2: Authentication and Authorization with RLS](https://supabase.com/blog/flutter-authentication-and-authorization-with-rls) - [Build a Flutter app with Very Good CLI and Supabase](https://verygood.ventures/blog/flutter-app-very-good-cli-supabase) From f92d4369934cb9b2d870bd182c46f4f0bb90ce18 Mon Sep 17 00:00:00 2001 From: dannykng Date: Fri, 13 Jan 2023 18:19:04 -0800 Subject: [PATCH 047/261] Update callout copy --- apps/docs/docs/ref/javascript/v1/introduction.mdx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/docs/docs/ref/javascript/v1/introduction.mdx b/apps/docs/docs/ref/javascript/v1/introduction.mdx index 6d050a1f668..2c275e45e33 100644 --- a/apps/docs/docs/ref/javascript/v1/introduction.mdx +++ b/apps/docs/docs/ref/javascript/v1/introduction.mdx @@ -15,9 +15,8 @@ hideTitle: true
- You're viewing the docs for an older version of the `supabase-js` library. View the [latest - version](/docs/reference/javascript/introduction) or follow the [supabase-js v2 - guide](/docs/reference/javascript/v1/upgrade-guide) to upgrade to the latest version. + You're viewing the docs for an older version of the `supabase-js` library. Learn how to upgrade to + the [upgrade to the latest version](/docs/reference/javascript/v1/upgrade-guide). This reference documents every object and method available in Supabase's isomorphic JavaScript From 0f1b18680265376fce92790dd863d53da953cbfd Mon Sep 17 00:00:00 2001 From: dannykng Date: Fri, 13 Jan 2023 18:21:13 -0800 Subject: [PATCH 048/261] Fix typo --- apps/docs/docs/ref/javascript/v1/introduction.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/docs/docs/ref/javascript/v1/introduction.mdx b/apps/docs/docs/ref/javascript/v1/introduction.mdx index 2c275e45e33..a62134bdd69 100644 --- a/apps/docs/docs/ref/javascript/v1/introduction.mdx +++ b/apps/docs/docs/ref/javascript/v1/introduction.mdx @@ -15,8 +15,8 @@ hideTitle: true
- You're viewing the docs for an older version of the `supabase-js` library. Learn how to upgrade to - the [upgrade to the latest version](/docs/reference/javascript/v1/upgrade-guide). + You're viewing the docs for an older version of the `supabase-js` library. Learn how to [upgrade + to the latest version](/docs/reference/javascript/v1/upgrade-guide). This reference documents every object and method available in Supabase's isomorphic JavaScript From 1345dba7c5cf94147a266c1f2940ac16d8ddcb54 Mon Sep 17 00:00:00 2001 From: j0hnta <22534726+johnta0@users.noreply.github.com> Date: Sat, 14 Jan 2023 16:13:16 +0900 Subject: [PATCH 049/261] Update prixma.mdx * Project ref should be different for each user, so made it unspecific. * Update supabase.red, seemingly the old domain, to supabase.co. --- apps/docs/pages/guides/integrations/prisma.mdx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/docs/pages/guides/integrations/prisma.mdx b/apps/docs/pages/guides/integrations/prisma.mdx index 6a80e95fee6..840028a00c7 100644 --- a/apps/docs/pages/guides/integrations/prisma.mdx +++ b/apps/docs/pages/guides/integrations/prisma.mdx @@ -68,7 +68,7 @@ By default, Prisma migrations will try to drop the `postgres` database, which ca Create a shadow database in your PostgreSQL server within the same Supabase project using the `psql` CLI and the `DATABASE_URL` from the previous steps (or use the local database). ```bash -psql postgresql://postgres:[YOUR-PASSWORD]@db.vdbnhqozmlzdsaejdxwr.supabase.red:5432 +psql postgresql://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:5432 ``` After you connect to your project's PostgreSQL instance, create another database (e.g., `postgres_shadow`): @@ -83,8 +83,8 @@ In the `.env` file, update `DATABASE_URL` and `SHADOW_DATABASE_URL` to the conne ```env # .env -DATABASE_URL="postgres://postgres:[YOUR-PASSWORD]@db.vdbnhqozmlzdsaejdxwr.supabase.co:5432/postgres" -SHADOW_DATABASE_URL="postgres://postgres:[YOUR-PASSWORD]@db.vdbnhqozmlzdsaejdxwr.supabase.co:5432/postgres_shadow" +DATABASE_URL="postgres://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:5432/postgres" +SHADOW_DATABASE_URL="postgres://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:5432/postgres_shadow" ``` In the `schema.prisma` file, change the `provider` from "sqlite" to `"postgresql"` and add the `shadowDatabaseUrl` property. @@ -145,8 +145,8 @@ When migrating, you need to use the non-pooled connection URL (like the one used ```env # .env -DATABASE_URL="postgres://postgres:[YOUR-PASSWORD]@db.vdbnhqozmlzdsaejdxwr.supabase.co:6543/postgres?pgbouncer=true&connection_limit=1" -SHADOW_DATABASE_URL="postgres://postgres:[YOUR-PASSWORD]@db.vdbnhqozmlzdsaejdxwr.supabase.co:5432/postgres_shadow" +DATABASE_URL="postgres://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:6543/postgres?pgbouncer=true&connection_limit=1" +SHADOW_DATABASE_URL="postgres://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:5432/postgres_shadow" ``` Prisma Migrate uses database transactions to check out the current state of the database and the migrations table. However, the Migration Engine is designed to use a single connection to the database, and does not support connection pooling with PgBouncer. If you attempt to run Prisma Migrate commands in any environment that uses PgBouncer for connection pooling, you might see the following error: From 04276b17640ca81cbd3c97bd869dd239f79de8a7 Mon Sep 17 00:00:00 2001 From: Jonathan Stanley Date: Sat, 14 Jan 2023 21:13:21 -0500 Subject: [PATCH 050/261] add warn severity filter --- .../interfaces/Settings/Logs/Logs.constants.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/studio/components/interfaces/Settings/Logs/Logs.constants.ts b/studio/components/interfaces/Settings/Logs/Logs.constants.ts index 1ca035dd57f..97a3ab2dead 100644 --- a/studio/components/interfaces/Settings/Logs/Logs.constants.ts +++ b/studio/components/interfaces/Settings/Logs/Logs.constants.ts @@ -250,6 +250,7 @@ export const SQL_FILTER_TEMPLATES: any = { 'severity.log': `metadata.level = 'log'`, 'severity.info': `metadata.level = 'info'`, 'severity.debug': `metadata.level = 'debug'`, + 'severity.warn': `metadata.level = 'warn'`, }, auth_logs: { ..._SQL_FILTER_COMMON, @@ -473,22 +474,27 @@ export const FILTER_OPTIONS: FilterTableSet = { { key: 'error', label: 'Error', - description: 'Show all events that have error severity', + description: 'Show all events that are "error" severity', + }, + { + key: 'warn', + label: 'Warning', + description: 'Show all events that are "warn" severity', }, { key: 'info', label: 'Info', - description: 'Show all events that have error severity', + description: 'Show all events that are "info" severity', }, { key: 'debug', label: 'Debug', - description: 'Show all events that have error severity', + description: 'Show all events that are "debug" severity', }, { key: 'log', label: 'Log', - description: 'Show all events that are log severity', + description: 'Show all events that are "log" severity', }, ], }, From b8d9ac0a2204e344d585cda7a4c1e988e11fde80 Mon Sep 17 00:00:00 2001 From: Lars Salling Date: Sun, 15 Jan 2023 14:08:07 +0100 Subject: [PATCH 051/261] fix: link typo --- apps/www/pages/realtime/Realtime.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/www/pages/realtime/Realtime.tsx b/apps/www/pages/realtime/Realtime.tsx index cc062d0bde4..890332c1478 100644 --- a/apps/www/pages/realtime/Realtime.tsx +++ b/apps/www/pages/realtime/Realtime.tsx @@ -238,7 +238,7 @@ function RealtimePage() {

, ]} // [TODO] Point to the correct docs URL - documentation_link={'/docs/guides//docs/guides/realtime/broadcast'} + documentation_link={'/docs/guides/realtime/broadcast'} /> From 502d9d51c72a82dfd0c2f5c36a2c9c39b4b14324 Mon Sep 17 00:00:00 2001 From: Lukas <69743171+quick007@users.noreply.github.com> Date: Sun, 15 Jan 2023 16:54:16 -0800 Subject: [PATCH 052/261] Fix example so it works correctly --- apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx b/apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx index 8f5d318c151..9985e50fb92 100644 --- a/apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx +++ b/apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx @@ -584,7 +584,7 @@ export async function middleware(req: NextRequest) { } export const config = { - matcher: '/middleware-protected', + matcher: '/middleware-protected/:path*', } ``` From 17b0cc80d1c7a5e3872e24aada6defce93dd3c4d Mon Sep 17 00:00:00 2001 From: Inian Date: Mon, 16 Jan 2023 11:59:47 +0800 Subject: [PATCH 053/261] update docs for storage getPublicURL --- spec/enrichments/tsdoc_v2/combined.json | 2 +- spec/enrichments/tsdoc_v2/storage.json | 2 +- spec/enrichments/tsdoc_v2/storage_dereferenced.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/enrichments/tsdoc_v2/combined.json b/spec/enrichments/tsdoc_v2/combined.json index 0c72523e032..57242f4b5bf 100644 --- a/spec/enrichments/tsdoc_v2/combined.json +++ b/spec/enrichments/tsdoc_v2/combined.json @@ -79596,7 +79596,7 @@ "kindString": "Call signature", "flags": {}, "comment": { - "shortText": "Retrieves the URL for an asset in a public bucket.\nThis function does not verify if the bucket is public. If a public URL is created for a bucket which is not public, you will not be able to download the asset." + "shortText": "A simple convenience function to get the URL for an asset in a public bucket. If you do not want to use this function, you can construct the public URL by concatenating the bucket URL with the path to the asset.\nThis function does not verify if the bucket is public. If a public URL is created for a bucket which is not public, you will not be able to download the asset." }, "parameters": [ { diff --git a/spec/enrichments/tsdoc_v2/storage.json b/spec/enrichments/tsdoc_v2/storage.json index 475ba699fc3..fb8baa1c895 100644 --- a/spec/enrichments/tsdoc_v2/storage.json +++ b/spec/enrichments/tsdoc_v2/storage.json @@ -5471,7 +5471,7 @@ "kindString": "Call signature", "flags": {}, "comment": { - "shortText": "Retrieves the URL for an asset in a public bucket.\nThis function does not verify if the bucket is public. If a public URL is created for a bucket which is not public, you will not be able to download the asset." + "shortText": "A simple convenience function to get the URL for an asset in a public bucket. If you do not want to use this function, you can construct the public URL by concatenating the bucket URL with the path to the asset.\nThis function does not verify if the bucket is public. If a public URL is created for a bucket which is not public, you will not be able to download the asset." }, "parameters": [ { diff --git a/spec/enrichments/tsdoc_v2/storage_dereferenced.json b/spec/enrichments/tsdoc_v2/storage_dereferenced.json index 634dd23b469..53596ea9650 100644 --- a/spec/enrichments/tsdoc_v2/storage_dereferenced.json +++ b/spec/enrichments/tsdoc_v2/storage_dereferenced.json @@ -5875,7 +5875,7 @@ "kindString": "Call signature", "flags": {}, "comment": { - "shortText": "Retrieves the URL for an asset in a public bucket.\nThis function does not verify if the bucket is public. If a public URL is created for a bucket which is not public, you will not be able to download the asset." + "shortText": "A simple convenience function to get the URL for an asset in a public bucket. If you do not want to use this function, you can construct the public URL by concatenating the bucket URL with the path to the asset.\nThis function does not verify if the bucket is public. If a public URL is created for a bucket which is not public, you will not be able to download the asset." }, "parameters": [ { From 240453a854539bba59a089dde66603a2cd3b0cb7 Mon Sep 17 00:00:00 2001 From: Ollie Halliday <22655069+Hallidayo@users.noreply.github.com> Date: Mon, 16 Jan 2023 09:46:08 +0000 Subject: [PATCH 054/261] changed order() example --- spec/supabase_js_v2.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/supabase_js_v2.yml b/spec/supabase_js_v2.yml index 7e220e2dc23..0cd16c93dbd 100644 --- a/spec/supabase_js_v2.yml +++ b/spec/supabase_js_v2.yml @@ -3429,8 +3429,8 @@ functions: code: | ```ts const { data, error } = await supabase - .from('cities') - .select('name', 'country_id') + .from('countries') + .select('id', 'name') .order('id', { ascending: false }) ``` data: From c68d82eff3ab9b086bec65c74f191233be6db531 Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Mon, 16 Jan 2023 09:20:07 -0330 Subject: [PATCH 055/261] Update studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx --- .../to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx b/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx index 855bd029827..a3abf1d1ec1 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx @@ -281,7 +281,7 @@ const PreviewPane = () => { disabled={isSubmitting} type="number" id="expiresIn" - label="Enter the duration for which the URL will be valid for" + label="Enter the duration for which the URL will be valid." actions={

seconds

} /> From c2e83fbf607691c5ea0de5e0ac37ae69e644c7e3 Mon Sep 17 00:00:00 2001 From: Mohit Saini Date: Mon, 16 Jan 2023 20:32:31 +0530 Subject: [PATCH 056/261] Move LW6 link from menu to footer #11641 --- apps/www/components/Nav/index.tsx | 21 --------------------- apps/www/data/Footer.json | 4 ++-- 2 files changed, 2 insertions(+), 23 deletions(-) diff --git a/apps/www/components/Nav/index.tsx b/apps/www/components/Nav/index.tsx index 93e7402a1ab..387aefacc5d 100644 --- a/apps/www/components/Nav/index.tsx +++ b/apps/www/components/Nav/index.tsx @@ -234,19 +234,6 @@ const Nav = () => { Blog -
@@ -376,14 +363,6 @@ const Nav = () => { Blog - - - Launch Week 6 - -

Products available:

diff --git a/apps/www/data/Footer.json b/apps/www/data/Footer.json index fdfb80a9acf..1422e016687 100644 --- a/apps/www/data/Footer.json +++ b/apps/www/data/Footer.json @@ -27,8 +27,8 @@ "url": "/pricing" }, { - "text": "Beta", - "url": "/beta" + "text": "Launch Week 6", + "url": "/launch-week" } ] }, From 4d1d95be2234058197e6165aa7fc132711f36f70 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 17 Jan 2023 00:17:44 +0800 Subject: [PATCH 057/261] Refactor FileExplorerRow to tsx and reduce prop drilldown --- .../Storage/StorageExplorer/FileExplorer.js | 59 +++------ .../StorageExplorer/FileExplorerColumn.js | 31 ++--- ...FileExplorerRow.js => FileExplorerRow.tsx} | 124 +++++++++++------- .../Storage/StorageExplorer/PreviewPane.tsx | 6 +- .../StorageExplorer/StorageExplorer.js | 81 +----------- 5 files changed, 110 insertions(+), 191 deletions(-) rename studio/components/to-be-cleaned/Storage/StorageExplorer/{FileExplorerRow.js => FileExplorerRow.tsx} (78%) diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorer.js b/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorer.js index c76f2d2a2e4..21b30091755 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorer.js +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorer.js @@ -1,10 +1,11 @@ import React, { useEffect, useRef } from 'react' -import { STORAGE_VIEWS, CONTEXT_MENU_KEYS } from '../Storage.constants' +import { STORAGE_VIEWS, CONTEXT_MENU_KEYS } from '../Storage.constants' import ItemContextMenu from './ItemContextMenu' import FolderContextMenu from './FolderContextMenu' import ColumnContextMenu from './ColumnContextMenu' import FileExplorerColumn from './FileExplorerColumn' +import { useStorageStore } from 'localStores/storageExplorer/StorageExplorerStore' const FileExplorer = ({ view = STORAGE_VIEWS.COLUMNS, @@ -12,18 +13,7 @@ const FileExplorer = ({ openedFolders = [], selectedItems = [], selectedFilePreview = {}, - onCheckItem = () => {}, - onSelectItemDelete = () => {}, - onSelectItemRename = () => {}, - onSelectItemMove = () => {}, - onSelectFile = () => {}, - onRenameFile = () => {}, onFilesUpload = () => {}, - onCopyFileURL = () => {}, - onDownloadFile = () => {}, - onSelectFolder = () => {}, - onRenameFolder = () => {}, - onCreateFolder = () => {}, onSelectAllItemsInColumn = () => {}, onSelectColumnEmptySpace = () => {}, onSelectCreateFolder = () => {}, @@ -33,6 +23,15 @@ const FileExplorer = ({ onColumnLoadMore = () => {}, }) => { const fileExplorerRef = useRef(null) + const storageExplorerStore = useStorageStore() + + const { + downloadFile, + setSelectedItemsToDelete, + setSelectedItemToRename, + setSelectedItemsToMove, + copyFileURLToClipboard, + } = storageExplorerStore useEffect(() => { if (fileExplorerRef) { @@ -58,16 +57,16 @@ const FileExplorer = ({ /> setSelectedItemToRename(file)} + onSelectItemMove={(file) => setSelectedItemsToMove([file])} + onSelectItemDelete={(file) => setSelectedItemsToDelete([file])} + onCopyFileURL={async (file) => await copyFileURLToClipboard(file)} + onDownloadFile={async (file) => await downloadFile(file)} /> setSelectedItemToRename(folder)} + onDeleteFolder={(folder) => setSelectedItemsToDelete([folder])} /> {view === STORAGE_VIEWS.COLUMNS ? (
@@ -80,18 +79,7 @@ const FileExplorer = ({ openedFolders={openedFolders} selectedItems={selectedItems} selectedFilePreview={selectedFilePreview} - onCheckItem={onCheckItem} - onSelectFile={onSelectFile} - onRenameFile={onRenameFile} - onCopyFileURL={onCopyFileURL} onFilesUpload={onFilesUpload} - onDownloadFile={onDownloadFile} - onSelectFolder={onSelectFolder} - onRenameFolder={onRenameFolder} - onCreateFolder={onCreateFolder} - onSelectItemDelete={onSelectItemDelete} - onSelectItemRename={onSelectItemRename} - onSelectItemMove={onSelectItemMove} onSelectAllItemsInColumn={onSelectAllItemsInColumn} onSelectColumnEmptySpace={onSelectColumnEmptySpace} onColumnLoadMore={onColumnLoadMore} @@ -108,18 +96,7 @@ const FileExplorer = ({ column={columns[columns.length - 1]} selectedItems={selectedItems} selectedFilePreview={selectedFilePreview} - onCheckItem={onCheckItem} - onSelectFile={onSelectFile} - onRenameFile={onRenameFile} - onCopyFileURL={onCopyFileURL} onFilesUpload={onFilesUpload} - onDownloadFile={onDownloadFile} - onSelectFolder={onSelectFolder} - onRenameFolder={onRenameFolder} - onCreateFolder={onCreateFolder} - onSelectItemDelete={onSelectItemDelete} - onSelectItemRename={onSelectItemRename} - onSelectItemMove={onSelectItemMove} onSelectAllItemsInColumn={onSelectAllItemsInColumn} onSelectColumnEmptySpace={onSelectColumnEmptySpace} onColumnLoadMore={onColumnLoadMore} diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerColumn.js b/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerColumn.js index bc418c2f92a..db2a5886e1b 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerColumn.js +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerColumn.js @@ -57,18 +57,7 @@ const FileExplorerColumn = ({ openedFolders = [], selectedItems = [], selectedFilePreview = {}, - onCheckItem = () => {}, - onSelectItemDelete = () => {}, - onSelectItemRename = () => {}, - onSelectItemMove = () => {}, - onSelectFile = () => {}, - onRenameFile = () => {}, - onCopyFileURL = () => {}, onFilesUpload = () => {}, - onDownloadFile = () => {}, - onSelectFolder = () => {}, - onRenameFolder = () => {}, - onCreateFolder = () => {}, onSelectAllItemsInColumn = () => {}, onSelectColumnEmptySpace = () => {}, onColumnLoadMore = () => {}, @@ -212,17 +201,15 @@ const FileExplorerColumn = ({ selectedItems, openedFolders, selectedFilePreview, - onCheckItem, - onSelectFile, - onRenameFile, - onCopyFileURL, - onDownloadFile, - onSelectFolder, - onRenameFolder, - onCreateFolder, - onSelectItemDelete, - onSelectItemRename, - onSelectItemMove, + // onCheckItem, + // onRenameFile, + // onCopyFileURL, + // onDownloadFile, + // onRenameFolder, + // onCreateFolder, + // onSelectItemDelete, + // onSelectItemRename, + // onSelectItemMove, }} ItemComponent={FileExplorerRow} getItemSize={(index) => (index !== 0 && index === columnItems.length ? 85 : 37)} diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerRow.js b/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerRow.tsx similarity index 78% rename from studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerRow.js rename to studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerRow.tsx index 6712cc90e09..fcb43622051 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerRow.js +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerRow.tsx @@ -1,4 +1,4 @@ -import { useState, useRef, useEffect } from 'react' +import { FC, useState, useRef, useEffect } from 'react' import { find, has, isEmpty, isEqual } from 'lodash' import { Checkbox, @@ -11,10 +11,10 @@ import { IconFile, IconAlertCircle, IconDownload, - IconTrash, - IconCopy, IconEdit, IconMove, + IconClipboard, + IconTrash2, } from 'ui' import SVG from 'react-inlinesvg' import * as Tooltip from '@radix-ui/react-tooltip' @@ -24,11 +24,11 @@ import { STORAGE_ROW_TYPES, STORAGE_ROW_STATUS, CONTEXT_MENU_KEYS, -} from '../Storage.constants.ts' +} from '../Storage.constants' import { formatBytes } from 'lib/helpers' import { useStorageStore } from 'localStores/storageExplorer/StorageExplorerStore' -const RowIcon = ({ view, status, fileType, mimeType }) => { +const RowIcon = ({ view, status, fileType, mimeType }: any) => { if (view === STORAGE_VIEWS.LIST && status === STORAGE_ROW_STATUS.LOADING) { return } @@ -43,7 +43,6 @@ const RowIcon = ({ view, status, fileType, mimeType }) => { return ( code.replace(/svg/, 'svg class="w-4 h-4 text-color-inherit opacity-75"') } @@ -66,28 +65,43 @@ const RowIcon = ({ view, status, fileType, mimeType }) => { return } -const FileExplorerRow = ({ +interface Props { + item: any + view: string + columnIndex: number + selectedItems: any[] + openedFolders: any[] + selectedFilePreview: any +} + +const FileExplorerRow: FC = ({ item = {}, view = STORAGE_VIEWS.COLUMNS, columnIndex = 0, selectedItems = [], openedFolders = [], selectedFilePreview = {}, - - onCheckItem = () => {}, - onSelectFile = () => {}, - onRenameFile = () => {}, - onCopyFileURL = () => {}, - onDownloadFile = () => {}, - onSelectFolder = () => {}, - onRenameFolder = () => {}, - onCreateFolder = () => {}, - onSelectItemDelete = () => {}, - onSelectItemRename = () => {}, - onSelectItemMove = () => {}, }) => { const storageExplorerStore = useStorageStore() - const { downloadFolder } = storageExplorerStore + const { + popColumnAtIndex, + pushOpenedFolderAtIndex, + popOpenedFoldersAtIndex, + setFilePreview, + closeFilePreview, + clearSelectedItems, + addNewFolder, + renameFolder, + renameFile, + setSelectedItems, + setSelectedItemsToDelete, + setSelectedItemToRename, + setSelectedItemsToMove, + fetchFolderContents, + downloadFile, + downloadFolder, + copyFileURLToClipboard, + } = storageExplorerStore const itemWithColumnIndex = { ...item, columnIndex } const isSelected = find(selectedItems, item) !== undefined @@ -95,25 +109,47 @@ const FileExplorerRow = ({ openedFolders.length > columnIndex ? isEqual(openedFolders[columnIndex], item) : false const isPreviewed = !isEmpty(selectedFilePreview) && isEqual(selectedFilePreview.id, item.id) + const onSelectFile = async (columnIndex: number, file: any) => { + popColumnAtIndex(columnIndex) + popOpenedFoldersAtIndex(columnIndex - 1) + setFilePreview(file) + clearSelectedItems() + } + + const onSelectFolder = async (columnIndex: number, folder: any) => { + closeFilePreview() + clearSelectedItems(columnIndex + 1) + popOpenedFoldersAtIndex(columnIndex - 1) + pushOpenedFolderAtIndex(folder, columnIndex) + await fetchFolderContents(folder.id, folder.name, columnIndex) + } + + const onCheckItem = (item: any) => { + if (find(selectedItems, item) === undefined) { + setSelectedItems(selectedItems.concat([item])) + } else { + setSelectedItems(selectedItems.filter((selectedItem: any) => item.id !== selectedItem.id)) + } + closeFilePreview() + } + if (item.status === STORAGE_ROW_STATUS.EDITING) { - const inputRef = useRef(null) + const inputRef = useRef(null) const [itemName, setItemName] = useState(item.name) useEffect(() => { - if (inputRef.current) { - inputRef.current.select() - } + if (inputRef.current) inputRef.current.select() }, []) - const onSetItemName = (event) => { + const onSetItemName = async (event: any) => { event.preventDefault() event.stopPropagation() if (item.type === STORAGE_ROW_TYPES.FILE) { - onRenameFile(item, itemName, columnIndex) + await renameFile(item, itemName, columnIndex) } else if (has(item, 'id')) { - onRenameFolder(itemWithColumnIndex, itemName, columnIndex) + renameFolder(itemWithColumnIndex, itemName, columnIndex) } else { - onCreateFolder(itemName, columnIndex) + addNewFolder(itemName, columnIndex) } } @@ -146,57 +182,55 @@ const FileExplorerRow = ({ } const rowOptions = - item.type === STORAGE_ROW_TYPES.BUCKET - ? [{ name: 'Delete', onClick: () => onSelectItemDelete(itemWithColumnIndex) }] - : item.type === STORAGE_ROW_TYPES.FOLDER + item.type === STORAGE_ROW_TYPES.FOLDER ? [ { name: 'Rename', icon: , - onClick: () => onSelectItemRename(itemWithColumnIndex), + onClick: () => setSelectedItemToRename(itemWithColumnIndex), }, { name: 'Download', icon: , onClick: () => downloadFolder(itemWithColumnIndex), }, - { name: 'Separator' }, + { name: 'Separator', icon: undefined, onClick: undefined }, { name: 'Delete', - icon: , - onClick: () => onSelectItemDelete(itemWithColumnIndex), + icon: , + onClick: () => setSelectedItemsToDelete([itemWithColumnIndex]), }, ] : [ ...(!item.isCorrupted ? [ { - name: 'Copy URL', - icon: , - onClick: () => onCopyFileURL(itemWithColumnIndex), + name: 'Get URL', + icon: , + onClick: async () => await copyFileURLToClipboard(itemWithColumnIndex), }, { name: 'Rename', icon: , - onClick: () => onSelectItemRename(itemWithColumnIndex), + onClick: () => setSelectedItemToRename(itemWithColumnIndex), }, { name: 'Move', icon: , - onClick: () => onSelectItemMove(itemWithColumnIndex), + onClick: () => setSelectedItemsToMove([itemWithColumnIndex]), }, { name: 'Download', icon: , - onClick: () => onDownloadFile(itemWithColumnIndex), + onClick: async () => await downloadFile(itemWithColumnIndex), }, - { name: 'Separator' }, + { name: 'Separator', icon: undefined, onClick: undefined }, ] : []), { name: 'Delete', - icon: , - onClick: () => onSelectItemDelete(itemWithColumnIndex), + icon: , + onClick: () => setSelectedItemsToDelete([itemWithColumnIndex]), }, ] @@ -206,7 +240,7 @@ const FileExplorerRow = ({ const updatedAt = item.updated_at ? new Date(item.updated_at).toLocaleString() : '-' const { show } = useContextMenu() - const displayMenu = (event, rowType) => { + const displayMenu = (event: any, rowType: any) => { show(event, { id: rowType === STORAGE_ROW_TYPES.FILE diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx b/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx index 855bd029827..2b64d35abd9 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx @@ -198,7 +198,7 @@ const PreviewPane = () => { onClick={async () => await copyFileURLToClipboard(file)} disabled={file.isCorrupted} > - Copy URL + Get URL ) : ( { iconRight={} disabled={file.isCorrupted} > - Get Signed URL + Get URL )} @@ -254,7 +254,7 @@ const PreviewPane = () => { header="Custom expiry for signed URL" visible={showExpiryInput} alignFooter="right" - confirmText="Get signed URL" + confirmText="Get URL" onCancel={() => setShowExpiryInput(false)} >
{ const { columns, selectedFilePreview, - setFilePreview, closeFilePreview, selectedItems, setSelectedItems, clearSelectedItems, selectedItemsToDelete, - setSelectedItemsToDelete, clearSelectedItemsToDelete, openedFolders, - pushOpenedFolderAtIndex, popColumnAtIndex, popOpenedFoldersAtIndex, - setSelectedItemToRename, selectedItemsToMove, - setSelectedItemsToMove, clearSelectedItemsToMove, view, setView, setSortBy, setSortByOrder, currentBucketName, - copyFileURLToClipboard, openBucket, loadExplorerPreferences, addNewFolderPlaceholder, - addNewFolder, fetchFolderContents, fetchMoreFolderContents, - renameFolder, deleteFolder, uploadFiles, deleteFiles, - downloadFile, - renameFile, moveFiles, } = storageExplorerStore @@ -100,35 +90,9 @@ const StorageExplorer = observer(({ bucket }) => { openBucket(bucket) }, [bucket]) - /** Navigation methods */ - - const onSelectFile = async (columnIndex, file) => { - popColumnAtIndex(columnIndex) - popOpenedFoldersAtIndex(columnIndex - 1) - setFilePreview(file) - clearSelectedItems() - } - - const onSelectFolder = async (columnIndex, folder) => { - closeFilePreview() - clearSelectedItems(columnIndex + 1) - popOpenedFoldersAtIndex(columnIndex - 1) - pushOpenedFolderAtIndex(folder, columnIndex) - await fetchFolderContents(folder.id, folder.name, columnIndex) - } - /** Checkbox selection methods */ /** [Joshen] We'll only support checkbox selection for files ONLY */ - const onCheckItem = (item) => { - if (find(selectedItems, item) === undefined) { - setSelectedItems(selectedItems.concat([item])) - } else { - setSelectedItems(selectedItems.filter((selectedItem) => item.id !== selectedItem.id)) - } - closeFilePreview() - } - const onSelectAllItemsInColumn = (columnIndex) => { const columnFiles = columns[columnIndex].items .filter((item) => item.type === STORAGE_ROW_TYPES.FILE) @@ -155,38 +119,6 @@ const StorageExplorer = observer(({ bucket }) => { addNewFolderPlaceholder(columnIndex) } - const onCreateFolder = (folderName, columnIndex) => { - addNewFolder(folderName, columnIndex) - } - - const onRenameFolder = (folder, newName, columnIndex) => { - renameFolder(folder, newName, columnIndex) - } - - const onSelectItemDelete = (file) => { - setSelectedItemsToDelete([file]) - } - - const onSelectItemRename = (file) => { - setSelectedItemToRename(file) - } - - const onSelectItemMove = (file) => { - setSelectedItemsToMove([file]) - } - - const onCopyFileURL = async (file) => { - await copyFileURLToClipboard(file) - } - - const onDownloadFile = async (file) => { - await downloadFile(file) - } - - const onRenameFile = async (file, newName, columnIndex) => { - await renameFile(file, newName, columnIndex) - } - const onFilesUpload = async (event, columnIndex = -1) => { event.persist() const items = event.target.files || event.dataTransfer.items @@ -255,18 +187,7 @@ const StorageExplorer = observer(({ bucket }) => { openedFolders={openedFolders} selectedItems={selectedItems} selectedFilePreview={selectedFilePreview} - onCheckItem={onCheckItem} - onSelectFile={onSelectFile} - onRenameFile={onRenameFile} onFilesUpload={onFilesUpload} - onCopyFileURL={onCopyFileURL} - onDownloadFile={onDownloadFile} - onSelectFolder={onSelectFolder} - onRenameFolder={onRenameFolder} - onCreateFolder={onCreateFolder} - onSelectItemDelete={onSelectItemDelete} - onSelectItemRename={onSelectItemRename} - onSelectItemMove={onSelectItemMove} onSelectAllItemsInColumn={onSelectAllItemsInColumn} onSelectColumnEmptySpace={onSelectColumnEmptySpace} onSelectCreateFolder={onSelectCreateFolder} From 1d668321135bc99090e54a9843888129fd898419 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Thu, 12 Jan 2023 17:06:31 +0800 Subject: [PATCH 058/261] Update subscription change page with new design that includes team tier --- .../Billing/PlanSelection/PlanSelection.tsx | 32 +++- .../PlanSelection/Plans/PlanCTAButton.tsx | 47 ++++-- .../Billing/PlanSelection/Plans/PlanCard.tsx | 131 +++++++++++++++ .../PlanSelection/Plans/Plans.Constants.ts | 40 ++--- .../Billing/PlanSelection/Plans/Plans.tsx | 154 ------------------ studio/tailwind.config.js | 4 + 6 files changed, 204 insertions(+), 204 deletions(-) create mode 100644 studio/components/interfaces/Billing/PlanSelection/Plans/PlanCard.tsx delete mode 100644 studio/components/interfaces/Billing/PlanSelection/Plans/Plans.tsx diff --git a/studio/components/interfaces/Billing/PlanSelection/PlanSelection.tsx b/studio/components/interfaces/Billing/PlanSelection/PlanSelection.tsx index c2c52459e0e..3c54921b215 100644 --- a/studio/components/interfaces/Billing/PlanSelection/PlanSelection.tsx +++ b/studio/components/interfaces/Billing/PlanSelection/PlanSelection.tsx @@ -2,8 +2,9 @@ import Link from 'next/link' import { FC } from 'react' import { Transition } from '@headlessui/react' -import Plans from './Plans/Plans' +import PlanCard from './Plans/PlanCard' import { formatTierOptions } from './PlanSelection.utils' +import { Button, IconExternalLink } from 'ui' interface Props { visible: boolean @@ -16,7 +17,10 @@ const PlanSelection: FC = ({ visible, tiers, currentPlan, onSelectPlan }) const formattedTiers = formatTierOptions(tiers) // [Kevin] TODO Remove after team plan is generally available - const gridCols = formattedTiers.length === 3 ? 'gap-8 xl:grid-cols-3' : 'gap-4 xl:grid-cols-4' + const gridCols = + formattedTiers.length === 3 + ? 'gap-8 lg:grid-cols-3' + : 'gap-4 lg:grid-cols-3 xl:grid-cols-billingWithTeam' return ( = ({ visible, tiers, currentPlan, onSelectPlan })

Change your project's subscription

- + {formattedTiers.map((plan) => { + return ( + onSelectPlan(plan)} + /> + ) + })}
diff --git a/studio/components/interfaces/Billing/PlanSelection/Plans/PlanCTAButton.tsx b/studio/components/interfaces/Billing/PlanSelection/Plans/PlanCTAButton.tsx index 05dbf005a53..ca1595eaf6c 100644 --- a/studio/components/interfaces/Billing/PlanSelection/Plans/PlanCTAButton.tsx +++ b/studio/components/interfaces/Billing/PlanSelection/Plans/PlanCTAButton.tsx @@ -17,8 +17,8 @@ const PlanCTAButton: FC = ({ plan, currentPlan, onSelectPlan }) => { const projectUpdateDisabled = useFlag('disableProjectCreationAndUpdate') const getButtonType = (plan: any, currentPlan: any) => { - if (['Enterprise', 'Free tier'].includes(plan.name)) { - // Enterprise/Free are always default + if (['Free tier'].includes(plan.name)) { + // Free is always default return 'default' } else if (currentPlan.prod_id === STRIPE_PRODUCT_IDS.FREE) { // If the current plan is free, other plans are primary @@ -90,7 +90,7 @@ const PlanCTAButton: FC = ({ plan, currentPlan, onSelectPlan }) => { return ( - @@ -100,20 +100,33 @@ const PlanCTAButton: FC = ({ plan, currentPlan, onSelectPlan }) => { return (
- - {!disabled && projectUpdateDisabled && ( -

- Subscription changes are currently disabled, our engineers are working on a fix -

- )} + + + + + {!disabled && projectUpdateDisabled && ( + + +
+ + Subscription changes are currently disabled, our engineers are working on a fix + +
+
+ )} +
) } diff --git a/studio/components/interfaces/Billing/PlanSelection/Plans/PlanCard.tsx b/studio/components/interfaces/Billing/PlanSelection/Plans/PlanCard.tsx new file mode 100644 index 00000000000..e9ebd88adec --- /dev/null +++ b/studio/components/interfaces/Billing/PlanSelection/Plans/PlanCard.tsx @@ -0,0 +1,131 @@ +import { STRIPE_PRODUCT_IDS } from 'lib/constants' +import { FC } from 'react' +import { IconCheck } from 'ui' + +import { useFlag } from 'hooks' +import PlanCTAButton from './PlanCTAButton' +import { PRICING_META } from './Plans.Constants' + +interface Props { + plan: any + currentPlan: any + onSelectPlan: () => void +} + +const PlanCard: FC = ({ plan, currentPlan, onSelectPlan }) => { + const teamTierEnabled = useFlag('teamTier') + + const planMeta = PRICING_META[plan.id] + const isEnterprise = plan.id === 'Enterprise' + const isCurrentPlan = + plan.id === currentPlan?.prod_id || + (plan.id === STRIPE_PRODUCT_IDS.PRO && currentPlan?.prod_id === STRIPE_PRODUCT_IDS.PRO) || + (plan.id === STRIPE_PRODUCT_IDS.PRO && currentPlan?.prod_id === STRIPE_PRODUCT_IDS.PAYG) + + return ( +
+
+
+
+

+ {planMeta.name} +

+ {isCurrentPlan ? ( +
+ Current plan +
+ ) : planMeta.new ? ( +
New
+ ) : ( + <> + )} +
+

{planMeta.description}

+
+ {planMeta.priceMonthly !== undefined ? ( + <> +

Starting from

+
+

${planMeta.priceMonthly}

+

{planMeta.priceUnit}

+
+
+ {planMeta.warning} +
+ + ) : ( + + )} +
+
+
+

{planMeta.preface}

+
    + {planMeta.features.map((feature) => ( +
  • +
    +
    +

    {feature}

    +
  • + ))} +
+
+
+ {!isEnterprise && ( +
+
+

{planMeta.additional}

+

{planMeta.scale}

+
+ + +
+ )} +
+ ) +} + +export default PlanCard diff --git a/studio/components/interfaces/Billing/PlanSelection/Plans/Plans.Constants.ts b/studio/components/interfaces/Billing/PlanSelection/Plans/Plans.Constants.ts index 1e8b6f8e918..29bf1fe3ff6 100644 --- a/studio/components/interfaces/Billing/PlanSelection/Plans/Plans.Constants.ts +++ b/studio/components/interfaces/Billing/PlanSelection/Plans/Plans.Constants.ts @@ -9,10 +9,11 @@ import { STRIPE_PRODUCT_IDS } from 'lib/constants' export const PRICING_META = { [STRIPE_PRODUCT_IDS.FREE]: { name: 'Free', - href: '#', - priceMonthly: 0, - warning: 'Limit of 2 free projects', description: 'Perfect for passion projects & simple websites.', + priceMonthly: 0, + priceUnit: 'per month per project', + warning: 'Limit of 2 free projects', + preface: 'Get started with:', features: [ 'Up to 500MB database & 1GB file storage', 'Up to 2GB bandwidth', @@ -25,17 +26,15 @@ export const PRICING_META = { '2 million Realtime messages', 'Community support', ], - additional: 'Free projects are paused after 1 week of inactivity.', - - cta: 'Get Started', + scale: 'Free projects are paused after 1 week of inactivity.', }, [STRIPE_PRODUCT_IDS.PRO]: { name: 'Pro', - href: '#', - from: true, - priceMonthly: 25, - warning: '+ any additional usage', description: 'For production applications with the option to scale.', + priceMonthly: 25, + priceUnit: 'per month per project', + warning: '+ any additional usage', + preface: 'Everything in the Free plan, plus:', features: [ '8GB database & 100GB file storage', '50GB bandwidth', @@ -50,19 +49,17 @@ export const PRICING_META = { 'No project pausing', 'Email support', ], - scale: 'Additional fees apply for usage and storage beyond the limits above.', - shutdown: '', - preface: 'Everything in the base plan, plus', additional: 'Need more? Turn off your spend cap to Pay As You Grow ', - cta: 'Get Started', + scale: 'Additional fees apply for usage and storage beyond the limits above.', }, [STRIPE_PRODUCT_IDS.TEAM]: { + new: true, name: 'Team', - href: '#', - from: true, - priceMonthly: 599, - warning: '+ usage', description: 'For collaborative teams with different permissions and access.', + priceMonthly: 599, + priceUnit: 'per month per organization', + warning: '+ usage', + preface: 'The following features will apply to all projects within the organization:', features: [ 'Usage-based pricing', '100,000 monthly active users included', @@ -75,10 +72,6 @@ export const PRICING_META = { '14 day backups', '28 day log retention', ], - scale: '', - shutdown: '', - additional: '', - cta: 'Get Started', }, Enterprise: { name: 'Enterprise', @@ -94,8 +87,5 @@ export const PRICING_META = { `On-premise support`, `24×7×365 premium enterprise support`, ], - scale: '', - shutdown: '', - cta: 'Contact Us', }, } diff --git a/studio/components/interfaces/Billing/PlanSelection/Plans/Plans.tsx b/studio/components/interfaces/Billing/PlanSelection/Plans/Plans.tsx deleted file mode 100644 index a08e0b5b14c..00000000000 --- a/studio/components/interfaces/Billing/PlanSelection/Plans/Plans.tsx +++ /dev/null @@ -1,154 +0,0 @@ -import { FC } from 'react' -import { IconCheck } from 'ui' - -import { STRIPE_PRODUCT_IDS } from 'lib/constants' -import { StripeProduct } from 'components/interfaces/Billing' -import PlanCTAButton from './PlanCTAButton' -import { PRICING_META } from './Plans.Constants' - -const AnimatedGradientBackground = () => ( -
-) - -const CurrentSubscriptionBanner = () => ( -
-

Current plan

-
-) - -interface Props { - plans: any[] - currentPlan?: StripeProduct - onSelectPlan: (plan: any) => void -} - -/** - * JSX below for the mapped plans is also very similar to www/pages/pricing - * TO DO: move to use shared components for these - * - * https://github.com/supabase/supabase/blob/master/www/pages/pricing/index.tsx - */ - -const Plans: FC = ({ plans, currentPlan, onSelectPlan }) => { - return ( - <> - {/* Existing available plans */} - {plans.map((plan) => { - const isCurrentPlan = - plan.id === currentPlan?.prod_id || - (plan.id === STRIPE_PRODUCT_IDS.PRO && currentPlan?.prod_id === STRIPE_PRODUCT_IDS.PRO) || - (plan.id === STRIPE_PRODUCT_IDS.PRO && currentPlan?.prod_id === STRIPE_PRODUCT_IDS.PAYG) - - return ( -
- {plan.id === STRIPE_PRODUCT_IDS.PRO && ( -
- -
- )} -
- {isCurrentPlan && } -
- - {PRICING_META[plan.id].name} - -
-
- {PRICING_META[plan.id].priceMonthly !== undefined ? ( - <> -
- {PRICING_META[plan.id].from && ( - From - )} - ${PRICING_META[plan.id].priceMonthly} - /mo -
- - ) : ( - Contact Us - )} -
- {PRICING_META[plan.id].warning && ( -
- {PRICING_META[plan.id].warning} -
- )} -
-
-
-
- -
-

{PRICING_META[plan.id].description}

-
-
-
- {PRICING_META[plan.id].preface && ( -

{PRICING_META[plan.id].preface}

- )} -
    - {PRICING_META[plan.id].features.map((feature) => ( -
  • -
    -
    -

    - {feature} -

    -
  • - ))} -
-
-
-
- {PRICING_META[plan.id].additional && ( -

{PRICING_META[plan.id].additional}

- )} - {PRICING_META[plan.id].scale && ( -

{PRICING_META[plan.id].scale}

- )} - {PRICING_META[plan.id].shutdown && ( -

{PRICING_META[plan.id].shutdown}

- )} -
-
-
-
-
- ) - })} - - ) -} - -export default Plans diff --git a/studio/tailwind.config.js b/studio/tailwind.config.js index a8cab68a3f1..21148b6013e 100644 --- a/studio/tailwind.config.js +++ b/studio/tailwind.config.js @@ -1,3 +1,4 @@ +const { repeat } = require('lodash') const ui = require('./../packages/config/ui.config.js') const blueGray = { @@ -120,6 +121,9 @@ module.exports = ui({ fontSize: { grid: '13px', }, + gridTemplateColumns: { + billingWithTeam: 'repeat(3, minmax(0, 1fr)) 0.9fr', + }, colors: { // gray: { ...gray }, // green: { ...green }, From cbdb7ab7773b8f5253d15eb1e8a49b437b696f64 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Thu, 12 Jan 2023 17:31:33 +0800 Subject: [PATCH 059/261] Fix back button on update page --- studio/components/interfaces/Billing/EnterpriseUpdate.tsx | 4 ++-- studio/components/interfaces/Billing/ProUpgrade.tsx | 4 ++-- studio/components/interfaces/Billing/TeamUpgrade.tsx | 4 ++-- studio/components/ui/BackButton.tsx | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/studio/components/interfaces/Billing/EnterpriseUpdate.tsx b/studio/components/interfaces/Billing/EnterpriseUpdate.tsx index 969b5d58993..692007e2ae3 100644 --- a/studio/components/interfaces/Billing/EnterpriseUpdate.tsx +++ b/studio/components/interfaces/Billing/EnterpriseUpdate.tsx @@ -228,12 +228,12 @@ const EnterpriseUpdate: FC = ({
-
+

Change your project's subscription

diff --git a/studio/components/interfaces/Billing/ProUpgrade.tsx b/studio/components/interfaces/Billing/ProUpgrade.tsx index b4cf2635051..87d181d188e 100644 --- a/studio/components/interfaces/Billing/ProUpgrade.tsx +++ b/studio/components/interfaces/Billing/ProUpgrade.tsx @@ -231,13 +231,13 @@ const ProUpgrade: FC = ({ >
-
+
onSelectBack()} />

Change your project's subscription

diff --git a/studio/components/interfaces/Billing/TeamUpgrade.tsx b/studio/components/interfaces/Billing/TeamUpgrade.tsx index 7cc18e7847a..1c310177f1d 100644 --- a/studio/components/interfaces/Billing/TeamUpgrade.tsx +++ b/studio/components/interfaces/Billing/TeamUpgrade.tsx @@ -223,13 +223,13 @@ const TeamUpgrade: FC = ({ >
-
+
onSelectBack()} />

Change your project's subscription

diff --git a/studio/components/ui/BackButton.tsx b/studio/components/ui/BackButton.tsx index 0ae4923d8e7..5460c16a203 100644 --- a/studio/components/ui/BackButton.tsx +++ b/studio/components/ui/BackButton.tsx @@ -2,7 +2,7 @@ import { Button, IconArrowLeft } from 'ui' export function BackButton({ onClick }: { onClick: () => void }) { return ( -
+
From 622d4141fd4d8931658ce00ff4514236e250c90a Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Fri, 13 Jan 2023 14:22:12 +0800 Subject: [PATCH 060/261] Update copyt writing as per feedback --- .../interfaces/Billing/PlanSelection/Plans/Plans.Constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/components/interfaces/Billing/PlanSelection/Plans/Plans.Constants.ts b/studio/components/interfaces/Billing/PlanSelection/Plans/Plans.Constants.ts index 29bf1fe3ff6..025e54ea8a7 100644 --- a/studio/components/interfaces/Billing/PlanSelection/Plans/Plans.Constants.ts +++ b/studio/components/interfaces/Billing/PlanSelection/Plans/Plans.Constants.ts @@ -55,7 +55,7 @@ export const PRICING_META = { [STRIPE_PRODUCT_IDS.TEAM]: { new: true, name: 'Team', - description: 'For collaborative teams with different permissions and access.', + description: 'Collaborate with different permissions and access patterns.', priceMonthly: 599, priceUnit: 'per month per organization', warning: '+ usage', From 31439e786083509a2322d927683e10d61a9dac06 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Fri, 13 Jan 2023 15:27:49 +0800 Subject: [PATCH 061/261] Fix SQL editor exporting json data as [object Object] --- .../to-be-cleaned/SqlEditor/TabSqlQuery.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/studio/components/to-be-cleaned/SqlEditor/TabSqlQuery.js b/studio/components/to-be-cleaned/SqlEditor/TabSqlQuery.js index 28888b61c59..c377465d834 100644 --- a/studio/components/to-be-cleaned/SqlEditor/TabSqlQuery.js +++ b/studio/components/to-be-cleaned/SqlEditor/TabSqlQuery.js @@ -246,6 +246,16 @@ const ResultsDropdown = observer(() => { } } + const formatDataForCSV = (data = []) => { + return data.map((row) => { + const formattedRow = { ...row } + Object.keys(row).forEach((key) => { + if (typeof row[key] === 'object') formattedRow[key] = JSON.stringify(row[key]) + }) + return formattedRow + }) + } + return ( { From 8cc319e8630c6463a249f3657f089fe2aafa6b17 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 17 Jan 2023 13:26:22 +0800 Subject: [PATCH 062/261] More refactors to reduce props drilldown, and implement expiry options in context menu --- .../layouts/StorageLayout/BucketRow.tsx | 73 ++++++++++++ .../layouts/StorageLayout/StorageMenu.tsx | 83 +------------- .../StorageExplorer/CustomExpiryModal.tsx | 80 +++++++++++++ .../Storage/StorageExplorer/FileExplorer.js | 20 +--- .../StorageExplorer/FileExplorerRow.tsx | 6 + .../StorageExplorer/ItemContextMenu.js | 108 +++++++++++++----- .../Storage/StorageExplorer/PreviewPane.tsx | 63 +--------- .../StorageExplorer/StorageExplorer.js | 2 + .../storageExplorer/StorageExplorerStore.js | 5 + studio/styles/contextMenu.scss | 3 + 10 files changed, 258 insertions(+), 185 deletions(-) create mode 100644 studio/components/layouts/StorageLayout/BucketRow.tsx create mode 100644 studio/components/to-be-cleaned/Storage/StorageExplorer/CustomExpiryModal.tsx diff --git a/studio/components/layouts/StorageLayout/BucketRow.tsx b/studio/components/layouts/StorageLayout/BucketRow.tsx new file mode 100644 index 00000000000..f96a5d96b4c --- /dev/null +++ b/studio/components/layouts/StorageLayout/BucketRow.tsx @@ -0,0 +1,73 @@ +import { FC } from 'react' +import { Badge, Dropdown, IconLoader, IconMoreVertical, IconTrash } from 'ui' + +import ProductMenuItem from 'components/ui/ProductMenu/ProductMenuItem' +import { STORAGE_ROW_STATUS } from 'components/to-be-cleaned/Storage/Storage.constants' + +interface Props { + bucket: any + projectRef?: string + isSelected: boolean + onSelectDeleteBucket: (bucket: any) => void + onSelectToggleBucketPublic: (bucket: any) => void +} + +const BucketRow: FC = ({ + bucket = {}, + projectRef = '', + isSelected = false, + onSelectDeleteBucket = () => {}, + onSelectToggleBucketPublic = () => {}, +}) => { + return ( + +

+ {bucket.name} +

+ {bucket.public && Public} +
+ } + url={`/project/${projectRef}/storage/buckets/${bucket.id}`} + isActive={isSelected} + action={ + bucket.status === STORAGE_ROW_STATUS.LOADING ? ( + + ) : bucket.status === STORAGE_ROW_STATUS.READY ? ( + onSelectToggleBucketPublic(bucket)} + > + {bucket.public ? 'Make private' : 'Make public'} + , + , + } + key="delete-bucket" + onClick={() => onSelectDeleteBucket(bucket)} + > + Delete bucket + , + ]} + > + + + ) : ( +
+ ) + } + /> + ) +} + +export default BucketRow diff --git a/studio/components/layouts/StorageLayout/StorageMenu.tsx b/studio/components/layouts/StorageLayout/StorageMenu.tsx index 2889292f42f..441e07c40a9 100644 --- a/studio/components/layouts/StorageLayout/StorageMenu.tsx +++ b/studio/components/layouts/StorageLayout/StorageMenu.tsx @@ -2,28 +2,18 @@ import { FC } from 'react' import Link from 'next/link' import { useRouter } from 'next/router' import { observer } from 'mobx-react-lite' -import { useStore } from 'hooks' -import { - Badge, - Button, - Dropdown, - Menu, - IconLoader, - IconMoreVertical, - Alert, - IconEdit, - IconTrash, -} from 'ui' +import { useParams } from 'hooks' +import { Button, Menu, IconLoader, Alert, IconEdit } from 'ui' -import ProductMenuItem from 'components/ui/ProductMenu/ProductMenuItem' -import { STORAGE_ROW_STATUS } from 'components/to-be-cleaned/Storage/Storage.constants' +import BucketRow from './BucketRow' import { useStorageStore } from 'localStores/storageExplorer/StorageExplorerStore' interface Props {} const StorageMenu: FC = () => { const router = useRouter() - const { ref, bucketId } = router.query + const { ref, bucketId } = useParams() + const page = router.pathname.split('/')[4] as | undefined | 'policies' @@ -31,9 +21,6 @@ const StorageMenu: FC = () => { | 'usage' | 'logs' - const { ui } = useStore() - const projectRef = ui.selectedProject?.ref - const storageExplorerStore = useStorageStore() const { loaded, @@ -98,7 +85,7 @@ const StorageMenu: FC = () => {
- +

Policies

@@ -110,61 +97,3 @@ const StorageMenu: FC = () => { } export default observer(StorageMenu) - -const BucketRow = ({ - bucket = {}, - projectRef = '', - isSelected = false, - onSelectDeleteBucket = () => {}, - onSelectToggleBucketPublic = () => {}, -}: any) => { - return ( - -

- {bucket.name} -

- {bucket.public && Public} -
- } - url={`/project/${projectRef}/storage/buckets/${bucket.id}`} - isActive={isSelected} - action={ - bucket.status === STORAGE_ROW_STATUS.LOADING ? ( - - ) : bucket.status === STORAGE_ROW_STATUS.READY ? ( - onSelectToggleBucketPublic(bucket)} - > - {bucket.public ? 'Make private' : 'Make public'} - , - , - } - key="delete-bucket" - onClick={() => onSelectDeleteBucket(bucket)} - > - Delete bucket - , - ]} - > - - - ) : ( -
- ) - } - /> - ) -} diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/CustomExpiryModal.tsx b/studio/components/to-be-cleaned/Storage/StorageExplorer/CustomExpiryModal.tsx new file mode 100644 index 00000000000..53e9ed0a043 --- /dev/null +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/CustomExpiryModal.tsx @@ -0,0 +1,80 @@ +import { FC } from 'react' +import { Form, Modal, Input, Button } from 'ui' +import { observer } from 'mobx-react-lite' +import { useStorageStore } from 'localStores/storageExplorer/StorageExplorerStore' + +interface Props {} + +const CustomExpiryModal: FC = () => { + const storageExplorerStore = useStorageStore() + const { selectedFileCustomExpiry, setSelectedFileCustomExpiry, copyFileURLToClipboard } = + storageExplorerStore + + const visible = selectedFileCustomExpiry !== undefined + const onClose = () => setSelectedFileCustomExpiry(undefined) + + console.log(selectedFileCustomExpiry) + + return ( + onClose()} + > +
{ + setSubmitting(true) + await copyFileURLToClipboard(selectedFileCustomExpiry, values.expiresIn) + setSubmitting(false) + onClose() + }} + validate={(values: any) => { + const errors: any = {} + if (values.expiresIn !== '' && values.expiresIn <= 0) + errors.expiresIn = 'Expiry duration cannot be less than 0' + return errors + }} + > + {({ values, isSubmitting }: { values: any; isSubmitting: boolean }) => ( + <> +
+ + seconds

} + /> +
+
+ + +
+ + +
+
+ + )} + +
+ ) +} + +export default observer(CustomExpiryModal) diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorer.js b/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorer.js index 21b30091755..4e0f9af240b 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorer.js +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorer.js @@ -1,4 +1,5 @@ import React, { useEffect, useRef } from 'react' +import { observer } from 'mobx-react-lite' import { STORAGE_VIEWS, CONTEXT_MENU_KEYS } from '../Storage.constants' import ItemContextMenu from './ItemContextMenu' @@ -25,13 +26,7 @@ const FileExplorer = ({ const fileExplorerRef = useRef(null) const storageExplorerStore = useStorageStore() - const { - downloadFile, - setSelectedItemsToDelete, - setSelectedItemToRename, - setSelectedItemsToMove, - copyFileURLToClipboard, - } = storageExplorerStore + const { setSelectedItemToRename } = storageExplorerStore useEffect(() => { if (fileExplorerRef) { @@ -55,14 +50,7 @@ const FileExplorer = ({ onSelectSort={onChangeSortBy} onSelectSortByOrder={onChangeSortByOrder} /> - setSelectedItemToRename(file)} - onSelectItemMove={(file) => setSelectedItemsToMove([file])} - onSelectItemDelete={(file) => setSelectedItemsToDelete([file])} - onCopyFileURL={async (file) => await copyFileURLToClipboard(file)} - onDownloadFile={async (file) => await downloadFile(file)} - /> + setSelectedItemToRename(folder)} @@ -110,4 +98,4 @@ const FileExplorer = ({ ) } -export default FileExplorer +export default observer(FileExplorer) diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerRow.tsx b/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerRow.tsx index fcb43622051..7795203fa55 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerRow.tsx +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerRow.tsx @@ -208,6 +208,12 @@ const FileExplorerRow: FC = ({ name: 'Get URL', icon: , onClick: async () => await copyFileURLToClipboard(itemWithColumnIndex), + children: [ + { name: 'Expire in 1 week' }, + { name: 'Expire in 1 month' }, + { name: 'Expire in 1 year' }, + { name: 'Custom expiry' }, + ], }, { name: 'Rename', diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/ItemContextMenu.js b/studio/components/to-be-cleaned/Storage/StorageExplorer/ItemContextMenu.js index 993fd661b14..1b948c8bf12 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/ItemContextMenu.js +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/ItemContextMenu.js @@ -1,43 +1,89 @@ -import { Menu, Item } from 'react-contexify' +import { Menu, Item, Separator, Submenu } from 'react-contexify' import 'react-contexify/dist/ReactContexify.css' +import { IconClipboard, IconEdit, IconMove, IconDownload, IconTrash2, IconChevronRight } from 'ui' +import { useStorageStore } from 'localStores/storageExplorer/StorageExplorerStore' +import { observer } from 'mobx-react-lite' + +const ItemContextMenu = ({ id = '' }) => { + const storageExplorerStore = useStorageStore() + const { + downloadFile, + selectedBucket, + setSelectedItemsToDelete, + setSelectedItemToRename, + setSelectedItemsToMove, + setSelectedFileCustomExpiry, + copyFileURLToClipboard, + } = storageExplorerStore + const isPublic = selectedBucket.public + + const onHandleClick = async (event, item, expiresIn) => { + if (item.isCorrupted) return + switch (event) { + case 'copy': + if (expiresIn < 0) return setSelectedFileCustomExpiry(item) + else return await copyFileURLToClipboard(item, expiresIn) + case 'rename': + return setSelectedItemToRename(item) + case 'move': + return setSelectedItemsToMove([item]) + case 'download': + return await downloadFile(item) + default: + break + } + } -const ItemContextMenu = ({ - id = '', - onCopyFileURL = () => {}, - onSelectItemRename = () => {}, - onSelectItemMove = () => {}, - onDownloadFile = () => {}, - onSelectItemDelete = () => {}, -}) => { return ( - (!props.item.isCorrupted ? onCopyFileURL(props.item) : () => {})} - > - Copy URL + {isPublic ? ( + onHandleClick('copy', props.item)}> + + Copy URL + + ) : ( + + + Copy URL +
+ } + arrow={} + > + onHandleClick('copy', props.item, 60 * 60 * 24 * 7)}> + Expire in 1 week + + onHandleClick('copy', props.item, 60 * 60 * 24 * 30)}> + Expire in 1 month + + onHandleClick('copy', props.item, 60 * 60 * 24 * 365)}> + Expire in 1 year + + onHandleClick('copy', props.item, -1)}> + Custom expiry + + + )} + onHandleClick('rename', props.item)}> + + Rename - - !props.item.isCorrupted ? onSelectItemRename(props.item) : () => {} - } - > - Rename + onHandleClick('move', props.item)}> + + Move - (!props.item.isCorrupted ? onSelectItemMove(props.item) : () => {})} - > - Move + onHandleClick('download', props.item)}> + + Download - (!props.item.isCorrupted ? onDownloadFile(props.item) : () => {})} - > - Download - - onSelectItemDelete(props.item)}> - Delete + + setSelectedItemsToDelete([props.item])}> + + Delete ) } -export default ItemContextMenu +export default observer(ItemContextMenu) diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx b/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx index 2b64d35abd9..d4b8d87b1d2 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx @@ -101,10 +101,9 @@ const PreviewPane = () => { copyFileURLToClipboard, closeFilePreview, setSelectedItemsToDelete, + setSelectedFileCustomExpiry, } = storageExplorerStore - const [showExpiryInput, setShowExpiryInput] = useState(false) - const width = 450 const isOpen = !isEmpty(file) const size = file.metadata ? formatBytes(file.metadata.size) : null @@ -220,7 +219,7 @@ const PreviewPane = () => { > Expire in 1 year , - setShowExpiryInput(true)}> + setSelectedFileCustomExpiry(file)}> Custom expiry , ]} @@ -248,64 +247,6 @@ const PreviewPane = () => {
- setShowExpiryInput(false)} - > -
{ - setSubmitting(true) - await copyFileURLToClipboard(file, values.expiresIn) - setSubmitting(false) - setShowExpiryInput(false) - }} - validate={(values: any) => { - const errors: any = {} - if (values.expiresIn !== '' && values.expiresIn <= 0) - errors.expiresIn = 'Expiry duration cannot be less than 0' - return errors - }} - > - {({ values, isSubmitting }: { values: any; isSubmitting: boolean }) => ( - <> -
- - seconds

} - /> -
-
- - -
- - -
-
- - )} - -
) } diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/StorageExplorer.js b/studio/components/to-be-cleaned/Storage/StorageExplorer/StorageExplorer.js index 53aa7b512e5..b19374ff0ba 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/StorageExplorer.js +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/StorageExplorer.js @@ -11,6 +11,7 @@ import FileExplorerHeaderSelection from './FileExplorerHeaderSelection' import ConfirmDeleteModal from './ConfirmDeleteModal' import MoveItemsModal from './MoveItemsModal' import PreviewPane from './PreviewPane' +import CustomExpiryModal from './CustomExpiryModal' const StorageExplorer = observer(({ bucket }) => { const storageExplorerStore = useStorageStore() @@ -213,6 +214,7 @@ const StorageExplorer = observer(({ bucket }) => { onSelectCancel={clearSelectedItemsToMove} onSelectMove={onMoveSelectedFiles} /> +
) }) diff --git a/studio/localStores/storageExplorer/StorageExplorerStore.js b/studio/localStores/storageExplorer/StorageExplorerStore.js index 323a51d10ca..b9a1742668b 100644 --- a/studio/localStores/storageExplorer/StorageExplorerStore.js +++ b/studio/localStores/storageExplorer/StorageExplorerStore.js @@ -58,6 +58,7 @@ class StorageExplorerStore { selectedItemsToDelete = [] selectedItemsToMove = [] selectedFilePreview = {} + selectedFileCustomExpiry = undefined DEFAULT_OPTIONS = { limit: LIMIT, @@ -300,6 +301,10 @@ class StorageExplorerStore { this.selectedItemsToMove = [] } + setSelectedFileCustomExpiry = (item) => { + this.selectedFileCustomExpiry = item + } + addNewFolderPlaceholder = (columnIndex) => { const isPrepend = true const folderName = 'Untitled folder' diff --git a/studio/styles/contextMenu.scss b/studio/styles/contextMenu.scss index 88d9680880d..418b2fcd9a0 100644 --- a/studio/styles/contextMenu.scss +++ b/studio/styles/contextMenu.scss @@ -4,6 +4,9 @@ .react-contexify__item__content { @apply dark:text-white text-sm; } + .react-contexify__submenu { + @apply dark:bg-gray-300 border; + } } .react-contexify__item:not(.react-contexify__item--disabled):hover > .react-contexify__item__content, From e957ac14c552190a054d2f1dfc8566932b0399be Mon Sep 17 00:00:00 2001 From: thorwebdev Date: Tue, 17 Jan 2023 14:32:00 +0800 Subject: [PATCH 063/261] feat: schedule functions guide. --- .../NavigationMenu.constants.ts | 1 + .../guides/database/extensions/pgcron.mdx | 26 +++++++++++- .../guides/database/extensions/pgnet.mdx | 27 +++++++++--- .../guides/functions/schedule-functions.mdx | 42 +++++++++++++++++++ 4 files changed, 88 insertions(+), 8 deletions(-) create mode 100644 apps/docs/pages/guides/functions/schedule-functions.mdx diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index 2cb41f3a054..3cf62dee058 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -429,6 +429,7 @@ export const functions = { { name: 'Import Maps', url: '/guides/functions/import-maps', items: [] }, { name: 'Local Development', url: '/guides/functions/local-development', items: [] }, { name: 'Managing Secrets', url: '/guides/functions/secrets', items: [] }, + { name: 'Schedule Functions', url: '/guides/functions/schedule-functions', items: [] }, { name: 'Examples', url: '/guides/functions/examples', diff --git a/apps/docs/pages/guides/database/extensions/pgcron.mdx b/apps/docs/pages/guides/database/extensions/pgcron.mdx index 57a704e444a..1062db0fd32 100644 --- a/apps/docs/pages/guides/database/extensions/pgcron.mdx +++ b/apps/docs/pages/guides/database/extensions/pgcron.mdx @@ -63,6 +63,8 @@ The schedule uses the standard cron syntax, in which \* means "run every time pe * * * * * ``` +You can use [crontab.guru](https://crontab.guru/) to help validate your cron schedules. + ## Examples ### Delete data every week @@ -71,8 +73,8 @@ Delete old data on Saturday at 3:30am (GMT): ```sql select cron.schedule ( - 'webhook-every-minute', -- name of the cron job - '* * * * *', -- every minute + 'saturday-cleanup', -- name of the cron job + '30 3 * * 6', -- Saturday at 3:30am (GMT) $$ delete from events where event_time < now() - interval '1 week' $$ ); ``` @@ -85,6 +87,26 @@ Vacuum every day at 3:00am (GMT) SELECT cron.schedule('nightly-vacuum', '0 3 * * *', 'VACUUM'); ``` +### Invoke Supabase Edge Function every minute + +Make a POST request to a Supabase Edge Function every minute. Note: this requires the [`pg_net` extension](/docs/guides/database/extensions/pgnet) to be enabled. + +```sql +select + cron.schedule( + 'invoke-function-every-minute', + '* * * * *', -- every minute + $$ + select + net.http_post( + url:='https://project-ref.functions.supabase.co/function-name', + headers:='{"Content-Type": "application/json", "Authorization": "Bearer YOUR_ANON_KEY"}'::jsonb, + body:=concat('{"time": "', now(), '"}')::jsonb + ) as request_id; + $$ + ); +``` + ### Unschedule a job Unschedules a job called `'nightly-vacuum'` diff --git a/apps/docs/pages/guides/database/extensions/pgnet.mdx b/apps/docs/pages/guides/database/extensions/pgnet.mdx index 29283ab4430..18c51a8dd2a 100644 --- a/apps/docs/pages/guides/database/extensions/pgnet.mdx +++ b/apps/docs/pages/guides/database/extensions/pgnet.mdx @@ -53,11 +53,11 @@ Procedural languages are automatically installed within `pg_catalog`, so you don -## `http_get` [#http_get] +## `http_get` Creates an HTTP GET request returning the request's ID. HTTP requests are not started until the transaction is committed. -### Signature +### Signature [#get-signature] @@ -86,7 +86,7 @@ net.http_get( language plpgsql ``` -### Usage +### Usage [#get-usage] ```sql select net.http_get('https://news.ycombinator.com') as request_id; @@ -98,13 +98,13 @@ request_id After triggering `http_get`, use [`http_get_result`](#http_get_result) to get the result of the request. -## `http_post` [#http_post] +## `http_post` Creates an HTTP POST request with a JSON body, returning the request's ID. HTTP requests are not started until the transaction is committed. The body's character set encoding matches the database's `server_encoding` setting. -### Signature +### Signature [#post-signature] @@ -134,7 +134,7 @@ net.http_post( language plpgsql ``` -### Usage +### Usage [#post-usage] ```sql select @@ -150,6 +150,21 @@ request_id After triggering `http_post`, use [`http_get_result`](#http_get_result) to get the result of the request. +## Examples + +### Invoke a Supabase Edge Function + +Make a POST request to a Supabase Edge Function with auth header and JSON body payload: + +```sql +select + net.http_post( + url:='https://project-ref.functions.supabase.co/function-name', + headers:='{"Content-Type": "application/json", "Authorization": "Bearer YOUR_ANON_KEY"}'::jsonb, + body:='{"name": "pg_net"}'::jsonb + ) as request_id; +``` + ## Resources - Source code: [github.com/supabase/pg_net](https://github.com/supabase/pg_net/) diff --git a/apps/docs/pages/guides/functions/schedule-functions.mdx b/apps/docs/pages/guides/functions/schedule-functions.mdx new file mode 100644 index 00000000000..c5108b55523 --- /dev/null +++ b/apps/docs/pages/guides/functions/schedule-functions.mdx @@ -0,0 +1,42 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'schedule-functions', + title: 'Schedule Functions', + description: 'Schedule Functions with pg_cron.', +} + +The hosted Supabase Platform supports the [`pg_cron` extension](/docs/guides/database/extensions/pgcron), a simple cron-based job scheduler for PostgreSQL that runs inside the database. + +In combination with the [`pg_net` extension](/docs/guides/database/extensions/pgnet), this allows us to invoke functions periodically on a set schedule. + +## Examples + +### Invoke Function every minute + +Make a POST request to a Supabase Edge Function every minute: + +```sql +select + cron.schedule( + 'invoke-function-every-minute', + '* * * * *', -- every minute + $$ + select + net.http_post( + url:='https://project-ref.functions.supabase.co/function-name', + headers:='{"Content-Type": "application/json", "Authorization": "Bearer YOUR_ANON_KEY"}'::jsonb, + body:=concat('{"time": "', now(), '"}')::jsonb + ) as request_id; + $$ + ); +``` + +## Resources + +- [`pg_net` extension](/docs/guides/database/extensions/pgnet) +- [`pg_cron` extension](/docs/guides/database/extensions/pgcron) + +export const Page = ({ children }) => + +export default Page From f9c388db2de2e848b084086671f501be38504398 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Fri, 13 Jan 2023 17:37:59 +0800 Subject: [PATCH 064/261] Allow one screenshot to be uploaded in the feedback widget --- .../FeedbackDropdown/FeedbackDropdown.tsx | 5 +- .../FeedbackDropdown/FeedbackWidget.tsx | 80 ++++++++++++------- 2 files changed, 57 insertions(+), 28 deletions(-) diff --git a/studio/components/layouts/ProjectLayout/LayoutHeader/FeedbackDropdown/FeedbackDropdown.tsx b/studio/components/layouts/ProjectLayout/LayoutHeader/FeedbackDropdown/FeedbackDropdown.tsx index 0f4944909fc..f083736a6cb 100644 --- a/studio/components/layouts/ProjectLayout/LayoutHeader/FeedbackDropdown/FeedbackDropdown.tsx +++ b/studio/components/layouts/ProjectLayout/LayoutHeader/FeedbackDropdown/FeedbackDropdown.tsx @@ -20,7 +20,10 @@ const FeedbackDropdown = () => { return ( setIsOpen(e)} + onOpenChange={(e) => { + setIsOpen(e) + if (!e) setScreenshot(undefined) + }} size="content" side="bottom" align="end" diff --git a/studio/components/layouts/ProjectLayout/LayoutHeader/FeedbackDropdown/FeedbackWidget.tsx b/studio/components/layouts/ProjectLayout/LayoutHeader/FeedbackDropdown/FeedbackWidget.tsx index a8defc57682..8dc1a5999f7 100644 --- a/studio/components/layouts/ProjectLayout/LayoutHeader/FeedbackDropdown/FeedbackWidget.tsx +++ b/studio/components/layouts/ProjectLayout/LayoutHeader/FeedbackDropdown/FeedbackWidget.tsx @@ -1,14 +1,14 @@ import Link from 'next/link' -import { useState, useEffect, useRef, FC } from 'react' +import { useState, useEffect, useRef, FC, ChangeEvent } from 'react' import { useRouter } from 'next/router' -import * as Tooltip from '@radix-ui/react-tooltip' import { toPng } from 'html-to-image' -import { Button, Input, Popover, IconCamera, IconX } from 'ui' +import { Button, Input, Popover, IconCamera, IconX, IconImage, Dropdown, IconUpload } from 'ui' import { useStore } from 'hooks' import { post } from 'lib/common/fetch' import { API_URL } from 'lib/constants' import { convertB64toBlob, uploadAttachment } from './FeedbackDropdown.utils' +import { timeout } from 'lib/helpers' interface Props { onClose: () => void @@ -30,6 +30,7 @@ const FeedbackWidget: FC = ({ const { ui } = useStore() const inputRef = useRef(null) + const uploadButtonRef = useRef() const [isSending, setSending] = useState(false) const [isSavingScreenshot, setIsSavingScreenshot] = useState(false) @@ -42,7 +43,7 @@ const FeedbackWidget: FC = ({ setFeedback(e.target.value) } - const captureScreenshot = () => { + const captureScreenshot = async () => { setIsSavingScreenshot(true) function filter(node: HTMLElement) { @@ -52,6 +53,8 @@ const FeedbackWidget: FC = ({ return true } + // Give time for dropdown to close + await timeout(100) toPng(document.body, { filter }) .then((dataUrl: any) => setScreenshot(dataUrl)) .catch((error: any) => { @@ -67,6 +70,18 @@ const FeedbackWidget: FC = ({ }) } + const onFilesUpload = async (event: ChangeEvent) => { + event.persist() + const [file] = event.target.files || (event as any).dataTransfer.items + + const reader = new FileReader() + reader.onload = function (event) { + setScreenshot(event.target?.result as string) + } + reader.readAsDataURL(file) + event.target.value = '' + } + const sendFeedback = async () => { if (feedback.length === 0 && screenshot !== undefined) { return ui.setNotification({ @@ -138,33 +153,44 @@ const FeedbackWidget: FC = ({
) : ( - - - From 32190dbad8cceed46c2c07b06e211f3619abdd66 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Fri, 13 Jan 2023 17:39:51 +0800 Subject: [PATCH 065/261] Small style fix --- .../LayoutHeader/FeedbackDropdown/FeedbackWidget.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/components/layouts/ProjectLayout/LayoutHeader/FeedbackDropdown/FeedbackWidget.tsx b/studio/components/layouts/ProjectLayout/LayoutHeader/FeedbackDropdown/FeedbackWidget.tsx index 8dc1a5999f7..a8b8ec910f7 100644 --- a/studio/components/layouts/ProjectLayout/LayoutHeader/FeedbackDropdown/FeedbackWidget.tsx +++ b/studio/components/layouts/ProjectLayout/LayoutHeader/FeedbackDropdown/FeedbackWidget.tsx @@ -112,7 +112,7 @@ const FeedbackWidget: FC = ({ } return ( -
+
Date: Tue, 17 Jan 2023 15:15:42 +0800 Subject: [PATCH 066/261] Implement expiry options in file explorer row menu --- .../Storage/Storage.constants.ts | 6 ++ .../StorageExplorer/CustomExpiryModal.tsx | 2 - .../StorageExplorer/FileExplorerRow.tsx | 61 +++++++++++++++++-- .../StorageExplorer/ItemContextMenu.js | 20 ++++-- .../Storage/StorageExplorer/PreviewPane.tsx | 19 +++--- 5 files changed, 86 insertions(+), 22 deletions(-) diff --git a/studio/components/to-be-cleaned/Storage/Storage.constants.ts b/studio/components/to-be-cleaned/Storage/Storage.constants.ts index 2620cf9fb2d..94fecf605f3 100644 --- a/studio/components/to-be-cleaned/Storage/Storage.constants.ts +++ b/studio/components/to-be-cleaned/Storage/Storage.constants.ts @@ -1,3 +1,9 @@ +export const URL_EXPIRY_DURATION = { + WEEK: 60 * 60 * 24 * 7, + MONTH: 60 * 60 * 24 * 30, + YEAR: 60 * 60 * 24 * 365, +} + export const STORAGE_VIEWS = { COLUMNS: 'COLUMNS', LIST: 'LIST', diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/CustomExpiryModal.tsx b/studio/components/to-be-cleaned/Storage/StorageExplorer/CustomExpiryModal.tsx index 53e9ed0a043..08b26f795de 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/CustomExpiryModal.tsx +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/CustomExpiryModal.tsx @@ -13,8 +13,6 @@ const CustomExpiryModal: FC = () => { const visible = selectedFileCustomExpiry !== undefined const onClose = () => setSelectedFileCustomExpiry(undefined) - console.log(selectedFileCustomExpiry) - return ( = ({ setSelectedItemsToDelete, setSelectedItemToRename, setSelectedItemsToMove, + setSelectedFileCustomExpiry, fetchFolderContents, downloadFile, downloadFolder, @@ -207,12 +210,29 @@ const FileExplorerRow: FC = ({ { name: 'Get URL', icon: , - onClick: async () => await copyFileURLToClipboard(itemWithColumnIndex), children: [ - { name: 'Expire in 1 week' }, - { name: 'Expire in 1 month' }, - { name: 'Expire in 1 year' }, - { name: 'Custom expiry' }, + { + name: 'Expire in 1 week', + onClick: async () => + await copyFileURLToClipboard(itemWithColumnIndex, URL_EXPIRY_DURATION.WEEK), + }, + { + name: 'Expire in 1 month', + onClick: async () => + await copyFileURLToClipboard( + itemWithColumnIndex, + URL_EXPIRY_DURATION.MONTH + ), + }, + { + name: 'Expire in 1 year', + onClick: async () => + await copyFileURLToClipboard(itemWithColumnIndex, URL_EXPIRY_DURATION.YEAR), + }, + { + name: 'Custom expiry', + onClick: async () => setSelectedFileCustomExpiry(itemWithColumnIndex), + }, ], }, { @@ -382,7 +402,36 @@ const FileExplorerRow: FC = ({ align="end" overlay={[ rowOptions.map((option) => { - if (option.name === 'Separator') { + if ((option?.children ?? []).length > 0) { + return ( + { + return ( + + {child.name} + + ) + })} + > +
+
+ {option.icon} +

{option.name}

+
+ +
+
+ ) + } else if (option.name === 'Separator') { return } else { return ( diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/ItemContextMenu.js b/studio/components/to-be-cleaned/Storage/StorageExplorer/ItemContextMenu.js index 1b948c8bf12..eb48047e271 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/ItemContextMenu.js +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/ItemContextMenu.js @@ -1,8 +1,10 @@ +import { observer } from 'mobx-react-lite' import { Menu, Item, Separator, Submenu } from 'react-contexify' import 'react-contexify/dist/ReactContexify.css' import { IconClipboard, IconEdit, IconMove, IconDownload, IconTrash2, IconChevronRight } from 'ui' + +import { URL_EXPIRY_DURATION } from '../Storage.constants' import { useStorageStore } from 'localStores/storageExplorer/StorageExplorerStore' -import { observer } from 'mobx-react-lite' const ItemContextMenu = ({ id = '' }) => { const storageExplorerStore = useStorageStore() @@ -39,25 +41,31 @@ const ItemContextMenu = ({ id = '' }) => { {isPublic ? ( onHandleClick('copy', props.item)}> - Copy URL + Get URL ) : ( - Copy URL + Get URL
} arrow={} > - onHandleClick('copy', props.item, 60 * 60 * 24 * 7)}> + onHandleClick('copy', props.item, URL_EXPIRY_DURATION.WEEK)} + > Expire in 1 week - onHandleClick('copy', props.item, 60 * 60 * 24 * 30)}> + onHandleClick('copy', props.item, URL_EXPIRY_DURATION.MONTH)} + > Expire in 1 month - onHandleClick('copy', props.item, 60 * 60 * 24 * 365)}> + onHandleClick('copy', props.item, URL_EXPIRY_DURATION.YEAR)} + > Expire in 1 year onHandleClick('copy', props.item, -1)}> diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx b/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx index d4b8d87b1d2..205e6fbbba6 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx @@ -1,9 +1,8 @@ +import Link from 'next/link' import { isEmpty } from 'lodash' -import { useState } from 'react' import { Button, Dropdown, - Form, IconX, IconLoader, IconClipboard, @@ -11,13 +10,11 @@ import { IconTrash2, IconAlertCircle, IconChevronDown, - Modal, - Input, } from 'ui' import SVG from 'react-inlinesvg' import { formatBytes } from 'lib/helpers' import { Transition } from '@headlessui/react' -import Link from 'next/link' +import { URL_EXPIRY_DURATION } from '../Storage.constants' import { useStorageStore } from 'localStores/storageExplorer/StorageExplorerStore' const PreviewFile = ({ mimeType, previewUrl }: { mimeType: string; previewUrl: string }) => { @@ -205,17 +202,23 @@ const PreviewPane = () => { align="center" overlay={[ await copyFileURLToClipboard(file, 60 * 60 * 24 * 7)} + onClick={async () => + await copyFileURLToClipboard(file, URL_EXPIRY_DURATION.WEEK) + } > Expire in 1 week , await copyFileURLToClipboard(file, 60 * 60 * 24 * 30)} + onClick={async () => + await copyFileURLToClipboard(file, URL_EXPIRY_DURATION.MONTH) + } > Expire in 1 month , await copyFileURLToClipboard(file, 60 * 60 * 24 * 365)} + onClick={async () => + await copyFileURLToClipboard(file, URL_EXPIRY_DURATION.YEAR) + } > Expire in 1 year , From f052e13c6d2d4c46e2dcd6b0a9de983ba4e0d45d Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 17 Jan 2023 15:21:27 +0800 Subject: [PATCH 067/261] Shift position of refresh button on table editor --- studio/components/grid/components/header/Header.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/studio/components/grid/components/header/Header.tsx b/studio/components/grid/components/header/Header.tsx index 1b3e8ae89bc..4ad75e4eaef 100644 --- a/studio/components/grid/components/header/Header.tsx +++ b/studio/components/grid/components/header/Header.tsx @@ -40,10 +40,7 @@ const Header: FC = ({ sorts, filters, onAddColumn, onAddRow, header onAddRow={onAddRow} /> )} -
- - {headerActions} -
+
{headerActions}
) } @@ -77,6 +74,7 @@ const DefaultHeader: FC = ({ sorts, filters, onAddColumn, on return (
+
From 3c410b9d1a94ff223798dbf07a4956ffdfb2d32c Mon Sep 17 00:00:00 2001 From: Inian Date: Tue, 17 Jan 2023 19:38:08 +0800 Subject: [PATCH 068/261] fix: switch to aws roles --- .github/workflows/mirror.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/mirror.yml b/.github/workflows/mirror.yml index 0a4d1b1fd57..47563fba1ef 100644 --- a/.github/workflows/mirror.yml +++ b/.github/workflows/mirror.yml @@ -19,12 +19,16 @@ jobs: permissions: contents: read packages: write + id-token: write steps: + - name: configure aws credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + role-to-assume: ${{ secrets.PROD_AWS_ROLE }} + aws-region: us-east-1 - uses: docker/login-action@v2 with: registry: public.ecr.aws - username: ${{ secrets.PROD_ACCESS_KEY_ID }} - password: ${{ secrets.PROD_SECRET_ACCESS_KEY }} - uses: docker/login-action@v2 with: registry: ghcr.io From 391152b1c7e61b70cdb0a195f9eccb0f2bfda435 Mon Sep 17 00:00:00 2001 From: Rodrigo Mansueli Nunes Date: Tue, 17 Jan 2023 10:50:56 -0300 Subject: [PATCH 069/261] Update auth-azure.mdx Update to v2, fix typo in code. --- .../pages/guides/auth/social-login/auth-azure.mdx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/apps/docs/pages/guides/auth/social-login/auth-azure.mdx b/apps/docs/pages/guides/auth/social-login/auth-azure.mdx index d75a365b1d5..87104fdfb94 100644 --- a/apps/docs/pages/guides/auth/social-login/auth-azure.mdx +++ b/apps/docs/pages/guides/auth/social-login/auth-azure.mdx @@ -74,13 +74,12 @@ When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/aut ```js async function signInWithAzure() { - const { data, error } = await supabase.auth.signInWithOAuth( - { - provider: 'azure', - }, - { - scopes: 'email', + const { data, error } = await supabase.auth.signInWithOAuth({ + provider: 'azure', + options: { + scopes: 'email' } + } ) } ``` From baad78bff74f1f9d594648997f5fe626f102ff23 Mon Sep 17 00:00:00 2001 From: fenos Date: Tue, 17 Jan 2023 14:16:47 +0000 Subject: [PATCH 070/261] doc: automatic image optimisation detection --- .../guides/storage/image-transformations.mdx | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/apps/docs/pages/guides/storage/image-transformations.mdx b/apps/docs/pages/guides/storage/image-transformations.mdx index 98e3af03a79..0c558459810 100644 --- a/apps/docs/pages/guides/storage/image-transformations.mdx +++ b/apps/docs/pages/guides/storage/image-transformations.mdx @@ -59,6 +59,32 @@ supabase.storage.from('bucket').download('image.jpg', { }) ``` +## Automatic Image Optimisation Detection (WebP) + +When using the image transformation API we will automatically find the best format supported by the browser and return that to the client, without any code change. +For instance, if you use Chrome when viewing a jpeg image and using transformation options, you'll see that the content-type returned is `webp`. + +In result, this will lower the bandwidth that you send to your users and your application will load much faster. + + + We currently only support WebP, AVIF support will come in the near future + + +**Disabling automatic optimisation:** + +In case you'd like to return the original format of the image and **opt-out** from the automatic image optimisation detection, you can pass the `format=origin` parameter when requesting a transformed image, +this is also supported in the SDK starting from v2.2.0 + +```ts +await storage.from('bucket').download('image.jpeg', { + transform: { + width: 200, + height: 200, + format: 'origin', + }, +}) +``` + ## NextJS Loader You can use Supabase Image Transformation to optimise your NextJS images using a custom [Loader](https://nextjs.org/docs/api-reference/next/image#loader-configuration). From 2e54c98294b5a4894aa75d8979612447f4d8b51f Mon Sep 17 00:00:00 2001 From: Rodrigo Mansueli Nunes Date: Tue, 17 Jan 2023 11:29:26 -0300 Subject: [PATCH 071/261] Update apps/docs/pages/guides/auth/social-login/auth-azure.mdx Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- apps/docs/pages/guides/auth/social-login/auth-azure.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/pages/guides/auth/social-login/auth-azure.mdx b/apps/docs/pages/guides/auth/social-login/auth-azure.mdx index 87104fdfb94..e73b8a01103 100644 --- a/apps/docs/pages/guides/auth/social-login/auth-azure.mdx +++ b/apps/docs/pages/guides/auth/social-login/auth-azure.mdx @@ -75,7 +75,7 @@ When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/aut ```js async function signInWithAzure() { const { data, error } = await supabase.auth.signInWithOAuth({ - provider: 'azure', + provider: 'azure', options: { scopes: 'email' } From 009944c8b424280f4de08c6adb01a7333cec6a21 Mon Sep 17 00:00:00 2001 From: Rodrigo Mansueli Nunes Date: Tue, 17 Jan 2023 11:29:31 -0300 Subject: [PATCH 072/261] Update apps/docs/pages/guides/auth/social-login/auth-azure.mdx Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- apps/docs/pages/guides/auth/social-login/auth-azure.mdx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/docs/pages/guides/auth/social-login/auth-azure.mdx b/apps/docs/pages/guides/auth/social-login/auth-azure.mdx index e73b8a01103..57f398456b0 100644 --- a/apps/docs/pages/guides/auth/social-login/auth-azure.mdx +++ b/apps/docs/pages/guides/auth/social-login/auth-azure.mdx @@ -77,10 +77,9 @@ async function signInWithAzure() { const { data, error } = await supabase.auth.signInWithOAuth({ provider: 'azure', options: { - scopes: 'email' - } - } - ) + scopes: 'email', + }, + }) } ``` From 56a71fa1b3147bd8209bf2b774c58057c3249fdf Mon Sep 17 00:00:00 2001 From: dng Date: Tue, 17 Jan 2023 11:32:05 -0800 Subject: [PATCH 073/261] Update apps/www/pages/realtime/Realtime.tsx --- apps/www/pages/realtime/Realtime.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/www/pages/realtime/Realtime.tsx b/apps/www/pages/realtime/Realtime.tsx index 890332c1478..633b7bc7bae 100644 --- a/apps/www/pages/realtime/Realtime.tsx +++ b/apps/www/pages/realtime/Realtime.tsx @@ -238,7 +238,7 @@ function RealtimePage() {

, ]} // [TODO] Point to the correct docs URL - documentation_link={'/docs/guides/realtime/broadcast'} + documentation_link={'/docs/guides/realtime#broadcast'} /> From a1c8096af318837c3e4c05d49834e22c2e786d09 Mon Sep 17 00:00:00 2001 From: Isaiah Hamilton Date: Tue, 17 Jan 2023 20:01:42 -0500 Subject: [PATCH 074/261] titles are now capitalized --- supabase/functions/og-images/component/Docs.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/supabase/functions/og-images/component/Docs.tsx b/supabase/functions/og-images/component/Docs.tsx index eef40786d56..a8616368c26 100644 --- a/supabase/functions/og-images/component/Docs.tsx +++ b/supabase/functions/og-images/component/Docs.tsx @@ -42,7 +42,7 @@ const Docs = (props: Props) => {
-

{title}

+

{title.substring(0, 1).toUpperCase() + title.substring(1)}

{description}

@@ -54,4 +54,4 @@ const Docs = (props: Props) => { ) } -export default Docs \ No newline at end of file +export default Docs From 9f8bfaac75dde3e275e40bf88a3b4a4e7af720a2 Mon Sep 17 00:00:00 2001 From: Isaiah Hamilton Date: Tue, 17 Jan 2023 20:26:53 -0500 Subject: [PATCH 075/261] updating numbers --- apps/www/data/Community.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/www/data/Community.tsx b/apps/www/data/Community.tsx index 4a48cca53dd..ea9573cdcc4 100644 --- a/apps/www/data/Community.tsx +++ b/apps/www/data/Community.tsx @@ -30,7 +30,7 @@ const data: CommunityItem[] = [ }, { title: 'GitHub', - stat: '42,000+', + stat: '44,000+', statLabel: 'GitHub stars', img: 'github.png', invertImgDarkMode: true, @@ -42,7 +42,7 @@ const data: CommunityItem[] = [ }, { title: 'Twitter', - stat: '50,000+', + stat: '54,000+', statLabel: 'Followers', img: 'twitter.png', detail: () => ( @@ -53,7 +53,7 @@ const data: CommunityItem[] = [ }, { title: 'Discord', - stat: '10,000+', + stat: '12,000+', statLabel: 'SupaTroopers', img: 'discord.png', detail: () => ( From ad432c7382cde35110d829376327aaf9f199dabc Mon Sep 17 00:00:00 2001 From: Isaiah Hamilton Date: Tue, 17 Jan 2023 21:09:05 -0500 Subject: [PATCH 076/261] update docs jwt generator ui --- apps/docs/components/JwtGenerator.js | 15 +++++++-------- apps/docs/styles/main.scss | 16 ++++++++++++++++ 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/apps/docs/components/JwtGenerator.js b/apps/docs/components/JwtGenerator.js index 46dd4758e31..55fedafec05 100644 --- a/apps/docs/components/JwtGenerator.js +++ b/apps/docs/components/JwtGenerator.js @@ -1,7 +1,7 @@ import React, { useState } from 'react' import KJUR from 'jsrsasign' import CodeBlock from './CodeBlock/CodeBlock' -import { Button } from 'ui' +import { Button, Select, Input } from 'ui' const JWT_HEADER = { alg: 'HS256', typ: 'JWT' } const now = new Date() @@ -46,9 +46,8 @@ export default function JwtGenerator({}) {
-
- + ANON_KEY + SERVICE_KEY +