mirror of
https://github.com/supabase/supabase.git
synced 2026-10-14 05:39:11 +03:00
Merge branch 'master' into pr/13270
This commit is contained in:
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 (
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
File renamed without changes.
@@ -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!
|
||||
|
||||
@@ -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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
## 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).
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
## 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:
|
||||
|
||||

|
||||
|
||||
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
|
||||
@@ -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>
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
]
|
||||
@@ -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",
|
||||
|
||||
Generated
+358
-349
@@ -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",
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import React from 'react'
|
||||
|
||||
export * from './useParams'
|
||||
File renamed without changes.
@@ -2,6 +2,7 @@ import SidebarLayout from './layouts/SidebarLayout'
|
||||
|
||||
export * from './Button'
|
||||
export * from './Providers'
|
||||
export * from './hooks'
|
||||
export * from './gotrue'
|
||||
export * from './auth'
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
+7
-7
@@ -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)
|
||||
+38
-43
@@ -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)
|
||||
File renamed without changes.
@@ -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'
|
||||
|
||||
+2
-1
@@ -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'
|
||||
|
||||
+2
-1
@@ -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 {
|
||||
|
||||
+2
-1
@@ -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'
|
||||
|
||||
|
||||
+2
-1
@@ -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'
|
||||
|
||||
+2
-1
@@ -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
-1
@@ -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'
|
||||
|
||||
+2
-1
@@ -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,
|
||||
|
||||
+2
-1
@@ -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'
|
||||
|
||||
+2
-1
@@ -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
Reference in new issue
Block a user