Merge branch 'master' into pr/13270

This commit is contained in:
Jonathan Summers-Muir committed 2023-04-01 03:47:20 +08:00
commit b88c260516
144 files changed
+2012 -1177

No files matched your search

@@ -80,6 +80,7 @@ export const menuItems: NavMenu = {
label: 'Auth',
items: [
{ name: 'Overview', url: '/guides/auth', items: [] },
{ name: 'Quickstarts', url: '/guides/auth/quickstarts/react', items: [] },
{
name: 'Authentication',
url: undefined,
@@ -174,14 +175,6 @@ export const menuItems: NavMenu = {
{ name: 'Database Webhooks', url: '/guides/database/webhooks', items: [] },
{ name: 'Full Text Search', url: '/guides/database/full-text-search', items: [] },
{ name: 'Database Testing', url: '/guides/database/testing', items: [] },
{
name: 'Serverless APIs',
url: undefined,
items: [
{ name: 'Overview', url: '/guides/database/api', items: [] },
{ name: 'Generating Types', url: '/guides/database/api/generating-types', items: [] },
],
},
{
name: 'Extensions',
url: undefined,
@@ -228,6 +221,13 @@ export const menuItems: NavMenu = {
},
],
},
{
label: 'Serverless APIs',
items: [
{ name: 'Overview', url: '/guides/api', items: [] },
{ name: 'Generating Types', url: '/guides/api/generating-types', items: [] },
],
},
{
label: 'Edge Functions',
items: [
@@ -388,7 +388,6 @@ export const menuItems: NavMenu = {
url: undefined,
items: [
{ name: 'Appsmith', url: '/guides/integrations/appsmith', items: [] },
{ name: 'Dashibase', url: '/guides/integrations/dashibase', items: [] },
{ name: 'DhiWise', url: '/guides/integrations/dhiwise', items: [] },
{ name: 'Directus', url: '/guides/integrations/directus', items: [] },
{ name: 'Draftbit', url: '/guides/integrations/draftbit', items: [] },
@@ -27,6 +27,12 @@ const home = [
href: '/guides/database',
level: 'database',
},
{
label: 'Serverless APIs',
icon: '/img/icons/menu/reference',
href: '/guides/api',
level: 'api',
},
{
label: 'Auth',
icon: '/img/icons/menu/auth',
@@ -131,7 +131,7 @@ export const gettingstarted = {
}
export const cli = {
label: 'CLI',
title: 'CLI',
items: [
{ name: 'Overview', url: '/guides/cli' },
{ name: 'Local Development', url: '/guides/cli/local-development' },
@@ -253,12 +253,16 @@ export const PhoneLoginsItems = [
export const auth = {
icon: 'auth',
label: 'Auth',
title: 'Auth',
items: [
{
name: 'Overview',
url: '/guides/auth',
},
{
name: 'Quickstarts',
items: [{ name: 'Auth with React', url: '/guides/auth/quickstarts/react', items: [] }],
},
{
name: 'Authentication',
url: undefined,
@@ -339,7 +343,7 @@ export const auth = {
export const database = {
icon: 'database',
label: 'Database',
title: 'Database',
url: '/guides/database',
items: [
{ name: 'Database Connections', url: '/guides/database/connecting-to-postgres' },
@@ -348,14 +352,6 @@ export const database = {
{ name: 'Database Webhooks', url: '/guides/database/webhooks' },
{ name: 'Full Text Search', url: '/guides/database/full-text-search' },
{ name: 'Database Testing', url: '/guides/database/testing' },
{
name: 'Serverless APIs',
url: undefined,
items: [
{ name: 'Overview', url: '/guides/database/api' },
{ name: 'Generating Types', url: '/guides/database/api/generating-types' },
],
},
{
name: 'Extensions',
url: undefined,
@@ -496,9 +492,47 @@ export const database = {
],
}
export const api = {
icon: 'reference',
title: 'Serverless APIs',
url: '/guides/api',
items: [
{ name: 'Overview', url: '/guides/api', items: [] },
{ name: 'Quickstart', url: '/guides/api/quickstart', items: [] },
{
name: 'Guides',
url: '/guides/api',
items: [
{ name: 'Creating API routes', url: '/guides/api/creating-routes', items: [] },
{ name: 'How API Keys work', url: '/guides/api/api-keys', items: [] },
{ name: 'Securing your API', url: '/guides/api/securing-your-api', items: [] },
{
name: 'Querying joins and nested tables',
url: '/guides/api/joins-and-nesting',
items: [],
},
],
},
{
name: 'REST & REALTIME',
url: undefined,
items: [
{ name: 'Auto-generated Docs', url: '/guides/api/rest/auto-generated-docs', items: [] },
{ name: 'Client Libraries', url: '/guides/api/rest/client-libs', items: [] },
{ name: 'Generating Types', url: '/guides/api/rest/generating-types', items: [] },
],
},
{
name: 'GRAPHQL',
url: undefined,
items: [{ name: 'GraphiQL Documentation', url: '/guides/api/graphql/graphiql', items: [] }],
},
],
}
export const functions = {
icon: 'functions',
label: 'Edge Functions',
title: 'Edge Functions',
url: '/guides/functions',
items: [
{
@@ -565,7 +599,7 @@ export const functions = {
export const realtime = {
icon: 'realtime',
label: 'Realtime',
title: 'Realtime',
url: '/guides/realtime',
items: [
{
@@ -628,7 +662,7 @@ export const realtime = {
export const storage = {
icon: 'storage',
label: 'Storage',
title: 'Storage',
url: '/guides/storage',
items: [
{ name: 'Overview', url: '/guides/storage' },
@@ -660,7 +694,7 @@ export const supabase_cli = {
export const platform = {
icon: 'platform',
label: 'Platform',
title: 'Platform',
url: '/guides/platform',
items: [
{
@@ -732,7 +766,7 @@ export const platform = {
export const resources = {
icon: 'resources',
label: 'Resources',
title: 'Resources',
url: '/guides/resources',
items: [
{ name: 'Examples', url: '/guides/resources/examples' },
@@ -798,7 +832,7 @@ export const self_hosting = {
}
export const migrate = {
label: 'Migrate to Supabase',
title: 'Migrate to Supabase',
url: '/guides/migrate',
items: [
{ name: 'Firebase Auth', url: '/guides/migrations/firebase-auth' },
@@ -811,7 +845,7 @@ export const migrate = {
export const integrations = {
icon: 'integrations',
label: 'Integrations',
title: 'Integrations',
url: '/guides/integrations',
items: [
{ name: 'Overview', url: '/guides/integrations/integrations' },
@@ -856,7 +890,6 @@ export const integrations = {
url: undefined,
items: [
{ name: 'Appsmith', url: '/guides/integrations/appsmith' },
{ name: 'Dashibase', url: '/guides/integrations/dashibase' },
{ name: 'DhiWise', url: '/guides/integrations/dhiwise' },
{ name: 'Directus', url: '/guides/integrations/directus' },
{ name: 'Draftbit', url: '/guides/integrations/draftbit' },
@@ -85,6 +85,9 @@ const NavigationMenu = () => {
case url.includes(`/docs/guides/database`) && url:
menuState.setMenuLevelId('database')
break
case url.includes(`/docs/guides/api`) && url:
menuState.setMenuLevelId('api')
break
case url.includes(`/docs/guides/auth`) && url:
menuState.setMenuLevelId('auth')
break
@@ -174,6 +177,7 @@ const NavigationMenu = () => {
const isHomeActive = 'home' === level
const isGettingStartedActive = 'gettingstarted' === level
const isDatabaseActive = 'database' === level
const isApiActive = 'api' === level
const isAuthActive = 'auth' === level
const isFunctionsActive = 'functions' === level
const isRealtimeActive = 'realtime' === level
@@ -203,6 +207,7 @@ const NavigationMenu = () => {
<NavigationMenuHome active={isHomeActive} />
<NavigationMenuGuideList id={'gettingstarted'} active={isGettingStartedActive} />
<NavigationMenuGuideList id={'database'} active={isDatabaseActive} />
<NavigationMenuGuideList id={'api'} active={isApiActive} />
<NavigationMenuGuideList id={'auth'} active={isAuthActive} />
<NavigationMenuGuideList id={'functions'} active={isFunctionsActive} />
<NavigationMenuGuideList
@@ -21,7 +21,11 @@ const HeaderImage = React.memo(function HeaderImage(props: any) {
)
})
const HeaderLink = React.memo(function HeaderLink(props: any) {
const HeaderLink = React.memo(function HeaderLink(props: {
title: string
id: string
url: string
}) {
const router = useRouter()
return (
+4
View File
@@ -22,6 +22,10 @@ const levelsData = {
icon: '/docs/img/icons/menu/database',
name: 'Database',
},
api: {
icon: '/docs/img/icons/menu/database',
name: 'Serverless APIs',
},
auth: {
icon: '/docs/img/icons/menu/auth',
name: 'Auth',
+52
View File
@@ -0,0 +1,52 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'api',
title: 'Serverless APIs',
description: 'Auto-generating and Realtime APIs.',
sidebar_label: 'Overview',
video: 'https://www.youtube.com/v/rPAJJFdtPw0',
}
Supabase auto-generates three types of API directly from your database schema.
- REST - connect to your database through a restful interface, directly from the browser.
- Realtime - for listening to database changes.
- GraphQL - a fast and auto-generate GraphQL API.
All the APIs are auto-generated from your database and are designed to get you building as fast as possible, without writing a single line of code.
You can use them directly from the browser (two-tier architecture), or as a complement to your own API server (three-tier architecture).
## Features
- **Instant and auto-generated.** <br />As you update your database the changes are immediately accessible through your API.
- **Self documenting.** <br />Supabase generates documentation in the Dashboard which updates as you make database changes.
- **Secure.** <br />The API is configured to work with PostgreSQL's Row Level Security, provisioned behind an API gateway with key-auth enabled.
- **Fast.** <br />Our benchmarks for basic reads are more than 300% faster than Firebase. The API is a very thin layer on top of Postgres, which does most of the heavy lifting.
- **Scalable.** <br />The API can serve thousands of simultaneous requests, and works well for Serverless workloads.
## REST API [#rest-api-overview]
Supabase provides a RESTful API using [PostgREST](https://postgrest.org/). This is a very thin API layer on top of Postgres.
It provides everything you need from a CRUD API:
- Basic CRUD operations
- Deeply nested joins, allowing you to fetch data from multiple tables in a single fetch
- Works with Postgres Views
- Works with Postgres Functions
- Works with the Postgres security model - including Row Level Security, Roles, and Grants.
## GraphQL API [#graphql-api-overview]
GraphQL in Supabase works through [pg_graphql](https://supabase.com/blog/pg-graphql), an open source PostgreSQL extension for GraphQL.
## Realtime API [#realtime-api-overview]
Supabase provides a Realtime API using [Realtime](https://github.com/supabase/realtime). You can use this to listen to database changes over websockets.
Realtime leverages PostgreSQL's built-in logical replication. You can manage your Realtime API simply by managing Postgres publications.
Go to your project's [Replication section](https://app.supabase.com/project/_/database/replication) to get started.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
+70
View File
@@ -0,0 +1,70 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'api-keys',
title: 'Understanding API Keys',
description: 'Securing your Serverless API with Postgres Row Level Security.',
}
Supabase provides two default keys when you create a project: an `anon` key, and a `service_role` key. You can find both keys in the [API Settings](https://app.supabase.com/project/_/settings/api).
The Serverless APIs are designed to work with Postgres Row Level Security (RLS). These keys both map to Postgres roles. You can find an `anon` user and a `service_role` user in the [Roles](http://app.supabase.com/project/_/database/roles) section of the dashboard.
The keys are both long-lived JWTs. If you decode these keys, you will see that they contain the "role", an "issued date", and an "expiry date" ~10 years in the future.
```json
{
"role": "anon",
"iat": 1625137684,
"exp": 1940713684
}
```
## The `anon` key
The `anon` key has very few privileges. You can use it in your [RLS policies](/docs/guides/auth/row-level-security) for "anonymous" access. For example, this policy will allow access to the `profiles` table:
```sql
create policy "Allow anonymous access" on profiles to anon for
select
using (true);
```
And similarity for disallowing access:
```sql
create policy "Disallow anonymous access" on profiles to anon for
select
using (false);
```
If you are using [Supabase Auth](/docs/guides/auth/overview), then the `anon` role will automatically update to `authenticated` once a user is logged in:
```sql
create policy "Allow access to authenticated users" on profiles to authenticated for
select
using (true);
```
## The `service_role` key
The "service_role" is a predefined Postgres role with elevated privileges, designed to perform various administrative and service-related tasks. It can bypass Row Level Security, so it should only be used on a private server.
<Admonition type="caution">
Never expose the `service_role` key in a browser or anywhere where a user can see it.
</Admonition>
A common use case for the `service_role` key is running data analytics jobs on the backend. To support joins on user id, it is often useful to grant the service role read access to `auth.users` table.
```sql
grant
select
on table auth.users to service_role;
```
We have [partnered with GitHub](https://github.blog/changelog/2022-03-28-supabase-is-now-a-github-secret-scanning-partner/) to scan for Supabase `service_role` keys pushed to public repositories.
If they detect any keys with service_role privileges being pushed to GitHub, they will forward the API key to us, so that we can automatically revoke the detected secrets and notify you, protecting your data against malicious actors.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -0,0 +1,252 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'creating-routes',
title: 'Creating API Routes',
description:
'API routes are automatically created when you create Postgres Tables, Views, or Functions.',
}
API routes are automatically created when you create Postgres Tables, Views, or Functions.
## Create a table
Let's create our first API route by creating a table called `todos` to store tasks.
This creates a corresponding route `todos` which can accept `GET`, `POST`, `PATCH`, & `DELETE` requests.
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="dashboard"
>
<TabPanel id="dashboard" label="Dashboard">
1. Go to the [Table editor](https://app.supabase.com/project/_/editor) page in the Dashboard.
1. Click **New Table** and create a table with the name `todos`.
1. Click **Save**.
1. Click **New Column** and create a column with the name `task` and type `text`.
1. Click **Save**.
<video width="99%" muted playsInline controls="true">
<source
src="https://xguihxuzqibwxjnimxev.supabase.co/storage/v1/object/public/videos/docs/api/api-create-table-sm.mp4"
type="video/mp4"
muted
playsInline
/>
</video>
</TabPanel>
<TabPanel id="sql" label="SQL">
```sql
-- Create a table called "todos" with a column to store tasks.
create table
todos (
id bigint generated by default as identity primary key,
task text check (char_length(task) > 3)
);
```
</TabPanel>
</Tabs>
## API URL and Keys
Every Supabase project has a unique API URL. Your API is secured behind an API gateway which requires an API Key for every request.
1. Go to the [Settings](https://app.supabase.com/project/_/settings/general) page in the Dashboard.
2. Click **API** in the sidebar.
3. Find your API `URL`, `anon`, and `service_role` keys on this page.
<video width="99%" muted playsInline controls="true">
<source
src="https://xguihxuzqibwxjnimxev.supabase.co/storage/v1/object/public/videos/docs/api/api-url-and-key.mp4"
type="video/mp4"
muted
playsInline
/>
</video>
The REST API and the GraphQL API are both accessible through this URL:
- REST: `https://<project_ref>.supabase.co/rest/v1`
- GraphQL: `https://<project_ref>.supabase.co/graphql/v1`
Both of these routes require the `anon` key to be passed through an `apikey` header.
## Using the API
### REST API
You can interact with your API directly via HTTP requests, or you can use the client libraries which we provide.
Let's see how to make a request to the `todos` table which we created in the first step,
using the API URL (`SUPABASE_URL`) and Key (`SUPABASE_ANON_KEY`) we provided:
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="javascript"
>
<TabPanel id="javascript" label="Javascript">
```javascript
// Initialize the JS client
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)
// Make a request
const { data: todos, error } = await supabase.from('todos').select('*')
```
</TabPanel>
<TabPanel id="curl" label="cURL">
```bash
# Append /rest/v1/ to your URL, and then use the table name as the route
curl '<SUPABASE_URL>/rest/v1/todos' \
-H "apikey: <SUPABASE_ANON_KEY>" \
-H "Authorization: Bearer <SUPABASE_ANON_KEY>"
```
</TabPanel>
</Tabs>
JS Reference: [select()](/docs/reference/javascript/select),
[insert()](/docs/reference/javascript/insert),
[update()](/docs/reference/javascript/update),
[upsert()](/docs/reference/javascript/upsert),
[delete()](/docs/reference/javascript/delete),
[rpc()](/docs/reference/javascript/rpc) (call Postgres functions).
### GraphQL API
You can use any GraphQL client with the Supabase GraphQL API. For our GraphQL example we will use [urql](https://formidable.com/open-source/urql/docs/).
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="javascript"
>
<TabPanel id="javascript" label="Javascript">
```javascript
import { createClient, useQuery } from 'urql'
// Prepare API key and Authorization header
const headers = {
apikey: <SUPABASE_ANON_KEY>,
authorization: `Bearer ${<SUPABASE_ANON_KEY>}`,
}
// Create GraphQL client
// See: https://formidable.com/open-source/urql/docs/basics/react-preact/#setting-up-the-client
const client = createClient({
url: '<SUPABASE_URL>/graphql/v1',
fetchOptions: function createFetchOptions() {
return { headers }
},
})
// Prepare our GraphQL query
const TodosQuery = `
query {
todosCollection {
edges {
node {
id
title
}
}
}
}
`
// Query for the data (React)
const [result, reexecuteQuery] = useQuery({
query: TodosQuery,
})
// Read the result
const { data, fetching, error } = result
```
</TabPanel>
<TabPanel id="curl" label="cURL">
```bash
# Append /graphql/v1/ to your URL, and then use the table name as the route
curl --request POST '<SUPABASE_URL>/graphql/v1' \
-H 'apikey: <SUPABASE_ANON_KEY>' \
-H 'Authorization: Bearer <SUPABASE_ANON_KEY>' \
-H 'Content-Type: application/json' \
-d '{ "query":"{ todos(first: 3) { edges { node { id } } } }" }'
```
</TabPanel>
</Tabs>
### Realtime API
By default Realtime is disabled on your database. Let's turn on Realtime for the `todos` table.
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="dashboard"
>
<TabPanel id="dashboard" label="Dashboard">
1. Go to the [Database](https://app.supabase.com/project/_/database/tables) page in the Dashboard.
2. Click on **Replication** in the sidebar.
3. Control which database events are sent by toggling **Insert**, **Update**, and **Delete**.
4. Control which tables broadcast changes by selecting **Source** and toggling each table.
<video width="99%" muted playsInline controls="true">
<source
src="https://xguihxuzqibwxjnimxev.supabase.co/storage/v1/object/public/videos/docs/api/api-realtime.mp4"
type="video/mp4"
muted
playsInline
/>
</video>
</TabPanel>
<TabPanel id="sql" label="SQL">
```sql
alter
publication supabase_realtime add table todos;
```
</TabPanel>
</Tabs>
From the client, we can listen to any new data that is inserted into the `todos` table:
```javascript
// Initialize the JS client
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)
// Create a function to handle inserts
const handleInserts = (payload) => {
console.log('Change received!', payload)
}
// Listen to inserts
const { data: todos, error } = await supabase.from('todos').on('INSERT', handleInserts).subscribe()
```
Use [subscribe()](/docs/reference/javascript/subscribe) to listen to database changes.
The Realtime API works through PostgreSQL's replication functionality. Postgres sends database changes to a [publication](/docs/guides/database/replication#publications)
called `supabase_realtime`, and by managing this publication you can control which data is broadcast.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -0,0 +1,22 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'graphiql',
title: 'GraphiQL documentation',
description: 'Some docs on graphiql.',
video: 'https://www.youtube.com/v/7CqlTU9aOR4',
}
Every Supabase project has a GraphQL Endpoint: `https://<project_ref>.supabase.co/graphql/v1`.
This endpoint is compatible with any GraphiQL implementation that can pass an `apikey` header.
Some suggested applications:
- [paw.cloud](https://paw.cloud)
- [insomnia.rest](https://insomnia.rest)
- [postman.com/graphql](https://www.postman.com/graphql/)
- Self-hosted GraphiQL: GraphiQL can be served through a simple HTML file. See [this discussion](https://github.com/supabase/supabase/discussions/6144) for more details.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -0,0 +1,206 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'joins-and-nested-tables',
title: 'Querying Joins and Nested tables',
description: 'The Serverless APIs automatically detect relationships between Postgres tables.',
}
The Serverless APIs automatically detect relationships between Postgres tables. Since Postgres is a relational database, this is a very common scenario.
## One-to-many joins
Let's use an example database that stores `countries` and `cities`:
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="table"
>
<TabPanel id="table" label="Tables">
**Countries**
| `id` | `name` |
| ---- | -------------- |
| 1 | United Kingdom |
| 2 | United States |
**Cities**
| `id` | `name` | `country_id` |
| ---- | ----------- | ------------ |
| 1 | London | 1 |
| 2 | Manchester | 1 |
| 3 | Los Angeles | 2 |
| 4 | New York | 2 |
</TabPanel>
<TabPanel id="SQL" label="SQL">
```sql
create table
countries ("id" serial primary key, "name" text);
insert into
countries (id, name)
values
(1, 'United Kingdom'),
(2, 'United States');
create table
cities (
"id" serial primary key,
"name" text,
"country_id" int references "countries"
);
insert into
cities (name, country_id)
values
('London', 1),
('Manchester', 1),
('Los Angeles', 2),
('New York', 2);
```
</TabPanel>
</Tabs>
The APIs will automatically detect relationships based on the foreign keys:
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="js"
>
<TabPanel id="js" label="JavaScript">
```js
const { data, error } = await supabase.from('countries').select(`
id,
name,
cities ( id, name )
`)
```
</TabPanel>
<TabPanel id="dart" label="Dart">
```dart
final data = await supabase.from('todos').select('id, name, cities(id, name)');
```
</TabPanel>
<TabPanel id="graphql" label="GraphQL">
```javascript
const Query = `
query {
countriesCollection {
edges {
node {
id
name
cities {
id,
name
}
}
}
}
}
`
```
</TabPanel>
<TabPanel id="url" label="URL">
```bash
GET https://[REF].supabase.co/rest/v1/countries?select=id,name,cities(id,name)
```
</TabPanel>
</Tabs>
## Many-to-many joins
The Serverless APIs will detect many-to-many joins. For example, if you have a database which stored teams of users (where each user could below to many teams):
```sql
create table
users ("id" serial primary key, "name" text);
create table
teams ("id" serial primary key, "team_name" text);
create table
members (
"id" serial primary key,
"user_id" int references users,
"team_id" int references teams
);
```
In these cases you don't need to explicitly define the joining table (members). If we wanted to fetch all the teams and the members in each team:
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="js"
>
<TabPanel id="js" label="JavaScript">
```js
const { data, error } = await supabase.from('teams').select(`
id,
team_name,
users ( id, name )
`)
```
</TabPanel>
<TabPanel id="dart" label="Dart">
```dart
final data = await supabase.from('teams').select('id, team_name, users(id, name)');
```
</TabPanel>
<TabPanel id="graphql" label="GraphQL">
```javascript
const Query = `
query {
teamsCollection {
edges {
node {
id
team_name
users {
id,
name
}
}
}
}
}
`
```
</TabPanel>
<TabPanel id="url" label="URL">
```bash
GET https://[REF].supabase.co/rest/v1/teams?select=id,team_name,users(id,name)
```
</TabPanel>
</Tabs>
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
+236
View File
@@ -0,0 +1,236 @@
import Layout from '~/layouts/DefaultGuideLayout'
import StepHikeCompact from '~/components/StepHikeCompact'
export const meta = {
title: 'Build an API route in less than 2 minutes.',
subtitle: 'Create your first API route by creating a table called `todos` to store tasks.',
breadcrumb: 'API Quickstart',
}
Let's create our first REST route which we can query using `cURL` or the browser.
We'll create a database table called `todos` for storing tasks. This creates a corresponding API route `/rest/v1/todos` which can accept `GET`, `POST`, `PATCH`, & `DELETE` requests.
<StepHikeCompact>
<StepHikeCompact.Step step={1}>
<StepHikeCompact.Details title="Set up a Supabase project with a 'todos' table">
[Create a new project](https://app.supabase.com) in the Supabase Dashboard.
After your project is ready, create a table in your Supabase database. You can do this with either the Table interface or the [SQL Editor](https://app.supabase.com/project/_/sql).
</StepHikeCompact.Details>
<StepHikeCompact.Code>
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="sql"
>
<TabPanel id="sql" label="SQL">
```sql
-- Create a table called "todos"
-- with a column to store tasks.
create table todos (
id serial primary key,
task text
);
```
</TabPanel>
<TabPanel id="dashboard" label="Dashboard">
<video width="99%" muted playsInline controls="true">
<source
src="https://xguihxuzqibwxjnimxev.supabase.co/storage/v1/object/public/videos/docs/api/api-create-table-sm.mp4"
type="video/mp4"
muted
playsInline
/>
</video>
</TabPanel>
</Tabs>
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={2}>
<StepHikeCompact.Details title="Allow anonymous access">
Let's turn on Row Level Security for this table and allow anonymous access.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```sql
-- Turn on security
alter table "todos"
enable row level security;
-- Allow anonymous access
create policy "Allow anonymous access"
on todos
to anon
for select
using (true);
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={3}>
<StepHikeCompact.Details title="Insert some dummy data">
Now we can add some data to our table which we can access through our API.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```sql
insert into todos (task)
values
('Create tables'),
('Enable security'),
('Add data'),
('Fetch data from the API');
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={4}>
<StepHikeCompact.Details title="Fetch the data">
Find your API URL and Keys in your Dashboard [API Settings](https://app.supabase.com/project/_/settings/api). You can now query your "todos" table by appending `/rest/v1/todos` to the API URL.
Copy this block of code, substitute `<PROJECT_REF>` and `<ANON_KEY>`, then run it from a terminal.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```bash Terminal
curl 'https://<PROJECT_REF>.supabase.co/rest/v1/todos' \
-H "apikey: <ANON_KEY>" \
-H "Authorization: Bearer <ANON_KEY>"
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
</StepHikeCompact>
## Bonus
There are several options for accessing your data:
### Browser
You can query the route in your browser, by appending the `anon` key as a query parameter:
`https://<PROJECT_REF>.supabase.co/rest/v1/users?apikey=<ANON_KEY>`
### Client libraries
We provide a numerous [Client Libraries](https://github.com/supabase/supabase#client-libraries).
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="js"
>
<TabPanel id="js" label="JavaScript">
```js
const { data, error } = await supabase.from('todos').select()
```
</TabPanel>
<TabPanel id="dart" label="Dart">
```dart
final data = await supabase.from('todos').select('*');
```
</TabPanel>
<TabPanel id="python" label="Python">
```python
response = supabase.table('todos').select("*").execute()
```
</TabPanel>
</Tabs>
### GraphQL
Every table can be accessed through the GraphQL API by switching `/rest/v1` with `/graphql/v1`.
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="urql"
>
<TabPanel id="urql" label="urql">
```javascript
import { createClient, useQuery } from 'urql'
const URL = '<SUPABASE_URL>/graphql/v1'
const ANON_KEY = '<SUPABASE_ANON_KEY>'
// Prepare API key and Authorization header
const headers = {
apikey: `${ANON_KEY}`,
authorization: `Bearer ${ANON_KEY}`,
}
const client = createClient({
url: URL,
fetchOptions: function createFetchOptions() {
return { headers }
},
})
// Prepare our GraphQL query
const TodosQuery = `
query {
todosCollection {
edges {
node {
id
task
}
}
}
}
`
// Query for the data (React)
const [result, reexecuteQuery] = useQuery({
query: TodosQuery,
})
// Read the result
const { data, fetching, error } = result
```
</TabPanel>
</Tabs>
export const Page = ({ children }) => <Layout meta={meta} children={children} hideToc={true} />
export default Page
@@ -0,0 +1,26 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'auto-docs',
title: 'Auto-generated documentation',
description: 'Supabase provides documentation that updates automatically.',
}
Supabase generates documentation in the [Dashboard](https://app.supabase.com) which updates as you make database changes.
1. Go to the [API](https://app.supabase.com/project/_/api) page in the Dashboard.
2. Select any table under **Tables and Views** in the sidebar.
3. Switch between the JavaScript and the cURL docs using the tabs.
<video width="99%" muted playsInline controls="true">
<source
src="https://xguihxuzqibwxjnimxev.supabase.co/storage/v1/object/public/videos/docs/api/api-docs.mp4"
type="video/mp4"
muted
playsInline
/>
</video>
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -0,0 +1,33 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'client-libs',
title: 'Client Libraries',
description: 'Supabase provides several client libraries for the REST and Realtime APIs.',
video: 'https://www.youtube.com/v/7CqlTU9aOR4',
}
Supabase provides client libraries for the REST and Realtime APIs. Some libraries are officially supported, and some are contributed by the community.
## Official Libraries
| `Language` | `Source Code` | `Documentation` |
| --------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------- |
| Javascript/Typescript | [supabase-js](https://github.com/supabase/supabase-js) | [Docs](https://supabase.com/docs/reference/javascript/introduction) |
| Dart/Flutter | [supabase-dart](https://github.com/supabase/supabase-dart) | [Docs](https://supabase.com/docs/reference/dart/introduction) |
## Community Libraries
| `Language` | `Source Code` | `Documentation` |
| ----------------------- | -------------------------------------------------------------------------------- | --------------------------------------------------------------- |
| C# | [supabase-js](https://github.com/supabase-community/supabase-csharp) | [Docs](https://supabase.com/docs/reference/csharp/introduction) |
| Go | [supabase-go](https://github.com/supabase-community/supabase-go) | |
| Kotlin | [supabase-kt](https://github.com/supabase-community/supabase-kt) | |
| Python | [supabase-py](https://github.com/supabase-community/supabase-py) | [Docs](https://supabase.com/docs/reference/python/initializing) |
| Ruby | [supabase-rb](https://github.com/supabase-community/supabase-rb) | |
| Swift | [supabase-swift](https://github.com/supabase-community/supabase-swift) | |
| Godot Engine (GDScript) | [supabase-gdscript](https://github.com/supabase-community/godot-engine.supabase) | |
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -0,0 +1,66 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'securing-your-api',
title: 'Securing your API',
description: 'Securing your Serverless API with Postgres Row Level Security.',
}
The Serverless APIs are designed to work with Postgres Row Level Security (RLS). If you use [Supabase Auth](/docs/guides/auth), you can restrict data based on the logged-in user.
To control access to your data, you can use [Policies](/docs/guides/auth#policies).
## Enabling Row Level Security
If you create a table through the Dashboard, RLS will be enabled by default. This is not the case, however, if you create a table or view using SQL.
To enable RLS on any table:
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="dashboard"
>
<TabPanel id="dashboard" label="Dashboard">
1. Go to the [Authentication](https://app.supabase.com/project/_/auth/users) page in the Dashboard.
2. Click on **Policies** in the sidebar.
3. Select **Enable RLS** to enable Row Level Security.
</TabPanel>
<TabPanel id="sql" label="SQL">
```sql
alter table
todos enable row level security;
```
</TabPanel>
</Tabs>
With RLS enabled, you can create Policies that allow or disallow users to access and update data. We provide a detailed guide for creating Row Level Security Policies in our [Authorization documentation](/docs/guides/auth/row-level-security).
## Safeguards towards accidental deletes and updates
By default, all projects have the [safeupdate](https://github.com/eradman/pg-safeupdate) Postgres extension enabled for API queries.
This ensures that `delete()` and `update()` requests will fail if there are no filters provided.
To confirm that safeupdate is enabled for API queries, run the following query:
```sql
select
usename,
useconfig
from
pg_shadow
where
usename = 'authenticator';
```
The expected value for `useconfig` should be:
```sql
['session_preload_libraries=supautils, safeupdate']
```
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -255,7 +255,9 @@ export default function SupabaseProvider({ children }) {
const [supabase] = useState(() => createClient())
useEffect(() => {
const { data: { subscription } } = supabase.auth.onAuthStateChange((event, session) => {
const {
data: { subscription },
} = supabase.auth.onAuthStateChange((event, session) => {
if (session?.access_token !== accessToken) {
router.refresh()
}
@@ -306,11 +308,11 @@ export default function SupabaseProvider({ children }: { children: React.ReactNo
}
export const useSupabase = () => {
let context = useContext(Context);
let context = useContext(Context)
if (context === undefined) {
throw new Error("useSupabase must be used inside SupabaseProvider");
throw new Error('useSupabase must be used inside SupabaseProvider')
} else {
return context;
return context
}
}
```
@@ -515,7 +517,7 @@ import { useSupabase } from './supabase-provider'
// Supabase auth needs to be triggered client-side
export default function Login() {
const { supabase, session } = useSupabase()
const { supabase } = useSupabase()
const handleEmailLogin = async () => {
await supabase.auth.signInWithPassword({
@@ -555,7 +557,7 @@ import { useSupabase } from './supabase-provider'
// Supabase auth needs to be triggered client-side
export default function Login() {
const { supabase, session } = useSupabase()
const { supabase } = useSupabase()
const handleEmailLogin = async () => {
await supabase.auth.signInWithPassword({
@@ -86,7 +86,7 @@ import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = async ({ locals: { getSession } }) => {
return {
session: getSession()
session: await getSession()
};
};
```
@@ -0,0 +1,137 @@
import Layout from '~/layouts/DefaultGuideLayout'
import StepHikeCompact from '~/components/StepHikeCompact'
export const meta = {
title: 'Use Supabase Auth with React',
subtitle: 'Learn how to Supabase Auth with React.js.',
breadcrumb: 'Auth Quickstarts',
}
<StepHikeCompact>
<StepHikeCompact.Step step={1}>
<StepHikeCompact.Details title="Create a new Supabase project">
[Launch a new project](https://app.supabase.com) in the Supabase Dashboard.
Your new database has a table for storing your users. You can see that this table is currently empty by running some SQL in the [SQL Editor](https://app.supabase.com/project/_/sql).
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```sql SQL_EDITOR
select * from auth.users;
````
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={2}>
<StepHikeCompact.Details title="Create a React app">
Create a React app using the `create-react-app` command.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```bash Terminal
npx create-react-app my-app
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={3}>
<StepHikeCompact.Details title="Install the Supabase client library">
The fastest way to get started is to use Supabase's `auth-ui-react` library which provides a convenient interface for working with Supabase Auth from a React app.
Navigate to the React app and install the Supabase libraries.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```bash Terminal
cd my-app && npm install @supabase/supabase-js @supabase/auth-ui-react
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={4}>
<StepHikeCompact.Details title="Set up your login component">
In `index.js`, create a Supabase client using your [Project URL and public API (anon) key](https://app.supabase.com/project/_/settings/api).
You can configure the Auth component to display whenever there is no session inside `supabase.auth.getSession()`
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```js src/index.js
import './index.css'
import { useState, useEffect } from 'react'
import { createClient } from '@supabase/supabase-js'
import { Auth, ThemeSupa } from '@supabase/auth-ui-react'
const supabase = createClient('https://<project>.supabase.co', '<your-anon-key>')
export default function App() {
const [session, setSession] = useState(null)
useEffect(() => {
supabase.auth.getSession().then(({ data: { session } }) => {
setSession(session)
})
const {
data: { subscription },
} = supabase.auth.onAuthStateChange((_event, session) => {
setSession(session)
})
return () => subscription.unsubscribe()
}, [])
if (!session) {
return (<Auth supabaseClient={supabase} appearance={{ theme: ThemeSupa }} />)
}
else {
return (<div>Logged in!</div>)
}
}
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={5}>
<StepHikeCompact.Details title="Start the app">
Start the app, go to http://localhost:3000 in a browser, and open the browser console and you should be able to log in.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```bash Terminal
npm start
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
</StepHikeCompact>
export const Page = ({ children }) => <Layout meta={meta} children={children} hideToc={true} />
export default Page
@@ -130,6 +130,9 @@ if (refreshToken && accessToken) {
await supabase.auth.setSession({
refresh_token: refreshToken,
access_token: accessToken,
{
auth: { persistSession: false },
}
})
} else {
// make sure you handle this case!
-404
View File
@@ -1,404 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'api',
title: 'Serverless APIs',
description: 'Auto-generating and Realtime APIs.',
sidebar_label: 'Overview',
video: 'https://www.youtube.com/v/rPAJJFdtPw0',
}
Supabase auto-generates three types of API directly from your database schema.
- REST - interact with your database through a restful interface.
- Realtime - listen to database changes.
- GraphQL - [in beta](https://supabase.com/blog/pg-graphql).
The APIs are:
- **Instant and auto-generated.** <br />As you update your database the changes are immediately accessible through your API.
- **Self documenting.** <br />Supabase generates documentation in the Dashboard which updates as you make database changes.
- **Secure.** <br />The API is configured to work with PostgreSQL's Row Level Security, provisioned behind an API gateway with key-auth enabled.
- **Fast.** <br />Our benchmarks for basic reads are more than 300% faster than Firebase. The API is a very thin layer on top of Postgres, which does most of the heavy lifting.
- **Scalable.** <br />The API can serve thousands of simultaneous requests, and works well for Serverless workloads.
## REST API [#rest-api-overview]
Supabase provides a RESTful API using [PostgREST](https://postgrest.org/). This is a very thin API layer on top of Postgres.
It provides everything you need from a CRUD API:
- Basic CRUD operations
- Deeply nested joins, allowing you to fetch data from multiple tables in a single fetch
- Works with Postgres Views
- Works with Postgres Functions
- Works with the Postgres security model - including Row Level Security, Roles, and Grants.
<div className="video-container">
<iframe
src="https://www.youtube-nocookie.com/embed/rPAJJFdtPw0"
frameBorder="1"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>
</div>
## GraphQL API [#graphql-api-overview]
<Admonition type='note'>
GraphQL is in Beta, and may have breaking changes. It is only available on self-hosted setups and Supabase projects created after 28th March 2022.
</Admonition>
GraphQL in Supabase works through [pg_graphql](https://supabase.com/blog/pg-graphql), an open source PostgreSQL extension for GraphQL.
## Realtime API [#realtime-api-overview]
Supabase provides a Realtime API using [Realtime](https://github.com/supabase/realtime). You can use this to listen to database changes over websockets.
Realtime leverages PostgreSQL's built-in logical replication. You can manage your Realtime API simply by managing Postgres publications.
Go to your project's [Replication section](https://app.supabase.com/project/_/database/replication) to get started.
## Getting started
All APIs are auto-created from Database tables. After you have added tables or functions to your database, you can use the APIs provided.
### Creating API Routes
API routes are automatically created when you create Postgres Tables, Views, or Functions.
Let's create our first
API route by creating a table called `todos` to store tasks.
This creates a corresponding route `todos` which can accept `GET`, `POST`, `PATCH`, & `DELETE` requests.
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="dashboard"
>
<TabPanel id="dashboard" label="Dashboard">
1. Go to the [Table editor](https://app.supabase.com/project/_/editor) page in the Dashboard.
1. Click **New Table** and create a table with the name `todos`.
1. Click **Save**.
1. Click **New Column** and create a column with the name `task` and type `text`.
1. Click **Save**.
<video width="99%" muted playsInline controls="true">
<source
src="https://xguihxuzqibwxjnimxev.supabase.co/storage/v1/object/public/videos/docs/api/api-create-table-sm.mp4"
type="video/mp4"
muted
playsInline
/>
</video>
</TabPanel>
<TabPanel id="sql" label="SQL">
```sql
-- Create a table called "todos" with a column to store tasks.
create table todos (
id bigint generated by default as identity primary key,
task text check (char_length(task) > 3)
);
```
</TabPanel>
</Tabs>
### API URL and Keys
Every Supabase project has a unique API URL. Your API is secured behind an API gateway which requires an API Key for every request.
1. Go to the [Settings](https://app.supabase.com/project/_/settings/general) page in the Dashboard.
2. Click **API** in the sidebar.
3. Find your API `URL`, `anon`, and `service_role` keys on this page.
<video width="99%" muted playsInline controls="true">
<source
src="https://xguihxuzqibwxjnimxev.supabase.co/storage/v1/object/public/videos/docs/api/api-url-and-key.mp4"
type="video/mp4"
muted
playsInline
/>
</video>
The REST API and the GraphQL API are both accessible through this URL:
- REST: `https://<project_ref>.supabase.co/rest/v1`
- GraphQL: `https://<project_ref>.supabase.co/graphql/v1`
Both of these routes require the `anon` key to be passed through an `apikey` header.
#### API Keys
You are provided with two keys:
- an `anon` key, which is safe to be used in a browser context.
- a `service_role` key, which should only be used on a server. This key can bypass Row Level Security. NEVER use this key in a browser.
### Accessing the docs in the Dashboard
#### REST API [#rest-api-dashboard-docs]
Supabase generates documentation in the [Dashboard](https://app.supabase.com) which updates as you make database changes.
Let's view the documentation for a `countries` table which we created in our database.
1. Go to the [API](https://app.supabase.com/project/_/api) page in the Dashboard.
2. Find the `countries` table under **Tables and Views** in the sidebar.
3. Switch between the JavaScript and the cURL docs using the tabs.
<video width="99%" muted playsInline controls="true">
<source
src="https://xguihxuzqibwxjnimxev.supabase.co/storage/v1/object/public/videos/docs/api/api-docs.mp4"
type="video/mp4"
muted
playsInline
/>
</video>
#### GraphQL
The GraphQL Endpoint that we provide (`https://<project_ref>.supabase.co/graphql/v1`) is compatible with any GraphiQL implementation that can pass an `apikey` header.
Some suggested applications:
- [paw.cloud](https://paw.cloud)
- [insomnia.rest](https://insomnia.rest)
- [postman.com/graphql](https://www.postman.com/graphql/)
- Self-hosted GraphiQL: GraphiQL can be served through a simple HTML file. See [this discussion](https://github.com/supabase/supabase/discussions/6144) for more details.
## Using the API
### REST API
You can interact with your API directly via HTTP requests, or you can use the client libraries which we provide.
Let's see how to make a request to the `todos` table which we created in the first step,
using the API URL (`SUPABASE_URL`) and Key (`SUPABASE_ANON_KEY`) we provided:
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="javascript"
>
<TabPanel id="javascript" label="Javascript">
```javascript
// Initialize the JS client
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)
// Make a request
const { data: todos, error } = await supabase.from('todos').select('*')
```
</TabPanel>
<TabPanel id="curl" label="cURL">
```bash
# Append /rest/v1/ to your URL, and then use the table name as the route
curl '<SUPABASE_URL>/rest/v1/todos' \
-H "apikey: <SUPABASE_ANON_KEY>" \
-H "Authorization: Bearer <SUPABASE_ANON_KEY>"
```
</TabPanel>
</Tabs>
JS Reference: [select()](/docs/reference/javascript/select),
[insert()](/docs/reference/javascript/insert),
[update()](/docs/reference/javascript/update),
[upsert()](/docs/reference/javascript/upsert),
[delete()](/docs/reference/javascript/delete),
[rpc()](/docs/reference/javascript/rpc) (call Postgres functions).
### GraphQL API
You can use any GraphQL client with the Supabase GraphQL API. For our GraphQL example we will use [urql](https://formidable.com/open-source/urql/docs/).
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="javascript"
>
<TabPanel id="javascript" label="Javascript">
```javascript
import { createClient, useQuery } from 'urql'
// Prepare API key and Authorization header
const headers = {
apikey: <SUPABASE_ANON_KEY>,
authorization: `Bearer ${<SUPABASE_ANON_KEY>}`,
}
// Create GraphQL client
// See: https://formidable.com/open-source/urql/docs/basics/react-preact/#setting-up-the-client
const client = createClient({
url: '<SUPABASE_URL>/graphql/v1',
fetchOptions: function createFetchOptions() {
return { headers }
},
})
// Prepare our GraphQL query
const TodosQuery = `
query {
todosCollection {
edges {
node {
id
title
}
}
}
}
`
// Query for the data (React)
const [result, reexecuteQuery] = useQuery({
query: TodosQuery,
})
// Read the result
const { data, fetching, error } = result
```
</TabPanel>
<TabPanel id="curl" label="cURL">
```bash
# Append /graphql/v1/ to your URL, and then use the table name as the route
curl --request POST '<SUPABASE_URL>/graphql/v1' \
-H 'apikey: <SUPABASE_ANON_KEY>' \
-H 'Authorization: Bearer <SUPABASE_ANON_KEY>' \
-H 'Content-Type: application/json' \
-d '{ "query":"{ todos(first: 3) { edges { node { id } } } }" }'
```
</TabPanel>
</Tabs>
### Realtime API
By default Realtime is disabled on your database. Let's turn on Realtime for the `todos` table.
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="dashboard"
>
<TabPanel id="dashboard" label="Dashboard">
1. Go to the [Database](https://app.supabase.com/project/_/database/tables) page in the Dashboard.
2. Click on **Replication** in the sidebar.
3. Control which database events are sent by toggling **Insert**, **Update**, and **Delete**.
4. Control which tables broadcast changes by selecting **Source** and toggling each table.
<video width="99%" muted playsInline controls="true">
<source
src="https://xguihxuzqibwxjnimxev.supabase.co/storage/v1/object/public/videos/docs/api/api-realtime.mp4"
type="video/mp4"
muted
playsInline
/>
</video>
</TabPanel>
<TabPanel id="sql" label="SQL">
```sql
alter publication supabase_realtime add table todos;
```
</TabPanel>
</Tabs>
From the client, we can listen to any new data that is inserted into the `todos` table:
```javascript
// Initialize the JS client
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)
// Create a function to handle inserts
const handleInserts = (payload) => {
console.log('Change received!', payload)
}
// Listen to inserts
const { data: todos, error } = await supabase.from('todos').on('INSERT', handleInserts).subscribe()
```
Use [subscribe()](/docs/reference/javascript/subscribe) to listen to database changes.
The Realtime API works through PostgreSQL's replication functionality. Postgres sends database changes to a [publication](/docs/guides/database/replication#publications)
called `supabase_realtime`, and by managing this publication you can control which data is broadcast.
## API Security
### Securing your Routes
Your API is designed to work with Postgres Row Level Security (RLS). If you use Supabase [Auth](/docs/guides/auth), you can restrict data based on the logged-in user.
To control access to your data, you can use [Policies](/docs/guides/auth#policies).
When you create a table in Postgres, Row Level Security is disabled by default. To enable RLS:
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="dashboard"
>
<TabPanel id="dashboard" label="Dashboard">
1. Go to the [Authentication](https://app.supabase.com/project/_/auth/users) page in the Dashboard.
2. Click on **Policies** in the sidebar.
3. Select **Enable RLS** to enable Row Level Security.
</TabPanel>
<TabPanel id="sql" label="SQL">
```sql
alter table todos enable row level security;
```
</TabPanel>
</Tabs>
### The `service_role` key
Never expose the `service_role` key in a browser or anywhere where a user can see it. This Key is designed to bypass Row Level Security - so it should only be used on a private server.
A common use case for the `service_role` key is to run data analytics jobs on the backend. To support joins on user id, it is often useful to grant the service role read access to `auth.users` table.
```sql
grant select on table auth.users to service_role;
```
We have [partnered with GitHub](https://github.blog/changelog/2022-03-28-supabase-is-now-a-github-secret-scanning-partner/) to scan for Supabase `service_role` keys pushed to public repositories.
If they detect any keys with service_role privileges being pushed to GitHub, they will forward the API key to us, so that we can automatically revoke the detected secrets and notify you, protecting your data against malicious actors.
### Safeguards towards accidental deletes and updates
For all projects, by default, the Postgres extension [safeupdate](https://github.com/eradman/pg-safeupdate) is enabled for all queries coming from the API.
This ensures that any `delete()` or `update()` would fail if there are no accompanying filters provided.
To confirm that safeupdate is enabled for queries going through the API of your project, the following query could be run:
```sql
select usename, useconfig from pg_shadow where usename = 'authenticator';
```
The expected value for `useconfig` should be:
```sql
['session_preload_libraries=supautils, safeupdate']
```
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -149,35 +149,38 @@ Manage your projects programmatically. [Docs](/docs/reference/api).
Both Postgres and the Supabase Platform are production-ready. Some tools we offer on top of Postgres are still under development.
| Product | Feature | Stage |
| -------------------------- | ---------------------- | ------- |
| Database | Postgres | `GA` |
| Database | Triggers | `GA` |
| Database | Functions | `GA` |
| Database | Extensions | `GA` |
| Database | Full Text Search | `GA` |
| Database | Webhooks | `alpha` |
| Database | Point-in-Time Recovery | `alpha` |
| Database | Vault | `alpha` |
| Studio | | `GA` |
| Realtime | Postgres Changes | `GA` |
| Realtime | Broadcast | `beta` |
| Realtime | Presence | `beta` |
| Storage | | `GA` |
| Storage | S3 Backend | `GA` |
| Storage | CDN | `GA` |
| Storage | Smart CDN | `beta` |
| Storage | Image Transformations | `beta` |
| Edge Functions | | `beta` |
| Auth | OAuth Providers | `beta` |
| Auth | Passwordless | `beta` |
| Auth | Next.js Auth Helpers | `alpha` |
| Auth | SvelteKit Auth Helpers | `alpha` |
| Auth | Remix Auth Helpers | `alpha` |
| Management API | | `beta` |
| CLI | | `beta` |
| Client Library: JavaScript | | `GA` |
| Client Library: Dart | | `beta` |
| Product | Feature | Stage | Available on self-hosted |
| -------------------------- | ---------------------- | ------- | ------------------------------------------- |
| Database | Postgres | `GA` | ✅ |
| Database | Triggers | `GA` | ✅ |
| Database | Functions | `GA` | ✅ |
| Database | Extensions | `GA` | ✅ |
| Database | Full Text Search | `GA` | ✅ |
| Database | Webhooks | `GA` | ✅ |
| Database | Point-in-Time Recovery | `alpha` | 🚧 [wal-g](https://github.com/wal-g/wal-g) |
| Database | Vault | `alpha` | ✅ |
| Studio | | `GA` | ✅ |
| Realtime | Postgres Changes | `GA` | ✅ |
| Realtime | Broadcast | `beta` | ✅ |
| Realtime | Presence | `beta` | ✅ |
| Storage | | `GA` | ✅ |
| Storage | S3 Backend | `GA` | ✅ |
| Storage | CDN | `GA` | 🚧 [Cloudflare](https://www.cloudflare.com) |
| Storage | Smart CDN | `beta` | 🚧 [Cloudflare](https://www.cloudflare.com) |
| Storage | Image Transformations | `beta` | ✅ |
| Edge Functions | | `beta` | 🚧 [Deno Deploy](https://deno.com/deploy) |
| Auth | OAuth Providers | `beta` | ✅ |
| Auth | Passwordless | `beta` | ✅ |
| Auth | Next.js Auth Helpers | `alpha` | ✅ |
| Auth | SvelteKit Auth Helpers | `alpha` | ✅ |
| Auth | Remix Auth Helpers | `alpha` | ✅ |
| Management API | | `beta` | N/A |
| CLI | | `beta` | N/A |
| Client Library: JavaScript | | `GA` | N/A |
| Client Library: Dart | | `beta` | N/A |
- ✅ = Fully Available
- 🚧 = Available, but requires external tools or configuration
export const Page = ({ children }) => <Layout meta={meta} children={children} />
@@ -36,6 +36,7 @@ Then let's install the additional dependencies: [supabase-js](https://github.com
```bash
npm install @supabase/supabase-js
npm install react-native-elements @react-native-async-storage/async-storage react-native-url-polyfill
npx expo install expo-secure-store
```
Now let's create a helper file to initialize the Supabase client.
@@ -44,15 +45,28 @@ These variables will be exposed on the browser, and that's completely fine since
[Row Level Security](/docs/guides/auth#row-level-security) enabled on our Database.
```ts title=lib/supabase.ts
import AsyncStorage from '@react-native-async-storage/async-storage'
import * as SecureStore from "expo-secure-store";
import { createClient } from '@supabase/supabase-js'
const ExpoSecureStoreAdapter = {
getItem: (key: string) => {
return SecureStore.getItemAsync(key);
},
setItem: (key: string, value: string) => {
SecureStore.setItemAsync(key, value);
},
removeItem: (key: string) => {
SecureStore.deleteItemAsync(key);
},
};
const supabaseUrl = YOUR_REACT_NATIVE_SUPABASE_URL
const supabaseAnonKey = YOUR_REACT_NATIVE_SUPABASE_ANON_KEY
export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
auth: {
storage: AsyncStorage as any,
storage: ExpoSecureStoreAdapter as any,
autoRefreshToken: true,
persistSession: true,
detectSessionInUrl: false,
@@ -1,105 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'dashibase',
title: 'Dashibase',
description: 'Create an internal CRUD dashboard with Supabase and Dashibase in 15 mins.',
}
Are you tired of building admin panels? [Dashibase](https://dashibase.com/) is a super fast way to build internal CRUD dashboards with a Notion-like UI.
This guide walks you through how to build a customer admin panel using Supabase on the backend and Dashibase on the front so that you and your team can easily find and update customer information.
## Quick explanation
Just so we are on the same page, here are some of the terms we use:
- **Data source:** This is the data that powers your dashboard (i.e. your Supabase database).
- **Dashboard:** This is the friendly interface that you build on top of your Supabase database using Dashibase.
- **Page:** Each dashboard can have multiple pages. Each page can have multiple blocks of text or tables.
Now that is out of the way, let's build our customer admin panel.
(Start your stopwatch!)
## Step 1. Connect Supabase to Dashibase
First, in Dashibase, go to “Data Sources” via the left sidebar and click on “Connect data source” in the upper-right corner.
Note: We do not download or store any of your database data.
![Connect Supabase to Dashibase](/docs/img/guides/integrations/dashibase/connect-database.png)
Enter your database credentials, which you can find in Supabase under Settings > Database > Connection info. For additional security, you can also download your SSL certificate and upload it to Dashibase.
![Supabase credentials](/docs/img/guides/integrations/dashibase/supabase-credentials.png)
## Step 2. Set up your data access policy
Once you have connected your database, you can also control what your team can see and edit in your database via Dashibase. There are usually certain data that you don’t want others to edit or even see (e.g. address, email, identification number).
Note: If you prefer to restrict access to your database _before_ you connect to Dashibase for security reasons, you can [create a restricted user](https://aws.amazon.com/blogs/database/managing-postgresql-users-and-roles/) via [Supabase's SQL Editor](/docs/guides/database#the-sql-editor). The slight advantage of doing this in Dashibase is that there is a GUI, which makes it easier to set up the access control (but we understand some developers might find it more secure to restrict the access themselves).
![Set data access in Dashibase](/docs/img/guides/integrations/dashibase/set-data-access.png)
You can even control which columns within a table that others can see and edit. In the example below, for the `tags` table, the `name` column can be read and updated, the `created_at` column can be read, and the `id` column cannot even be read.
![Set column-level access in Dashibase](/docs/img/guides/integrations/dashibase/set-column-access.png)
Once you are happy with the data access control, you can decide who in your organization can edit this data source (usually other developers) or use this data source to create dashboards.
![Set user access to data source in Dashibase](/docs/img/guides/integrations/dashibase/data-source-user-access.png)
## Step 3. Build your dashboard
To create a dashboard, go to “Dashboards” via the left sidebar in Dashibase and click on “Create dashboard” in the upper-right corner.
Here’s where the fun begins. There are many ways to build your dashboards in Dashibase using features such as markdown formatting and slash command.
You will see some automatically-generated content in your dashboard so that you can set up your dashboard more quickly. For this example, let's clear them out and start from scratch.
![Automatically-generated content for your dashboard](/docs/img/guides/integrations/dashibase/initial-dashboard-content.png)
Using our Notion-like UI, you can quickly build out your dashboard through typing. For example, you can type '/table' to add a table from your database to your dashboard. No more dragging components across your screen and dropping them on a blank canvas repeatedly.
![Automatically-generated content for your dashboard](/docs/img/guides/integrations/dashibase/slash-command.gif)
If you have set up [foreign keys in your Supabase database](/docs/guides/database/tables#joining-tables-with-foreign-keys), you can easily show data from multiple tables on the table in your Dashibase dashboard. Simply click on the "Columns" button beside the "Filter" button and select the columns you want to show or hide. For this example, we want to show the customers' country and plan price, which are in separate `countries` and `plans` tables.
![Setting up the Customers table](/docs/img/guides/integrations/dashibase/customers-table.png)
For all the tables in your dashboard, you can rename the column headers, add filter and sort, adjust the column width, and more.
You can also add text to provide context or instructions for the rest of your team as they are using your dashboard. This saves you the trouble of having your dashboard and documentation in separate places.
![Customers table with instructions](/docs/img/guides/integrations/dashibase/customers-table-with-text.png)
Most importantly, we automatically generate a single-item view for each of the items on your table so that you do not have to manually create it yourself. You can easily edit or rearrange the blocks just like in Notion.
![Automatically generated single-item view](/docs/img/guides/integrations/dashibase/single-item-view.png)
## Step 4. Share your dashboard with your team
Finally, save and share the dashboard with your team. There are two levels of access:
- Edit dashboard: Edit the text blocks, edit the tables, etc.
- Use dashboard: View and update the data via the dashboard
Click on the “Share” button in the upper-right corner of your dashboard to share access:
![Share dashboard access](/docs/img/guides/integrations/dashibase/share-dashboard.png)
Now your team can use this customer admin panel to find and update customer information easily!
P.S. How long did we take? :)
## Resources
- [Dashibase website](https://dashibase.com/)
- [Dashibase open-source repo](https://github.com/dashibase)
- [Dashibase Twitter](https://twitter.com/dashibase)
- [Dashibase Discord](https://discord.gg/crhDFYvbbq)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
-6
View File
@@ -426,12 +426,6 @@
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/integrations/dashibase</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/integrations/dhiwise</loc>
<changefreq>weekly</changefreq>
+16
View File
@@ -1892,4 +1892,20 @@ module.exports = [
source: '/docs/guides/realtime/postgres-changes',
destination: '/docs/guides/realtime/extensions/postgres-changes',
},
// Serverless APIs
{
permanent: true,
source: '/docs/guides/database/api',
destination: '/docs/guides/api',
},
{
permanent: true,
source: '/docs/guides/database/api/generating-types',
destination: '/docs/guides/api/rest/generating-types',
},
{
permanent: true,
source: '/docs/guides/integrations/dashibase',
destination: '/docs',
},
]
+1 -1
View File
@@ -31,7 +31,7 @@
"gray-matter": "^4.0.2",
"markdown-toc": "^1.2.0",
"marked": "^4.0.12",
"next": "12.1.0",
"next": "12.3.2",
"next-compose-plugins": "^2.2.1",
"next-mdx-remote": "^4.1.0",
"next-seo": "^4.20.0",
+358 -349
View File
@@ -225,24 +225,6 @@
"react": "^16.13.1 || ^17.0.0"
}
},
"apps/docs/node_modules/@next/env": {
"version": "12.3.2",
"license": "MIT"
},
"apps/docs/node_modules/@next/swc-darwin-arm64": {
"version": "12.3.2",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"apps/docs/node_modules/ajv": {
"version": "6.12.6",
"dev": true,
@@ -413,97 +395,6 @@
"unist-util-visit": "^4.1.0"
}
},
"apps/docs/node_modules/next": {
"version": "12.3.2",
"license": "MIT",
"dependencies": {
"@next/env": "12.3.2",
"@swc/helpers": "0.4.11",
"caniuse-lite": "^1.0.30001406",
"postcss": "8.4.14",
"styled-jsx": "5.0.7",
"use-sync-external-store": "1.2.0"
},
"bin": {
"next": "dist/bin/next"
},
"engines": {
"node": ">=12.22.0"
},
"optionalDependencies": {
"@next/swc-android-arm-eabi": "12.3.2",
"@next/swc-android-arm64": "12.3.2",
"@next/swc-darwin-arm64": "12.3.2",
"@next/swc-darwin-x64": "12.3.2",
"@next/swc-freebsd-x64": "12.3.2",
"@next/swc-linux-arm-gnueabihf": "12.3.2",
"@next/swc-linux-arm64-gnu": "12.3.2",
"@next/swc-linux-arm64-musl": "12.3.2",
"@next/swc-linux-x64-gnu": "12.3.2",
"@next/swc-linux-x64-musl": "12.3.2",
"@next/swc-win32-arm64-msvc": "12.3.2",
"@next/swc-win32-ia32-msvc": "12.3.2",
"@next/swc-win32-x64-msvc": "12.3.2"
},
"peerDependencies": {
"fibers": ">= 3.1.0",
"node-sass": "^6.0.0 || ^7.0.0",
"react": "^17.0.2 || ^18.0.0-0",
"react-dom": "^17.0.2 || ^18.0.0-0",
"sass": "^1.3.0"
},
"peerDependenciesMeta": {
"fibers": {
"optional": true
},
"node-sass": {
"optional": true
},
"sass": {
"optional": true
}
}
},
"apps/docs/node_modules/next/node_modules/styled-jsx": {
"version": "5.0.7",
"license": "MIT",
"engines": {
"node": ">= 12.0.0"
},
"peerDependencies": {
"react": ">= 16.8.0 || 17.x.x || ^18.0.0-0"
},
"peerDependenciesMeta": {
"@babel/core": {
"optional": true
},
"babel-plugin-macros": {
"optional": true
}
}
},
"apps/docs/node_modules/postcss": {
"version": "8.4.14",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
}
],
"license": "MIT",
"dependencies": {
"nanoid": "^3.3.4",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"apps/docs/node_modules/proxy-compare": {
"version": "2.3.0",
"license": "MIT"
@@ -720,7 +611,7 @@
"gray-matter": "^4.0.2",
"markdown-toc": "^1.2.0",
"marked": "^4.0.12",
"next": "12.1.0",
"next": "12.3.2",
"next-compose-plugins": "^2.2.1",
"next-mdx-remote": "^4.1.0",
"next-seo": "^4.20.0",
@@ -4563,8 +4454,9 @@
}
},
"node_modules/@next/env": {
"version": "12.1.0",
"license": "MIT"
"version": "12.3.2",
"resolved": "https://registry.npmjs.org/@next/env/-/env-12.3.2.tgz",
"integrity": "sha512-upwtMaHxlv/udAWGq0kE+rg8huwmcxQPsKZFhS1R5iVO323mvxEBe1YrSXe1awLbg9sTIuEHbgxjLLt7JbeuAQ=="
},
"node_modules/@next/eslint-plugin-next": {
"version": "12.3.4",
@@ -4599,6 +4491,201 @@
"@mdx-js/react": "*"
}
},
"node_modules/@next/swc-android-arm-eabi": {
"version": "12.3.2",
"resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-12.3.2.tgz",
"integrity": "sha512-r2rrz+DZ8YYGqzVrbRrpP6GKzwozpOrnFbErc4k36vUTSFMag9yQahZfaBe06JYdqu/e5yhm/saIDEaSVPRP4g==",
"cpu": [
"arm"
],
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-android-arm64": {
"version": "12.3.2",
"resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-12.3.2.tgz",
"integrity": "sha512-B+TINJhCf+CrY1+b3/JWQlkecv53rAGa/gA7gi5B1cnBa/2Uvoe+Ue0JeCefTjfiyl1ScsyNx+NcESY8Ye2Ngg==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-darwin-arm64": {
"version": "12.3.2",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-12.3.2.tgz",
"integrity": "sha512-PTUfe1ZrwjsiuTmr3bOM9lsoy5DCmfYsLOUF9ZVhtbi5MNJVmUTy4VZ06GfrvnCO5hGCr48z3vpFE9QZ0qLcPw==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-darwin-x64": {
"version": "12.3.2",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-12.3.2.tgz",
"integrity": "sha512-1HkjmS9awwlaeEY8Y01nRSNkSv3y+qnC/mjMPe/W66hEh3QKa/LQHqHeS7NOdEs19B2mhZ7w+EgMRXdLQ0Su8w==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-freebsd-x64": {
"version": "12.3.2",
"resolved": "https://registry.npmjs.org/@next/swc-freebsd-x64/-/swc-freebsd-x64-12.3.2.tgz",
"integrity": "sha512-h5Mx0BKDCJ5Vu/U8e07esF6PjPv1EJgmRbYWTUZMAflu13MQpCJkKEJir7+BeRfTXRfgFf+llc7uocrpd7mcrg==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-arm-gnueabihf": {
"version": "12.3.2",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-12.3.2.tgz",
"integrity": "sha512-EuRZAamoxfe/WoWRaC0zsCAoE4gs/mEhilcloNM4J5Mnb3PLY8PZV394W7t5tjBjItMCF7l2Ebwjwtm46tq2RA==",
"cpu": [
"arm"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-arm64-gnu": {
"version": "12.3.2",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-12.3.2.tgz",
"integrity": "sha512-T9GCFyOIb4S3acA9LqflUYD+QZ94iZketHCqKdoO0Nx0OCHIgGJV5rotDe8TDXwh/goYpIfyHU4j1qqw4w4VnA==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-arm64-musl": {
"version": "12.3.2",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-12.3.2.tgz",
"integrity": "sha512-hxNVZS6L3c2z3l9EH2GP0MGQ9exu6O8cohYNZyqC9WUl6C03sEn8xzDH1y+NgD3fVurvYkGU5F0PDddJJLfDIw==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-x64-gnu": {
"version": "12.3.2",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-12.3.2.tgz",
"integrity": "sha512-fCPkLuwDwY8/QeXxciJJjDHG09liZym/Bhb4A+RLFQ877wUkwFsNWDUTSdUx0YXlYK/1gf67BKauqKkOKp6CYw==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-x64-musl": {
"version": "12.3.2",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-12.3.2.tgz",
"integrity": "sha512-o+GifBIQ2K+/MEFxHsxUZoU3bsuVFLXZYWd3idimFHiVdDCVYiKsY6mYMmKDlucX+9xRyOCkKL9Tjf+3tuXJpw==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-win32-arm64-msvc": {
"version": "12.3.2",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-12.3.2.tgz",
"integrity": "sha512-crii66irzGGMSUR0L8r9+A06eTv7FTXqw4rgzJ33M79EwQJOdpY7RVKXLQMurUhniEeQEEOfamiEdPIi/qxisw==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-win32-ia32-msvc": {
"version": "12.3.2",
"resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-12.3.2.tgz",
"integrity": "sha512-5hRUSvn3MdQ4nVRu1rmKxq5YJzpTtZfaC/NyGw6wa4NSF1noUn/pdQGUr+I5Qz3CZkd1gZzzC0eaXQHlrk0E2g==",
"cpu": [
"ia32"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-win32-x64-msvc": {
"version": "12.3.2",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-12.3.2.tgz",
"integrity": "sha512-tpQJYUH+TzPMIsdVl9fH8uDg47iwiNjKY+8e9da3dXqlkztKzjSw0OwSADoqh3KrifplXeKSta+BBGLdBqg3sg==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"license": "MIT",
@@ -26036,14 +26123,16 @@
"dev": true
},
"node_modules/next": {
"version": "12.1.0",
"license": "MIT",
"version": "12.3.2",
"resolved": "https://registry.npmjs.org/next/-/next-12.3.2.tgz",
"integrity": "sha512-orzvvebCwOqaz1eA5ZA0R5dbKxqtJyw7yeig7kDspu6p8OrplfyelzpvMHcDTKscv/l0nn/0l0v3mSsE8w4k7A==",
"dependencies": {
"@next/env": "12.1.0",
"caniuse-lite": "^1.0.30001283",
"postcss": "8.4.5",
"styled-jsx": "5.0.0",
"use-subscription": "1.5.1"
"@next/env": "12.3.2",
"@swc/helpers": "0.4.11",
"caniuse-lite": "^1.0.30001406",
"postcss": "8.4.14",
"styled-jsx": "5.0.7",
"use-sync-external-store": "1.2.0"
},
"bin": {
"next": "dist/bin/next"
@@ -26052,17 +26141,19 @@
"node": ">=12.22.0"
},
"optionalDependencies": {
"@next/swc-android-arm64": "12.1.0",
"@next/swc-darwin-arm64": "12.1.0",
"@next/swc-darwin-x64": "12.1.0",
"@next/swc-linux-arm-gnueabihf": "12.1.0",
"@next/swc-linux-arm64-gnu": "12.1.0",
"@next/swc-linux-arm64-musl": "12.1.0",
"@next/swc-linux-x64-gnu": "12.1.0",
"@next/swc-linux-x64-musl": "12.1.0",
"@next/swc-win32-arm64-msvc": "12.1.0",
"@next/swc-win32-ia32-msvc": "12.1.0",
"@next/swc-win32-x64-msvc": "12.1.0"
"@next/swc-android-arm-eabi": "12.3.2",
"@next/swc-android-arm64": "12.3.2",
"@next/swc-darwin-arm64": "12.3.2",
"@next/swc-darwin-x64": "12.3.2",
"@next/swc-freebsd-x64": "12.3.2",
"@next/swc-linux-arm-gnueabihf": "12.3.2",
"@next/swc-linux-arm64-gnu": "12.3.2",
"@next/swc-linux-arm64-musl": "12.3.2",
"@next/swc-linux-x64-gnu": "12.3.2",
"@next/swc-linux-x64-musl": "12.3.2",
"@next/swc-win32-arm64-msvc": "12.3.2",
"@next/swc-win32-ia32-msvc": "12.3.2",
"@next/swc-win32-x64-msvc": "12.3.2"
},
"peerDependencies": {
"fibers": ">= 3.1.0",
@@ -26509,19 +26600,26 @@
}
},
"node_modules/next/node_modules/postcss": {
"version": "8.4.5",
"license": "MIT",
"version": "8.4.14",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz",
"integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
}
],
"dependencies": {
"nanoid": "^3.1.30",
"nanoid": "^3.3.4",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.1"
"source-map-js": "^1.0.2"
},
"engines": {
"node": "^10 || ^12 || >=14"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
}
},
"node_modules/nice-try": {
@@ -33750,13 +33848,14 @@
}
},
"node_modules/styled-jsx": {
"version": "5.0.0",
"license": "MIT",
"version": "5.0.7",
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.0.7.tgz",
"integrity": "sha512-b3sUzamS086YLRuvnaDigdAewz1/EFYlHpYBP5mZovKEdQQOIIYq8lApylub3HHZ6xFjV051kkGU7cudJmrXEA==",
"engines": {
"node": ">= 12.0.0"
},
"peerDependencies": {
"react": ">= 16.8.0 || 17.x.x || 18.x.x"
"react": ">= 16.8.0 || 17.x.x || ^18.0.0-0"
},
"peerDependenciesMeta": {
"@babel/core": {
@@ -35540,16 +35639,6 @@
}
}
},
"node_modules/use-subscription": {
"version": "1.5.1",
"license": "MIT",
"dependencies": {
"object-assign": "^4.1.1"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0"
}
},
"node_modules/use-sync-external-store": {
"version": "1.2.0",
"license": "MIT",
@@ -37519,24 +37608,6 @@
"webpack-bundle-analyzer": "4.3.0"
}
},
"studio/node_modules/@next/env": {
"version": "12.3.2",
"license": "MIT"
},
"studio/node_modules/@next/swc-darwin-arm64": {
"version": "12.3.2",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"studio/node_modules/@tailwindcss/typography": {
"version": "0.5.8",
"dev": true,
@@ -37764,81 +37835,15 @@
"version": "1.0.1",
"license": "ISC"
},
"studio/node_modules/next": {
"version": "12.3.2",
"license": "MIT",
"dependencies": {
"@next/env": "12.3.2",
"@swc/helpers": "0.4.11",
"caniuse-lite": "^1.0.30001406",
"postcss": "8.4.14",
"styled-jsx": "5.0.7",
"use-sync-external-store": "1.2.0"
},
"bin": {
"next": "dist/bin/next"
},
"engines": {
"node": ">=12.22.0"
},
"optionalDependencies": {
"@next/swc-android-arm-eabi": "12.3.2",
"@next/swc-android-arm64": "12.3.2",
"@next/swc-darwin-arm64": "12.3.2",
"@next/swc-darwin-x64": "12.3.2",
"@next/swc-freebsd-x64": "12.3.2",
"@next/swc-linux-arm-gnueabihf": "12.3.2",
"@next/swc-linux-arm64-gnu": "12.3.2",
"@next/swc-linux-arm64-musl": "12.3.2",
"@next/swc-linux-x64-gnu": "12.3.2",
"@next/swc-linux-x64-musl": "12.3.2",
"@next/swc-win32-arm64-msvc": "12.3.2",
"@next/swc-win32-ia32-msvc": "12.3.2",
"@next/swc-win32-x64-msvc": "12.3.2"
},
"peerDependencies": {
"fibers": ">= 3.1.0",
"node-sass": "^6.0.0 || ^7.0.0",
"react": "^17.0.2 || ^18.0.0-0",
"react-dom": "^17.0.2 || ^18.0.0-0",
"sass": "^1.3.0"
},
"peerDependenciesMeta": {
"fibers": {
"optional": true
},
"node-sass": {
"optional": true
},
"sass": {
"optional": true
}
}
},
"studio/node_modules/next/node_modules/styled-jsx": {
"version": "5.0.7",
"license": "MIT",
"engines": {
"node": ">= 12.0.0"
},
"peerDependencies": {
"react": ">= 16.8.0 || 17.x.x || ^18.0.0-0"
},
"peerDependenciesMeta": {
"@babel/core": {
"optional": true
},
"babel-plugin-macros": {
"optional": true
}
}
},
"studio/node_modules/openapi-types": {
"version": "9.3.1",
"license": "MIT"
},
"studio/node_modules/postcss": {
"version": "8.4.14",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz",
"integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==",
"dev": true,
"funding": [
{
"type": "opencollective",
@@ -40511,7 +40516,9 @@
}
},
"@next/env": {
"version": "12.1.0"
"version": "12.3.2",
"resolved": "https://registry.npmjs.org/@next/env/-/env-12.3.2.tgz",
"integrity": "sha512-upwtMaHxlv/udAWGq0kE+rg8huwmcxQPsKZFhS1R5iVO323mvxEBe1YrSXe1awLbg9sTIuEHbgxjLLt7JbeuAQ=="
},
"@next/eslint-plugin-next": {
"version": "12.3.4",
@@ -40536,6 +40543,84 @@
"version": "12.3.4",
"requires": {}
},
"@next/swc-android-arm-eabi": {
"version": "12.3.2",
"resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-12.3.2.tgz",
"integrity": "sha512-r2rrz+DZ8YYGqzVrbRrpP6GKzwozpOrnFbErc4k36vUTSFMag9yQahZfaBe06JYdqu/e5yhm/saIDEaSVPRP4g==",
"optional": true
},
"@next/swc-android-arm64": {
"version": "12.3.2",
"resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-12.3.2.tgz",
"integrity": "sha512-B+TINJhCf+CrY1+b3/JWQlkecv53rAGa/gA7gi5B1cnBa/2Uvoe+Ue0JeCefTjfiyl1ScsyNx+NcESY8Ye2Ngg==",
"optional": true
},
"@next/swc-darwin-arm64": {
"version": "12.3.2",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-12.3.2.tgz",
"integrity": "sha512-PTUfe1ZrwjsiuTmr3bOM9lsoy5DCmfYsLOUF9ZVhtbi5MNJVmUTy4VZ06GfrvnCO5hGCr48z3vpFE9QZ0qLcPw==",
"optional": true
},
"@next/swc-darwin-x64": {
"version": "12.3.2",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-12.3.2.tgz",
"integrity": "sha512-1HkjmS9awwlaeEY8Y01nRSNkSv3y+qnC/mjMPe/W66hEh3QKa/LQHqHeS7NOdEs19B2mhZ7w+EgMRXdLQ0Su8w==",
"optional": true
},
"@next/swc-freebsd-x64": {
"version": "12.3.2",
"resolved": "https://registry.npmjs.org/@next/swc-freebsd-x64/-/swc-freebsd-x64-12.3.2.tgz",
"integrity": "sha512-h5Mx0BKDCJ5Vu/U8e07esF6PjPv1EJgmRbYWTUZMAflu13MQpCJkKEJir7+BeRfTXRfgFf+llc7uocrpd7mcrg==",
"optional": true
},
"@next/swc-linux-arm-gnueabihf": {
"version": "12.3.2",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-12.3.2.tgz",
"integrity": "sha512-EuRZAamoxfe/WoWRaC0zsCAoE4gs/mEhilcloNM4J5Mnb3PLY8PZV394W7t5tjBjItMCF7l2Ebwjwtm46tq2RA==",
"optional": true
},
"@next/swc-linux-arm64-gnu": {
"version": "12.3.2",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-12.3.2.tgz",
"integrity": "sha512-T9GCFyOIb4S3acA9LqflUYD+QZ94iZketHCqKdoO0Nx0OCHIgGJV5rotDe8TDXwh/goYpIfyHU4j1qqw4w4VnA==",
"optional": true
},
"@next/swc-linux-arm64-musl": {
"version": "12.3.2",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-12.3.2.tgz",
"integrity": "sha512-hxNVZS6L3c2z3l9EH2GP0MGQ9exu6O8cohYNZyqC9WUl6C03sEn8xzDH1y+NgD3fVurvYkGU5F0PDddJJLfDIw==",
"optional": true
},
"@next/swc-linux-x64-gnu": {
"version": "12.3.2",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-12.3.2.tgz",
"integrity": "sha512-fCPkLuwDwY8/QeXxciJJjDHG09liZym/Bhb4A+RLFQ877wUkwFsNWDUTSdUx0YXlYK/1gf67BKauqKkOKp6CYw==",
"optional": true
},
"@next/swc-linux-x64-musl": {
"version": "12.3.2",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-12.3.2.tgz",
"integrity": "sha512-o+GifBIQ2K+/MEFxHsxUZoU3bsuVFLXZYWd3idimFHiVdDCVYiKsY6mYMmKDlucX+9xRyOCkKL9Tjf+3tuXJpw==",
"optional": true
},
"@next/swc-win32-arm64-msvc": {
"version": "12.3.2",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-12.3.2.tgz",
"integrity": "sha512-crii66irzGGMSUR0L8r9+A06eTv7FTXqw4rgzJ33M79EwQJOdpY7RVKXLQMurUhniEeQEEOfamiEdPIi/qxisw==",
"optional": true
},
"@next/swc-win32-ia32-msvc": {
"version": "12.3.2",
"resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-12.3.2.tgz",
"integrity": "sha512-5hRUSvn3MdQ4nVRu1rmKxq5YJzpTtZfaC/NyGw6wa4NSF1noUn/pdQGUr+I5Qz3CZkd1gZzzC0eaXQHlrk0E2g==",
"optional": true
},
"@next/swc-win32-x64-msvc": {
"version": "12.3.2",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-12.3.2.tgz",
"integrity": "sha512-tpQJYUH+TzPMIsdVl9fH8uDg47iwiNjKY+8e9da3dXqlkztKzjSw0OwSADoqh3KrifplXeKSta+BBGLdBqg3sg==",
"optional": true
},
"@nodelib/fs.scandir": {
"version": "2.1.5",
"requires": {
@@ -49391,13 +49476,6 @@
"version": "1.6.22",
"requires": {}
},
"@next/env": {
"version": "12.3.2"
},
"@next/swc-darwin-arm64": {
"version": "12.3.2",
"optional": true
},
"ajv": {
"version": "6.12.6",
"dev": true,
@@ -49497,44 +49575,6 @@
"version": "2.0.0-rc3",
"requires": {}
},
"next": {
"version": "12.3.2",
"requires": {
"@next/env": "12.3.2",
"@next/swc-android-arm-eabi": "12.3.2",
"@next/swc-android-arm64": "12.3.2",
"@next/swc-darwin-arm64": "12.3.2",
"@next/swc-darwin-x64": "12.3.2",
"@next/swc-freebsd-x64": "12.3.2",
"@next/swc-linux-arm-gnueabihf": "12.3.2",
"@next/swc-linux-arm64-gnu": "12.3.2",
"@next/swc-linux-arm64-musl": "12.3.2",
"@next/swc-linux-x64-gnu": "12.3.2",
"@next/swc-linux-x64-musl": "12.3.2",
"@next/swc-win32-arm64-msvc": "12.3.2",
"@next/swc-win32-ia32-msvc": "12.3.2",
"@next/swc-win32-x64-msvc": "12.3.2",
"@swc/helpers": "0.4.11",
"caniuse-lite": "^1.0.30001406",
"postcss": "8.4.14",
"styled-jsx": "5.0.7",
"use-sync-external-store": "1.2.0"
},
"dependencies": {
"styled-jsx": {
"version": "5.0.7",
"requires": {}
}
}
},
"postcss": {
"version": "8.4.14",
"requires": {
"nanoid": "^3.3.4",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
}
},
"proxy-compare": {
"version": "2.3.0"
},
@@ -55594,32 +55634,39 @@
"dev": true
},
"next": {
"version": "12.1.0",
"version": "12.3.2",
"resolved": "https://registry.npmjs.org/next/-/next-12.3.2.tgz",
"integrity": "sha512-orzvvebCwOqaz1eA5ZA0R5dbKxqtJyw7yeig7kDspu6p8OrplfyelzpvMHcDTKscv/l0nn/0l0v3mSsE8w4k7A==",
"requires": {
"@next/env": "12.1.0",
"@next/swc-android-arm64": "12.1.0",
"@next/swc-darwin-arm64": "12.1.0",
"@next/swc-darwin-x64": "12.1.0",
"@next/swc-linux-arm-gnueabihf": "12.1.0",
"@next/swc-linux-arm64-gnu": "12.1.0",
"@next/swc-linux-arm64-musl": "12.1.0",
"@next/swc-linux-x64-gnu": "12.1.0",
"@next/swc-linux-x64-musl": "12.1.0",
"@next/swc-win32-arm64-msvc": "12.1.0",
"@next/swc-win32-ia32-msvc": "12.1.0",
"@next/swc-win32-x64-msvc": "12.1.0",
"caniuse-lite": "^1.0.30001283",
"postcss": "8.4.5",
"styled-jsx": "5.0.0",
"use-subscription": "1.5.1"
"@next/env": "12.3.2",
"@next/swc-android-arm-eabi": "12.3.2",
"@next/swc-android-arm64": "12.3.2",
"@next/swc-darwin-arm64": "12.3.2",
"@next/swc-darwin-x64": "12.3.2",
"@next/swc-freebsd-x64": "12.3.2",
"@next/swc-linux-arm-gnueabihf": "12.3.2",
"@next/swc-linux-arm64-gnu": "12.3.2",
"@next/swc-linux-arm64-musl": "12.3.2",
"@next/swc-linux-x64-gnu": "12.3.2",
"@next/swc-linux-x64-musl": "12.3.2",
"@next/swc-win32-arm64-msvc": "12.3.2",
"@next/swc-win32-ia32-msvc": "12.3.2",
"@next/swc-win32-x64-msvc": "12.3.2",
"@swc/helpers": "0.4.11",
"caniuse-lite": "^1.0.30001406",
"postcss": "8.4.14",
"styled-jsx": "5.0.7",
"use-sync-external-store": "1.2.0"
},
"dependencies": {
"postcss": {
"version": "8.4.5",
"version": "8.4.14",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz",
"integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==",
"requires": {
"nanoid": "^3.1.30",
"nanoid": "^3.3.4",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.1"
"source-map-js": "^1.0.2"
}
}
}
@@ -60581,13 +60628,6 @@
"webpack-bundle-analyzer": "4.3.0"
}
},
"@next/env": {
"version": "12.3.2"
},
"@next/swc-darwin-arm64": {
"version": "12.3.2",
"optional": true
},
"@tailwindcss/typography": {
"version": "0.5.8",
"dev": true,
@@ -60726,41 +60766,14 @@
"internmap": {
"version": "1.0.1"
},
"next": {
"version": "12.3.2",
"requires": {
"@next/env": "12.3.2",
"@next/swc-android-arm-eabi": "12.3.2",
"@next/swc-android-arm64": "12.3.2",
"@next/swc-darwin-arm64": "12.3.2",
"@next/swc-darwin-x64": "12.3.2",
"@next/swc-freebsd-x64": "12.3.2",
"@next/swc-linux-arm-gnueabihf": "12.3.2",
"@next/swc-linux-arm64-gnu": "12.3.2",
"@next/swc-linux-arm64-musl": "12.3.2",
"@next/swc-linux-x64-gnu": "12.3.2",
"@next/swc-linux-x64-musl": "12.3.2",
"@next/swc-win32-arm64-msvc": "12.3.2",
"@next/swc-win32-ia32-msvc": "12.3.2",
"@next/swc-win32-x64-msvc": "12.3.2",
"@swc/helpers": "0.4.11",
"caniuse-lite": "^1.0.30001406",
"postcss": "8.4.14",
"styled-jsx": "5.0.7",
"use-sync-external-store": "1.2.0"
},
"dependencies": {
"styled-jsx": {
"version": "5.0.7",
"requires": {}
}
}
},
"openapi-types": {
"version": "9.3.1"
},
"postcss": {
"version": "8.4.14",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz",
"integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==",
"dev": true,
"requires": {
"nanoid": "^3.3.4",
"picocolors": "^1.0.0",
@@ -60881,7 +60894,9 @@
}
},
"styled-jsx": {
"version": "5.0.0",
"version": "5.0.7",
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.0.7.tgz",
"integrity": "sha512-b3sUzamS086YLRuvnaDigdAewz1/EFYlHpYBP5mZovKEdQQOIIYq8lApylub3HHZ6xFjV051kkGU7cudJmrXEA==",
"requires": {}
},
"stylis": {
@@ -62249,12 +62264,6 @@
"tslib": "^2.0.0"
}
},
"use-subscription": {
"version": "1.5.1",
"requires": {
"object-assign": "^4.1.1"
}
},
"use-sync-external-store": {
"version": "1.2.0",
"requires": {}
@@ -63193,7 +63202,7 @@
"gray-matter": "^4.0.2",
"markdown-toc": "^1.2.0",
"marked": "^4.0.12",
"next": "12.1.0",
"next": "12.3.2",
"next-compose-plugins": "^2.2.1",
"next-mdx-remote": "^4.1.0",
"next-seo": "^4.20.0",
+3
View File
@@ -0,0 +1,3 @@
import React from 'react'
export * from './useParams'
File renamed without changes.
+1
View File
@@ -2,6 +2,7 @@ import SidebarLayout from './layouts/SidebarLayout'
export * from './Button'
export * from './Providers'
export * from './hooks'
export * from './gotrue'
export * from './auth'
+5 -5
View File
@@ -177,7 +177,7 @@ functions:
- id: sign-up-with-redirect
name: Sign up with a redirect URL
description: |
- See [redirect URLs and wildcards](/docs/guides/auth/overview#redirect-urls-and-wildcards) to add additional redirect URLs to your project.
- See [redirect URLs and wildcards](/docs/guides/auth#redirect-urls-and-wildcards) to add additional redirect URLs to your project.
code: |
```js
const { data, error } = await supabase.auth.signUp(
@@ -232,7 +232,7 @@ functions:
- If you're using an email, you can configure whether you want the user to receive a magiclink or a OTP.
- If you're using phone, you can configure whether you want the user to receive a OTP.
- The magic link's destination URL is determined by the [`SITE_URL`](/docs/reference/auth/config#site_url).
- See [redirect URLs and wildcards](/docs/guides/auth/overview#redirect-urls-and-wildcards) to add additional redirect URLs to your project.
- See [redirect URLs and wildcards](/docs/guides/auth#redirect-urls-and-wildcards) to add additional redirect URLs to your project.
- Magic links and OTPs share the same implementation. To send users a one-time code instead of a magic link, [modify the magic link email template](https://app.supabase.com/project/_/auth/templates) to include `{{ .Token }}` instead of `{{ .ConfirmationURL }}`.
examples:
- id: sign-in-with-email
@@ -263,7 +263,7 @@ functions:
$ref: '@supabase/gotrue-js.GoTrueClient.signInWithOAuth'
notes: |
- This method is used for signing in using a third-party provider.
- Supabase supports many different [third-party providers](https://supabase.com/docs/guides/auth#providers).
- Supabase supports many different [third-party providers](https://supabase.com/docs/guides/auth#configure-third-party-providers).
examples:
- id: sign-in-using-a-third-party-provider
name: Sign in using a third-party provider
@@ -279,7 +279,7 @@ functions:
isSpotlight: false
description: |
- When the third-party provider successfully authenticates the user, the provider redirects the user to the URL specified in the `redirectTo` parameter. This parameter defaults to the [`SITE_URL`](/docs/reference/auth/config#site_url). It does not redirect the user immediately after invoking this method.
- See [redirect URLs and wildcards](/docs/guides/auth/overview#redirect-urls-and-wildcards) to add additional redirect URLs to your project.
- See [redirect URLs and wildcards](/docs/guides/auth#redirect-urls-and-wildcards) to add additional redirect URLs to your project.
code: |
```js
const { data, error } = await supabase.auth.signInWithOAuth({
@@ -753,7 +753,7 @@ functions:
You can use [`onAuthStateChange()`](/docs/reference/javascript/auth-onauthstatechange) to listen and invoke a callback function on these events.
- When the user clicks the reset link in the email they are redirected back to your application.
You can configure the URL that the user is redirected to with the `redirectTo` parameter.
See [redirect URLs and wildcards](/docs/guides/auth/overview#redirect-urls-and-wildcards) to add additional redirect URLs to your project.
See [redirect URLs and wildcards](/docs/guides/auth#redirect-urls-and-wildcards) to add additional redirect URLs to your project.
- After the user has been redirected successfully, prompt them for a new password and call `updateUser()`:
```js
const { data, error } = await supabase.auth.updateUser({
@@ -5,7 +5,8 @@ import { SupaRow } from '../../types'
import { NullValue } from '../common'
import * as Tooltip from '@radix-ui/react-tooltip'
import { Button, IconArrowRight } from 'ui'
import { useParams, useStore } from 'hooks'
import { useStore } from 'hooks'
import { useParams } from 'common/hooks'
export const ForeignKeyFormatter = (props: PropsWithChildren<FormatterProps<SupaRow, unknown>>) => {
const { ref, id } = useParams()
@@ -1,7 +1,8 @@
import { useState } from 'react'
import { Button, IconAlertCircle, IconExternalLink, Modal } from 'ui'
import Link from 'next/link'
import { useParams, useStore } from 'hooks'
import { useStore } from 'hooks'
import { useParams } from 'common/hooks'
import ConfirmationModal from 'components/ui/ConfirmationModal'
import type { PostgresTable } from '@supabase/postgres-meta'
@@ -29,7 +30,7 @@ export default function RLSBannerWarning() {
<>
{!isAcknowledged && !rlsEnabled && isPublicTable ? (
<div>
<div className="text-center bg-amber-500 text-amber-900 text-xs py-2.5 flex items-center justify-center relative">
<div className="text-center bg-amber-500 text-amber-1100 dark:text-amber-900 text-xs py-2.5 flex items-center justify-center relative">
<IconAlertCircle size={16} strokeWidth={2} />
<span className="uppercase font-bold ml-2">Warning</span>: This table is publicly
readable and writable.{' '}
@@ -41,7 +42,7 @@ export default function RLSBannerWarning() {
<div className="ml-20 absolute right-2">
<Button
type="outline"
className="text-white hover:text-scale-1200"
className="hover:text-scale-1200 text-amber-900 dark:text-amber-900 border border-amber-800"
onClick={() => setIsOpen(true)}
>
Dismiss
@@ -1,7 +1,7 @@
import { useQueryClient } from '@tanstack/react-query'
import { SupaTable } from 'components/grid/types'
import { sqlKeys } from 'data/sql/keys'
import { useParams } from 'hooks'
import { useParams } from 'common/hooks'
import { useEffect, useState } from 'react'
import { Button, IconCheck, IconRefreshCw } from 'ui'
import { SupabaseGridQueue } from '../../constants'
@@ -7,7 +7,7 @@
// "https://uptime-monitor-fe.vercel.app/"
// Supports wildcards, port numbers at the end, paths at the end and query params
const baseDomainRegex =
const baseUrlRegex =
/^((ftp|http|https):\/\/)?(www.)?(?!.*(ftp|http|https|www.))[a-zA-Z0-9_*-]+(\.[a-zA-Z0-9_*-]+)+((\/)[\w#]+)*(\/\w+\?[a-zA-Z0-9_]+=\w+(&[a-zA-Z0-9_]+=\w+)*)+(?:\.[a-z]+)*(?::\d+)?(?![^<]*(?:<\/\w+>|\/?>))(.*)?\/?(.)*?$/gm
// iOS deep linking scheme https://benoitpasquier.com/deep-linking-url-scheme-ios/
@@ -21,7 +21,7 @@ const localhostRegex = /^(?:^|\s)((https?:\/\/)?(?:localhost|[\w-]+(?:\.[\w-]+)+
const chromeExtensionRegex = /chrome-extension:\/\/([a-zA-Z]*)/gm
// combine the above regexes
export const domainRegex = new RegExp(
`(${baseDomainRegex.source})|(${localhostRegex.source})|(${appRegex.source})|(${chromeExtensionRegex.source})`,
export const urlRegex = new RegExp(
`(${baseUrlRegex.source})|(${localhostRegex.source})|(${appRegex.source})|(${chromeExtensionRegex.source})`,
'i'
)
@@ -3,7 +3,8 @@ 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 { useStore } from 'hooks'
import { useParams } from 'common/hooks'
import { PolicyEditorModal, PolicyTableRow } from 'components/interfaces/Auth/Policies'
import type { PostgresRole, PostgresTable } from '@supabase/postgres-meta'
@@ -9,10 +9,10 @@ import { HorizontalShimmerWithIcon } from 'components/ui/Shimmers'
interface Props {
canUpdate: boolean
onSelectDomainToDelete: (domain: string) => void
onSelectUrlToDelete: (url: string) => void
}
const DomainList: FC<Props> = ({ canUpdate, onSelectDomainToDelete }) => {
const RedirectUrlList: FC<Props> = ({ canUpdate, onSelectUrlToDelete }) => {
const { authConfig } = useStore()
const URI_ALLOW_LIST_ARRAY = authConfig.config.URI_ALLOW_LIST
@@ -31,20 +31,20 @@ const DomainList: FC<Props> = ({ canUpdate, onSelectDomainToDelete }) => {
</ValueContainer>
</>
) : URI_ALLOW_LIST_ARRAY.length > 0 ? (
URI_ALLOW_LIST_ARRAY.map((domain: string) => {
URI_ALLOW_LIST_ARRAY.map((url: string) => {
return (
<ValueContainer key={domain}>
<ValueContainer key={url}>
<div className="flex items-center gap-4 font-mono">
<span className="text-scale-900">
<IconGlobe strokeWidth={2} size={14} />
</span>
<span className="text-sm">{domain}</span>
<span className="text-sm">{url}</span>
</div>
{canUpdate && (
<Button
type="default"
icon={<IconTrash />}
onClick={() => onSelectDomainToDelete(domain)}
onClick={() => onSelectUrlToDelete(url)}
>
Remove
</Button>
@@ -69,4 +69,4 @@ const DomainList: FC<Props> = ({ canUpdate, onSelectDomainToDelete }) => {
)
}
export default observer(DomainList)
export default observer(RedirectUrlList)
@@ -7,10 +7,10 @@ import { PermissionAction } from '@supabase/shared-types/out/constants'
import { checkPermissions, useStore } from 'hooks'
import { FormHeader } from 'components/ui/Forms'
import { domainRegex } from '../Auth.constants'
import DomainList from './DomainList'
import { urlRegex } from '../Auth.constants'
import RedirectUrlList from './RedirectUrlList'
const RedirectDomains = () => {
const RedirectUrls = () => {
const { authConfig, ui } = useStore()
const URI_ALLOW_LIST_ARRAY = authConfig.config.URI_ALLOW_LIST
@@ -19,24 +19,24 @@ const RedirectDomains = () => {
const [open, setOpen] = useState(false)
const [isDeleting, setIsDeleting] = useState(false)
const [selectedDomainToDelete, setSelectedDomainToDelete] = useState<string>()
const [selectedUrlToDelete, setSelectedUrlToDelete] = useState<string>()
const canUpdateConfig = checkPermissions(PermissionAction.UPDATE, 'custom_config_gotrue')
const newDomainSchema = object({
domain: string().matches(domainRegex, 'URL is not valid').required(),
const newUrlSchema = object({
url: string().matches(urlRegex, 'URL is not valid').required(),
})
const onAddNewDomain = async (values: any, { setSubmitting }: any) => {
const onAddNewUrl = async (values: any, { setSubmitting }: any) => {
setSubmitting(true)
const payload = URI_ALLOW_LIST_ARRAY
payload.push(values.domain)
payload.push(values.url)
const payloadString = payload.toString()
if (payloadString.length > 2 * 1024) {
ui.setNotification({
message: 'Too many redirect domains, please remove some or try to use wildcards',
message: 'Too many redirect URLs, please remove some or try to use wildcards',
category: 'error',
})
@@ -47,36 +47,36 @@ const RedirectDomains = () => {
const { error } = await authConfig.update({ URI_ALLOW_LIST: payloadString })
if (!error) {
setOpen(false)
ui.setNotification({ category: 'success', message: 'Successfully added domain' })
ui.setNotification({ category: 'success', message: 'Successfully added URL' })
} else {
ui.setNotification({
error,
category: 'error',
message: `Failed to update domain: ${error?.message}`,
message: `Failed to update URL: ${error?.message}`,
})
}
setSubmitting(false)
}
const onConfirmDeleteDomain = async (domain?: string) => {
if (!domain) return
const onConfirmDeleteUrl = async (url?: string) => {
if (!url) return
setIsDeleting(true)
// Remove selectedDomain from array and update
const payload = URI_ALLOW_LIST_ARRAY.filter((e: string) => e !== domain)
// Remove selectedUrl from array and update
const payload = URI_ALLOW_LIST_ARRAY.filter((e: string) => e !== url)
const { error } = await authConfig.update({ URI_ALLOW_LIST: payload.toString() })
if (!error) {
setSelectedDomainToDelete(undefined)
ui.setNotification({ category: 'success', message: 'Successfully removed domain' })
setSelectedUrlToDelete(undefined)
ui.setNotification({ category: 'success', message: 'Successfully removed URL' })
} else {
ui.setNotification({
error,
category: 'error',
message: `Failed to remove domain: ${error?.message}`,
message: `Failed to remove URL: ${error?.message}`,
})
}
@@ -115,50 +115,45 @@ const RedirectDomains = () => {
)}
</Tooltip.Root>
</div>
<DomainList canUpdate={canUpdateConfig} onSelectDomainToDelete={setSelectedDomainToDelete} />
<RedirectUrlList canUpdate={canUpdateConfig} onSelectUrlToDelete={setSelectedUrlToDelete} />
<Modal
hideFooter
size="small"
visible={open}
onCancel={() => setOpen(!open)}
header={<h3 className="text-sm">Add a new domain</h3>}
header={<h3 className="text-sm">Add a new URL</h3>}
>
<Form
validateOnBlur
id="new-domain-form"
initialValues={{ domain: '' }}
validationSchema={newDomainSchema}
onSubmit={onAddNewDomain}
id="new-redirect-url-form"
initialValues={{ url: '' }}
validationSchema={newUrlSchema}
onSubmit={onAddNewUrl}
>
{({ isSubmitting }: { isSubmitting: boolean }) => {
return (
<div className="mb-4 space-y-4 pt-4">
<div className="px-5">
<p className="text-sm text-scale-1100">
This will add a domain to a list of allowed domains that can interact with your
This will add a URL to a list of allowed URLs that can interact with your
Authenticaton services for this project.
</p>
</div>
<div className="border-overlay-border border-t" />
<div className="px-5">
<Input
id="domain"
name="domain"
label="Domain"
placeholder="https://mydomain.com"
/>
<Input id="url" name="url" label="URL" placeholder="https://mydomain.com" />
</div>
<div className="border-overlay-border border-t" />
<div className="px-5">
<Button
block
form="new-domain-form"
form="new-redirect-url-form"
htmlType="submit"
size="medium"
disabled={isSubmitting}
loading={isSubmitting}
>
Add domain
Add URL
</Button>
</div>
</div>
@@ -169,18 +164,18 @@ const RedirectDomains = () => {
<Modal
hideFooter
size="small"
visible={selectedDomainToDelete !== undefined}
header={<h3 className="text-sm">Remove domain</h3>}
onCancel={() => setSelectedDomainToDelete(undefined)}
visible={selectedUrlToDelete !== undefined}
header={<h3 className="text-sm">Remove URL</h3>}
onCancel={() => setSelectedUrlToDelete(undefined)}
>
<div className="mb-4 space-y-4 pt-4">
<div className="px-5">
<p className="mb-2 text-sm text-scale-1100">
Are you sure you want to remove{' '}
<span className="text-scale-1200">{selectedDomainToDelete}</span>?
<span className="text-scale-1200">{selectedUrlToDelete}</span>?
</p>
<p className="text-sm text-scale-1100">
This domain will no longer work with your authentication configuration.
<p className="text-scale-1100 text-sm">
This URL will no longer work with your authentication configuration.
</p>
</div>
<div className="border-overlay-border border-t"></div>
@@ -189,7 +184,7 @@ const RedirectDomains = () => {
block
type="default"
size="medium"
onClick={() => setSelectedDomainToDelete(undefined)}
onClick={() => setSelectedUrlToDelete(undefined)}
>
Cancel
</Button>
@@ -198,9 +193,9 @@ const RedirectDomains = () => {
size="medium"
type="warning"
loading={isDeleting}
onClick={() => onConfirmDeleteDomain(selectedDomainToDelete)}
onClick={() => onConfirmDeleteUrl(selectedUrlToDelete)}
>
{isDeleting ? 'Removing...' : 'Remove domain'}
{isDeleting ? 'Removing...' : 'Remove URL'}
</Button>
</div>
</div>
@@ -209,4 +204,4 @@ const RedirectDomains = () => {
)
}
export default observer(RedirectDomains)
export default observer(RedirectUrls)
@@ -13,7 +13,7 @@ import {
FormSectionLabel,
} from 'components/ui/Forms'
import { useStore, checkPermissions } from 'hooks'
import { domainRegex } from './../Auth.constants'
import { urlRegex } from './../Auth.constants'
import { defaultDisabledSmtpFormValues } from './SmtpForm.constants'
import { generateFormValues, isSmtpEnabled } from './SmtpForm.utils'
@@ -55,7 +55,7 @@ const SmtpForm = () => {
},
then: (schema) =>
schema
.matches(domainRegex, 'Must be a valid URL or IP address')
.matches(urlRegex, 'Must be a valid URL or IP address')
.required('Host URL is required.'),
otherwise: (schema) => schema,
}),
@@ -2,7 +2,8 @@ import { FC } from 'react'
import Link from 'next/link'
import { Badge, Button, IconExternalLink, Radio } from 'ui'
import { useFlag, useParams } from 'hooks'
import { useFlag } from 'hooks'
import { useParams } from 'common/hooks'
import { getProductPrice } from '../Billing.utils'
import DisabledWarningDueToIncident from 'components/ui/DisabledWarningDueToIncident'
import { SubscriptionAddon } from './AddOns.types'
@@ -2,7 +2,8 @@ import Link from 'next/link'
import { FC } from 'react'
import { Badge, Button, Radio } from 'ui'
import { useFlag, useParams } from 'hooks'
import { useFlag } from 'hooks'
import { useParams } from 'common/hooks'
import { getProductPrice } from '../Billing.utils'
import DisabledWarningDueToIncident from 'components/ui/DisabledWarningDueToIncident'
import { SubscriptionAddon } from './AddOns.types'
@@ -2,7 +2,8 @@ import Link from 'next/link'
import { FC } from 'react'
import { Badge, IconAlertCircle, Radio, Button } from 'ui'
import { useFlag, useParams, useStore } from 'hooks'
import { useFlag, useStore } from 'hooks'
import { useParams } from 'common/hooks'
import { SubscriptionAddon } from './AddOns.types'
import { getProductPrice } from '../Billing.utils'
import DisabledWarningDueToIncident from 'components/ui/DisabledWarningDueToIncident'
@@ -1,7 +1,7 @@
import { FC } from 'react'
import Link from 'next/link'
import { Button } from 'ui'
import { useParams } from 'hooks'
import { useParams } from 'common/hooks'
interface Props {
currentOption?: any
@@ -5,7 +5,8 @@ import { Button, Loading } from 'ui'
import * as Tooltip from '@radix-ui/react-tooltip'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { checkPermissions, useFlag, useParams } from 'hooks'
import { checkPermissions, useFlag } from 'hooks'
import { useParams } from 'common/hooks'
import { STRIPE_PRODUCT_IDS } from 'lib/constants'
import { formatBytes } from 'lib/helpers'
@@ -2,7 +2,8 @@ import Link from 'next/link'
import { Button, IconCalendar } from 'ui'
import { FormPanel } from 'components/ui/Forms'
import * as Tooltip from '@radix-ui/react-tooltip'
import { checkPermissions, useParams } from 'hooks'
import { checkPermissions } from 'hooks'
import { useParams } from 'common/hooks'
import { useProjectSubscriptionQuery } from 'data/subscriptions/project-subscription-query'
import { getPITRRetentionDuration } from './PITR.utils'
import { PermissionAction } from '@supabase/shared-types/out/constants'
@@ -4,7 +4,8 @@ import { partition, isNull } from 'lodash'
import { Input, IconSearch, IconAlertCircle, Button, IconBookOpen } from 'ui'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useStore, checkPermissions, useParams } from 'hooks'
import { useStore, checkPermissions } from 'hooks'
import { useParams } from 'common/hooks'
import ExtensionCard from './ExtensionCard'
import { HIDDEN_EXTENSIONS } from './Extensions.constants'
import NoSearchResults from 'components/ui/NoSearchResults'
@@ -3,7 +3,8 @@ import { useState, useRef, useEffect } from 'react'
import { PostgresTrigger } from '@supabase/postgres-meta'
import { Button, SidePanel, Form, Input, Listbox, Checkbox, Radio, Badge, Modal } from 'ui'
import { useParams, useStore } from 'hooks'
import { useStore } from 'hooks'
import { useParams } from 'common/hooks'
import { tryParseJson, uuidv4 } from 'lib/helpers'
import HTTPRequestFields from './HTTPRequestFields'
import { isValidHttpUrl } from './Hooks.utils'
@@ -11,7 +11,8 @@ import {
} from 'ui'
import { FormSection, FormSectionLabel, FormSectionContent } from 'components/ui/Forms'
import { HTTPArgument } from './EditHookPanel'
import { useParams, useStore } from 'hooks'
import { useStore } from 'hooks'
import { useParams } from 'common/hooks'
import { useEdgeFunctionsQuery } from 'data/edge-functions/edge-functions-query'
import clsx from 'clsx'
import { uuidv4 } from 'lib/helpers'
@@ -6,7 +6,8 @@ import * as Tooltip from '@radix-ui/react-tooltip'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { Badge, Button, Dropdown, IconMoreVertical, IconTrash, IconEdit3 } from 'ui'
import { checkPermissions, useParams, useStore } from 'hooks'
import { checkPermissions, useStore } from 'hooks'
import { useParams } from 'common/hooks'
import { BASE_PATH } from 'lib/constants'
import Table from 'components/to-be-cleaned/Table'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
@@ -6,7 +6,8 @@ import { observer } from 'mobx-react-lite'
import { Button, Form, Input, IconArrowLeft, IconExternalLink, IconEdit, IconTrash } from 'ui'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { checkPermissions, useParams, useStore } from 'hooks'
import { checkPermissions, useStore } from 'hooks'
import { useParams } from 'common/hooks'
import {
FormPanel,
FormActions,
@@ -16,7 +16,8 @@ import {
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { VaultSecret } from 'types'
import { checkPermissions, useImmutableValue, useParams, useStore } from 'hooks'
import { checkPermissions, useImmutableValue, useStore } from 'hooks'
import { useParams } from 'common/hooks'
import { useFDWsQuery } from 'data/fdw/fdws-query'
import { useFDWUpdateMutation } from 'data/fdw/fdw-update-mutation'
@@ -6,7 +6,8 @@ import * as Tooltip from '@radix-ui/react-tooltip'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { Collapsible, IconChevronUp, Button, IconExternalLink, IconTrash, IconEdit } from 'ui'
import { useParams, useStore, checkPermissions } from 'hooks'
import { useStore, checkPermissions } from 'hooks'
import { useParams } from 'common/hooks'
import { WrapperMeta } from './Wrappers.types'
import { FDW } from 'data/fdw/fdws-query'
import { useFDWDeleteMutation } from 'data/fdw/fdw-delete-mutation'
@@ -5,7 +5,8 @@ import { observer } from 'mobx-react-lite'
import * as Tooltip from '@radix-ui/react-tooltip'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { checkPermissions, useParams, useStore } from 'hooks'
import { checkPermissions, useStore } from 'hooks'
import { useParams } from 'common/hooks'
import { BASE_PATH } from 'lib/constants'
const WrappersDisabledState = () => {
@@ -6,7 +6,8 @@ import { Button, Dropdown, IconPlus } from 'ui'
import * as Tooltip from '@radix-ui/react-tooltip'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useParams, checkPermissions } from 'hooks'
import { checkPermissions } from 'hooks'
import { useParams } from 'common/hooks'
import { WRAPPERS } from './Wrappers.constants'
interface Props {
@@ -4,7 +4,8 @@ import React from 'react'
import { Button, IconDownload, IconExternalLink } from 'ui'
import { API_ADMIN_URL } from 'lib/constants'
import { get } from 'lib/common/fetch'
import { useParams, useStore } from 'hooks'
import { useStore } from 'hooks'
import { useParams } from 'common/hooks'
import CodeSnippet from 'components/to-be-cleaned/Docs/CodeSnippet'
interface Props {
@@ -231,49 +231,51 @@ const ResourceContent = ({
</div>
</>
)}
<>
<h3 className="text-scale-1200 mt-4 px-6">Subscribe to changes</h3>
<div className="doc-section">
<article className="text ">
<p>
Supabase provides realtime functionality and broadcasts database changes to authorized
users depending on Row Level Security (RLS) policies.
</p>
<p>
<a href="https://supabase.com/docs/reference/javascript/subscribe" target="_blank">
Learn more
</a>
</p>
</article>
<article className="code">
<CodeSnippet
selectedLang={selectedLang}
snippet={Snippets.subscribeAll(resourceMeta.camelCase, resourceId)}
/>
<CodeSnippet
selectedLang={selectedLang}
snippet={Snippets.subscribeInserts(resourceMeta.camelCase, resourceId)}
/>
<CodeSnippet
selectedLang={selectedLang}
snippet={Snippets.subscribeUpdates(resourceMeta.camelCase, resourceId)}
/>
<CodeSnippet
selectedLang={selectedLang}
snippet={Snippets.subscribeDeletes(resourceMeta.camelCase, resourceId)}
/>
<CodeSnippet
selectedLang={selectedLang}
snippet={Snippets.subscribeEq(
resourceMeta.camelCase,
resourceId,
'column_name',
'someValue'
)}
/>
</article>
</div>
</>
{(methods.includes('DELETE') || methods.includes('POST') || methods.includes('PATCH')) && (
<>
<h3 className="text-scale-1200 mt-4 px-6">Subscribe to changes</h3>
<div className="doc-section">
<article className="text ">
<p>
Supabase provides realtime functionality and broadcasts database changes to
authorized users depending on Row Level Security (RLS) policies.
</p>
<p>
<a href="https://supabase.com/docs/reference/javascript/subscribe" target="_blank">
Learn more
</a>
</p>
</article>
<article className="code">
<CodeSnippet
selectedLang={selectedLang}
snippet={Snippets.subscribeAll(resourceMeta.camelCase, resourceId)}
/>
<CodeSnippet
selectedLang={selectedLang}
snippet={Snippets.subscribeInserts(resourceMeta.camelCase, resourceId)}
/>
<CodeSnippet
selectedLang={selectedLang}
snippet={Snippets.subscribeUpdates(resourceMeta.camelCase, resourceId)}
/>
<CodeSnippet
selectedLang={selectedLang}
snippet={Snippets.subscribeDeletes(resourceMeta.camelCase, resourceId)}
/>
<CodeSnippet
selectedLang={selectedLang}
snippet={Snippets.subscribeEq(
resourceMeta.camelCase,
resourceId,
'column_name',
'someValue'
)}
/>
</article>
</div>
</>
)}
</>
)
}
@@ -17,7 +17,8 @@ import {
IconExternalLink,
} from 'ui'
import { useStore, useParams, checkPermissions } from 'hooks'
import { useStore, checkPermissions } from 'hooks'
import { useParams } from 'common/hooks'
import Panel from 'components/ui/Panel'
import CommandRender from '../CommandRender'
import { generateCLICommands } from './EdgeFunctionDetails.utils'
@@ -5,7 +5,8 @@ import { observer } from 'mobx-react-lite'
import { IconCheck, IconClipboard } from 'ui'
import * as Tooltip from '@radix-ui/react-tooltip'
import { useParams, useStore } from 'hooks'
import { useStore } from 'hooks'
import { useParams } from 'common/hooks'
import Table from 'components/to-be-cleaned/Table'
import { EdgeFunctionsResponse } from 'data/edge-functions/edge-functions-query'
@@ -1,7 +1,7 @@
import CommandRender from 'components/interfaces/Functions/CommandRender'
import { useAccessTokensQuery } from 'data/access-tokens/access-tokens-query'
import { useProjectApiQuery } from 'data/config/project-api-query'
import { useParams } from 'hooks'
import { useParams } from 'common/hooks'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { FC, useState } from 'react'
@@ -6,7 +6,8 @@ import { IconAlertCircle, IconLoader, Input } from 'ui'
import { useProjectApiQuery } from 'data/config/project-api-query'
import { useJwtSecretUpdatingStatusQuery } from 'data/config/jwt-secret-updating-status-query'
import { checkPermissions, useParams } from 'hooks'
import { checkPermissions } from 'hooks'
import { useParams } from 'common/hooks'
import Snippets from 'components/to-be-cleaned/Docs/Snippets'
import Panel from 'components/ui/Panel'
import SimpleCodeBlock from 'components/to-be-cleaned/SimpleCodeBlock'
@@ -7,7 +7,7 @@ import ChartHandler from 'components/to-be-cleaned/Charts/ChartHandler'
import Panel from 'components/ui/Panel'
import { DATE_FORMAT, METRICS } from 'lib/constants'
import { ChartIntervals } from 'types'
import { useParams } from 'hooks'
import { useParams } from 'common/hooks'
import { useProjectLogStatsQuery } from 'data/logs/project-log-stats-query'
const CHART_INTERVALS: ChartIntervals[] = [
@@ -2,7 +2,7 @@ import { FC } from 'react'
import { observer } from 'mobx-react-lite'
import { IconLoader, IconAlertCircle } from 'ui'
import { useParams } from 'hooks'
import { useParams } from 'common/hooks'
import { ProjectUsage, NewProjectPanel } from 'components/interfaces/Home'
import InformationBox from 'components/ui/InformationBox'
import { ProjectUsageResponseUsageKeys, useProjectUsageQuery } from 'data/usage/project-usage-query'
@@ -1,7 +1,8 @@
import { useEffect } from 'react'
import { Form, Input } from 'ui'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useStore, checkPermissions, useParams } from 'hooks'
import { useStore, checkPermissions } from 'hooks'
import { useParams } from 'common/hooks'
import { API_URL } from 'lib/constants'
import { patch } from 'lib/common/fetch'
import { FormActions, FormPanel, FormSection, FormSectionContent } from 'components/ui/Forms'
@@ -2,7 +2,8 @@ import { useState, useEffect } from 'react'
import { observer } from 'mobx-react-lite'
import { Project } from 'types'
import { useParams, useStore } from 'hooks'
import { useStore } from 'hooks'
import { useParams } from 'common/hooks'
import { API_URL } from 'lib/constants'
import { get } from 'lib/common/fetch'
@@ -3,7 +3,8 @@ import { observer } from 'mobx-react-lite'
import { Form, Input } from 'ui'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useStore, checkPermissions, useParams } from 'hooks'
import { useStore, checkPermissions } from 'hooks'
import { useParams } from 'common/hooks'
import { API_URL } from 'lib/constants'
import { patch } from 'lib/common/fetch'
@@ -2,10 +2,11 @@ import { isNil } from 'lodash'
import { useEffect, useState } from 'react'
import { object, string } from 'yup'
import * as Tooltip from '@radix-ui/react-tooltip'
import { Button, Form, IconMail, Input, Modal, Select } from 'ui'
import { Button, Form, IconMail, Input, Listbox, Modal } from 'ui'
import { Member, Role } from 'types'
import { checkPermissions, useParams, useStore } from 'hooks'
import { checkPermissions, useStore } from 'hooks'
import { useParams } from 'common/hooks'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useOrganizationMemberInviteCreateMutation } from 'data/organizations/organization-member-invite-create-mutation'
@@ -155,7 +156,7 @@ const InviteMemberButton = ({
<div className="space-y-4">
<div className="space-y-2">
{roles && (
<Select
<Listbox
id="role"
name="role"
label="Member role"
@@ -166,11 +167,11 @@ const InviteMemberButton = ({
}
>
{roles.map((role: any) => (
<Select.Option key={role.id} value={role.id}>
<Listbox.Option key={role.id} value={role.id} label={role.name}>
{role.name}
</Select.Option>
</Listbox.Option>
))}
</Select>
</Listbox>
)}
</div>
@@ -5,7 +5,8 @@ import { FC } from 'react'
import { Button, Dropdown, IconMoreHorizontal, IconTrash } from 'ui'
import { confirmAlert } from 'components/to-be-cleaned/ModalsDeprecated/ConfirmModal'
import { checkPermissions, useParams, useStore } from 'hooks'
import { checkPermissions, useStore } from 'hooks'
import { useParams } from 'common/hooks'
import { Member, Role } from 'types'
import { useOrganizationMemberDeleteMutation } from 'data/organizations/organization-member-delete-mutation'
@@ -5,7 +5,8 @@ import { observer } from 'mobx-react-lite'
import { Badge, Button, Loading, Listbox, IconUser, Modal, IconAlertCircle, IconLoader } from 'ui'
import { Member } from 'types'
import { useStore, useParams } from 'hooks'
import { useStore } from 'hooks'
import { useParams } from 'common/hooks'
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
import Table from 'components/to-be-cleaned/Table'
@@ -3,7 +3,8 @@ import { observer } from 'mobx-react-lite'
import { Button, Input, IconSearch } from 'ui'
import * as Tooltip from '@radix-ui/react-tooltip'
import { useStore, useParams } from 'hooks'
import { useStore } from 'hooks'
import { useParams } from 'common/hooks'
import { post } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import { useProfileQuery } from 'data/profile/profile-query'
@@ -16,7 +16,8 @@ import {
} from 'ui'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { checkPermissions, useParams } from 'hooks'
import { checkPermissions } from 'hooks'
import { useParams } from 'common/hooks'
import { uuidv4 } from 'lib/helpers'
import { METRIC_CATEGORIES, METRICS, TIME_PERIODS_REPORTS } from 'lib/constants'
import { useProjectContentStore } from 'stores/projectContentStore'
@@ -23,7 +23,8 @@ import {
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { uuidv4 } from 'lib/helpers'
import { useStore, checkPermissions, useParams } from 'hooks'
import { useStore, checkPermissions } from 'hooks'
import { useParams } from 'common/hooks'
import { useJwtSecretUpdatingStatusQuery } from 'data/config/jwt-secret-updating-status-query'
import { useJwtSecretUpdateMutation } from 'data/config/jwt-secret-update-mutation'
import { useProjectPostgrestConfigQuery } from 'data/config/project-postgrest-config-query'
@@ -4,7 +4,8 @@ import { observer } from 'mobx-react-lite'
import { Input, Form, IconAlertCircle, InputNumber } from 'ui'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { checkPermissions, useStore, useParams } from 'hooks'
import { checkPermissions, useStore } from 'hooks'
import { useParams } from 'common/hooks'
import { useProjectPostgrestConfigQuery } from 'data/config/project-postgrest-config-query'
import { useProjectPostgrestConfigUpdateMutation } from 'data/config/project-postgrest-config-update-mutation'
import MultiSelect from 'components/ui/MultiSelect'
@@ -3,7 +3,8 @@ import { JwtSecretUpdateError, JwtSecretUpdateStatus } from '@supabase/shared-ty
import { useEffect, useRef } from 'react'
import { IconAlertCircle, Input } from 'ui'
import { useParams, useStore } from 'hooks'
import { useStore } from 'hooks'
import { useParams } from 'common/hooks'
import { useJwtSecretUpdatingStatusQuery } from 'data/config/jwt-secret-updating-status-query'
import { useProjectApiQuery } from 'data/config/project-api-query'
@@ -5,7 +5,8 @@ import generator from 'generate-password'
import { Input, Button, Modal } from 'ui'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { checkPermissions, useParams, useStore } from 'hooks'
import { checkPermissions, useStore } from 'hooks'
import { useParams } from 'common/hooks'
import { patch } from 'lib/common/fetch'
import { passwordStrength } from 'lib/helpers'
import { API_URL, DEFAULT_MINIMUM_PASSWORD_STRENGTH } from 'lib/constants'
@@ -3,7 +3,8 @@ import { Address4 } from 'ip-address'
import { Button, Form, IconHelpCircle, Input, Modal } from 'ui'
import * as Tooltip from '@radix-ui/react-tooltip'
import { useStore, useParams } from 'hooks'
import { useStore } from 'hooks'
import { useParams } from 'common/hooks'
import InformationBox from 'components/ui/InformationBox'
import { checkIfPrivate, getAddressEndRange } from './NetworkRestrictions.utils'
import { useNetworkRestrictionsApplyMutation } from 'data/network-restrictions/network-retrictions-apply-mutation'
@@ -1,7 +1,8 @@
import { FC, useState } from 'react'
import { Modal, Button } from 'ui'
import { useStore, useParams } from 'hooks'
import { useStore } from 'hooks'
import { useParams } from 'common/hooks'
import { useNetworkRestrictionsApplyMutation } from 'data/network-restrictions/network-retrictions-apply-mutation'
interface Props {
@@ -1,7 +1,8 @@
import { FC, useState } from 'react'
import { Modal, Button } from 'ui'
import { useStore, useParams } from 'hooks'
import { useStore } from 'hooks'
import { useParams } from 'common/hooks'
import { useNetworkRestrictionsApplyMutation } from 'data/network-restrictions/network-retrictions-apply-mutation'
import InformationBox from 'components/ui/InformationBox'
@@ -4,7 +4,8 @@ import * as Tooltip from '@radix-ui/react-tooltip'
import { Button, IconAlertCircle, IconExternalLink, IconGlobe, IconLock } from 'ui'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { checkPermissions, useParams } from 'hooks'
import { checkPermissions } from 'hooks'
import { useParams } from 'common/hooks'
import Panel from 'components/ui/Panel'
import { FormPanel, FormHeader } from 'components/ui/Forms'
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
@@ -1,7 +1,8 @@
import { FC, useState } from 'react'
import { Alert, Button, Form, Input, Modal } from 'ui'
import { useStore, useParams } from 'hooks'
import { useStore } from 'hooks'
import { useParams } from 'common/hooks'
import { useNetworkRestrictionsApplyMutation } from 'data/network-restrictions/network-retrictions-apply-mutation'
interface Props {
@@ -4,7 +4,8 @@ import * as Tooltip from '@radix-ui/react-tooltip'
import { Button, IconDownload, Toggle, IconLoader, Alert } from 'ui'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { checkPermissions, useParams, useStore, useFlag } from 'hooks'
import { checkPermissions, useStore, useFlag } from 'hooks'
import { useParams } from 'common/hooks'
import {
FormHeader,
FormPanel,
@@ -2,7 +2,8 @@ import Link from 'next/link'
import { observer } from 'mobx-react-lite'
import { IconAlertCircle } from 'ui'
import { useParams, useStore } from 'hooks'
import { useStore } from 'hooks'
import { useParams } from 'common/hooks'
import { useProjectApiQuery } from 'data/config/project-api-query'
import { useCustomDomainsQuery } from 'data/custom-domains/custom-domains-query'
import Panel from 'components/ui/Panel'
@@ -4,7 +4,8 @@ import { observer } from 'mobx-react-lite'
import { Button, Form, IconExternalLink, Input } from 'ui'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { checkPermissions, useParams, useStore } from 'hooks'
import { checkPermissions, useStore } from 'hooks'
import { useParams } from 'common/hooks'
import {
FormActions,
FormPanel,
@@ -4,7 +4,8 @@ import { Alert, Badge, Button, IconPackage, Input } from 'ui'
import { observer } from 'mobx-react-lite'
import * as Tooltip from '@radix-ui/react-tooltip'
import { useFlag, useParams, useStore } from 'hooks'
import { useFlag, useStore } from 'hooks'
import { useParams } from 'common/hooks'
import { PRICING_TIER_PRODUCT_IDS } from 'lib/constants'
import {
FormHeader,
@@ -12,7 +12,8 @@ import {
IconExternalLink,
} from 'ui'
import { useParams, useStore } from 'hooks'
import { useStore } from 'hooks'
import { useParams } from 'common/hooks'
import { post } from 'lib/common/fetch'
import { API_ADMIN_URL, PROJECT_STATUS } from 'lib/constants'
import InformationBox from 'components/ui/InformationBox'
@@ -22,7 +22,7 @@ import ShimmerLine from 'components/ui/ShimmerLine'
import LoadingOpacity from 'components/ui/LoadingOpacity'
import { useUpgradePrompt } from 'hooks/misc/useUpgradePrompt'
import UpgradePrompt from './UpgradePrompt'
import { useParams } from 'hooks'
import { useParams } from 'common/hooks'
import { useProjectSubscriptionQuery } from 'data/subscriptions/project-subscription-query'
/**
@@ -18,7 +18,8 @@ import {
IconExternalLink,
} from 'ui'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useParams, useStore, checkPermissions } from 'hooks'
import { useStore, checkPermissions } from 'hooks'
import { useParams } from 'common/hooks'
import Divider from 'components/ui/Divider'
const DEFAULT_KEY_NAME = 'No description provided'
@@ -16,7 +16,8 @@ import {
import * as Tooltip from '@radix-ui/react-tooltip'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { VaultSecret } from 'types'
import { useParams, useStore, checkPermissions } from 'hooks'
import { useStore, checkPermissions } from 'hooks'
import { useParams } from 'common/hooks'
interface Props {
secret: VaultSecret
@@ -5,7 +5,8 @@ import { FC, Fragment, useState, useEffect } from 'react'
import { IconSearch, Input, Button, Listbox, IconLoader, IconExternalLink, IconX } from 'ui'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useStore, useParams, checkPermissions } from 'hooks'
import { useStore, checkPermissions } from 'hooks'
import { useParams } from 'common/hooks'
import SecretRow from './SecretRow'
import EditSecretModal from './EditSecretModal'
import DeleteSecretModal from './DeleteSecretModal'
@@ -5,7 +5,8 @@ import * as Tooltip from '@radix-ui/react-tooltip'
import { Button, IconExternalLink } from 'ui'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { checkPermissions, useParams, useStore } from 'hooks'
import { checkPermissions, useStore } from 'hooks'
import { useParams } from 'common/hooks'
import { BASE_PATH } from 'lib/constants'
interface Props {}
@@ -1,7 +1,8 @@
import { useState } from 'react'
import { SidePanel, IconBookOpen } from 'ui'
import { useParams, useStore } from 'hooks'
import { useStore } from 'hooks'
import { useParams } from 'common/hooks'
import { GeneralContent, ResourceContent } from '../Docs'
import LangSelector from '../Docs/LangSelector'
import GeneratingTypes from '../Docs/GeneratingTypes'
@@ -10,7 +10,8 @@ import type {
PostgresType,
} from '@supabase/postgres-meta'
import { useFlag, useParams, useStore } from 'hooks'
import { useFlag, useStore } from 'hooks'
import { useParams } from 'common/hooks'
import ActionBar from '../ActionBar'
import HeaderTitle from './HeaderTitle'
import ColumnType from './ColumnType'
@@ -5,7 +5,7 @@ import { Dictionary } from 'components/grid'
import { Modal } from 'ui'
import type { PostgresTable, PostgresColumn } from '@supabase/postgres-meta'
import { useStore } from 'hooks'
import { useStore, useUrlState } from 'hooks'
import { entityTypeKeys } from 'data/entity-types/keys'
import { useTableRowCreateMutation } from 'data/table-rows/table-row-create-mutation'
import { useTableRowUpdateMutation } from 'data/table-rows/table-row-update-mutation'
@@ -66,6 +66,7 @@ const SidePanelEditor = ({
onTableCreated = noop,
onColumnSaved = noop,
}: SidePanelEditorProps) => {
const [_, setParams] = useUrlState({ arrayKeys: ['filter', 'sort'] })
const { meta, ui } = useStore()
const queryClient = useQueryClient()
@@ -263,6 +264,14 @@ const SidePanelEditor = ({
if (response?.error) {
ui.setNotification({ category: 'error', message: response.error.message })
} else {
if (
!isNewRecord &&
payload.name &&
selectedColumnToEdit &&
selectedColumnToEdit.name !== payload.name
) {
reAddRenamedColumnSortAndFilter(selectedColumnToEdit.name, payload.name)
}
queryClient.invalidateQueries(sqlKeys.query(project?.ref, ['foreign-key-constraints']))
await Promise.all([
meta.tables.loadById(selectedTable!.id),
@@ -283,6 +292,28 @@ const SidePanelEditor = ({
resolve()
}
/**
* Adds the renamed column's filter and/or sort rules.
*/
const reAddRenamedColumnSortAndFilter = (oldColumnName: string, newColumnName: string) => {
setParams((prevParams) => {
const existingFilters = (prevParams?.filter ?? []) as string[]
const existingSorts = (prevParams?.sort ?? []) as string[]
return {
...prevParams,
filter: existingFilters.map((filter: string) => {
const [column] = filter.split(':')
return column === oldColumnName ? filter.replace(column, newColumnName) : filter
}),
sort: existingSorts.map((sort: string) => {
const [column] = sort.split(':')
return column === oldColumnName ? sort.replace(column, newColumnName) : sort
}),
}
})
}
const saveTable = async (
payload: any,
columns: ColumnField[],
@@ -7,7 +7,8 @@ import { PermissionAction } from '@supabase/shared-types/out/constants'
import { QueryKey, useQueryClient } from '@tanstack/react-query'
import { SchemaView } from 'types'
import { checkPermissions, useFlag, useStore, useParams } from 'hooks'
import { checkPermissions, useFlag, useStore } from 'hooks'
import { useParams } from 'common/hooks'
import GridHeaderActions from './GridHeaderActions'
import NotFoundState from './NotFoundState'
import SidePanelEditor from './SidePanelEditor'
@@ -3,7 +3,8 @@ import { FC, ReactNode, useState } from 'react'
import { observer } from 'mobx-react-lite'
import { Button, IconExternalLink, IconCode, Modal, IconTerminal } from 'ui'
import { checkPermissions, useParams, withAuth } from 'hooks'
import { checkPermissions, withAuth } from 'hooks'
import { useParams } from 'common/hooks'
import FunctionsNav from '../interfaces/Functions/FunctionsNav'
import BaseLayout from 'components/layouts'
import NoPermission from 'components/ui/NoPermission'
@@ -44,11 +45,11 @@ const FunctionsLayout: FC<Props> = ({ title, children }) => {
<>
<div className="mx-auto max-w-5xl py-24 px-5">
<div
className="item-center
flex
className="item-center
flex
flex-col
justify-between
gap-y-4
justify-between
gap-y-4
xl:flex-row"
>
<div className="flex items-center gap-3">
@@ -2,7 +2,8 @@ import Link from 'next/link'
import { observer } from 'mobx-react-lite'
import { IS_PLATFORM, PRICING_TIER_PRODUCT_IDS } from 'lib/constants'
import { useFlag, useParams, useStore } from 'hooks'
import { useFlag, useStore } from 'hooks'
import { useParams } from 'common/hooks'
import BreadcrumbsView from './BreadcrumbsView'
import OrgDropdown from './OrgDropdown'
import ProjectDropdown from './ProjectDropdown'
@@ -13,7 +13,7 @@ import {
generateToolRoutes,
} from './NavigationBar.utils'
import NavigationIconButton from './NavigationIconButton'
import { useParams } from 'hooks/misc/useParams'
import { useParams } from 'common/hooks'
interface Props {}
@@ -1,4 +1,5 @@
import { useParams, useStore } from 'hooks'
import { useStore } from 'hooks'
import { useParams } from 'common/hooks'
import { observer } from 'mobx-react-lite'
import { createContext, PropsWithChildren, useContext, useMemo } from 'react'
import { Project } from 'types'
@@ -2,7 +2,8 @@ import Head from 'next/head'
import { FC, ReactNode, PropsWithChildren, Fragment } from 'react'
import { observer } from 'mobx-react-lite'
import { useRouter } from 'next/router'
import { useStore, withAuth, useFlag, useParams } from 'hooks'
import { useStore, withAuth, useFlag } from 'hooks'
import { useParams } from 'common/hooks'
import { PROJECT_STATUS } from 'lib/constants'
import Connecting from 'components/ui/Loading'
Loaded 100 of 144 files, more files were not shown because too many files have changed in this diff. Show more