mirror of
https://github.com/supabase/supabase.git
synced 2026-10-08 10:55:06 +03:00
Merge branch 'master' into docs/tutorials
This commit is contained in:
157 files changed
+3295
-2042
No files matched your search
+19
-6
@@ -1,7 +1,20 @@
|
||||
node_modules/
|
||||
# Root folder of this turbo monorepo
|
||||
# Doesn't send useless context to Docker daemon
|
||||
.git
|
||||
.github
|
||||
about
|
||||
apps
|
||||
docker
|
||||
examples
|
||||
i18n
|
||||
**/node_modules
|
||||
spec
|
||||
supabase
|
||||
tests
|
||||
|
||||
**/.next
|
||||
.vercel
|
||||
.next
|
||||
.env.local
|
||||
.env.production
|
||||
.env.dev
|
||||
.env.*
|
||||
.env.*
|
||||
|
||||
.dockerignore
|
||||
studio/Dockerfile
|
||||
@@ -0,0 +1,20 @@
|
||||
name: reviewdog
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, edited]
|
||||
|
||||
jobs:
|
||||
misspell:
|
||||
name: runner / misspell
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code.
|
||||
uses: actions/checkout@v1
|
||||
- name: misspell
|
||||
uses: reviewdog/action-misspell@v1
|
||||
with:
|
||||
github_token: ${{ secrets.github_token }}
|
||||
locale: "US"
|
||||
reporter: github-pr-review
|
||||
level: error
|
||||
exclude: "*.css"
|
||||
@@ -14,12 +14,22 @@ import { useInView } from 'react-intersection-observer'
|
||||
*/
|
||||
|
||||
interface Props {
|
||||
tag: string
|
||||
tag?: string
|
||||
parseAnchors?: boolean
|
||||
customAnchor?: string
|
||||
}
|
||||
|
||||
const Heading: React.FC<Props> = ({ tag, children }) => {
|
||||
/**
|
||||
* This TOC is used in .mdx files and in .tsx files.
|
||||
* In mdx files, we need to parse the content and format them to match the
|
||||
* expected tocList format (text, link,level).
|
||||
*
|
||||
* In tsx files, we can generate this tocList directly. For these files, we don't
|
||||
* need to parse the <a> and generate anchors
|
||||
*/
|
||||
const Heading: React.FC<Props> = ({ tag, parseAnchors = false, customAnchor, children }) => {
|
||||
const HeadingTag = `${tag}` as any
|
||||
const anchor = getAnchor(children)
|
||||
const anchor = customAnchor ? customAnchor : getAnchor(children)
|
||||
const link = `#${anchor}`
|
||||
|
||||
const { ref } = useInView({
|
||||
@@ -33,7 +43,7 @@ const Heading: React.FC<Props> = ({ tag, children }) => {
|
||||
|
||||
return (
|
||||
<HeadingTag id={anchor} ref={ref} className="group scroll-mt-24">
|
||||
{removeAnchor(children)}
|
||||
{parseAnchors ? removeAnchor(children) : children}
|
||||
{anchor && (
|
||||
<a href={link} className="ml-2 opacity-0 group-hover:opacity-100 transition">
|
||||
#
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
If you upload additional profile photos, they'll accumulate
|
||||
in the `avatars` bucket because of their random names with only the latest being referenced
|
||||
from `public.profiles` and the older versions getting orphaned.
|
||||
|
||||
To automatically remove obsolete storage objects, extend the database
|
||||
triggers. Note that it is not sufficient to delete the objects from the
|
||||
`storage.objects` table because that would orphan and leak the actual storage objects in
|
||||
the S3 backend. Instead, invoke the storage API within Postgres via the `http` extension.
|
||||
|
||||
Enable the [http extension for the `extensions` schema](https://app.supabase.com/project/_/database/extensions) in the Dashboard.
|
||||
Then, define the following SQL functions in the SQL Editor to delete
|
||||
storage objects via the API:
|
||||
|
||||
```SQL
|
||||
create or replace function delete_storage_object(bucket text, object text, out status int, out content varchar)
|
||||
returns record
|
||||
language 'plpgsql'
|
||||
security definer
|
||||
as $$
|
||||
declare
|
||||
project_url varchar := '<YOURPROJECTURL>';
|
||||
service_role_key varchar := '<YOURSERVICEROLEKEY>'; -- full access needed
|
||||
url varchar := project_url||'/storage/v1/object/'||bucket||'/'||object;
|
||||
begin
|
||||
select
|
||||
into status, content
|
||||
result.status::int, result.content::varchar
|
||||
FROM extensions.http((
|
||||
'DELETE',
|
||||
url,
|
||||
ARRAY[extensions.http_header('authorization','Bearer '||service_role_key)],
|
||||
NULL,
|
||||
NULL)::extensions.http_request) as result;
|
||||
end;
|
||||
$$;
|
||||
|
||||
create or replace function delete_avatar(avatar_url text, out status int, out content varchar)
|
||||
returns record
|
||||
language 'plpgsql'
|
||||
security definer
|
||||
as $$
|
||||
begin
|
||||
select
|
||||
into status, content
|
||||
result.status, result.content
|
||||
from public.delete_storage_object('avatars', avatar_url) as result;
|
||||
end;
|
||||
$$;
|
||||
|
||||
```
|
||||
|
||||
Next, add a trigger that removes any obsolete avatar whenever the
|
||||
profile is updated or deleted:
|
||||
|
||||
```SQL
|
||||
create or replace function delete_old_avatar()
|
||||
returns trigger
|
||||
language 'plpgsql'
|
||||
security definer
|
||||
as $$
|
||||
declare
|
||||
status int;
|
||||
content varchar;
|
||||
begin
|
||||
if coalesce(old.avatar_url, '') <> ''
|
||||
and (tg_op = 'DELETE' or (old.avatar_url <> new.avatar_url)) then
|
||||
select
|
||||
into status, content
|
||||
result.status, result.content
|
||||
from public.delete_avatar(old.avatar_url) as result;
|
||||
if status <> 200 then
|
||||
raise warning 'Could not delete avatar: % %', status, content;
|
||||
end if;
|
||||
end if;
|
||||
if tg_op = 'DELETE' then
|
||||
return old;
|
||||
end if;
|
||||
return new;
|
||||
end;
|
||||
$$;
|
||||
|
||||
create trigger before_profile_changes
|
||||
before update of avatar_url or delete on public.profiles
|
||||
for each row execute function public.delete_old_avatar();
|
||||
|
||||
```
|
||||
|
||||
Finally, delete the `public.profile` row before a user is deleted.
|
||||
If this step is omitted, you won't be able to delete users without
|
||||
first manually deleting their avatar image.
|
||||
|
||||
```SQL
|
||||
create or replace function delete_old_profile()
|
||||
returns trigger
|
||||
language 'plpgsql'
|
||||
security definer
|
||||
as $$
|
||||
begin
|
||||
delete from public.profiles where id = old.id;
|
||||
return old;
|
||||
end;
|
||||
$$;
|
||||
|
||||
create trigger before_delete_user
|
||||
before delete on auth.users
|
||||
for each row execute function public.delete_old_profile();
|
||||
|
||||
```
|
||||
@@ -101,7 +101,7 @@ const home = [
|
||||
level: 'reference_python',
|
||||
},
|
||||
{
|
||||
label: 'Tools Reference',
|
||||
label: 'Tools',
|
||||
},
|
||||
{
|
||||
label: 'Management API',
|
||||
@@ -112,7 +112,7 @@ const home = [
|
||||
{
|
||||
label: 'Supabase CLI',
|
||||
icon: '/img/icons/menu/reference-cli',
|
||||
href: '/reference/cli/introduction',
|
||||
href: '/guides/cli',
|
||||
level: 'reference_javascript',
|
||||
},
|
||||
],
|
||||
|
||||
@@ -48,6 +48,9 @@ export const gettingstarted = {
|
||||
{ name: 'React', url: '/guides/getting-started/quickstarts/reactjs', items: [] },
|
||||
{ name: 'NextJS', url: '/guides/getting-started/quickstarts/nextjs', items: [] },
|
||||
{ name: 'Flutter', url: '/guides/getting-started/quickstarts/flutter', items: [] },
|
||||
{ name: 'SvelteKit', url: '/guides/getting-started/quickstarts/sveltekit', items: [] },
|
||||
{ name: 'SolidJS', url: '/guides/getting-started/quickstarts/solidjs', items: [] },
|
||||
{ name: 'Vue', url: '/guides/getting-started/quickstarts/vue', items: [] },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -485,6 +488,25 @@ export const storage = {
|
||||
],
|
||||
}
|
||||
|
||||
export const supabase_cli = {
|
||||
icon: 'reference-cli',
|
||||
title: 'Supabase CLI',
|
||||
url: '/guides/cli',
|
||||
items: [
|
||||
{ name: 'Overview', url: '/guides/cli', items: [] },
|
||||
{ name: 'Local Development', url: '/guides/cli/local-development', items: [] },
|
||||
{ name: 'Managing Environments', url: '/guides/cli/managing-environments', items: [] },
|
||||
{
|
||||
name: 'Reference',
|
||||
url: undefined,
|
||||
items: [
|
||||
{ name: 'Commands', url: '/reference/cli/introduction', items: [] },
|
||||
{ name: 'Configuration', url: '/reference/cli/config', items: [] },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const platform = {
|
||||
icon: 'platform',
|
||||
label: 'Platform',
|
||||
@@ -589,26 +611,6 @@ export const resources = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Supabase CLI',
|
||||
items: [
|
||||
{
|
||||
name: 'Overview',
|
||||
url: '/guides/resources/supabase-cli',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
name: 'Local Development',
|
||||
url: '/guides/resources/supabase-cli/local-development',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
name: 'Managing Environments',
|
||||
url: '/guides/resources/supabase-cli/managing-environments',
|
||||
items: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@@ -105,6 +105,9 @@ const NavigationMenu = () => {
|
||||
case url.includes(`/docs/guides/integrations`) && url:
|
||||
menuState.setMenuLevelId('integrations')
|
||||
break
|
||||
case url.includes(`/docs/guides/cli`) && url:
|
||||
menuState.setMenuLevelId('supabase_cli')
|
||||
break
|
||||
// JS v1
|
||||
case url.includes(`/docs/reference/javascript/v1`) && url:
|
||||
menuState.setMenuLevelId('reference_javascript_v1')
|
||||
@@ -125,6 +128,9 @@ const NavigationMenu = () => {
|
||||
case url.includes(`/docs/reference/python`) && url:
|
||||
menuState.setMenuLevelId('reference_python_v2')
|
||||
break
|
||||
case url.includes(`/docs/reference/cli/config`) && url:
|
||||
menuState.setMenuLevelId('supabase_cli')
|
||||
break
|
||||
case url.includes(`/docs/reference/cli`) && url:
|
||||
menuState.setMenuLevelId('reference_cli')
|
||||
break
|
||||
@@ -164,6 +170,7 @@ const NavigationMenu = () => {
|
||||
const isFunctionsActive = 'functions' === level
|
||||
const isRealtimeActive = 'realtime' === level
|
||||
const isStorageActive = 'storage' === level
|
||||
const issupabase_cliActive = 'supabase_cli' === level
|
||||
const isPlatformActive = 'platform' === level
|
||||
const isResourcesActive = 'resources' === level
|
||||
const isSelfHosting = 'self_hosting' === level
|
||||
@@ -191,6 +198,7 @@ const NavigationMenu = () => {
|
||||
<NavigationMenuGuideList id={'functions'} active={isFunctionsActive} />
|
||||
<NavigationMenuGuideList id={'realtime'} active={isRealtimeActive} />
|
||||
<NavigationMenuGuideList id={'storage'} active={isStorageActive} />
|
||||
<NavigationMenuGuideList id={'supabase_cli'} active={issupabase_cliActive} />
|
||||
<NavigationMenuGuideList id={'platform'} active={isPlatformActive} />
|
||||
<NavigationMenuGuideList id={'resources'} active={isResourcesActive} />
|
||||
<NavigationMenuGuideList id={'self_hosting'} active={isSelfHosting} />
|
||||
|
||||
@@ -179,7 +179,7 @@ const Content: React.FC<INavigationMenuRefList> = ({ id, lib, commonSections, sp
|
||||
if (!sections) console.error('no common sections imported')
|
||||
|
||||
const menu = NavItems[id]
|
||||
console.log(menu)
|
||||
//console.log(menu)
|
||||
const databaseFunctions = find(sections, { title: 'Database' })
|
||||
? find(sections, { title: 'Database' }).items
|
||||
: []
|
||||
|
||||
@@ -20,6 +20,7 @@ import QuickstartIntro from './MDX/quickstart_intro.mdx'
|
||||
import ProjectSetup from './MDX/project_setup.mdx'
|
||||
import SocialProviderSetup from './MDX/social_provider_setup.mdx'
|
||||
import SocialProviderSettingsSupabase from './MDX/social_provider_settings_supabase.mdx'
|
||||
import StorageManagement from './MDX/storage_management.mdx'
|
||||
import { Mermaid } from 'mdx-mermaid/lib/Mermaid'
|
||||
import InlineCodeTag from './CustomHTMLElements/InlineCode'
|
||||
import React from 'react'
|
||||
@@ -49,6 +50,7 @@ const components = {
|
||||
ProjectSetup,
|
||||
SocialProviderSetup,
|
||||
SocialProviderSettingsSupabase,
|
||||
StorageManagement,
|
||||
Mermaid,
|
||||
Extensions,
|
||||
Alert: (props: any) => (
|
||||
|
||||
@@ -468,6 +468,13 @@
|
||||
"installed_version": "1.1",
|
||||
"comment": "generate universally unique identifiers (UUIDs)"
|
||||
},
|
||||
{
|
||||
"name": "vector",
|
||||
"schema": "extensions",
|
||||
"default_version": "0.4.0",
|
||||
"installed_version": null,
|
||||
"comment": "vector data type with similarity search"
|
||||
},
|
||||
{
|
||||
"name": "xml2",
|
||||
"schema": null,
|
||||
|
||||
@@ -17,7 +17,7 @@ hideTitle: true
|
||||
The Supabase CLI provides tools to develop your project locally and deploy to the Supabase Platform.
|
||||
The CLI is still under development, but it contains all the functionality for working with your Supabase projects and the Supabase Platform.
|
||||
|
||||
- Run Supabase locally: [`supabase start`](/docs/reference/cli/usage#supabase-start)
|
||||
- Run Supabase locally: [`supabase init`](/docs/reference/cli/usage#supabase-init) and [`supabase start`](/docs/reference/cli/usage#supabase-start)
|
||||
- Manage database migrations: [`supabase migration`](/docs/reference/cli/usage#supabase-migration)
|
||||
- CI/CD for releasing to production: [`supabase db push`](/docs/reference/cli/usage#supabase-db-push)
|
||||
- Manage your Supabase projects: [`supabase projects`](/docs/reference/cli/usage#supabase-projects)
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
---
|
||||
id: initializing
|
||||
title: 'Initializing'
|
||||
slug: initializing
|
||||
---
|
||||
|
||||
<RefSubLayout.EducationRow>
|
||||
<RefSubLayout.Details>
|
||||
|
||||
Initialize Supabase with the static `initialize()` method on `Supabase` class.
|
||||
|
||||
</RefSubLayout.Details>
|
||||
|
||||
<RefSubLayout.Examples>
|
||||
|
||||
<Tabs
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="flutter"
|
||||
>
|
||||
<TabPanel id="flutter" label="Flutter">
|
||||
|
||||
```dart main.dart
|
||||
Future<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
await Supabase.initialize(
|
||||
url: 'https://xyzcompany.supabase.co',
|
||||
anonKey: 'public-anon-key',
|
||||
);
|
||||
|
||||
runApp(MyApp());
|
||||
}
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="dart" label="Other Dart Project">
|
||||
|
||||
```dart
|
||||
final supabase = SupabaseClient(
|
||||
'https://xyzcompany.supabase.co',
|
||||
'public-anon-key',
|
||||
);
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
</RefSubLayout.Examples>
|
||||
</RefSubLayout.EducationRow>
|
||||
@@ -39,6 +39,10 @@ const levelsData = {
|
||||
icon: '/docs/img/icons/menu/storage',
|
||||
name: 'Storage',
|
||||
},
|
||||
supabase_cli: {
|
||||
icon: '/docs/img/icons/menu/reference-cli',
|
||||
name: 'Supabase CLI',
|
||||
},
|
||||
platform: {
|
||||
icon: '/docs/img/icons/menu/platform',
|
||||
name: 'Platform',
|
||||
|
||||
@@ -1,161 +0,0 @@
|
||||
import Layout from '~/layouts/DefaultGuideLayout'
|
||||
|
||||
export const meta = {
|
||||
title: 'Overview',
|
||||
description: 'An overview of Supabase\'s architecture and product principles.',
|
||||
}
|
||||
|
||||
Supabase is open source. We choose open source tools which are scalable and make them simple to use.
|
||||
|
||||
Supabase is not a 1-to-1 mapping of Firebase. While we are building many of the features that Firebase offers, we are not going about it the same way:
|
||||
our technological choices are quite different; everything we use is open source; and wherever possible, we use and support existing tools rather than developing from scratch.
|
||||
|
||||
Most notably, we use Postgres rather than a NoSQL store. This choice was deliberate. We believe that no other database offers the functionality required to compete with Firebase,
|
||||
while maintaining the scalability required to go beyond it.
|
||||
|
||||
## Architecture
|
||||
|
||||
Each Supabase project consists of several tools:
|
||||
|
||||

|
||||
|
||||
### PostgreSQL (Database)
|
||||
|
||||
PostgreSQL is the core of Supabase. We do not abstract the PostgreSQL database — you can access it and use it with full privileges. We simply provide tools which makes PostgreSQL as easy to use as Firebase.
|
||||
|
||||
- Official Docs: [postgresql.org/docs](https://www.postgresql.org/docs/current/index.html)
|
||||
- Source code: [github.com/postgres/postgres](https://github.com/postgres/postgres) (mirror)
|
||||
- License: [PostgreSQL Licence](https://www.postgresql.org/about/licence/)
|
||||
- Language: C
|
||||
|
||||
### Studio (Dashboard)
|
||||
|
||||
An open source Dashboard for managing your database and services.
|
||||
|
||||
- Official Docs: [Supabase docs](/docs)
|
||||
- Source code: [github.com/supabase/supabase](https://github.com/supabase/supabase/tree/master/studio)
|
||||
- License: [Apache 2](https://github.com/supabase/supabase/blob/master/LICENSE)
|
||||
- Language: TypeScript
|
||||
|
||||
### GoTrue (Auth)
|
||||
|
||||
A JWT-based API for managing users and issuing access tokens. This integrates with PostgreSQL's Row Level Security and the API servers.
|
||||
|
||||
- Official Docs: [Supabase Auth reference docs](/docs/reference/auth)
|
||||
- Source code: [github.com/supabase/gotrue](https://github.com/supabase/gotrue)
|
||||
- License: [MIT](https://github.com/supabase/gotrue/blob/master/LICENSE)
|
||||
- Language: Go
|
||||
|
||||
### PostgREST (API)
|
||||
|
||||
A standalone web server that turns your PostgreSQL database directly into a RESTful API.
|
||||
We use this with our [pg_graphql](https://github.com/supabase/pg_graphql) extension to provide a GraphQL API.
|
||||
|
||||
- Official Docs: [postgrest.org](https://postgrest.org/)
|
||||
- Source code: [github.com/PostgREST/postgrest](https://github.com/PostgREST/postgrest)
|
||||
- License: [MIT](https://github.com/PostgREST/postgrest/blob/main/LICENSE)
|
||||
- Language: Haskel
|
||||
|
||||
### Realtime (API & multiplayer)
|
||||
|
||||
A scalable websocket engine for managing user Presence, broadcasting messages, and streaming database changes.
|
||||
|
||||
- Official Docs: [Supabase Realtime docs](/docs/guides/realtime)
|
||||
- Source code: [github.com/supabase/realtime](https://github.com/supabase/realtime)
|
||||
- License: [Apache 2](https://github.com/supabase/realtime/blob/main/LICENSE)
|
||||
- Language: Elixir
|
||||
|
||||
### Storage API (large file storage)
|
||||
|
||||
An S3-compatible object storage service that stores metadata in Postgres.
|
||||
|
||||
- Official Docs: [Supabase Storage reference docs](/docs/reference/storage)
|
||||
- Source code: [github.com/supabase/storage-api](https://github.com/supabase/storage-api)
|
||||
- License: [Apache 2.0](https://github.com/supabase/storage-api/blob/master/LICENSE)
|
||||
- Language: NodeJS / TypeScript
|
||||
|
||||
### Deno (Edge Functions)
|
||||
|
||||
A modern runtime for JavaScript and TypeScript.
|
||||
|
||||
- Official Docs: [deno.land](https://deno.land/)
|
||||
- Source code: [github.com/denoland/deno](https://github.com/denoland/deno)
|
||||
- License: [MIT](https://github.com/denoland/deno/blob/main/LICENSE.md)
|
||||
- Language: TypeScript / Rust
|
||||
|
||||
### postgres-meta (Database management)
|
||||
|
||||
A RESTful API for managing your Postgres. Fetch tables, add roles, and run queries.
|
||||
|
||||
- Official Docs: [supabase.github.io/postgres-meta](https://supabase.github.io/postgres-meta/)
|
||||
- Source code: [github.com/supabase/postgres-meta](https://github.com/supabase/postgres-meta)
|
||||
- License: [Apache 2.0](https://github.com/supabase/postgres-meta/blob/master/LICENSE)
|
||||
- Language: NodeJS / TypeScript
|
||||
|
||||
### PgBouncer
|
||||
|
||||
A lightweight connection pooler for PostgreSQL. This is useful for connecting to Postgres when using Serverless functions.
|
||||
|
||||
- Official Docs: [pgbouncer.org](http://www.pgbouncer.org/)
|
||||
- Source code: [pgbouncer/pgbouncer](https://github.com/pgbouncer/pgbouncer)
|
||||
- License: [ISC](https://github.com/pgbouncer/pgbouncer/blob/master/COPYRIGHT)
|
||||
- Language: C
|
||||
|
||||
### Kong (API Gateway)
|
||||
|
||||
A cloud-native API gateway, built on top of Nginx.
|
||||
|
||||
- Official Docs: [docs.konghq.com](https://docs.konghq.com/)
|
||||
- Source code: [github.com/kong/kong](https://github.com/kong/kong)
|
||||
- License: [Apache 2.0](https://github.com/Kong/kong/blob/master/LICENSE)
|
||||
- Language: Lua
|
||||
|
||||
## Product Principles
|
||||
|
||||
It is our goal to provide an architecture that any large-scale company would design for themselves,
|
||||
and then provide tooling around that architecture that is easy-to-use for indie-developers and small teams.
|
||||
|
||||
We use a series of principles to ensure that scalability and usability are never mutually exclusive:
|
||||
|
||||
### Everything works in isolation
|
||||
|
||||
Each system must work as a standalone tool with as few moving parts as possible.
|
||||
The litmus test for this is: "Can a user run this product with nothing but a Postgres database?"
|
||||
|
||||
### Everything is integrated
|
||||
|
||||
Supabase is composable. Even though every product works in isolation, each product on the platform needs to 10x the other products.
|
||||
For integration, each tool should expose an API and Webhooks.
|
||||
|
||||
### Everything is extensible
|
||||
|
||||
We're deliberate about adding a new tool, and prefer instead to extend an existing one.
|
||||
This is the opposite of many cloud providers whose product offering expands into niche use-cases. We provide _primitives_ for developers, which allow them to achieve any goal.
|
||||
Less, but better.
|
||||
|
||||
### Everything is portable
|
||||
|
||||
To avoid lock-in, we make it easy to migrate in and out. Our cloud offering is compatible with our self-hosted product.
|
||||
We use existing standards to increase portability (like pg_dump an CSV files). If a new standard emerges which competes with a "Supabase" approach, we will deprecate the approach in favor of the standard.
|
||||
This forces us compete on experience. We aim to be the best Postgres hosting service.
|
||||
|
||||
### Play the long game
|
||||
|
||||
We sacrifice short-term wins for long-term gains. For example, it is tempting to run a fork of Postgres with additional functionality which only our customers need.
|
||||
Instead, we prefer to support efforts to upstream missing functionality so that the entire community benefits. This has the additional benefit of ensuring portability and longevity.
|
||||
|
||||
### Build for developers
|
||||
|
||||
"Developers" are a specific profile of user: they are _builders_.
|
||||
When assessing impact as a function of effort, developers have a large efficiency due to the type of products and systems they can build.
|
||||
As the profile of a developer changes over time, Supabase will continue to evolve the product to fit this evolving profile.
|
||||
|
||||
### Support existing tools
|
||||
|
||||
Supabase supports existing tools and communities wherever possible. Supabase is more like a "community of communities" - each tool typically has its own community
|
||||
which we work with.
|
||||
Open source is something we approach [collaboratively](https://supabase.com/blog/supabase-series-b#giving-back): we employ maintainers, sponsor projects, invest in businesses, and develop our own open source tools.
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
export default Page
|
||||
@@ -217,7 +217,7 @@ import { createClient, useQuery } from 'urql'
|
||||
// Prepare API key and Authorization header
|
||||
const headers = {
|
||||
apikey: <SUPABASE_ANON_KEY>,
|
||||
authorization: `Bearer ${<SUPABASE_ANON_KEY}`>
|
||||
authorization: `Bearer ${<SUPABASE_ANON_KEY>}`,
|
||||
}
|
||||
|
||||
// Create GraphQL client
|
||||
|
||||
@@ -140,7 +140,7 @@ export async function middleware(req) {
|
||||
|
||||
<TabPanel id="ts" label="TypeScript">
|
||||
|
||||
Create a new `/app/middleware.js` file and populate with the following:
|
||||
Create a new `/app/middleware.ts` file and populate with the following:
|
||||
|
||||
```tsx title="middleware.ts"
|
||||
import { createMiddlewareSupabaseClient } from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import Layout from '~/layouts/DefaultGuideLayout'
|
||||
|
||||
export const meta = {
|
||||
id: 'cli',
|
||||
title: 'Supabase CLI',
|
||||
description:
|
||||
'The Supabase CLI provides tools to develop your project locally and deploy to the Supabase Platform.',
|
||||
@@ -111,7 +110,8 @@ brew upgrade supabase
|
||||
|
||||
## See also
|
||||
|
||||
- [Supabase CLI Reference](/docs/reference/cli/usage)
|
||||
- [Supabase CLI Reference](/docs/reference/cli/introduction)
|
||||
- [Supabase CLI Configuration](/docs/reference/cli/config)
|
||||
- [Local Development](/docs/guides/cli/local-development)
|
||||
- [Managing Environments](/docs/guides/cli/managing-environments)
|
||||
|
||||
+1
-2
@@ -34,9 +34,8 @@ supabase login
|
||||
<Admonition type="note">
|
||||
|
||||
If you installed the Supabase CLI via NPM you may have to use `npx supabase login`.
|
||||
|
||||
</Admonition>
|
||||
|
||||
</Admonition>
|
||||
|
||||
## Initialize your project
|
||||
|
||||
File renamed without changes.
@@ -77,7 +77,7 @@ You must define the "data type" when you create a column.
|
||||
### Data types
|
||||
|
||||
Every column is a predefined type. PostgreSQL provides many [default types](https://www.postgresql.org/docs/current/datatype.html), and you can even design your own (or use extensions)
|
||||
if the default types don't fit your needs.
|
||||
if the default types don't fit your needs. You can use any data type that Postgres supports via the SQL editor. We only support a subset of these in the Table Editor in an effort to keep the experience simple for people with less experience with databases.
|
||||
|
||||
<details>
|
||||
<summary>Show/Hide default data types</summary>
|
||||
|
||||
@@ -6,6 +6,15 @@ export const meta = {
|
||||
description: 'Using Import Maps.',
|
||||
}
|
||||
|
||||
<div class="video-container">
|
||||
<iframe
|
||||
src="https://www.youtube-nocookie.com/embed/ILr3cneZuFk"
|
||||
frameBorder="1"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
</div>
|
||||
|
||||
Since [Supabase CLI](https://supabase.com/docs/guides/resources/supabase-cli) version `1.33.0` [import maps](https://deno.land/manual@v1.29.2/node/import_maps) can be used with Supabase Edge Functions.
|
||||
|
||||
[Import maps](https://github.com/WICG/import-maps#the-import-map) is a web-platform standard that allows you to use bare specifiers with Deno without having to install the Node.js package locally.
|
||||
|
||||
@@ -25,7 +25,7 @@ By default, Edge Functions have access to these secrets:
|
||||
|
||||
Let's create a local file for storing our secrets, and inside it we can store a secret `MY_NAME`:
|
||||
|
||||
```jsx
|
||||
```bash
|
||||
echo "MY_NAME=Yoda" >> ./supabase/.env.local
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
import Layout from '~/layouts/DefaultGuideLayout'
|
||||
import StepHikeCompact from '~/components/StepHikeCompact'
|
||||
|
||||
export const meta = {
|
||||
title: 'Use Supabase with SolidJS',
|
||||
subtitle:
|
||||
'Learn how to create a Supabase project, add some sample data to your database, and query the data from a SolidJS app.',
|
||||
breadcrumb: 'Framework Quickstarts',
|
||||
}
|
||||
|
||||
<StepHikeCompact>
|
||||
|
||||
<StepHikeCompact.Step step={1}>
|
||||
<StepHikeCompact.Details title="Set up a Supabase project with sample data">
|
||||
|
||||
[Create a new project](https://app.supabase.com) in the Supabase Dashboard.
|
||||
|
||||
After your project is ready, create a table in your Supabase database using the [SQL Editor](https://app.supabase.com/project/_/sql) in the Dashboard. Use the following SQL statement to create a `countries` table with some sample data.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```sql SQL_EDITOR
|
||||
-- Create the table
|
||||
CREATE TABLE countries (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL
|
||||
);
|
||||
-- Insert some sample data into the table
|
||||
INSERT INTO countries (name) VALUES ('United States');
|
||||
INSERT INTO countries (name) VALUES ('Canada');
|
||||
INSERT INTO countries (name) VALUES ('Mexico');
|
||||
````
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={2}>
|
||||
|
||||
<StepHikeCompact.Details title="Create a SolidJS app">
|
||||
|
||||
Create a SolidJS app using the `degit` command.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```bash Terminal
|
||||
npx degit solidjs/templates/js 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 the `supabase-js` client library which provides a convenient interface for working with Supabase from a SolidJS app.
|
||||
|
||||
Navigate to the SolidJS app and install `supabase-js`.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```bash Terminal
|
||||
cd my-app && npm install @supabase/supabase-js
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={4}>
|
||||
<StepHikeCompact.Details title="Query data from the app">
|
||||
|
||||
In `App.jsx`, create a Supabase client using your [Project URL and public API (anon) key](https://app.supabase.com/project/_/settings/api).
|
||||
|
||||
Add a `getCountries` function to fetch the data and display the query result to the page.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
|
||||
```jsx src/App.jsx
|
||||
import { createClient } from "@supabase/supabase-js";
|
||||
import { createEffect, createSignal, For } from "solid-js";
|
||||
|
||||
const supabase = createClient('https://<project>.supabase.co', '<your-anon-key>');
|
||||
|
||||
function App() {
|
||||
const [countries, setCountries] = createSignal();
|
||||
|
||||
createEffect(() => {
|
||||
getCountries();
|
||||
});
|
||||
|
||||
async function getCountries() {
|
||||
const { data } = await supabase.from("countries").select();
|
||||
setCountries(data);
|
||||
}
|
||||
|
||||
return (
|
||||
<ul>
|
||||
<For each={countries()}>{(country) => <li>{country.name}</li>}</For>
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={5}>
|
||||
<StepHikeCompact.Details title="Start the app">
|
||||
|
||||
Start the app and go to http://localhost:3000 in a browser and you should see the list of countries.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```bash Terminal
|
||||
npm run dev
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
</StepHikeCompact>
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} hideToc={true} />
|
||||
|
||||
export default Page
|
||||
@@ -0,0 +1,166 @@
|
||||
import Layout from '~/layouts/DefaultGuideLayout'
|
||||
import StepHikeCompact from '~/components/StepHikeCompact'
|
||||
|
||||
export const meta = {
|
||||
title: 'Use Supabase with SvelteKit',
|
||||
subtitle:
|
||||
'Learn how to create a Supabase project, add some sample data to your database, and query the data from a SvelteKit app.',
|
||||
breadcrumb: 'Framework Quickstarts',
|
||||
}
|
||||
|
||||
<StepHikeCompact>
|
||||
|
||||
<StepHikeCompact.Step step={1}>
|
||||
<StepHikeCompact.Details title="Set up a Supabase project with sample data">
|
||||
|
||||
[Create a new project](https://app.supabase.com) in the Supabase Dashboard.
|
||||
|
||||
After your project is ready, create a table in your Supabase database using the [SQL Editor](https://app.supabase.com/project/_/sql) in the Dashboard. Use the following SQL statement to create a `countries` table with some sample data.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```sql SQL_EDITOR
|
||||
-- Create the table
|
||||
CREATE TABLE countries (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL
|
||||
);
|
||||
-- Insert some sample data into the table
|
||||
INSERT INTO countries (name) VALUES ('United States');
|
||||
INSERT INTO countries (name) VALUES ('Canada');
|
||||
INSERT INTO countries (name) VALUES ('Mexico');
|
||||
````
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={2}>
|
||||
|
||||
<StepHikeCompact.Details title="Create a SvelteKit app">
|
||||
|
||||
Create a SvelteKit app using the `npm create` command.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```bash Terminal
|
||||
npm create svelte@latest myapp
|
||||
```
|
||||
|
||||
</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 the `supabase-js` client library which provides a convenient interface for working with Supabase from a SvelteKit app.
|
||||
|
||||
Navigate to the SvelteKit app and install `supabase-js`.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```bash Terminal
|
||||
cd my-app && npm install @supabase/supabase-js
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={4}>
|
||||
<StepHikeCompact.Details title="Create the Supabase client">
|
||||
|
||||
Create a `/src/lib` directory in your SvelteKit app, create a file called `supabaseClient.js` and add the following code to initialize the Supabase client with your [project URL and public API (anon) key](https://app.supabase.com/project/_/settings/api).
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```js src/lib/supabaseClient.js
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
|
||||
export const supabase = createClient('https://<project>.supabase.co', '<your-anon-key>')
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={5}>
|
||||
<StepHikeCompact.Details title="Query data from the app">
|
||||
|
||||
Use `load` method to fetch the data server-side and display the query results as a simple list.
|
||||
|
||||
Create `+page.server.js` file in the `routes` directory with the following code.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
|
||||
```js src/routes/+page.server.js
|
||||
import { supabase } from "$lib/supabaseClient";
|
||||
|
||||
export async function load() {
|
||||
const { data } = await supabase.from("countries").select();
|
||||
return {
|
||||
countries: data ?? [],
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
<StepHikeCompact.Details title="">
|
||||
|
||||
Replace the existing content in your `+page.svelte` file in the `routes` directory with the following code.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
|
||||
```svelte src/routes/+page.svelte
|
||||
<script>
|
||||
export let data;
|
||||
let { countries } = data;
|
||||
$: ({ countries } = data);
|
||||
</script>
|
||||
|
||||
<ul>
|
||||
{#each countries as country}
|
||||
<li>{country.name}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={6}>
|
||||
<StepHikeCompact.Details title="Start the app">
|
||||
|
||||
Start the app and go to http://localhost:5173 in a browser and you should see the list of countries.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```bash Terminal
|
||||
npm run dev
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
</StepHikeCompact>
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} hideToc={true} />
|
||||
|
||||
export default Page
|
||||
@@ -0,0 +1,153 @@
|
||||
import Layout from '~/layouts/DefaultGuideLayout'
|
||||
import StepHikeCompact from '~/components/StepHikeCompact'
|
||||
|
||||
export const meta = {
|
||||
title: 'Use Supabase with Vue',
|
||||
subtitle:
|
||||
'Learn how to create a Supabase project, add some sample data to your database, and query the data from a Vue app.',
|
||||
breadcrumb: 'Framework Quickstarts',
|
||||
}
|
||||
|
||||
<StepHikeCompact>
|
||||
|
||||
<StepHikeCompact.Step step={1}>
|
||||
<StepHikeCompact.Details title="Set up a Supabase project with sample data">
|
||||
|
||||
[Create a new project](https://app.supabase.com) in the Supabase Dashboard.
|
||||
|
||||
After your project is ready, create a table in your Supabase database using the [SQL Editor](https://app.supabase.com/project/_/sql) in the Dashboard. Use the following SQL statement to create a `countries` table with some sample data.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```sql SQL_EDITOR
|
||||
-- Create the table
|
||||
CREATE TABLE countries (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL
|
||||
);
|
||||
-- Insert some sample data into the table
|
||||
INSERT INTO countries (name) VALUES ('United States');
|
||||
INSERT INTO countries (name) VALUES ('Canada');
|
||||
INSERT INTO countries (name) VALUES ('Mexico');
|
||||
````
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={2}>
|
||||
|
||||
<StepHikeCompact.Details title="Create a Vue app">
|
||||
|
||||
Create a Vue app using the `npm init` command.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```sh Terminal
|
||||
npm init vue@latest 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 the `supabase-js` client library which provides a convenient interface for working with Supabase from a Vue app.
|
||||
|
||||
Navigate to the Vue app and install `supabase-js`.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```bash Terminal
|
||||
cd my-app && npm install @supabase/supabase-js
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={4}>
|
||||
<StepHikeCompact.Details title="Create the Supabase client">
|
||||
|
||||
Create a `/src/lib` directory in your Vue app, create a file called `supabaseClient.js` and add the following code to initialize the Supabase client with your [project URL and public API (anon) key](https://app.supabase.com/project/_/settings/api).
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```js src/lib/supabaseClient.js
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
|
||||
export const supabase = createClient('https://<project>.supabase.co', '<your-anon-key>')
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={5}>
|
||||
<StepHikeCompact.Details title="Query data from the app">
|
||||
|
||||
Replace the existing content in your `App.vue` file with the following code.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
|
||||
```vue src/App.vue
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { supabase } from './lib/supabaseClient'
|
||||
|
||||
const countries = ref([])
|
||||
|
||||
async function getCountries() {
|
||||
const { data } = await supabase.from('countries').select()
|
||||
countries.value = data
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getCountries()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ul>
|
||||
<li v-for="country in countries" :key="country.id">{{ country.name }}</li>
|
||||
</ul>
|
||||
</template>
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={6}>
|
||||
<StepHikeCompact.Details title="Start the app">
|
||||
|
||||
Start the app and go to http://localhost:5173 in a browser and you should see the list of countries.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```bash Terminal
|
||||
npm run dev
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
</StepHikeCompact>
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} hideToc={true} />
|
||||
|
||||
export default Page
|
||||
@@ -525,6 +525,10 @@ export class AccountComponent implements OnInit {
|
||||
}
|
||||
```
|
||||
|
||||
### Storage management
|
||||
|
||||
<StorageManagement />
|
||||
|
||||
At this stage you have a fully functional application!
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
@@ -498,6 +498,10 @@ Now you will need to run the prebuild command to get the application working on
|
||||
expo prebuild
|
||||
```
|
||||
|
||||
### Storage management
|
||||
|
||||
<StorageManagement />
|
||||
|
||||
At this stage you have a fully functional application!
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
@@ -792,6 +792,10 @@ class _AccountPageState extends State<AccountPage> {
|
||||
}
|
||||
```
|
||||
|
||||
### Storage management
|
||||
|
||||
<StorageManagement />
|
||||
|
||||
Congratulations, you've built a fully functional user management app using Flutter and Supabase!
|
||||
|
||||
## See also
|
||||
|
||||
@@ -521,6 +521,10 @@ template: `
|
||||
`
|
||||
```
|
||||
|
||||
### Storage management
|
||||
|
||||
<StorageManagement />
|
||||
|
||||
At this stage you have a fully functional application!
|
||||
|
||||
## See also
|
||||
|
||||
@@ -494,6 +494,10 @@ return (
|
||||
<Avatar url={profile.avatar_url} onUpload={updateProfile}></Avatar>
|
||||
```
|
||||
|
||||
### Storage management
|
||||
|
||||
<StorageManagement />
|
||||
|
||||
At this stage you have a fully functional application!
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
@@ -554,6 +554,10 @@ export default defineComponent({
|
||||
</script>
|
||||
```
|
||||
|
||||
### Storage management
|
||||
|
||||
<StorageManagement />
|
||||
|
||||
At this stage you have a fully functional application!
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
@@ -717,6 +717,10 @@ return (
|
||||
)
|
||||
```
|
||||
|
||||
### Storage management
|
||||
|
||||
<StorageManagement />
|
||||
|
||||
At this stage you have a fully functional application!
|
||||
|
||||
## See also
|
||||
|
||||
@@ -417,6 +417,10 @@ And then we can add the widget to the Account page:
|
||||
</script>
|
||||
```
|
||||
|
||||
### Storage management
|
||||
|
||||
<StorageManagement />
|
||||
|
||||
That is it! You should now be able to upload a profile photo to Supabase Storage and you have a fully functional application.
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
@@ -396,6 +396,10 @@ return (
|
||||
)
|
||||
```
|
||||
|
||||
### Storage management
|
||||
|
||||
<StorageManagement />
|
||||
|
||||
At this stage you have a fully functional application!
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
@@ -621,6 +621,10 @@ return (
|
||||
)
|
||||
```
|
||||
|
||||
### Storage management
|
||||
|
||||
<StorageManagement />
|
||||
|
||||
At this stage you have a fully functional application!
|
||||
|
||||
## See also
|
||||
|
||||
@@ -414,6 +414,10 @@ return (
|
||||
)
|
||||
```
|
||||
|
||||
### Storage management
|
||||
|
||||
<StorageManagement />
|
||||
|
||||
At this stage you have a fully functional application!
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
@@ -367,6 +367,10 @@ And then we can add the widget to the Account page:
|
||||
</form>
|
||||
```
|
||||
|
||||
### Storage management
|
||||
|
||||
<StorageManagement />
|
||||
|
||||
At this stage you have a fully functional application!
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
@@ -433,6 +433,10 @@ And then we can add the widget to the Account page:
|
||||
</form>
|
||||
```
|
||||
|
||||
### Storage management
|
||||
|
||||
<StorageManagement />
|
||||
|
||||
At this stage you have a fully functional application!
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
@@ -378,6 +378,10 @@ And then we can add the widget to the Account page:
|
||||
</template>
|
||||
```
|
||||
|
||||
### Storage management
|
||||
|
||||
<StorageManagement />
|
||||
|
||||
At this stage you have a fully functional application!
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
@@ -18,7 +18,7 @@ Once created, the pipeline backfills all your historical data from Firestore and
|
||||
|
||||
Before you begin, you'll need:
|
||||
|
||||
- An [Estuary account](https://go.estuary.dev/sign-up).
|
||||
- An Estuary account. [Head to the web app to start for free](https://dashboard.estuary.dev).
|
||||
|
||||
- For your Firestore database:
|
||||
|
||||
@@ -31,36 +31,35 @@ Before you begin, you'll need:
|
||||
|
||||
You'll start by creating a **capture**, a task in Flow that connects to your data source system: in this case, Firestore. This process will create one or more data **collections**, backed by a real-time data lake.
|
||||
|
||||
1. Go to the Flow web application at [dashboard.estuary.dev](http://dashboard.estuary.dev) and sign in using the credentials provided by your Estuary account manager.
|
||||
1. Go to the [**Captures** tab](https://dashboard.estuary.dev/captures) of the Flow web app and choose **New Capture**.
|
||||
|
||||
2. Click the **Captures** tab and choose **New Capture**.
|
||||
|
||||
3. Locate and select the **Google Firestore** card.
|
||||
2. Locate and select the **Google Firestore** card.
|
||||
|
||||
A form appears with the properties required for a Firestore capture.
|
||||
|
||||
4. Set a name for your capture.
|
||||
3. Set a name for your capture.
|
||||
|
||||
Click inside the **Name** field to generate a drop-down menu of available **prefixes** and select one (likely, this will be the name of your organization). Append a unique capture name after the `/` to create the full name, for example, `acmeCo/myFirestoreCapture`.
|
||||
|
||||
5. Fill out the required properties for Firestore.
|
||||
4. Fill out the required properties for Firestore.
|
||||
|
||||
- **Database**: Flow can autodetect the database name, but you may optionally specify it here. This is helpful if the service account used has access to multiple Firebase projects. Your database name usually follows the format `projects/$PROJECTID/databases/(default)`.
|
||||
- **Credentials**: The JSON service account key created per the prerequisites.
|
||||
**Database**: Flow can autodetect the database name, but you may optionally specify it here. This is helpful if the service account used has access to multiple Firebase projects. Your database name usually follows the format `projects/$PROJECTID/databases/(default)`.
|
||||
|
||||
**Credentials**: The JSON service account key created per the prerequisites.
|
||||
|
||||
6. Click **Discover Endpoint**.
|
||||
5. Click **Next**.
|
||||
|
||||
Flow uses the provided configuration to initiate a connection with Firestore. It generates a capture specification and details of the collections that it will create, once published.
|
||||
Flow uses the provided configuration to initiate a connection with Firestore. It maps each collection in the Firestore database to a Flow collection.
|
||||
|
||||
7. Use the **Specification Editor** to view the [JSON schemas](https://docs.estuary.dev/concepts/schemas/) for each collection and make sure they are formatted correctly for your needs. If they're not, you can edit them.
|
||||
6. Optionally, use the **Collection Selector** to remove any collections you don't need to migrate to Supabase.
|
||||
|
||||
8. Click **Save and publish**.
|
||||
7. Click **Save and Publish**.
|
||||
|
||||
You'll see a notification when the capture publishes successfully.
|
||||
You'll see a notification when the capture publishes successfully.
|
||||
|
||||
The data currently in your Firestore database has been captured, and future updates to it will be captured continuously.
|
||||
The data currently in your Firestore database has been captured to Flow, and future updates to it will be captured continuously.
|
||||
|
||||
Click **Materialize Collections** to continue.
|
||||
Click **Materialize Collections** to continue.
|
||||
|
||||
## Step 2: Materialize your collections to Postgres
|
||||
|
||||
@@ -72,23 +71,31 @@ A form appears with the properties required for a Postgres materialization.
|
||||
|
||||
2. Choose a unique name for your materialization like you did when naming your capture; for example, `acmeCo/mySupabaseMaterialization`.
|
||||
|
||||
3. Fill out the required properties for PostgreSQL. You can find most of these in Supabase by going to the **Settings** section and clicking **Database**.
|
||||
3. Fill out the required properties for Postgres. You can find most of these in Supabase by going to the **Settings** section and clicking **Database**.
|
||||
|
||||
- **Address**: Format at `<host>:<port>`.
|
||||
- **User**: Usually, this is `postgres`.
|
||||
- **Password**: The password you set when you created your Supabase project.
|
||||
**Address**: Format at `<host>:<port>`.
|
||||
|
||||
**User**: Usually, this is `postgres`.
|
||||
|
||||
**Password**: The password you set when you created your Supabase project.
|
||||
|
||||
4. Scroll down to view the **Collection Selector** and fill in the **Table** field for each collection.
|
||||
4. Click **Next**.
|
||||
|
||||
The collections you just created have already been selected, but you must provide names for the tables to which they'll be materialized.
|
||||
Flow initiates a connection with the database and the **Collection Selector** expands.
|
||||
It's populated with your collections from Firestore, each mapped to a Postgres table.
|
||||
|
||||
5. Click **Discover Endpoint**.
|
||||
5. For each collection, apply a stricter JSON schema.
|
||||
This ensure that the less-structured Firestore data will be written to a Postgres table in the correct shape.
|
||||
|
||||
Flow uses the provided configuration to initiate a connection to your Supabase Postgres database and generate a specification.
|
||||
In the Collection Selector, choose a collection and click its **Specification** tab.
|
||||
|
||||
Click **Schema Inference**. Flow scans the data in your collection and infers a new schema to use for materialization.
|
||||
|
||||
Review the new schema and click **Apply Inferred Schema**.
|
||||
|
||||
6. Click **Save and Publish**. You'll see a notification when the full materialization publishes successfully.
|
||||
6. Click **Save and Publish**. You'll see a notification when the materialization publishes successfully.
|
||||
|
||||
Your Firestore collections are copied to tables in Supabase. As long as you leave the capture and materialation running, any changes to the Firestore data will be reflected in Supabase in milliseconds.
|
||||
Your Firestore collections are copied to tables in Supabase. As long as you leave the capture and materialation running, any changes to the Firestore data will be reflected in Supabase in milliseconds.
|
||||
|
||||
## Resources
|
||||
|
||||
|
||||
@@ -14,7 +14,18 @@ export const meta = {
|
||||
- Emails
|
||||
- In-app notifications
|
||||
|
||||
This guide steps you through how you can integrate OneSignal with Supabase to create a seamless cloud messaging experience for your users using Database webhooks and edge functions through a simple Next.js application.
|
||||
Here is William giving us the overview of how OneSignal can work with Supabase to send notifications to your users.
|
||||
|
||||
<div className="video-container">
|
||||
<iframe
|
||||
src="https://www.youtube-nocookie.com/embed/mw0DLwItue4"
|
||||
frameBorder="1"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
</div>
|
||||
|
||||
In this guide, we will build a similar app and steps you through how you can integrate OneSignal with Supabase to create a seamless cloud messaging experience for your users using Database webhooks and edge functions through a simple Next.js application.
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -19,15 +19,11 @@ This value is also reported in the [database settings page](https://app.supabase
|
||||
|
||||
## Database storage management
|
||||
|
||||
Your project will be changed to read-only mode once it reaches 95% of your subscription quota, preventing further writes to your project.
|
||||
In read-only mode, clients will encounter errors such as `cannot execute INSERT in a read-only transaction`.
|
||||
Supabase uses network-attached storage to balance performance with scalability. For Pro and Enterprise projects, database storage expands ~1.5x automatically (e.g., 8GB -> 12GB) when you reach 90% of the database storage quota. Automatic database storage expansion can only occur once every six hours. Pro projects can store up to 1024TB.
|
||||
|
||||
Regular operation (read-write mode) is automatically re-enabled once database storage usage falls under 95%.
|
||||
All projects enter read-only mode when you reach 95% of the database storage quota. In read-only mode, clients will encounter errors such as `cannot execute INSERT in a read-only transaction`. Regular operation (read-write mode) is automatically re-enabled once usage is below 95% of the database storage quota.
|
||||
|
||||
If you are on Pro or Enterprise, database storage expands automatically, however you may also encounter this error if fill the database faster than it can expand.
|
||||
|
||||
Supabase uses network-attached storage, which balances performance with scalability. Pro projects can store up to 1024TB.
|
||||
If you intend to grow beyond this, reach out to our team to learn more about our Enterprise tier.
|
||||
If you need more than 1024TB of database storage or require multiple storage expansions in a short period of time, [contact us](https://app.supabase.com/support/new) to learn more about the Enterprise plan.
|
||||
|
||||
### Increasing available database storage
|
||||
|
||||
|
||||
@@ -121,7 +121,7 @@ channel.subscribe(async (status) => {
|
||||
})
|
||||
```
|
||||
|
||||
## Track and display which users are online
|
||||
## Track and Display Which Users Are Online
|
||||
|
||||
[Presence](/docs/guides/realtime#presence) stores and synchronize shared state across clients. The `sync` event is triggered whenever the shared state changes. The `join` event is triggered when new clients join the channel and `leave` event is triggered when clients leave.
|
||||
|
||||
|
||||
@@ -64,20 +64,6 @@ export const meta = {
|
||||
|
||||
</div>
|
||||
|
||||
<div className="grid gap-16 md:grid-cols-2">
|
||||
|
||||
<div>
|
||||
|
||||
### Supabase CLI
|
||||
|
||||
Develop your project locally, manage your Supabase projects, handle database migrations, CI/CD workflows, and generate types from your database schema, all from the comfort of your Terminal.
|
||||
|
||||
[Learn more](/docs/guides/resources/supabase-cli)
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{/* end of container */}
|
||||
|
||||
</div>
|
||||
|
||||
@@ -117,10 +117,8 @@ using ( bucket_id = 'public' );
|
||||
-- 1. Allow logged-in access to any files in the "restricted" bucket
|
||||
create policy "Restricted Access"
|
||||
on storage.objects for select
|
||||
using (
|
||||
bucket_id = 'restricted'
|
||||
and auth.role() = 'authenticated'
|
||||
);
|
||||
to authenticated
|
||||
using ( bucket_id = 'restricted' );
|
||||
```
|
||||
|
||||
### Allow individual access to a file
|
||||
|
||||
@@ -1,159 +0,0 @@
|
||||
import Layout from '~/layouts/DefaultLayout'
|
||||
import Link from 'next/link'
|
||||
import { GlassPanel } from 'ui'
|
||||
|
||||
export const meta = {
|
||||
title: 'Tutorials',
|
||||
}
|
||||
|
||||
### Webapps
|
||||
|
||||
<div className="grid grid-cols-12 gap-6 not-prose">
|
||||
{webapps.map((item) => {
|
||||
return (
|
||||
<Link href={`/${item.href}`} key={item.title} passHref>
|
||||
<a className={'col-span-4'}>
|
||||
<GlassPanel title={item.title} span="col-span-6" background={false} icon={item.icon}>
|
||||
{item.description}
|
||||
</GlassPanel>
|
||||
</a>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
### Mobile
|
||||
|
||||
<div className="grid grid-cols-12 gap-6 not-prose">
|
||||
{mobile.map((item) => {
|
||||
return (
|
||||
<Link href={`/${item.href}`} key={item.title} passHref>
|
||||
<a className={'col-span-4'}>
|
||||
<GlassPanel title={item.title} span="col-span-6" background={false} icon={item.icon}>
|
||||
{item.description}
|
||||
</GlassPanel>
|
||||
</a>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
### Low code
|
||||
|
||||
<div className="grid grid-cols-12 gap-6 not-prose">
|
||||
{lowCode.map((item) => {
|
||||
return (
|
||||
<Link href={`/${item.href}`} key={item.title} passHref>
|
||||
<a className={'col-span-4'}>
|
||||
<GlassPanel title={item.title} span="col-span-6" background={false} icon={item.icon}>
|
||||
{item.description}
|
||||
</GlassPanel>
|
||||
</a>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
export const webapps = [
|
||||
{
|
||||
title: 'NextJS',
|
||||
href: '/guides/tutorials/with-nextjs',
|
||||
description:
|
||||
'Learn how to build a user management app with NextJS and Supabase Database, Auth, and Storage functionality.',
|
||||
icon: '/docs/img/icons/nextjs-icon',
|
||||
},
|
||||
{
|
||||
title: 'React',
|
||||
href: '/guides/tutorials/with-react',
|
||||
description:
|
||||
'Learn how to build a user management app with React and Supabase Database, Auth, and Storage functionality.',
|
||||
icon: '/docs/img/icons/react-icon',
|
||||
},
|
||||
{
|
||||
title: 'Vue 3',
|
||||
href: '/guides/tutorials/with-vue-3',
|
||||
description:
|
||||
'Learn how to build a user management app with Vue 3 and Supabase Database, Auth, and Storage functionality.',
|
||||
icon: '/docs/img/icons/vuejs-icon',
|
||||
},
|
||||
{
|
||||
title: 'Nuxt 3',
|
||||
href: '/guides/tutorials/with-nuxt-3',
|
||||
description:
|
||||
'Learn how to build a user management app with Nuxt 3 and Supabase Database, Auth, and Storage functionality.',
|
||||
icon: '/docs/img/icons/nuxt-icon',
|
||||
},
|
||||
{
|
||||
title: 'Angular',
|
||||
href: '/guides/tutorials/with-angular',
|
||||
description:
|
||||
'Learn how to build a user management app with Angular and Supabase Database, Auth, and Storage functionality.',
|
||||
icon: '/docs/img/icons/angular-icon',
|
||||
},
|
||||
{
|
||||
title: 'RedwoodJS',
|
||||
href: '/guides/tutorials/with-redwoodjs',
|
||||
description:
|
||||
'Learn how to build a user management app with RedwoodJS and Supabase Database, Auth, and Storage functionality.',
|
||||
icon: '/docs/img/icons/redwood-icon',
|
||||
},
|
||||
{
|
||||
title: 'Svelte',
|
||||
href: '/guides/tutorials/with-svelte',
|
||||
description:
|
||||
'Learn how to build a user management app with Svelte and Supabase Database, Auth, and Storage functionality.',
|
||||
icon: '/docs/img/icons/svelte-icon',
|
||||
},
|
||||
{
|
||||
title: 'SvelteKit',
|
||||
href: '/guides/tutorials/with-sveltekit',
|
||||
description:
|
||||
'Learn how to build a user management app with SvelteKit and Supabase Database, Auth, and Storage functionality.',
|
||||
icon: '/docs/img/icons/svelte-icon',
|
||||
},
|
||||
]
|
||||
|
||||
export const mobile = [
|
||||
{
|
||||
title: 'Flutter',
|
||||
href: '/guides/tutorials/with-flutter',
|
||||
description:
|
||||
'Learn how to build a user management app with Flutter and Supabase Database, Auth, and Storage functionality.',
|
||||
icon: '/docs/img/icons/flutter-icon',
|
||||
},
|
||||
{
|
||||
title: 'Expo',
|
||||
href: '/guides/tutorials/with-expo',
|
||||
description:
|
||||
'Learn how to build a user management app with Expo and Supabase Database, Auth, and Storage functionality.',
|
||||
icon: '/docs/img/icons/expo-icon',
|
||||
},
|
||||
]
|
||||
|
||||
export const lowCode = [
|
||||
{
|
||||
title: 'Ionic React',
|
||||
href: '/guides/tutorials/with-ionic-react',
|
||||
description:
|
||||
'Learn how to build a user management app with Ionic React and Supabase Database, Auth, and Storage functionality.',
|
||||
icon: '/docs/img/icons/ionic-icon',
|
||||
},
|
||||
{
|
||||
title: 'Ionic Vue',
|
||||
href: '/guides/tutorials/with-ionic-vue',
|
||||
description:
|
||||
'Learn how to build a user management app with Ionic Vue and Supabase Database, Auth, and Storage functionality.',
|
||||
icon: '/docs/img/icons/ionic-icon',
|
||||
},
|
||||
{
|
||||
title: 'Ionic Angular',
|
||||
href: '/guides/tutorials/with-ionic-angular',
|
||||
description:
|
||||
'Learn how to build a user management app with Ionic Angular and Supabase Database, Auth, and Storage functionality.',
|
||||
icon: '/docs/img/icons/ionic-icon',
|
||||
},
|
||||
]
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
export default Page
|
||||
@@ -69,13 +69,28 @@ export const meta = {
|
||||
</Link>
|
||||
<Link href={`/guides/getting-started/quickstarts/nextjs`} passHref>
|
||||
<a className="no-underline">
|
||||
<IconPanel title="NextJS" icon="/docs/img/icons/nextjs-icon" />
|
||||
</a>
|
||||
<IconPanel title="NextJS" icon="/docs/img/icons/nextjs-icon" />
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={`/guides/getting-started/quickstarts/flutter`} passHref>
|
||||
<a className="no-underline">
|
||||
<IconPanel title="Flutter" icon="/docs/img/icons/flutter-icon" />
|
||||
</a>
|
||||
<IconPanel title="Flutter" icon="/docs/img/icons/flutter-icon" />
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={`/guides/getting-started/quickstarts/sveltekit`} passHref>
|
||||
<a className="no-underline">
|
||||
<IconPanel title="SvelteKit" icon="/docs/img/icons/svelte-icon" />
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={`/guides/getting-started/quickstarts/solidjs`} passHref>
|
||||
<a className="no-underline">
|
||||
<IconPanel title="SolidJS" icon="/docs/img/icons/solidjs-icon" />
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={`/guides/getting-started/quickstarts/vue`} passHref>
|
||||
<a className="no-underline">
|
||||
<IconPanel title="Vue" icon="/docs/img/icons/vuejs-icon" />
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
import Layout from '~/layouts/DefaultLayout'
|
||||
import Link from 'next/link'
|
||||
// import { useTheme } from 'common/Providers'
|
||||
import { GlassPanel } from 'ui'
|
||||
|
||||
{/* // export const { isDarkMode } = useTheme() */}
|
||||
|
||||
export const meta = {
|
||||
title: 'Auth',
|
||||
}
|
||||
|
||||
## Add and manage email and password, passwordless, OAuth, and mobile logins to your project through a suite of identity providers and APIs.
|
||||
|
||||
### Popular Guides
|
||||
|
||||
<div className="grid grid-cols-12 gap-6 not-prose">
|
||||
{topArticles.map((item) => {
|
||||
return (
|
||||
<Link
|
||||
href={`/${item.href}`}
|
||||
key={item.title}
|
||||
passHref
|
||||
>
|
||||
<a className={item.span ?? 'col-span-4'}>
|
||||
<GlassPanel
|
||||
title={item.title}
|
||||
header="/docs/img/cards/sample-card-header-4.svg"
|
||||
span="col-span-6"
|
||||
background={false}
|
||||
//* isDarkMode={isDarkMode}
|
||||
>
|
||||
{item.description}
|
||||
</GlassPanel>
|
||||
</a>
|
||||
</Link>
|
||||
)})}
|
||||
|
||||
</div>
|
||||
|
||||
export const topArticles = [
|
||||
{
|
||||
title: 'Enable Captcha Protection',
|
||||
header: '/docs/img/cards/sample-card-header-4.svg',
|
||||
href: '/guides/auth/auth-captcha',
|
||||
description: 'Keep your platform safe from bots and malicious scripts by implementing hCaptcha.',
|
||||
span: 'col-span-6',
|
||||
},
|
||||
{
|
||||
title: 'Auth Helpers',
|
||||
header: '/docs/img/cards/sample-card-header-4.svg',
|
||||
href: '/guides/auth/auth-helpers',
|
||||
description: 'A collection of framework-specific utilities for implementing Supabase Auth.',
|
||||
span: 'col-span-6',
|
||||
},
|
||||
{
|
||||
title: 'Email Login',
|
||||
header: '/docs/img/cards/sample-card-header-4.svg',
|
||||
href: '/guides/auth/auth-email',
|
||||
description: 'Learn how to add an email and password login flow to your platform.',
|
||||
},
|
||||
{
|
||||
title: 'Phone Login',
|
||||
header: '/docs/img/cards/sample-card-header-4.svg',
|
||||
href: '/guides/auth/phone-login',
|
||||
description: 'Learn how to add a login flow so your users can access your platform through one-time passwords via SMS.',
|
||||
},
|
||||
{
|
||||
title: 'Social Login',
|
||||
header: '/docs/img/cards/sample-card-header-4.svg',
|
||||
href: '/guides/auth/social-login',
|
||||
description: 'Learn how to add a login flow so your users can access your platform through social providers like Apple, Facebook, or Google.',
|
||||
},
|
||||
]
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
export default Page
|
||||
Whitespace-only changes.
@@ -2,16 +2,31 @@
|
||||
import specFile from '~/../../spec/cli_v1_config.yaml' assert { type: 'yml' }
|
||||
import { Parameter } from '~/lib/refGenerator/refTypes'
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
import GuidesTableOfContents from '~/components/GuidesTableOfContents'
|
||||
import { Heading } from '~/components/CustomHTMLElements'
|
||||
|
||||
// Parameters are grouped on the page by tag
|
||||
const TAGS = ['general', 'auth', 'api', 'database', 'dashboard', 'local', 'edge-functions']
|
||||
|
||||
const tocList = TAGS.map((tag) =>
|
||||
specFile.parameters
|
||||
.filter((param: Parameter) => param.tags[0] === tag)
|
||||
.map((parameter) => {
|
||||
console.log('the param', parameter)
|
||||
const text = parameter.id
|
||||
const link = `#${parameter.id}`
|
||||
const level = '2'
|
||||
return { text, link, level }
|
||||
})
|
||||
).flat()
|
||||
|
||||
export default function Config() {
|
||||
return (
|
||||
<div>
|
||||
<div className="flex my-16">
|
||||
<div className="grid grid-cols-12 relative gap-4">
|
||||
<div className="relative col-span-12 md:col-span-9 transition-all ease-out duration-100">
|
||||
<div className="w-full prose">
|
||||
<h1 className="text-4xl mb-16">{specFile.info.title}</h1>
|
||||
<h1 className="">CLI configuration</h1>
|
||||
<div className="max-w-xs w-32 h-[1px] bg-gradient-to-r from-brand-800 to-brand-900 my-8"></div>
|
||||
<ReactMarkdown>{specFile.info.description}</ReactMarkdown>
|
||||
<div>
|
||||
{TAGS.map((tag) =>
|
||||
@@ -22,11 +37,11 @@ export default function Config() {
|
||||
{index === 0 && <h2 className="text-xl capitalize">{tag}</h2>}
|
||||
<div className="mt-8">
|
||||
<div>
|
||||
<h2 className="text-xl font-medium text-scale-1200 font-mono">
|
||||
<Heading tag="h2" parseAnchors={false} customAnchor={parameter.id}>
|
||||
<span className="mr-2">$</span>
|
||||
{parameter.title}
|
||||
</h2>
|
||||
<div className="grid" id={parameter.id}>
|
||||
</Heading>
|
||||
<div className="grid">
|
||||
<div className="border-b pb-8" key={parameter.id}>
|
||||
<div className=" mb-16">
|
||||
<p className="mb-4 scroll-mt-16 mt-0 text-scale-1100 text-base">
|
||||
@@ -63,6 +78,14 @@ export default function Config() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="md:col-span-3">
|
||||
<div className="sticky top-20 border-l">
|
||||
<span className="block font-mono text-xs uppercase text-scale-1200 pl-5 mb-4">
|
||||
On this page
|
||||
</span>
|
||||
<GuidesTableOfContents list={tocList} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,724 +0,0 @@
|
||||
import Layout from '~/layouts/tutorials/TutorialLayout'
|
||||
|
||||
export const meta = {
|
||||
title: 'Creating a user management dashboard with NextJS and Supabase',
|
||||
author: 'Rich Haines',
|
||||
video: 'https://www.youtube-nocookie.com/embed/0Fs96oZ4se0',
|
||||
}
|
||||
|
||||
In this 25-minute guide, we’ll be building a collaborative user managment app using NextJS and Supabase.
|
||||
As users add and move rectangles in a canvas, changes will be automatically synced and persisted, allowing for a canvas that updates in real-time across clients.
|
||||
Users will also be able to see other users selections, and undo and redo actions.
|
||||
|
||||

|
||||
|
||||
### GitHub
|
||||
|
||||
Should you get stuck while working through the guide, refer to [this repo](https://github.com/supabase/supabase/tree/master/examples/user-management/nextjs-ts-user-management).
|
||||
|
||||
## Building the App
|
||||
|
||||
Let's start building the Next.js app from scratch.
|
||||
|
||||
### Initialize a Next.js app
|
||||
|
||||
We can use [`create-next-app`](https://nextjs.org/docs/getting-started) to initialize
|
||||
an app called `supabase-nextjs`:
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="js"
|
||||
>
|
||||
<Tabs.Panel id="js" label="JavaScript">
|
||||
|
||||
```bash
|
||||
npx create-next-app@latest --use-npm supabase-nextjs
|
||||
cd supabase-nextjs
|
||||
```
|
||||
|
||||
</Tabs.Panel>
|
||||
<Tabs.Panel id="ts" label="TypeScript">
|
||||
|
||||
```bash
|
||||
npx create-next-app@latest --ts --use-npm supabase-nextjs
|
||||
cd supabase-nextjs
|
||||
```
|
||||
|
||||
</Tabs.Panel>
|
||||
</Tabs>
|
||||
|
||||
Then install the Supabase client library: [supabase-js](https://github.com/supabase/supabase-js)
|
||||
|
||||
```bash
|
||||
npm install @supabase/supabase-js
|
||||
```
|
||||
|
||||
And finally we want to save the environment variables in a `.env.local`.
|
||||
All we need are the API URL and the `anon` key that you copied [earlier](#get-the-api-keys).
|
||||
|
||||
```bash title=.env.local
|
||||
NEXT_PUBLIC_SUPABASE_URL=YOUR_SUPABASE_URL
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
|
||||
```
|
||||
|
||||
And one optional step is to update the CSS file `styles/globals.css` to make the app look nice.
|
||||
You can find the full contents of this file [here](https://raw.githubusercontent.com/supabase/supabase/master/examples/user-management/nextjs-ts-user-management/styles/globals.css).
|
||||
|
||||
### Set up a Login component
|
||||
|
||||
#### Supabase Auth Helpers
|
||||
|
||||
Next.js is a highly versatile framework offering pre-rendering at build time (SSG), server-side rendering at request time (SSR), API routes, and middleware edge-functions.
|
||||
|
||||
It can be challenging to authenticate your users in all these different environments, that's why we've created the [Supabase Auth Helpers](https://supabase.com/docs/guides/auth/auth-helpers/nextjs) to make user management and data fetching within Next.js as easy as possible.
|
||||
|
||||
Install the auth helpers for React and Next.js
|
||||
|
||||
```bash
|
||||
npm install @supabase/auth-helpers-react @supabase/auth-helpers-nextjs
|
||||
```
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="js"
|
||||
>
|
||||
<Tabs.Panel id="js" label="JavaScript">
|
||||
|
||||
Wrap your `pages/_app.js` component with the `SessionContextProvider` component:
|
||||
|
||||
```jsx title=pages/_app.js
|
||||
import '../styles/globals.css'
|
||||
import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'
|
||||
import { SessionContextProvider } from '@supabase/auth-helpers-react'
|
||||
|
||||
function MyApp({ Component, pageProps }) {
|
||||
const [supabaseClient] = useState(() => createBrowserSupabaseClient())
|
||||
|
||||
return (
|
||||
<SessionContextProvider
|
||||
supabaseClient={supabaseClient}
|
||||
initialSession={pageProps.initialSession}
|
||||
>
|
||||
<Component {...pageProps} />
|
||||
</SessionContextProvider>
|
||||
)
|
||||
}
|
||||
export default MyApp
|
||||
```
|
||||
|
||||
</Tabs.Panel>
|
||||
<Tabs.Panel id="ts" label="TypeScript">
|
||||
|
||||
Wrap your `pages/_app.tsx` component with the `SessionContextProvider` component:
|
||||
|
||||
```jsx lines=2,8 title=pages/_app.tsx
|
||||
import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'
|
||||
import { SessionContextProvider, Session } from '@supabase/auth-helpers-react'
|
||||
|
||||
function MyApp({
|
||||
Component,
|
||||
pageProps,
|
||||
}: AppProps<{
|
||||
initialSession: Session,
|
||||
}>) {
|
||||
const [supabaseClient] = useState(() => createBrowserSupabaseClient())
|
||||
|
||||
return (
|
||||
<SessionContextProvider
|
||||
supabaseClient={supabaseClient}
|
||||
initialSession={pageProps.initialSession}
|
||||
>
|
||||
<Component {...pageProps} />
|
||||
</SessionContextProvider>
|
||||
)
|
||||
}
|
||||
export default MyApp
|
||||
```
|
||||
|
||||
See the [Auth Helpers docs](/docs/guides/auth/auth-helpers/nextjs#usage-with-typescript) for more details on usage with TypeScript.
|
||||
|
||||
</Tabs.Panel>
|
||||
</Tabs>
|
||||
|
||||
#### Supabase Auth UI
|
||||
|
||||
We can use the [Supabase Auth UI](https://supabase.com/docs/guides/auth/auth-helpers/auth-ui) a pre-built React component for authenticating users via OAuth, email, and magic links.
|
||||
|
||||
Install the Supabase Auth UI for React
|
||||
|
||||
```bash
|
||||
npm install @supabase/auth-ui-react
|
||||
```
|
||||
|
||||
Add the `Auth` component to your home page
|
||||
|
||||
```jsx title=pages/index.js
|
||||
import { Auth, ThemeSupa } from '@supabase/auth-ui-react'
|
||||
import { useSession, useSupabaseClient } from '@supabase/auth-helpers-react'
|
||||
|
||||
const Home = () => {
|
||||
const session = useSession()
|
||||
const supabase = useSupabaseClient()
|
||||
|
||||
return (
|
||||
<div className="container" style={{ padding: '50px 0 100px 0' }}>
|
||||
{!session ? (
|
||||
<Auth supabaseClient={supabase} appearance={{ theme: ThemeSupa }} theme="dark" />
|
||||
) : (
|
||||
<p>Account page will go here.</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Home
|
||||
```
|
||||
|
||||
### Account page
|
||||
|
||||
After a user is signed in we can allow them to edit their profile details and manage their account.
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="js"
|
||||
>
|
||||
<Tabs.Panel id="js" label="JavaScript">
|
||||
|
||||
Let's create a new component for that called `Account.js` within a `components` folder.
|
||||
|
||||
```tsx title=components/Account.js
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useUser, useSupabaseClient } from '@supabase/auth-helpers-react'
|
||||
|
||||
export default function Account({ session }) {
|
||||
const supabase = useSupabaseClient()
|
||||
const user = useUser()
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [username, setUsername] = useState(null)
|
||||
const [website, setWebsite] = useState(null)
|
||||
const [avatar_url, setAvatarUrl] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
getProfile()
|
||||
}, [session])
|
||||
|
||||
async function getProfile() {
|
||||
try {
|
||||
setLoading(true)
|
||||
|
||||
let { data, error, status } = await supabase
|
||||
.from('profiles')
|
||||
.select(`username, website, avatar_url`)
|
||||
.eq('id', user.id)
|
||||
.single()
|
||||
|
||||
if (error && status !== 406) {
|
||||
throw error
|
||||
}
|
||||
|
||||
if (data) {
|
||||
setUsername(data.username)
|
||||
setWebsite(data.website)
|
||||
setAvatarUrl(data.avatar_url)
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Error loading user data!')
|
||||
console.log(error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
async function updateProfile({ username, website, avatar_url }) {
|
||||
try {
|
||||
setLoading(true)
|
||||
|
||||
const updates = {
|
||||
id: user.id,
|
||||
username,
|
||||
website,
|
||||
avatar_url,
|
||||
updated_at: new Date().toISOString(),
|
||||
}
|
||||
|
||||
let { error } = await supabase.from('profiles').upsert(updates)
|
||||
if (error) throw error
|
||||
alert('Profile updated!')
|
||||
} catch (error) {
|
||||
alert('Error updating the data!')
|
||||
console.log(error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="form-widget">
|
||||
<div>
|
||||
<label htmlFor="email">Email</label>
|
||||
<input id="email" type="text" value={session.user.email} disabled />
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="username">Username</label>
|
||||
<input
|
||||
id="username"
|
||||
type="text"
|
||||
value={username || ''}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="website">Website</label>
|
||||
<input
|
||||
id="website"
|
||||
type="website"
|
||||
value={website || ''}
|
||||
onChange={(e) => setWebsite(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
className="button primary block"
|
||||
onClick={() => updateProfile({ username, website, avatar_url })}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? 'Loading ...' : 'Update'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button className="button block" onClick={() => supabase.auth.signOut()}>
|
||||
Sign Out
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</Tabs.Panel>
|
||||
<Tabs.Panel id="ts" label="TypeScript">
|
||||
|
||||
Let's create a new component for that called `Account.tsx` within a `components` folder.
|
||||
|
||||
```tsx title=components/Account.tsx
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useUser, useSupabaseClient, Session } from '@supabase/auth-helpers-react'
|
||||
import { Database } from '../utils/database.types'
|
||||
type Profiles = Database['public']['Tables']['profiles']['Row']
|
||||
|
||||
export default function Account({ session }: { session: Session }) {
|
||||
const supabase = useSupabaseClient<Database>()
|
||||
const user = useUser()
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [username, setUsername] = useState<Profiles['username']>(null)
|
||||
const [website, setWebsite] = useState<Profiles['website']>(null)
|
||||
const [avatar_url, setAvatarUrl] = useState<Profiles['avatar_url']>(null)
|
||||
|
||||
useEffect(() => {
|
||||
getProfile()
|
||||
}, [session])
|
||||
|
||||
async function getProfile() {
|
||||
try {
|
||||
setLoading(true)
|
||||
if (!user) throw new Error('No user')
|
||||
|
||||
let { data, error, status } = await supabase
|
||||
.from('profiles')
|
||||
.select(`username, website, avatar_url`)
|
||||
.eq('id', user.id)
|
||||
.single()
|
||||
|
||||
if (error && status !== 406) {
|
||||
throw error
|
||||
}
|
||||
|
||||
if (data) {
|
||||
setUsername(data.username)
|
||||
setWebsite(data.website)
|
||||
setAvatarUrl(data.avatar_url)
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Error loading user data!')
|
||||
console.log(error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
async function updateProfile({
|
||||
username,
|
||||
website,
|
||||
avatar_url,
|
||||
}: {
|
||||
username: Profiles['username']
|
||||
website: Profiles['website']
|
||||
avatar_url: Profiles['avatar_url']
|
||||
}) {
|
||||
try {
|
||||
setLoading(true)
|
||||
if (!user) throw new Error('No user')
|
||||
|
||||
const updates = {
|
||||
id: user.id,
|
||||
username,
|
||||
website,
|
||||
avatar_url,
|
||||
updated_at: new Date().toISOString(),
|
||||
}
|
||||
|
||||
let { error } = await supabase.from('profiles').upsert(updates)
|
||||
if (error) throw error
|
||||
alert('Profile updated!')
|
||||
} catch (error) {
|
||||
alert('Error updating the data!')
|
||||
console.log(error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="form-widget">
|
||||
<div>
|
||||
<label htmlFor="email">Email</label>
|
||||
<input id="email" type="text" value={session.user.email} disabled />
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="username">Username</label>
|
||||
<input
|
||||
id="username"
|
||||
type="text"
|
||||
value={username || ''}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="website">Website</label>
|
||||
<input
|
||||
id="website"
|
||||
type="website"
|
||||
value={website || ''}
|
||||
onChange={(e) => setWebsite(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
className="button primary block"
|
||||
onClick={() => updateProfile({ username, website, avatar_url })}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? 'Loading ...' : 'Update'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button className="button block" onClick={() => supabase.auth.signOut()}>
|
||||
Sign Out
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</Tabs.Panel>
|
||||
</Tabs>
|
||||
|
||||
### Launch!
|
||||
|
||||
Now that we have all the components in place, let's update `pages/index.js`:
|
||||
|
||||
```jsx lines=3,14 title=pages/index.js
|
||||
import { Auth, ThemeSupa } from '@supabase/auth-ui-react'
|
||||
import { useSession, useSupabaseClient } from '@supabase/auth-helpers-react'
|
||||
import Account from '../components/Account'
|
||||
|
||||
const Home = () => {
|
||||
const session = useSession()
|
||||
const supabase = useSupabaseClient()
|
||||
|
||||
return (
|
||||
<div className="container" style={{ padding: '50px 0 100px 0' }}>
|
||||
{!session ? (
|
||||
<Auth supabaseClient={supabase} appearance={{ theme: ThemeSupa }} theme="dark" />
|
||||
) : (
|
||||
<Account session={session} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Home
|
||||
```
|
||||
|
||||
Once that's done, run this in a terminal window:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
And then open the browser to [localhost:3000](http://localhost:3000) and you should see the completed app.
|
||||
|
||||
## Bonus: Profile photos
|
||||
|
||||
Every Supabase project is configured with [Storage](/docs/guides/storage) for managing large files like
|
||||
photos and videos.
|
||||
|
||||
### Create an upload widget
|
||||
|
||||
Let's create an avatar widget for the user so that they can upload a profile photo. We can start by creating a new component:
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="js"
|
||||
>
|
||||
<Tabs.Panel id="js" label="JavaScript">
|
||||
|
||||
```jsx title=components/Avatar.js
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useSupabaseClient } from '@supabase/auth-helpers-react'
|
||||
|
||||
export default function Avatar({ uid, url, size, onUpload }) {
|
||||
const supabase = useSupabaseClient()
|
||||
const [avatarUrl, setAvatarUrl] = useState(null)
|
||||
const [uploading, setUploading] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (url) downloadImage(url)
|
||||
}, [url])
|
||||
|
||||
async function downloadImage(path) {
|
||||
try {
|
||||
const { data, error } = await supabase.storage.from('avatars').download(path)
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
const url = URL.createObjectURL(data)
|
||||
setAvatarUrl(url)
|
||||
} catch (error) {
|
||||
console.log('Error downloading image: ', error)
|
||||
}
|
||||
}
|
||||
|
||||
const uploadAvatar = async (event) => {
|
||||
try {
|
||||
setUploading(true)
|
||||
|
||||
if (!event.target.files || event.target.files.length === 0) {
|
||||
throw new Error('You must select an image to upload.')
|
||||
}
|
||||
|
||||
const file = event.target.files[0]
|
||||
const fileExt = file.name.split('.').pop()
|
||||
const fileName = `${uid}.${fileExt}`
|
||||
const filePath = `${fileName}`
|
||||
|
||||
let { error: uploadError } = await supabase.storage
|
||||
.from('avatars')
|
||||
.upload(filePath, file, { upsert: true })
|
||||
|
||||
if (uploadError) {
|
||||
throw uploadError
|
||||
}
|
||||
|
||||
onUpload(filePath)
|
||||
} catch (error) {
|
||||
alert('Error uploading avatar!')
|
||||
console.log(error)
|
||||
} finally {
|
||||
setUploading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{avatarUrl ? (
|
||||
<img
|
||||
src={avatarUrl}
|
||||
alt="Avatar"
|
||||
className="avatar image"
|
||||
style={{ height: size, width: size }}
|
||||
/>
|
||||
) : (
|
||||
<div className="avatar no-image" style={{ height: size, width: size }} />
|
||||
)}
|
||||
<div style={{ width: size }}>
|
||||
<label className="button primary block" htmlFor="single">
|
||||
{uploading ? 'Uploading ...' : 'Upload'}
|
||||
</label>
|
||||
<input
|
||||
style={{
|
||||
visibility: 'hidden',
|
||||
position: 'absolute',
|
||||
}}
|
||||
type="file"
|
||||
id="single"
|
||||
accept="image/*"
|
||||
onChange={uploadAvatar}
|
||||
disabled={uploading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</Tabs.Panel>
|
||||
<Tabs.Panel id="ts" label="TypeScript">
|
||||
|
||||
```tsx title=components/Avatar.tsx
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useSupabaseClient } from '@supabase/auth-helpers-react'
|
||||
import { Database } from '../utils/database.types'
|
||||
type Profiles = Database['public']['Tables']['profiles']['Row']
|
||||
|
||||
export default function Avatar({
|
||||
uid,
|
||||
url,
|
||||
size,
|
||||
onUpload,
|
||||
}: {
|
||||
uid: string
|
||||
url: Profiles['avatar_url']
|
||||
size: number
|
||||
onUpload: (url: string) => void
|
||||
}) {
|
||||
const supabase = useSupabaseClient<Database>()
|
||||
const [avatarUrl, setAvatarUrl] = useState<Profiles['avatar_url']>(null)
|
||||
const [uploading, setUploading] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (url) downloadImage(url)
|
||||
}, [url])
|
||||
|
||||
async function downloadImage(path: string) {
|
||||
try {
|
||||
const { data, error } = await supabase.storage.from('avatars').download(path)
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
const url = URL.createObjectURL(data)
|
||||
setAvatarUrl(url)
|
||||
} catch (error) {
|
||||
console.log('Error downloading image: ', error)
|
||||
}
|
||||
}
|
||||
|
||||
const uploadAvatar: React.ChangeEventHandler<HTMLInputElement> = async (event) => {
|
||||
try {
|
||||
setUploading(true)
|
||||
|
||||
if (!event.target.files || event.target.files.length === 0) {
|
||||
throw new Error('You must select an image to upload.')
|
||||
}
|
||||
|
||||
const file = event.target.files[0]
|
||||
const fileExt = file.name.split('.').pop()
|
||||
const fileName = `${uid}.${fileExt}`
|
||||
const filePath = `${fileName}`
|
||||
|
||||
let { error: uploadError } = await supabase.storage
|
||||
.from('avatars')
|
||||
.upload(filePath, file, { upsert: true })
|
||||
|
||||
if (uploadError) {
|
||||
throw uploadError
|
||||
}
|
||||
|
||||
onUpload(filePath)
|
||||
} catch (error) {
|
||||
alert('Error uploading avatar!')
|
||||
console.log(error)
|
||||
} finally {
|
||||
setUploading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{avatarUrl ? (
|
||||
<img
|
||||
src={avatarUrl}
|
||||
alt="Avatar"
|
||||
className="avatar image"
|
||||
style={{ height: size, width: size }}
|
||||
/>
|
||||
) : (
|
||||
<div className="avatar no-image" style={{ height: size, width: size }} />
|
||||
)}
|
||||
<div style={{ width: size }}>
|
||||
<label className="button primary block" htmlFor="single">
|
||||
{uploading ? 'Uploading ...' : 'Upload'}
|
||||
</label>
|
||||
<input
|
||||
style={{
|
||||
visibility: 'hidden',
|
||||
position: 'absolute',
|
||||
}}
|
||||
type="file"
|
||||
id="single"
|
||||
accept="image/*"
|
||||
onChange={uploadAvatar}
|
||||
disabled={uploading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</Tabs.Panel>
|
||||
</Tabs>
|
||||
|
||||
### Add the new widget
|
||||
|
||||
And then we can add the widget to the Account page:
|
||||
|
||||
```jsx title=components/Account.js
|
||||
// Import the new component
|
||||
import Avatar from './Avatar'
|
||||
|
||||
// ...
|
||||
|
||||
return (
|
||||
<div className="form-widget">
|
||||
{/* Add to the body */}
|
||||
<Avatar
|
||||
uid={user.id}
|
||||
url={avatar_url}
|
||||
size={150}
|
||||
onUpload={(url) => {
|
||||
setAvatarUrl(url)
|
||||
updateProfile({ username, website, avatar_url: url })
|
||||
}}
|
||||
/>
|
||||
{/* ... */}
|
||||
</div>
|
||||
)
|
||||
```
|
||||
|
||||
## Next steps
|
||||
|
||||
At this stage you have a fully functional application!
|
||||
|
||||
- See the complete [example on GitHub](https://github.com/supabase/supabase/tree/master/examples/user-management/nextjs-ts-user-management) and deploy it to Vercel.
|
||||
- Explore the [pre-built Auth UI for React](https://supabase.com/docs/guides/auth/auth-helpers/auth-ui).
|
||||
- Explore the [Auth Helpers for Next.js](https://supabase.com/docs/guides/auth/auth-helpers/nextjs).
|
||||
- Explore the [Supabase Cache Helpers](https://github.com/psteinroe/supabase-cache-helpers).
|
||||
- See the [Next.js Subscription Payments Starter](https://github.com/vercel/nextjs-subscription-payments) template on GitHub.
|
||||
- Got a question? [Ask here](https://github.com/supabase/supabase/discussions).
|
||||
- Sign in: [app.supabase.com](https://app.supabase.com)
|
||||
|
||||
export default ({ children }) => <Layout meta={meta} children={children} />
|
||||
@@ -6,6 +6,8 @@ image: launch-week-6/crdt/og-pg-crdt.png
|
||||
thumb: launch-week-6/crdt/og-pg-crdt.png
|
||||
tags:
|
||||
- launch-week
|
||||
- postgres
|
||||
- planetpg
|
||||
date: '2022-12-10'
|
||||
toc_depth: 2
|
||||
---
|
||||
|
||||
+26
-90
@@ -1155,7 +1155,7 @@ module.exports = [
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/reference/cli/about',
|
||||
destination: '/docs/reference/cli',
|
||||
destination: '/docs/reference/cli/introduction',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
@@ -1165,118 +1165,44 @@ module.exports = [
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/reference/cli/supabase-help',
|
||||
destination: '/docs/reference/cli/usage',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/reference/cli/supabase-login',
|
||||
destination: '/docs/reference/cli/usage',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/reference/cli/supabase-link',
|
||||
destination: '/docs/reference/cli/usage',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/reference/cli/supabase-init',
|
||||
destination: '/docs/reference/cli/usage',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/reference/cli/supabase-start',
|
||||
destination: '/docs/reference/cli/usage',
|
||||
destination: '/docs/reference/cli/global-flags',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/reference/cli/supabase-db-branch-list',
|
||||
destination: '/docs/reference/cli/usage',
|
||||
destination: '/docs/reference/supabase-db',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/reference/cli/supabase-db-branch-create',
|
||||
destination: '/docs/reference/cli/usage',
|
||||
destination: '/docs/reference/supabase-db',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/reference/cli/supabase-db-branch-delete',
|
||||
destination: '/docs/reference/cli/usage',
|
||||
destination: '/docs/reference/supabase-db',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/reference/cli/supabase-db-switch',
|
||||
destination: '/docs/reference/cli/usage',
|
||||
destination: '/docs/reference/supabase-db',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/reference/cli/supabase-db-changes',
|
||||
destination: '/docs/reference/cli/usage',
|
||||
destination: '/docs/reference/supabase-db',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/reference/cli/supabase-db-commit',
|
||||
destination: '/docs/reference/cli/usage',
|
||||
destination: '/docs/reference/supabase-db',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/reference/cli/supabase-db-remote-set',
|
||||
destination: '/docs/reference/cli/usage',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/reference/cli/supabase-functions-delete',
|
||||
destination: '/docs/reference/cli/usage',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/reference/cli/supabase-functions-deploy',
|
||||
destination: '/docs/reference/cli/usage',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/reference/cli/supabase-functions-new',
|
||||
destination: '/docs/reference/cli/usage',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/reference/cli/supabase-functions-serve',
|
||||
destination: '/docs/reference/cli/usage',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/reference/cli/supabase-migration-new',
|
||||
destination: '/docs/reference/cli/usage',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/reference/cli/supabase-orgs-list',
|
||||
destination: '/docs/reference/cli/usage',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/reference/cli/supabase-projects-list',
|
||||
destination: '/docs/reference/cli/usage',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/reference/cli/supabase-projects-create',
|
||||
destination: '/docs/reference/cli/usage',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/reference/cli/supabase-secrets-list',
|
||||
destination: '/docs/reference/cli/usage',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/reference/cli/supabase-secrets-set',
|
||||
destination: '/docs/reference/cli/usage',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/reference/cli/supabase-secrets-unset',
|
||||
destination: '/docs/reference/cli/usage',
|
||||
destination: '/docs/reference/supabase-db',
|
||||
},
|
||||
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/reference/tools/reference-auth',
|
||||
@@ -1711,6 +1637,11 @@ module.exports = [
|
||||
source: '/docs/guides/database',
|
||||
destination: '/docs/guides/database/overview',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/getting-started',
|
||||
destination: '/docs/guides/getting-started/architecture',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/architecture',
|
||||
@@ -1786,6 +1717,11 @@ module.exports = [
|
||||
source: '/docs/guides/with-ionic-angular',
|
||||
destination: '/docs/guides/getting-started/tutorials/with-ionic-angular',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/guides/tutorials',
|
||||
destination: '/docs/guides/getting-started#tutorials',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/guides/hosting/overview',
|
||||
@@ -1808,18 +1744,18 @@ module.exports = [
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/guides/cli',
|
||||
destination: '/docs/guides/resources/supabase-cli',
|
||||
source: '/docs/guides/resources/supabase-cli',
|
||||
destination: '/docs/guides/cli',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/guides/cli/local-development',
|
||||
destination: '/docs/guides/resources/supabase-cli/local-development',
|
||||
source: '/docs/guides/resources/supabase-cli/local-development',
|
||||
destination: '/docs/guides/cli/local-development',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/guides/cli/managing-environments',
|
||||
destination: '/docs/guides/resources/supabase-cli/managing-environments',
|
||||
source: '/docs/guides/resources/supabase-cli/managing-environments',
|
||||
destination: '/docs/guides/cli/managing-environments',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"next-seo": "^4.20.0",
|
||||
"next-transpile-modules": "^9.0.0",
|
||||
"nodemailer": "^6.7.3",
|
||||
"octokit": "^2.0.10",
|
||||
"octokit": "^2.0.14",
|
||||
"parse-numeric-range": "^1.3.0",
|
||||
"react": "^17.0.2",
|
||||
"react-copy-to-clipboard": "^5.0.2",
|
||||
|
||||
@@ -199,7 +199,7 @@ const Index = ({}: Props) => {
|
||||
border-gray-100 bg-white p-5
|
||||
pt-14 dark:border-gray-600"
|
||||
>
|
||||
<a href="mailto:support@supabase.io">
|
||||
<a href="https://app.supabase.com/support/new">
|
||||
<Button size="medium" type="default" iconRight={<IconMail />}>
|
||||
Email Support
|
||||
</Button>
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
node_modules/
|
||||
.vercel
|
||||
.next
|
||||
.env.local
|
||||
.env.production
|
||||
.env.dev
|
||||
.env.*
|
||||
@@ -9,8 +9,13 @@ version: "3.8"
|
||||
services:
|
||||
studio:
|
||||
container_name: supabase-studio
|
||||
image: supabase/studio:20221214-4eecc99
|
||||
image: supabase/studio:20230127-123d704
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: [ "CMD", "node", "-e", "require('http').get('http://localhost:3000/api/profile', (r) => {if (r.statusCode !== 200) throw new Error(r.statusCode)})" ]
|
||||
timeout: 5s
|
||||
interval: 5s
|
||||
retries: 3
|
||||
ports:
|
||||
- ${STUDIO_PORT}:3000/tcp
|
||||
environment:
|
||||
@@ -22,8 +27,6 @@ services:
|
||||
|
||||
SUPABASE_URL: http://kong:8000
|
||||
SUPABASE_PUBLIC_URL: ${SUPABASE_PUBLIC_URL}
|
||||
# Kept for backwards compatibility with studio:0.22.08
|
||||
SUPABASE_REST_URL: ${API_EXTERNAL_URL}/rest/v1/
|
||||
SUPABASE_ANON_KEY: ${ANON_KEY}
|
||||
SUPABASE_SERVICE_KEY: ${SERVICE_ROLE_KEY}
|
||||
|
||||
@@ -51,6 +54,11 @@ services:
|
||||
depends_on:
|
||||
db: # Disable this if you are using an external Postgres database
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: [ "CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:9999/health" ]
|
||||
timeout: 5s
|
||||
interval: 5s
|
||||
retries: 3
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
GOTRUE_API_HOST: 0.0.0.0
|
||||
@@ -109,6 +117,11 @@ services:
|
||||
depends_on:
|
||||
db: # Disable this if you are using an external Postgres database
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: [ "CMD", "bash", "-c", "printf \\0 > /dev/tcp/localhost/4000" ]
|
||||
timeout: 5s
|
||||
interval: 5s
|
||||
retries: 3
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
PORT: 4000
|
||||
@@ -139,6 +152,11 @@ services:
|
||||
condition: service_started
|
||||
imgproxy:
|
||||
condition: service_started
|
||||
healthcheck:
|
||||
test: [ "CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:5000/status" ]
|
||||
timeout: 5s
|
||||
interval: 5s
|
||||
retries: 3
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
ANON_KEY: ${ANON_KEY}
|
||||
@@ -161,6 +179,11 @@ services:
|
||||
imgproxy:
|
||||
container_name: supabase-imgproxy
|
||||
image: darthsim/imgproxy:v3.11
|
||||
healthcheck:
|
||||
test: [ "CMD", "imgproxy", "health" ]
|
||||
timeout: 5s
|
||||
interval: 5s
|
||||
retries: 3
|
||||
environment:
|
||||
IMGPROXY_BIND: ":5001"
|
||||
IMGPROXY_LOCAL_FILESYSTEM_ROOT: /
|
||||
@@ -170,7 +193,7 @@ services:
|
||||
|
||||
meta:
|
||||
container_name: supabase-meta
|
||||
image: supabase/postgres-meta:v0.52.1
|
||||
image: supabase/postgres-meta:v0.59.0
|
||||
depends_on:
|
||||
db: # Disable this if you are using an external Postgres database
|
||||
condition: service_healthy
|
||||
@@ -186,7 +209,7 @@ services:
|
||||
# Comment out everything below this point if you are using an external Postgres database
|
||||
db:
|
||||
container_name: supabase-db
|
||||
image: supabase/postgres:14.1.0.89
|
||||
image: supabase/postgres:14.1.0.106
|
||||
healthcheck:
|
||||
test: pg_isready -U postgres -h localhost
|
||||
interval: 5s
|
||||
|
||||
@@ -6,7 +6,7 @@ router
|
||||
.get('/oak-server', (context) => {
|
||||
context.response.body = 'This is an example Oak server running on Edge Functions!'
|
||||
})
|
||||
.post('/oak-server/greet', (context) => {
|
||||
.post('/oak-server/greet', async (context) => {
|
||||
// Note: request body will be streamed to the function as chunks, set limit to 0 to fully read it.
|
||||
const result = context.request.body({ type: 'json', limit: 0 })
|
||||
const body = await result.value
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@
|
||||
|
||||
За пълната документация посетете [supabase.com/docs](https://supabase.com/docs)
|
||||
|
||||
За да видите как да допринасяте, посетете [Getting Started](./DEVELOPERS.md)
|
||||
За да видите как да допринасяте, посетете [Getting Started](../DEVELOPERS.md)
|
||||
|
||||
## Общност & поддръжка
|
||||
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@
|
||||
|
||||
Katso täydelliset dokumentaatiot osoitteesta [supabase.com/docs](https://supabase.com/docs)
|
||||
|
||||
Katso ohjeet osallistumiseen vierailemalla [Aloitus](./DEVELOPERS.md)
|
||||
Katso ohjeet osallistumiseen vierailemalla [Aloitus](../DEVELOPERS.md)
|
||||
|
||||
## Yhteisö ja tuki
|
||||
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@
|
||||
|
||||
Pour une documentation complète, visitez [supabase.com/docs](https://supabase.com/docs)
|
||||
|
||||
Pour voir comment Contribuer, visitez [Getting Started](./DEVELOPERS.md)
|
||||
Pour voir comment Contribuer, visitez [Getting Started](../DEVELOPERS.md)
|
||||
|
||||
## Communauté et Support
|
||||
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@
|
||||
|
||||
詳しいドキュメントは[supabase.com/docs](https://supabase.com/docs)をご覧ください。
|
||||
|
||||
コントリビュート方法は[Getting Started](./DEVELOPERS.md)をご覧ください。
|
||||
コントリビュート方法は[Getting Started](../DEVELOPERS.md)をご覧ください。
|
||||
|
||||
## コミュニティとサポート
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
Para documentação completa, visite [supabase.com/docs](https://supabase.com/docs)
|
||||
|
||||
Para ver como Contribuir, visite [Primeiros Passos](./DEVELOPERS.md)
|
||||
Para ver como Contribuir, visite [Primeiros Passos](../DEVELOPERS.md)
|
||||
|
||||
## Comunidade & Suporte
|
||||
|
||||
@@ -142,7 +142,7 @@ Nossa biblioteca de cliente é modular. Cada sub-biblioteca é uma implementaç
|
||||
<td>-</td>
|
||||
<td><a href="https://github.com/supabase-community/gotrue-java" target="_blank" rel="noopener noreferrer">gotrue-java</a></td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td><a href="https://github.com/supabase-community/storage-java" target="_blank" rel="noopener noreferrer">storage-java</a></td>
|
||||
<td>-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@
|
||||
|
||||
Za punu dokumentaciju, posetite [supabase.com/docs](https://supabase.com/docs)
|
||||
|
||||
Da vidite kako možete doprineti razvoju, posetite [Getting Started](./DEVELOPERS.md)
|
||||
Da vidite kako možete doprineti razvoju, posetite [Getting Started](../DEVELOPERS.md)
|
||||
|
||||
## Zajednica & Podrška
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
Để có tài liệu đầy đủ, hãy truy cập [supabase.com/docs](https://supabase.com/docs)
|
||||
|
||||
Để biết cách Đóng góp, hãy truy cập [Bắt đầu](./DEVELOPERS.md)
|
||||
Để biết cách Đóng góp, hãy truy cập [Bắt đầu](../DEVELOPERS.md)
|
||||
|
||||
## Hỗ trợ & Cộng đồng
|
||||
|
||||
|
||||
Generated
+1766
-302
File diff suppressed because it is too large.
Load diff
@@ -62,5 +62,8 @@
|
||||
"mdx-mermaid": "^1.3.2",
|
||||
"mermaid": "^9.2.2",
|
||||
"mini-svg-data-uri": "^1.4.4"
|
||||
},
|
||||
"overrides": {
|
||||
"pgsql-parser": "13.4.0"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
clispec: '001'
|
||||
info:
|
||||
id: cli
|
||||
version: 1.33.0
|
||||
version: 1.34.5
|
||||
title: Supabase CLI
|
||||
language: sh
|
||||
source: https://github.com/supabase/cli
|
||||
@@ -247,12 +247,15 @@ commands:
|
||||
- id: supabase-start
|
||||
title: supabase start
|
||||
summary: Start containers for Supabase local development
|
||||
description: |-
|
||||
description: |2
|
||||
|
||||
Starts the Supabase local development stack.
|
||||
|
||||
Requires `supabase/config.toml` to be created in your current working directory by running `supabase init`.
|
||||
|
||||
All service containers are started by default. You can exclude those not needed by passing in `-x` flag.
|
||||
|
||||
Health checks are automatically added to verify the started containers. Use `--ignore-health-check` flag to ignore these errors.
|
||||
examples:
|
||||
- id: basic-usage
|
||||
name: Basic usage
|
||||
@@ -268,6 +271,13 @@ commands:
|
||||
Excluding container: supabase/studio:20221214-4eecc99
|
||||
Seeding data supabase/seed.sql...
|
||||
Started supabase local development setup.
|
||||
- id: ignore-health-check
|
||||
name: Ignore service health checks
|
||||
code: supabase start --ignore-health-check
|
||||
response: |
|
||||
Seeding data supabase/seed.sql...
|
||||
service not healthy: [supabase_storage_cli]
|
||||
Started supabase local development setup.
|
||||
tags:
|
||||
- local-dev
|
||||
links: []
|
||||
@@ -279,6 +289,10 @@ commands:
|
||||
description: |
|
||||
Names of containers to not start. [gotrue, realtime, storage-api, imgproxy, kong, inbucket, postgrest, pgadmin-schema-diff, migra, postgres-meta, studio, deno-relay]
|
||||
default_value: '[]'
|
||||
- id: ignore-health-check
|
||||
name: --ignore-health-check
|
||||
description: Ignore unhealthy services and exit 0
|
||||
default_value: 'false'
|
||||
- id: supabase-secrets
|
||||
title: supabase secrets
|
||||
summary: Manage Supabase secrets
|
||||
@@ -595,7 +609,8 @@ commands:
|
||||
- id: supabase-login
|
||||
title: supabase login
|
||||
summary: Authenticate using an access token
|
||||
description: |-
|
||||
description: |2
|
||||
|
||||
Connect the Supabase CLI to your Supabase account by logging in with your [personal access token](https://app.supabase.com/account/tokens).
|
||||
|
||||
Your access token is stored securely in [native credentials storage](https://github.com/zalando/go-keyring#dependencies). If native credentials storage is unavailable, it will be written to a plain text file at `~/.supabase/access-token`.
|
||||
@@ -620,7 +635,8 @@ commands:
|
||||
- id: supabase-link
|
||||
title: supabase link
|
||||
summary: Link to a Supabase project
|
||||
description: |-
|
||||
description: |2
|
||||
|
||||
Link your local development project to a hosted Supabase project.
|
||||
|
||||
PostgREST configurations are fetched from the Supabase platform and validated against your local configuration file.
|
||||
@@ -659,7 +675,8 @@ commands:
|
||||
- id: supabase-init
|
||||
title: supabase init
|
||||
summary: Initialize a local project
|
||||
description: |-
|
||||
description: |2
|
||||
|
||||
Initialize configurations for Supabase local development.
|
||||
|
||||
A `supabase/config.toml` file is created in your current working directory. This configuration is specific to each local project.
|
||||
@@ -738,6 +755,7 @@ commands:
|
||||
subcommands:
|
||||
- supabase-functions-delete
|
||||
- supabase-functions-deploy
|
||||
- supabase-functions-download
|
||||
- supabase-functions-new
|
||||
- supabase-functions-serve
|
||||
flags: []
|
||||
@@ -770,6 +788,20 @@ commands:
|
||||
usage: supabase functions new <Function name>
|
||||
subcommands: []
|
||||
flags: []
|
||||
- id: supabase-functions-download
|
||||
title: supabase functions download
|
||||
summary: Download a Function from Supabase
|
||||
description: |
|
||||
Download the source code for a Function from the linked Supabase project.
|
||||
tags: []
|
||||
links: []
|
||||
usage: supabase functions download <Function name> [flags]
|
||||
subcommands: []
|
||||
flags:
|
||||
- id: project-ref
|
||||
name: --project-ref <string>
|
||||
description: Project ref of the Supabase project.
|
||||
default_value: ''
|
||||
- id: supabase-functions-deploy
|
||||
title: supabase functions deploy
|
||||
summary: Deploy a Function to Supabase
|
||||
|
||||
@@ -231,6 +231,12 @@
|
||||
"slug": "supabase-functions-new",
|
||||
"type": "cli-command"
|
||||
},
|
||||
{
|
||||
"id": "supabase-functions-download",
|
||||
"title": "Download a function",
|
||||
"slug": "supabase-functions-download",
|
||||
"type": "cli-command"
|
||||
},
|
||||
{
|
||||
"id": "supabase-functions-serve",
|
||||
"title": "Serve a function",
|
||||
|
||||
@@ -677,7 +677,7 @@
|
||||
},
|
||||
{
|
||||
"id": "subscribe",
|
||||
"title": "Subscribe to database changes",
|
||||
"title": "Subscribe to channel",
|
||||
"slug": "subscribe",
|
||||
"product": "realtime",
|
||||
"type": "function"
|
||||
@@ -847,6 +847,7 @@
|
||||
},
|
||||
{
|
||||
"title": "Misc",
|
||||
"excludes": ["reference_dart_v1", "reference_dart_v0"],
|
||||
|
||||
"items": [
|
||||
{
|
||||
|
||||
+11
-11
@@ -5010,8 +5010,8 @@ functions:
|
||||
})
|
||||
.subscribe()
|
||||
```
|
||||
- id: listening-to-a-specific-table
|
||||
name: Listening to a specific table
|
||||
- id: listen-to-a-specific-table
|
||||
name: Listen to a specific table
|
||||
js: |
|
||||
```js
|
||||
supabase
|
||||
@@ -5021,8 +5021,8 @@ functions:
|
||||
})
|
||||
.subscribe()
|
||||
```
|
||||
- id: listening-to-inserts
|
||||
name: Listening to inserts
|
||||
- id: listen-to-inserts
|
||||
name: Listen to inserts
|
||||
js: |
|
||||
```js
|
||||
supabase
|
||||
@@ -5032,8 +5032,8 @@ functions:
|
||||
})
|
||||
.subscribe()
|
||||
```
|
||||
- id: listening-to-updates
|
||||
name: Listening to updates
|
||||
- id: listen-to-updates
|
||||
name: Listen to updates
|
||||
description: |
|
||||
By default, Supabase will send only the updated record. If you want to receive the previous values as well you can
|
||||
enable full replication for the table you are listening to:
|
||||
@@ -5050,8 +5050,8 @@ functions:
|
||||
})
|
||||
.subscribe()
|
||||
```
|
||||
- id: listening-to-deletes
|
||||
name: Listening to deletes
|
||||
- id: listen-to-deletes
|
||||
name: Listen to deletes
|
||||
description: |
|
||||
By default, Supabase does not send deleted records. If you want to receive the deleted record you can
|
||||
enable full replication for the table you are listening too:
|
||||
@@ -5068,8 +5068,8 @@ functions:
|
||||
})
|
||||
.subscribe()
|
||||
```
|
||||
- id: listening-to-multiple-events
|
||||
name: Listening to multiple events
|
||||
- id: listen-to-multiple-events
|
||||
name: Listen to multiple events
|
||||
description: You can chain listeners if you want to listen to multiple events for each table.
|
||||
js: |
|
||||
```js
|
||||
@@ -5080,7 +5080,7 @@ functions:
|
||||
.subscribe()
|
||||
```
|
||||
- id: listening-to-row-level-changes
|
||||
name: Listening to row level changes
|
||||
name: Listen to row level changes
|
||||
description: You can listen to individual rows using the format `{table}:{col}=eq.{val}` - where `{col}` is the column name, and `{val}` is the value which you want to match.
|
||||
notes: |
|
||||
- ``eq`` filter works with all database types as under the hood, it's casting both the filter value and the database value to the correct type and then comparing them.
|
||||
|
||||
+13
-13
@@ -700,8 +700,8 @@ functions:
|
||||
})
|
||||
.subscribe();
|
||||
```
|
||||
- id: listening-to-a-specific-table
|
||||
name: Listening to a specific table
|
||||
- id: listen-to-a-specific-table
|
||||
name: Listen to a specific table
|
||||
code: |
|
||||
```dart
|
||||
final mySubscription = supabase
|
||||
@@ -711,8 +711,8 @@ functions:
|
||||
})
|
||||
.subscribe();
|
||||
```
|
||||
- id: listening-to-inserts
|
||||
name: Listening to inserts
|
||||
- id: listen-to-inserts
|
||||
name: Listen to inserts
|
||||
code: |
|
||||
```dart
|
||||
final mySubscription = supabase
|
||||
@@ -722,8 +722,8 @@ functions:
|
||||
})
|
||||
.subscribe();
|
||||
```
|
||||
- id: listening-to-updates
|
||||
name: Listening to updates
|
||||
- id: listen-to-updates
|
||||
name: Listen to updates
|
||||
description: |
|
||||
By default, Supabase will send only the updated record. If you want to receive the previous values as well you can
|
||||
enable full replication for the table you are listening too:
|
||||
@@ -740,8 +740,8 @@ functions:
|
||||
})
|
||||
.subscribe();
|
||||
```
|
||||
- id: listening-to-deletes
|
||||
name: Listening to deletes
|
||||
- id: listen-to-deletes
|
||||
name: Listen to deletes
|
||||
description: |
|
||||
By default, Supabase does not send deleted records. If you want to receive the deleted record you can
|
||||
enable full replication for the table you are listening too:
|
||||
@@ -758,8 +758,8 @@ functions:
|
||||
})
|
||||
.subscribe();
|
||||
```
|
||||
- id: listening-to-multiple-events
|
||||
name: Listening to multiple events
|
||||
- id: listen-to-multiple-events
|
||||
name: Listen to multiple events
|
||||
description: You can chain listeners if you want to listen to multiple events for each table.
|
||||
code: |
|
||||
```dart
|
||||
@@ -770,7 +770,7 @@ functions:
|
||||
.subscribe();
|
||||
```
|
||||
- id: listening-to-row-level-changes
|
||||
name: Listening to row level changes
|
||||
name: Listen to row level changes
|
||||
description: You can listen to individual rows using the format `{table}:{col}=eq.{val}` - where `{col}` is the column name, and `{val}` is the value which you want to match.
|
||||
code: |
|
||||
```dart
|
||||
@@ -816,8 +816,8 @@ functions:
|
||||
- `stream()` will emit the initial data as well as any further change on the database as `Stream` of `List<Map<String, dynamic>>` by combining Postgrest and Realtime.
|
||||
- Takes a list of primary key columns as its argument.
|
||||
examples:
|
||||
- id: listening-to-a-specific-table
|
||||
name: Listening to a specific table
|
||||
- id: listen-to-a-specific-table
|
||||
name: Listen to a specific table
|
||||
isSpotlight: true
|
||||
code: |
|
||||
```dart
|
||||
|
||||
+48
-13
@@ -16,6 +16,41 @@ info:
|
||||
version: '0.0.1'
|
||||
|
||||
functions:
|
||||
- id: initializing
|
||||
title: 'Initializing'
|
||||
$ref: '@supabase/supabase-js.index.SupabaseClient.constructor'
|
||||
description: |
|
||||
You can initialize Supabase with the static `initialize()` method of `Supabase` class.
|
||||
|
||||
The Supabase client is your entrypoint to the rest of the Supabase functionality
|
||||
and is the easiest way to interact with everything we offer within the Supabase ecosystem.
|
||||
|
||||
examples:
|
||||
- id: flutter-initialize
|
||||
name: For Flutter
|
||||
code: |
|
||||
```dart
|
||||
Future<void> main() async {
|
||||
await Supabase.initialize(
|
||||
url: 'https://xyzcompany.supabase.co',
|
||||
anonKey: 'public-anon-key',
|
||||
);
|
||||
|
||||
runApp(MyApp());
|
||||
}
|
||||
|
||||
// Get a reference your Supabase client
|
||||
final supabase = Supabase.instance.client;
|
||||
```
|
||||
- id: for-other-dart-projects
|
||||
name: For other Dart projects
|
||||
code: |
|
||||
```dart
|
||||
final supabase = SupabaseClient(
|
||||
'https://xyzcompany.supabase.co',
|
||||
'public-anon-key',
|
||||
);
|
||||
```
|
||||
- id: sign-up
|
||||
title: 'signUp()'
|
||||
description: |
|
||||
@@ -696,8 +731,8 @@ functions:
|
||||
},
|
||||
).subscribe();
|
||||
```
|
||||
- id: listening-to-a-specific-table
|
||||
name: Listening to a specific table
|
||||
- id: listen-to-a-specific-table
|
||||
name: Listen to a specific table
|
||||
code: |
|
||||
```dart
|
||||
supabase.channel('public:countries').on(
|
||||
@@ -708,8 +743,8 @@ functions:
|
||||
},
|
||||
).subscribe();
|
||||
```
|
||||
- id: listening-to-inserts
|
||||
name: Listening to inserts
|
||||
- id: listen-to-inserts
|
||||
name: Listen to inserts
|
||||
code: |
|
||||
```dart
|
||||
supabase.channel('public:countries').on(
|
||||
@@ -720,8 +755,8 @@ functions:
|
||||
},
|
||||
).subscribe();
|
||||
```
|
||||
- id: listening-to-updates
|
||||
name: Listening to updates
|
||||
- id: listen-to-updates
|
||||
name: Listen to updates
|
||||
description: |
|
||||
By default, Supabase will send only the updated record. If you want to receive the previous values as well you can
|
||||
enable full replication for the table you are listening too:
|
||||
@@ -739,8 +774,8 @@ functions:
|
||||
},
|
||||
).subscribe();
|
||||
```
|
||||
- id: listening-to-deletes
|
||||
name: Listening to deletes
|
||||
- id: listen-to-deletes
|
||||
name: Listen to deletes
|
||||
description: |
|
||||
By default, Supabase does not send deleted records. If you want to receive the deleted record you can
|
||||
enable full replication for the table you are listening too:
|
||||
@@ -758,8 +793,8 @@ functions:
|
||||
},
|
||||
).subscribe();
|
||||
```
|
||||
- id: listening-to-multiple-events
|
||||
name: Listening to multiple events
|
||||
- id: listen-to-multiple-events
|
||||
name: Listen to multiple events
|
||||
description: You can chain listeners if you want to listen to multiple events for each table.
|
||||
code: |
|
||||
```dart
|
||||
@@ -774,7 +809,7 @@ functions:
|
||||
}).subscribe();
|
||||
```
|
||||
- id: listening-to-row-level-changes
|
||||
name: Listening to row level changes
|
||||
name: Listen to row level changes
|
||||
description: You can listen to individual rows using the format `{table}:{col}=eq.{val}` - where `{col}` is the column name, and `{val}` is the value which you want to match.
|
||||
code: |
|
||||
```dart
|
||||
@@ -841,8 +876,8 @@ functions:
|
||||
- `stream()` will emit the initial data as well as any further change on the database as `Stream` of `List<Map<String, dynamic>>` by combining Postgrest and Realtime.
|
||||
- Takes a list of primary key columns as its argument.
|
||||
examples:
|
||||
- id: listening-to-a-specific-table
|
||||
name: Listening to a specific table
|
||||
- id: listen-to-a-specific-table
|
||||
name: Listen to a specific table
|
||||
isSpotlight: true
|
||||
code: |
|
||||
```dart
|
||||
|
||||
+12
-12
@@ -1138,8 +1138,8 @@ functions:
|
||||
})
|
||||
.subscribe()
|
||||
```
|
||||
- id: listening-to-a-specific-table
|
||||
name: Listening to a specific table
|
||||
- id: listen-to-a-specific-table
|
||||
name: Listen to a specific table
|
||||
code: |
|
||||
```js
|
||||
const mySubscription = supabase
|
||||
@@ -1149,8 +1149,8 @@ functions:
|
||||
})
|
||||
.subscribe()
|
||||
```
|
||||
- id: listening-to-inserts
|
||||
name: Listening to inserts
|
||||
- id: listen-to-inserts
|
||||
name: Listen to inserts
|
||||
code: |
|
||||
```js
|
||||
const mySubscription = supabase
|
||||
@@ -1160,8 +1160,8 @@ functions:
|
||||
})
|
||||
.subscribe()
|
||||
```
|
||||
- id: listening-to-updates
|
||||
name: Listening to updates
|
||||
- id: listen-to-updates
|
||||
name: Listen to updates
|
||||
description: |
|
||||
By default, Supabase will send only the updated record. If you want to receive the previous values as well you can
|
||||
enable full replication for the table you are listening too:
|
||||
@@ -1178,8 +1178,8 @@ functions:
|
||||
})
|
||||
.subscribe()
|
||||
```
|
||||
- id: listening-to-deletes
|
||||
name: Listening to deletes
|
||||
- id: listen-to-deletes
|
||||
name: Listen to deletes
|
||||
description: |
|
||||
By default, Supabase does not send deleted records. If you want to receive the deleted record you can
|
||||
enable full replication for the table you are listening too:
|
||||
@@ -1196,8 +1196,8 @@ functions:
|
||||
})
|
||||
.subscribe()
|
||||
```
|
||||
- id: listening-to-multiple-events
|
||||
name: Listening to multiple events
|
||||
- id: listen-to-multiple-events
|
||||
name: Listen to multiple events
|
||||
description: You can chain listeners if you want to listen to multiple events for each table.
|
||||
code: |
|
||||
```js
|
||||
@@ -1208,7 +1208,7 @@ functions:
|
||||
.subscribe()
|
||||
```
|
||||
- id: listening-to-row-level-changes
|
||||
name: Listening to row level changes
|
||||
name: Listen to row level changes
|
||||
description: You can listen to individual rows using the format `{table}:{col}=eq.{val}` - where `{col}` is the column name, and `{val}` is the value which you want to match.
|
||||
notes: |
|
||||
- ``eq`` filter works with all database types as under the hood, it's casting both the filter value and the database value to the correct type and then comparing them.
|
||||
@@ -1272,7 +1272,7 @@ functions:
|
||||
name: List buckets
|
||||
isSpotlight: true
|
||||
code: |
|
||||
```js
|
||||
```ts
|
||||
const { data, error } = await supabase
|
||||
.storage
|
||||
.listBuckets()
|
||||
|
||||
+106
-20
@@ -156,7 +156,7 @@ functions:
|
||||
password: 'example-password',
|
||||
})
|
||||
```
|
||||
- id: sign-up-with-additional-user-metadata.
|
||||
- id: sign-up-with-additional-user-metadata
|
||||
name: Sign up with additional user metadata
|
||||
isSpotlight: false
|
||||
code: |
|
||||
@@ -174,6 +174,22 @@ 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.
|
||||
code: |
|
||||
```js
|
||||
const { data, error } = await supabase.auth.signUp(
|
||||
{
|
||||
email: 'example@email.com',
|
||||
password: 'example-password',
|
||||
options: {
|
||||
emailRedirectTo: 'https://example.com/welcome'
|
||||
}
|
||||
}
|
||||
)
|
||||
```
|
||||
- id: sign-in-with-password
|
||||
title: 'signInWithPassword()'
|
||||
$ref: '@supabase/gotrue-js.GoTrueClient.signInWithPassword'
|
||||
@@ -217,6 +233,7 @@ functions:
|
||||
- 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.
|
||||
- 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
|
||||
name: Sign in with email
|
||||
@@ -3980,42 +3997,111 @@ functions:
|
||||
- When a delete occurs, the contents of old_record will be broadcast to all subscribers to that table so ensure that each table's replica identity only contains information that is safe to expose publicly.
|
||||
- The channel name must exactly match the schema/table/filter you want to listen to separated by colons. See below examples for additional context.
|
||||
examples:
|
||||
- id: listen-to-broadcast
|
||||
name: Listen to broadcast messages
|
||||
isSpotlight: true
|
||||
code: |
|
||||
```js
|
||||
supabase
|
||||
.channel('any')
|
||||
.on('broadcast', { event: 'cursor-pos' }, payload => {
|
||||
console.log('Cursor position received!', payload)
|
||||
})
|
||||
.subscribe((status) => {
|
||||
if (status === 'SUBSCRIBED') {
|
||||
channel.send({
|
||||
type: 'broadcast',
|
||||
event: 'cursor-pos',
|
||||
payload: { x: Math.random(), y: Math.random() },
|
||||
})
|
||||
}
|
||||
})
|
||||
```
|
||||
- id: listen-to-presence-sync
|
||||
name: Listen to presence sync
|
||||
isSpotlight: true
|
||||
code: |
|
||||
```js
|
||||
const channel = supabase.channel('any')
|
||||
channel
|
||||
.on('presence', { event: 'sync' }, () => {
|
||||
console.log('Synced presence state: ', channel.presenceState())
|
||||
})
|
||||
.subscribe(async (status) => {
|
||||
if (status === 'SUBSCRIBED') {
|
||||
await channel.track({ online_at: new Date().toISOString() })
|
||||
}
|
||||
})
|
||||
```
|
||||
- id: listen-to-presence-join
|
||||
name: Listen to presence join
|
||||
isSpotlight: true
|
||||
code: |
|
||||
```js
|
||||
const channel = supabase.channel('any')
|
||||
channel
|
||||
.on('presence', { event: 'join' }, ({ newPresences }) => {
|
||||
console.log('Newly joined presences: ', newPresences)
|
||||
})
|
||||
.subscribe(async (status) => {
|
||||
if (status === 'SUBSCRIBED') {
|
||||
await channel.track({ online_at: new Date().toISOString() })
|
||||
}
|
||||
})
|
||||
```
|
||||
- id: listen-to-presence-leave
|
||||
name: Listen to presence leave
|
||||
isSpotlight: true
|
||||
code: |
|
||||
```js
|
||||
const channel = supabase.channel('any')
|
||||
channel
|
||||
.on('presence', { event: 'leave' }, ({ leftPresences }) => {
|
||||
console.log('Newly left presences: ', leftPresences)
|
||||
})
|
||||
.subscribe(async (status) => {
|
||||
if (status === 'SUBSCRIBED') {
|
||||
await channel.track({ online_at: new Date().toISOString() })
|
||||
await channel.untrack()
|
||||
}
|
||||
})
|
||||
```
|
||||
- id: listen-to-all-database-changes
|
||||
name: Listen to all database changes
|
||||
isSpotlight: true
|
||||
code: |
|
||||
```js
|
||||
supabase
|
||||
.channel('*')
|
||||
.channel('any')
|
||||
.on('postgres_changes', { event: '*', schema: '*' }, payload => {
|
||||
console.log('Change received!', payload)
|
||||
})
|
||||
.subscribe()
|
||||
```
|
||||
- id: listening-to-a-specific-table
|
||||
name: Listening to a specific table
|
||||
- id: listen-to-a-specific-table
|
||||
name: Listen to a specific table
|
||||
code: |
|
||||
```js
|
||||
supabase
|
||||
.channel('public:countries')
|
||||
.channel('any')
|
||||
.on('postgres_changes', { event: '*', schema: 'public', table: 'countries' }, payload => {
|
||||
console.log('Change received!', payload)
|
||||
})
|
||||
.subscribe()
|
||||
```
|
||||
- id: listening-to-inserts
|
||||
name: Listening to inserts
|
||||
- id: listen-to-inserts
|
||||
name: Listen to inserts
|
||||
code: |
|
||||
```js
|
||||
supabase
|
||||
.channel('public:countries')
|
||||
.channel('any')
|
||||
.on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'countries' }, payload => {
|
||||
console.log('Change received!', payload)
|
||||
})
|
||||
.subscribe()
|
||||
```
|
||||
- id: listening-to-updates
|
||||
name: Listening to updates
|
||||
- id: listen-to-updates
|
||||
name: Listen to updates
|
||||
description: |
|
||||
By default, Supabase will send only the updated record. If you want to receive the previous values as well you can
|
||||
enable full replication for the table you are listening to:
|
||||
@@ -4026,14 +4112,14 @@ functions:
|
||||
code: |
|
||||
```js
|
||||
supabase
|
||||
.channel('public:countries')
|
||||
.channel('any')
|
||||
.on('postgres_changes', { event: 'UPDATE', schema: 'public', table: 'countries' }, payload => {
|
||||
console.log('Change received!', payload)
|
||||
})
|
||||
.subscribe()
|
||||
```
|
||||
- id: listening-to-deletes
|
||||
name: Listening to deletes
|
||||
- id: listen-to-deletes
|
||||
name: Listen to deletes
|
||||
description: |
|
||||
By default, Supabase does not send deleted records. If you want to receive the deleted record you can
|
||||
enable full replication for the table you are listening too:
|
||||
@@ -4044,32 +4130,32 @@ functions:
|
||||
code: |
|
||||
```js
|
||||
supabase
|
||||
.channel('public:countries')
|
||||
.channel('any')
|
||||
.on('postgres_changes', { event: 'DELETE', schema: 'public', table: 'countries' }, payload => {
|
||||
console.log('Change received!', payload)
|
||||
})
|
||||
.subscribe()
|
||||
```
|
||||
- id: listening-to-multiple-events
|
||||
name: Listening to multiple events
|
||||
- id: listen-to-multiple-events
|
||||
name: Listen to multiple events
|
||||
description: You can chain listeners if you want to listen to multiple events for each table.
|
||||
code: |
|
||||
```js
|
||||
supabase
|
||||
.channel('public:countries')
|
||||
.channel('any')
|
||||
.on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'countries' }, handleRecordInserted)
|
||||
.on('postgres_changes', { event: 'DELETE', schema: 'public', table: 'countries' }, handleRecordDeleted)
|
||||
.subscribe()
|
||||
```
|
||||
- id: listening-to-row-level-changes
|
||||
name: Listening to row level changes
|
||||
name: Listen to row level changes
|
||||
description: You can listen to individual rows using the format `{table}:{col}=eq.{val}` - where `{col}` is the column name, and `{val}` is the value which you want to match.
|
||||
notes: |
|
||||
- ``eq`` filter works with all database types as under the hood, it's casting both the filter value and the database value to the correct type and then comparing them.
|
||||
code: |
|
||||
```js
|
||||
supabase
|
||||
.channel('public:countries:id=eq.200')
|
||||
.channel('any')
|
||||
.on('postgres_changes', { event: 'UPDATE', schema: 'public', table: 'countries', filter: 'id=eq.200' }, handleRecordUpdated)
|
||||
.subscribe()
|
||||
```
|
||||
@@ -4127,7 +4213,7 @@ functions:
|
||||
name: List buckets
|
||||
isSpotlight: true
|
||||
code: |
|
||||
```js
|
||||
```ts
|
||||
const { data, error } = await supabase
|
||||
.storage
|
||||
.listBuckets()
|
||||
|
||||
@@ -17,7 +17,6 @@ info:
|
||||
|
||||
functions:
|
||||
- id: initializing
|
||||
$ref: '@supabase/supabase-js.index.SupabaseClient.constructor'
|
||||
description: |
|
||||
You can initialize a new Supabase client using the `create_client()` method.
|
||||
|
||||
@@ -50,7 +49,6 @@ functions:
|
||||
|
||||
- id: sign-up
|
||||
title: 'sign_up()'
|
||||
$ref: '@supabase/gotrue-js.GoTrueClient.signUp'
|
||||
notes: |
|
||||
- By default, the user needs to verify their email address before logging in. To turn this off, disable **Confirm email** in [your project](https://app.supabase.com/project/_/auth/providers).
|
||||
- **Confirm email** determines if users need to confirm their email address after signing up.
|
||||
@@ -88,7 +86,6 @@ functions:
|
||||
```
|
||||
- id: sign-out
|
||||
title: 'sign_out()'
|
||||
$ref: '@supabase/gotrue-js.GoTrueClient.signOut'
|
||||
notes: |
|
||||
- In order to use the `signOut()` method, the user needs to be signed in first.
|
||||
examples:
|
||||
@@ -100,7 +97,6 @@ functions:
|
||||
```
|
||||
- id: verify-otp
|
||||
title: 'verify_otp()'
|
||||
$ref: '@supabase/gotrue-js.GoTrueClient.verifyOtp'
|
||||
notes: |
|
||||
- The `verifyOtp` method takes in different verification types. If a phone number is used, the type can either be `sms` or `phone_change`. If an email address is used, the type can be one of the following: `signup`, `magiclink`, `recovery`, `invite` or `email_change`.
|
||||
- The verification type used should be determined based on the corresponding auth method called before `verifyOtp` to sign up / sign-in a user.
|
||||
@@ -114,7 +110,6 @@ functions:
|
||||
|
||||
- id: get-session
|
||||
title: 'get_session_from_url'
|
||||
$ref: '@supabase/gotrue-js.GoTrueClient.getSession'
|
||||
examples:
|
||||
- id: get-session
|
||||
name: Get the session data from URL
|
||||
@@ -125,7 +120,6 @@ functions:
|
||||
|
||||
- id: set-session
|
||||
title: 'set_session()'
|
||||
$ref: '@supabase/gotrue-js.GoTrueClient.setSession'
|
||||
notes: |
|
||||
- `setSession()` takes in a refresh token and uses it to get a new session.
|
||||
- The refresh token can only be used once to obtain a new session.
|
||||
@@ -142,7 +136,6 @@ functions:
|
||||
```
|
||||
- id: refresh-session
|
||||
title: 'refresh_ession()'
|
||||
$ref: '@supabase/gotrue-js.GoTrueClient.refreshSession'
|
||||
notes: |
|
||||
- This method will refresh the session whether the current one is expired or not.
|
||||
- Both examples destructure `user` and `session` from `data`. This is not required; so `const { data, error } =` is also valid.
|
||||
@@ -155,7 +148,6 @@ functions:
|
||||
```
|
||||
- id: select
|
||||
title: 'Fetch data: select()'
|
||||
$ref: '@supabase/postgrest-js.PostgrestQueryBuilder.select'
|
||||
notes: |
|
||||
- By default, Supabase projects return a maximum of 1,000 rows. This setting can be changed in your project's [API settings](https://app.supabase.com/project/_/settings/api). It's recommended that you keep it low to limit the payload size of accidental or malicious requests.
|
||||
- `apikey` is a reserved keyword if you're using the [Supabase Platform](/docs/guides/platform) and [should be avoided as a column name](https://github.com/supabase/supabase/issues/5465).
|
||||
@@ -181,7 +173,6 @@ functions:
|
||||
title: 'invoke()'
|
||||
description: |
|
||||
Invoke a Supabase Function.
|
||||
$ref: '@supabase/functions-js.F'
|
||||
notes: |
|
||||
- Requires an Authorization header.
|
||||
- When you pass in a body to your function, we automatically attach the Content-Type header for `Blob`, `ArrayBuffer`, `File`, `FormData` and `String`. If it doesn't match any of these types we assume the payload is `json`, serialise it and attach the `Content-Type` header as `application/json`. You can override this behaviour by passing in a `Content-Type` header of your own.
|
||||
@@ -204,7 +195,6 @@ functions:
|
||||
|
||||
- id: list-buckets
|
||||
title: 'list_buckets()'
|
||||
$ref: '@supabase/storage-js.packages/StorageBucketApi.default.listBuckets'
|
||||
notes: |
|
||||
- RLS policy permissions required:
|
||||
- `buckets` table permissions: `select`
|
||||
@@ -220,7 +210,6 @@ functions:
|
||||
|
||||
- id: get-bucket
|
||||
title: 'get_bucket()'
|
||||
$ref: '@supabase/storage-js.packages/StorageBucketApi.default.getBucket'
|
||||
notes: |
|
||||
- RLS policy permissions required:
|
||||
- `buckets` table permissions: `select`
|
||||
@@ -236,7 +225,6 @@ functions:
|
||||
|
||||
- id: create-bucket
|
||||
title: 'create_bucket()'
|
||||
$ref: '@supabase/storage-js.packages/StorageBucketApi.default.createBucket'
|
||||
notes: |
|
||||
- RLS policy permissions required:
|
||||
- `buckets` table permissions: `insert`
|
||||
@@ -252,7 +240,6 @@ functions:
|
||||
|
||||
- id: empty-bucket
|
||||
title: 'empty_bucket()'
|
||||
$ref: '@supabase/storage-js.packages/StorageBucketApi.default.emptyBucket'
|
||||
notes: |
|
||||
- RLS policy permissions required:
|
||||
- `buckets` table permissions: `select`
|
||||
@@ -267,7 +254,6 @@ functions:
|
||||
```
|
||||
- id: delete-bucket
|
||||
title: 'delete_bucket()'
|
||||
$ref: '@supabase/storage-js.packages/StorageBucketApi.default.deleteBucket'
|
||||
notes: |
|
||||
- RLS policy permissions required:
|
||||
- `buckets` table permissions: `select` and `delete`
|
||||
@@ -283,7 +269,6 @@ functions:
|
||||
|
||||
- id: from-upload
|
||||
title: 'from_.upload()'
|
||||
$ref: '@supabase/storage-js.packages/StorageFileApi.default.upload'
|
||||
notes: |
|
||||
- RLS policy permissions required:
|
||||
- `buckets` table permissions: none
|
||||
@@ -299,7 +284,6 @@ functions:
|
||||
```
|
||||
- id: from-move
|
||||
title: 'from_.move()'
|
||||
$ref: '@supabase/storage-js.packages/StorageFileApi.default.move'
|
||||
notes: |
|
||||
- RLS policy permissions required:
|
||||
- `buckets` table permissions: none
|
||||
@@ -315,7 +299,6 @@ functions:
|
||||
|
||||
- id: from-create-signed-url
|
||||
title: 'from_.create_signed_url()'
|
||||
$ref: '@supabase/storage-js.packages/StorageFileApi.default.createSignedUrl'
|
||||
notes: |
|
||||
- RLS policy permissions required:
|
||||
- `buckets` table permissions: none
|
||||
@@ -331,7 +314,6 @@ functions:
|
||||
|
||||
- id: from-get-public-url
|
||||
title: 'from_.get_public_url()'
|
||||
$ref: '@supabase/storage-js.packages/StorageFileApi.default.getPublicUrl'
|
||||
notes: |
|
||||
- The bucket needs to be set to public, either via [updateBucket()](/docs/reference/javascript/storage-updatebucket) or by going to Storage on [app.supabase.com](https://app.supabase.com), clicking the overflow menu on a bucket and choosing "Make public"
|
||||
- RLS policy permissions required:
|
||||
@@ -348,7 +330,6 @@ functions:
|
||||
|
||||
- id: from-download
|
||||
title: 'from_.download()'
|
||||
$ref: '@supabase/storage-js.packages/StorageFileApi.default.download'
|
||||
notes: |
|
||||
- RLS policy permissions required:
|
||||
- `buckets` table permissions: none
|
||||
@@ -366,7 +347,6 @@ functions:
|
||||
|
||||
- id: from-remove
|
||||
title: 'from_.remove()'
|
||||
$ref: '@supabase/storage-js.packages/StorageFileApi.default.remove'
|
||||
notes: |
|
||||
- RLS policy permissions required:
|
||||
- `buckets` table permissions: none
|
||||
@@ -381,7 +361,6 @@ functions:
|
||||
```
|
||||
- id: from-list
|
||||
title: 'from_.list()'
|
||||
$ref: '@supabase/storage-js.packages/StorageFileApi.default.list'
|
||||
notes: |
|
||||
- RLS policy permissions required:
|
||||
- `buckets` table permissions: none
|
||||
@@ -396,6 +375,5 @@ functions:
|
||||
```
|
||||
- id: subscribe
|
||||
title: on().subscribe()
|
||||
$ref: '@supabase/realtime-js.RealtimeChannel.on'
|
||||
notes: |
|
||||
- We are implementing this feature at the moment. If you have queries feel free to open an issue on the [realtime-py](https://github.com/supabase-community) repository.
|
||||
@@ -1,7 +0,0 @@
|
||||
node_modules/
|
||||
.vercel
|
||||
.next
|
||||
.env.local
|
||||
.env.production
|
||||
.env.dev
|
||||
.env.*
|
||||
+1
-3
@@ -6,13 +6,11 @@ DEFAULT_PROJECT_NAME=Default Project
|
||||
|
||||
SUPABASE_URL=http://localhost:8000
|
||||
SUPABASE_PUBLIC_URL=http://localhost:8000
|
||||
NEXT_PUBLIC_SUPABASE_URL=$SUPABASE_URL
|
||||
NEXT_PUBLIC_SUPABASE_PUBLIC_URL=$SUPABASE_PUBLIC_URL
|
||||
SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE
|
||||
SUPABASE_SERVICE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q
|
||||
|
||||
# TODO: evaluate these variables at runtime instead
|
||||
# https://nextjs.org/docs/basic-features/environment-variables#loading-environment-variables
|
||||
NEXT_PUBLIC_SITE_URL=http://localhost:3000
|
||||
NEXT_PUBLIC_GOTRUE_URL=$NEXT_PUBLIC_SUPABASE_PUBLIC_URL/auth/v1
|
||||
NEXT_PUBLIC_GOTRUE_URL=$SUPABASE_PUBLIC_URL/auth/v1
|
||||
NEXT_PUBLIC_HCAPTCHA_SITE_KEY=10000000-ffff-ffff-ffff-000000000001
|
||||
+40
-21
@@ -1,9 +1,11 @@
|
||||
# To be run in the root of the turbo monorepo
|
||||
# NOTE: It's highly raccomended to use the new builder, Buildkit. https://docs.docker.com/build/buildkit/
|
||||
## USAGE:
|
||||
# Build: docker build --target production -t supabase/studio:latest .
|
||||
# Build: docker buildx build --target production -t supabase/studio:latest .
|
||||
# Run: docker run -p 3000:3000 supabase/studio
|
||||
# Deploy: docker push supabase/studio:latest
|
||||
# Clean build:
|
||||
# docker build --target production --no-cache -t supabase/studio:latest .
|
||||
# docker buildx build --target production --no-cache -t supabase/studio:latest .
|
||||
# docker builder prune
|
||||
|
||||
FROM node:16-slim as base
|
||||
@@ -11,36 +13,53 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
python3 \
|
||||
build-essential \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
RUN npm i -g turbo
|
||||
|
||||
FROM base as builder
|
||||
WORKDIR /usr/src/app
|
||||
COPY package*.json .
|
||||
COPY turbo.json .
|
||||
COPY packages packages
|
||||
COPY studio studio
|
||||
# the following command is still unsupported for npm // https://github.com/vercel/turborepo/issues/1830
|
||||
# RUN npm i -g turbo
|
||||
# RUN turbo prune --scope=studio --docker
|
||||
# RUN npm clean-install && npm cache clean --force
|
||||
# Calculate if dependencies are outdated
|
||||
FROM base as turbo
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
# Prune unneeded dependencies with turbo (from apps/ for example)
|
||||
RUN turbo prune --scope=studio --docker
|
||||
|
||||
# Install dev dependencies (only if needed)
|
||||
FROM base as deps
|
||||
WORKDIR /app
|
||||
COPY --from=turbo /app/out/json/ .
|
||||
COPY --from=turbo /app/out/package-lock.json ./
|
||||
RUN npm clean-install && npm cache clean --force
|
||||
|
||||
FROM deps as deps-prod
|
||||
RUN npm prune --production
|
||||
# Remove packages dependencies (not needed in production)
|
||||
RUN rm -rf /app/packages
|
||||
|
||||
# builder contains dependencies and source code not compiled
|
||||
FROM deps as builder
|
||||
WORKDIR /app
|
||||
COPY --from=turbo /app/out/full ./
|
||||
ENTRYPOINT ["docker-entrypoint.sh"]
|
||||
|
||||
FROM builder as dev
|
||||
COPY --from=builder /usr/src/app ./
|
||||
RUN npm clean-install && npm cache clean --force
|
||||
EXPOSE 8082
|
||||
CMD ["npx", "turbo", "run", "dev", "--filter=studio"]
|
||||
|
||||
# Compile NextJS
|
||||
FROM builder as productionprep
|
||||
WORKDIR /app
|
||||
COPY --from=builder /usr/src/app ./
|
||||
RUN npm clean-install \
|
||||
&& npm cache clean --force \
|
||||
&& npx turbo run build --scope=studio --include-dependencies --no-deps \
|
||||
&& rm -rf ./studio/.next/cache/webpack \
|
||||
&& npm prune --production
|
||||
RUN npx turbo run build --scope=studio --include-dependencies --no-deps \
|
||||
&& rm -rf ./studio/.next/cache/webpack
|
||||
|
||||
# Copy only compiled code and dependencies
|
||||
FROM node:16-slim as production
|
||||
COPY --from=productionprep /app /app
|
||||
# Copy package(s).json and package-lock.json
|
||||
COPY --from=turbo /app/out/json /app
|
||||
# Copy all dependencies for production
|
||||
COPY --from=deps-prod /app /app
|
||||
# Copy necessary files
|
||||
COPY --from=turbo /app/out/full/studio /app/studio
|
||||
COPY --from=productionprep /app/studio/.next /app/studio/.next
|
||||
WORKDIR /app/studio
|
||||
EXPOSE 3000
|
||||
HEALTHCHECK --interval=5s --timeout=5s --retries=3 CMD node -e "require('http').get('http://localhost:3000/api/profile', (r) => {if (r.statusCode !== 200) throw new Error(r.statusCode)})"
|
||||
CMD ["npm", "run", "start"]
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Listbox } from '@supabase/ui'
|
||||
import { Select } from 'ui'
|
||||
import { EditorProps } from '@supabase/react-data-grid'
|
||||
|
||||
import { useTrackedState } from 'components/grid/store'
|
||||
|
||||
interface SelectEditorProps<TRow, TSummaryRow = unknown> extends EditorProps<TRow, TSummaryRow> {
|
||||
options: { label: string; value: string }[]
|
||||
options: { label: string; _value: string }[]
|
||||
}
|
||||
|
||||
export function SelectEditor<TRow, TSummaryRow = unknown>({
|
||||
@@ -19,11 +19,12 @@ export function SelectEditor<TRow, TSummaryRow = unknown>({
|
||||
|
||||
const value = row[column.key as keyof TRow] as unknown as string
|
||||
|
||||
function onChange(value: string) {
|
||||
if (!value || value == '') {
|
||||
function onChange(event: any) {
|
||||
debugger
|
||||
if (!event.target.value || event.target.value == '') {
|
||||
onRowChange({ ...row, [column.key]: null }, true)
|
||||
} else {
|
||||
onRowChange({ ...row, [column.key]: value }, true)
|
||||
onRowChange({ ...row, [column.key]: event.target.value }, true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +33,7 @@ export function SelectEditor<TRow, TSummaryRow = unknown>({
|
||||
}
|
||||
|
||||
return (
|
||||
<Listbox
|
||||
<Select
|
||||
autoFocus
|
||||
id="select-editor"
|
||||
name="select-editor"
|
||||
@@ -44,14 +45,12 @@ export function SelectEditor<TRow, TSummaryRow = unknown>({
|
||||
onChange={onChange}
|
||||
onBlur={onBlur}
|
||||
>
|
||||
<Listbox.Option id="NULL" label="NULL" value="">
|
||||
NULL
|
||||
</Listbox.Option>
|
||||
{options.map(({ label, value }) => (
|
||||
<Listbox.Option key={value} label={label} value={value}>
|
||||
<Select.Option value="">NULL</Select.Option>
|
||||
{options.map(({ label, _value }) => (
|
||||
<Select.Option key={_value} value={_value} selected={_value === value}>
|
||||
{label}
|
||||
</Listbox.Option>
|
||||
</Select.Option>
|
||||
))}
|
||||
</Listbox>
|
||||
</Select>
|
||||
)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Tabs } from 'ui'
|
||||
import { Button, IconExternalLink, Tabs } from 'ui'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
|
||||
import { TEMPLATES_SCHEMAS } from 'stores/authConfig/schema'
|
||||
@@ -8,10 +8,20 @@ import TemplateEditor from './TemplateEditor'
|
||||
const EmailTemplates = observer(() => {
|
||||
return (
|
||||
<div>
|
||||
<FormHeader
|
||||
title="Email Templates"
|
||||
description="Customize the emails that will be sent out to your users."
|
||||
/>
|
||||
<div className='flex justify-between items-center'>
|
||||
<FormHeader
|
||||
title="Email Templates"
|
||||
description="Customize the emails that will be sent out to your users."
|
||||
/>
|
||||
<Button type="link" icon={<IconExternalLink size={14} strokeWidth={1.5} />}>
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://supabase.com/docs/guides/auth/auth-email-templates"
|
||||
>
|
||||
Email Templates Documentation
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
<FormPanel>
|
||||
<Tabs
|
||||
scrollable
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useRouter } from 'next/router'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { useParams, useStore } from 'hooks'
|
||||
import { PolicyEditorModal, PolicyTableRow } from 'components/interfaces/Auth/Policies'
|
||||
import { PostgresRole, PostgresTable } from '@supabase/postgres-meta'
|
||||
import type { PostgresRole, PostgresTable } from '@supabase/postgres-meta'
|
||||
|
||||
import ConfirmModal from 'components/ui/Dialogs/ConfirmDialog'
|
||||
import NoSearchResults from 'components/to-be-cleaned/NoSearchResults'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PostgresPolicy } from '@supabase/postgres-meta'
|
||||
import type { PostgresPolicy } from '@supabase/postgres-meta'
|
||||
import { has, isEmpty, isEqual } from 'lodash'
|
||||
import {
|
||||
PolicyFormField,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { FC } from 'react'
|
||||
import { PostgresRole } from '@supabase/postgres-meta'
|
||||
import type { PostgresRole } from '@supabase/postgres-meta'
|
||||
|
||||
import MultiSelect from 'components/ui/MultiSelect'
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import PolicyDefinition from './PolicyDefinition'
|
||||
import PolicyAllowedOperation from './PolicyAllowedOperation'
|
||||
import PolicyRoles from './PolicyRoles'
|
||||
import PolicyEditorFooter from './PolicyEditorFooter'
|
||||
import { PostgresRole } from '@supabase/postgres-meta'
|
||||
import type { PostgresRole } from '@supabase/postgres-meta'
|
||||
|
||||
// Exposed for StoragePoliciesEditor.js
|
||||
export { PolicyName, PolicyRoles }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Modal } from 'ui'
|
||||
import { FC, useState, useEffect } from 'react'
|
||||
import { isEmpty } from 'lodash'
|
||||
import { PostgresRole } from '@supabase/postgres-meta'
|
||||
import type { PostgresRole } from '@supabase/postgres-meta'
|
||||
|
||||
import { useStore } from 'hooks'
|
||||
import PolicyEditorModalTitle from './PolicyEditorModalTitle'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { FC } from 'react'
|
||||
import * as Tooltip from '@radix-ui/react-tooltip'
|
||||
import { Button, Dropdown, IconEdit, IconTrash, IconMoreVertical } from 'ui'
|
||||
import { PostgresPolicy } from '@supabase/postgres-meta'
|
||||
import type { PostgresPolicy } from '@supabase/postgres-meta'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
|
||||
import { checkPermissions } from 'hooks'
|
||||
|
||||
@@ -3,7 +3,7 @@ import { FC } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import * as Tooltip from '@radix-ui/react-tooltip'
|
||||
import { Badge, Button } from 'ui'
|
||||
import { PostgresTable } from '@supabase/postgres-meta'
|
||||
import type { PostgresTable } from '@supabase/postgres-meta'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
import { checkPermissions } from 'hooks'
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { FC } from 'react'
|
||||
import { PostgresTable, PostgresPolicy } from '@supabase/postgres-meta'
|
||||
import type { PostgresTable, PostgresPolicy } from '@supabase/postgres-meta'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { useStore } from 'hooks'
|
||||
|
||||
|
||||
@@ -37,6 +37,8 @@ const ExitSurvey: FC<Props> = ({ freeTier, subscription, onSelectBack }) => {
|
||||
// Tracking submitting state separately outside of form component cause of
|
||||
// the additional dynamic confirmation modal that we're doing
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
const [isSubmittingDowngradeModal, setIsSubmittingDowngradeModal] = useState(false)
|
||||
|
||||
const [isSuccessful, setIsSuccessful] = useState(false)
|
||||
const [showConfirmModal, setShowConfirmModal] = useState(false)
|
||||
const [subscriptionPreview, setSubscriptionPreview] = useState<SubscriptionPreview>()
|
||||
@@ -123,6 +125,7 @@ const ExitSurvey: FC<Props> = ({ freeTier, subscription, onSelectBack }) => {
|
||||
|
||||
try {
|
||||
setIsSubmitting(true)
|
||||
setIsSubmittingDowngradeModal(true)
|
||||
|
||||
// Trigger subscription downgrade
|
||||
const tier = freeTier.prices[0].id
|
||||
@@ -175,6 +178,7 @@ const ExitSurvey: FC<Props> = ({ freeTier, subscription, onSelectBack }) => {
|
||||
})
|
||||
} finally {
|
||||
setIsSubmitting(false)
|
||||
setIsSubmittingDowngradeModal(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,7 +297,10 @@ const ExitSurvey: FC<Props> = ({ freeTier, subscription, onSelectBack }) => {
|
||||
visible={showConfirmModal}
|
||||
size="large"
|
||||
header="Downgrading project to free"
|
||||
onCancel={() => setShowConfirmModal(false)}
|
||||
onCancel={() => {
|
||||
setShowConfirmModal(false)
|
||||
setIsSubmitting(false)
|
||||
}}
|
||||
>
|
||||
<div className="space-y-4 py-4">
|
||||
<Modal.Content>
|
||||
@@ -314,14 +321,21 @@ const ExitSurvey: FC<Props> = ({ freeTier, subscription, onSelectBack }) => {
|
||||
<Modal.Separator />
|
||||
<Modal.Content>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button block type="default" onClick={() => setShowConfirmModal(false)}>
|
||||
<Button
|
||||
block
|
||||
type="default"
|
||||
onClick={() => {
|
||||
setShowConfirmModal(false)
|
||||
setIsSubmitting(false)
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
block
|
||||
htmlType="submit"
|
||||
loading={isSubmitting}
|
||||
disabled={isSubmitting}
|
||||
loading={isSubmittingDowngradeModal}
|
||||
disabled={isSubmittingDowngradeModal}
|
||||
onClick={() => downgradeProject()}
|
||||
>
|
||||
Confirm
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { FC, useEffect, useState } from 'react'
|
||||
import { Button, Input, Form, Modal, Listbox, IconPlus, IconDatabase } from 'ui'
|
||||
import { PostgresExtension, PostgresSchema } from '@supabase/postgres-meta'
|
||||
import type { PostgresExtension, PostgresSchema } from '@supabase/postgres-meta'
|
||||
|
||||
import { useStore } from 'hooks'
|
||||
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { FC, useState } from 'react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { Badge, Toggle } from 'ui'
|
||||
import { PostgresPublication, PostgresTable } from '@supabase/postgres-meta'
|
||||
import type { PostgresPublication, PostgresTable } from '@supabase/postgres-meta'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
|
||||
import { checkPermissions, useStore } from 'hooks'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { FC, useState } from 'react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { Button, Input, IconChevronLeft, IconSearch, IconAlertCircle } from 'ui'
|
||||
import { PostgresPublication } from '@supabase/postgres-meta'
|
||||
import type { PostgresPublication } from '@supabase/postgres-meta'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
|
||||
import { checkPermissions, useStore } from 'hooks'
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Input, Button, IconSearch, IconPlus, IconChevronLeft, IconEdit3, IconTr
|
||||
import { useStore } from 'hooks'
|
||||
import Table from 'components/to-be-cleaned/Table'
|
||||
import NoSearchResults from 'components/to-be-cleaned/NoSearchResults'
|
||||
import { PostgresTable } from '@supabase/postgres-meta'
|
||||
import type { PostgresTable } from '@supabase/postgres-meta'
|
||||
|
||||
interface Props {
|
||||
selectedTable: PostgresTable
|
||||
@@ -25,11 +25,11 @@ const ColumnList: FC<Props> = ({
|
||||
const { meta } = useStore()
|
||||
const [filterString, setFilterString] = useState<string>('')
|
||||
const columns =
|
||||
filterString.length === 0
|
||||
(filterString.length === 0
|
||||
? selectedTable.columns
|
||||
: selectedTable.columns.filter((column: any) => column.name.includes(filterString))
|
||||
: selectedTable.columns?.filter((column: any) => column.name.includes(filterString))) ?? []
|
||||
|
||||
const isLocked = meta.excludedSchemas.includes(selectedTable?.schema ?? '')
|
||||
const isLocked = meta.excludedSchemas.includes(selectedTable.schema ?? '')
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -18,7 +18,7 @@ import { partition } from 'lodash'
|
||||
import { useStore } from 'hooks'
|
||||
import Table from 'components/to-be-cleaned/Table'
|
||||
import NoSearchResults from 'components/to-be-cleaned/NoSearchResults'
|
||||
import { PostgresTable, PostgresSchema } from '@supabase/postgres-meta'
|
||||
import type { PostgresTable, PostgresSchema } from '@supabase/postgres-meta'
|
||||
|
||||
interface Props {
|
||||
selectedSchema: string
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useRouter } from 'next/router'
|
||||
import { Button, IconTerminal, IconMaximize2, IconMinimize2, IconBookOpen, IconCode } from 'ui'
|
||||
import { useProjectSettings } from 'hooks'
|
||||
import { useProjectSettingsQuery } from 'data/config/project-settings-query'
|
||||
import { useAccessTokens } from 'hooks/queries/useAccessTokens'
|
||||
import { Commands } from './Functions.types'
|
||||
import CommandRender from 'components/interfaces/Functions/CommandRender'
|
||||
@@ -18,17 +18,17 @@ const TerminalInstructions: FC<Props> = ({ closable = false }) => {
|
||||
const { ui } = useStore()
|
||||
|
||||
const { tokens } = useAccessTokens()
|
||||
const { services } = useProjectSettings(ref as string | undefined)
|
||||
const { data: settings } = useProjectSettingsQuery({ projectRef: ref as string })
|
||||
|
||||
const [showInstructions, setShowInstructions] = useState(!closable)
|
||||
|
||||
// Get the API service
|
||||
const API_SERVICE_ID = 1
|
||||
const apiService = (services ?? []).find((x: any) => x.app.id == API_SERVICE_ID)
|
||||
const apiKeys = apiService?.service_api_keys ?? []
|
||||
const anonKey = apiKeys.find((x: any) => x.name === 'anon key')?.api_key
|
||||
const apiService = settings?.autoApiService
|
||||
const anonKey = apiService?.service_api_keys.find((x: any) => x.name === 'anon key')
|
||||
? apiService.defaultApiKey
|
||||
: undefined
|
||||
|
||||
const endpoint = apiService?.app_config.endpoint ?? ''
|
||||
const endpoint = apiService?.endpoint ?? ''
|
||||
const endpointSections = endpoint.split('.')
|
||||
const functionsEndpoint = [
|
||||
...endpointSections.slice(0, 1),
|
||||
|
||||
@@ -5,9 +5,8 @@ import { Input, IconLoader, IconAlertCircle } from 'ui'
|
||||
import { JwtSecretUpdateStatus } from '@supabase/shared-types/out/events'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
|
||||
import { checkPermissions, useJwtSecretUpdateStatus, useProjectSettings } from 'hooks'
|
||||
import { DEFAULT_PROJECT_API_SERVICE_ID, IS_PLATFORM } from 'lib/constants'
|
||||
import { PROJECT_ENDPOINT_PROTOCOL } from 'pages/api/constants'
|
||||
import { useProjectSettingsQuery } from 'data/config/project-settings-query'
|
||||
import { checkPermissions, useJwtSecretUpdateStatus } from 'hooks'
|
||||
import Snippets from 'components/to-be-cleaned/Docs/Snippets'
|
||||
import Panel from 'components/ui/Panel'
|
||||
import SimpleCodeBlock from 'components/to-be-cleaned/SimpleCodeBlock'
|
||||
@@ -23,10 +22,10 @@ const APIKeys = () => {
|
||||
const [selectedLanguage, setSelectedLanguage] = useState(availableLanguages[0])
|
||||
|
||||
const {
|
||||
services,
|
||||
data: settings,
|
||||
isError: isProjectSettingsError,
|
||||
isLoading: isProjectSettingsLoading,
|
||||
} = useProjectSettings(ref as string | undefined)
|
||||
} = useProjectSettingsQuery({ projectRef: ref as string })
|
||||
|
||||
const {
|
||||
jwtSecretUpdateStatus,
|
||||
@@ -37,8 +36,7 @@ const APIKeys = () => {
|
||||
const canReadAPIKeys = checkPermissions(PermissionAction.READ, 'service_api_keys')
|
||||
|
||||
// Get the API service
|
||||
const apiService = (services ?? []).find((x: any) => x.app.id == DEFAULT_PROJECT_API_SERVICE_ID)
|
||||
const apiConfig = apiService?.app_config ?? {}
|
||||
const apiService = settings?.autoApiService
|
||||
const apiKeys = apiService?.service_api_keys ?? []
|
||||
|
||||
// API keys should not be empty. However it can be populated with a delay on project creation
|
||||
@@ -46,7 +44,7 @@ const APIKeys = () => {
|
||||
const isNotUpdatingJwtSecret =
|
||||
jwtSecretUpdateStatus === undefined || jwtSecretUpdateStatus === JwtSecretUpdateStatus.Updated
|
||||
|
||||
const apiUrl = `${IS_PLATFORM ? 'https' : PROJECT_ENDPOINT_PROTOCOL}://${apiConfig.endpoint}`
|
||||
const apiUrl = `${apiService?.protocol ?? 'https'}://${apiService?.endpoint ?? '-'}`
|
||||
const anonKey = apiKeys.find((key: any) => key.tags === 'anon')
|
||||
|
||||
const clientInitSnippet: any = Snippets.init(apiUrl)
|
||||
@@ -111,7 +109,7 @@ const APIKeys = () => {
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm">API Key</p>
|
||||
<div className="flex items-center space-x-1 -ml-1">
|
||||
{anonKey.tags?.split(',').map((x: any, i: number) => (
|
||||
{anonKey?.tags?.split(',').map((x: any, i: number) => (
|
||||
<code key={`${x}${i}`} className="text-xs">
|
||||
{x}
|
||||
</code>
|
||||
@@ -121,7 +119,7 @@ const APIKeys = () => {
|
||||
</div>
|
||||
}
|
||||
copy={canReadAPIKeys && isNotUpdatingJwtSecret}
|
||||
reveal={anonKey.tags !== 'anon' && canReadAPIKeys && isNotUpdatingJwtSecret}
|
||||
reveal={anonKey?.tags !== 'anon' && canReadAPIKeys && isNotUpdatingJwtSecret}
|
||||
value={
|
||||
!canReadAPIKeys
|
||||
? 'You need additional permissions to view API keys'
|
||||
@@ -129,7 +127,7 @@ const APIKeys = () => {
|
||||
? 'JWT secret update failed, new API key may have issues'
|
||||
: jwtSecretUpdateStatus === JwtSecretUpdateStatus.Updating
|
||||
? 'Updating JWT secret...'
|
||||
: anonKey.api_key
|
||||
: apiService?.defaultApiKey
|
||||
}
|
||||
onChange={() => {}}
|
||||
descriptionText={
|
||||
|
||||
@@ -613,10 +613,8 @@ values
|
||||
sql: `
|
||||
--
|
||||
-- For use with:
|
||||
-- https://github.com/supabase/supabase/tree/master/examples/todo-list/nextjs-todo-list or
|
||||
-- https://github.com/supabase/supabase/tree/master/examples/todo-list/react-todo-list or
|
||||
-- https://github.com/supabase/supabase/tree/master/examples/todo-list/sveltejs-todo-list or
|
||||
-- https://github.com/supabase/supabase/tree/master/examples/todo-list/vue3-ts-todo-list
|
||||
-- https://github.com/supabase/examples-archive/tree/main/supabase-js-v1/todo-list
|
||||
--
|
||||
|
||||
create table todos (
|
||||
|
||||
@@ -3,18 +3,15 @@ import { useRouter } from 'next/router'
|
||||
import { JwtSecretUpdateError, JwtSecretUpdateStatus } from '@supabase/shared-types/out/events'
|
||||
import { IconAlertCircle, Input } from 'ui'
|
||||
|
||||
import {
|
||||
useStore,
|
||||
useProjectSettings,
|
||||
useProjectPostgrestConfig,
|
||||
useJwtSecretUpdateStatus,
|
||||
} from 'hooks'
|
||||
import { configKeys } from 'data/config/keys'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { useProjectSettingsQuery } from 'data/config/project-settings-query'
|
||||
import { useStore, useProjectPostgrestConfig, useJwtSecretUpdateStatus } from 'hooks'
|
||||
|
||||
import Panel from 'components/ui/Panel'
|
||||
import PostgrestConfig from './PostgrestConfig'
|
||||
import { DisplayApiSettings } from 'components/ui/ProjectSettings'
|
||||
import { JWT_SECRET_UPDATE_ERROR_MESSAGES } from './API.constants'
|
||||
import { PROJECT_ENDPOINT_PROTOCOL } from 'pages/api/constants'
|
||||
import { IS_PLATFORM } from 'lib/constants'
|
||||
import JWTSettings from './JWTSettings'
|
||||
|
||||
@@ -26,8 +23,9 @@ const ServiceList: FC<Props> = ({ projectRef }) => {
|
||||
const { ui } = useStore()
|
||||
const router = useRouter()
|
||||
const { ref } = router.query
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
const { services, isError, mutateSettings } = useProjectSettings(ref as string | undefined)
|
||||
const { data: settings, isError } = useProjectSettingsQuery({ projectRef: ref as string })
|
||||
const { mutateConfig } = useProjectPostgrestConfig(ref as string | undefined)
|
||||
const { jwtSecretUpdateError, jwtSecretUpdateStatus }: any = useJwtSecretUpdateStatus(ref)
|
||||
|
||||
@@ -41,7 +39,7 @@ const ServiceList: FC<Props> = ({ projectRef }) => {
|
||||
switch (jwtSecretUpdateStatus) {
|
||||
case Updated:
|
||||
mutateConfig()
|
||||
mutateSettings()
|
||||
queryClient.invalidateQueries({ queryKey: configKeys.settings(projectRef) })
|
||||
ui.setNotification({ category: 'success', message: 'Successfully updated JWT secret' })
|
||||
break
|
||||
case Failed:
|
||||
@@ -57,9 +55,8 @@ const ServiceList: FC<Props> = ({ projectRef }) => {
|
||||
}, [jwtSecretUpdateStatus])
|
||||
|
||||
// Get the API service
|
||||
const API_SERVICE_ID = 1
|
||||
const apiService = services ? services.find((x: any) => x.app.id == API_SERVICE_ID) : {}
|
||||
const apiConfig = apiService?.app_config
|
||||
const apiService = settings?.autoApiService
|
||||
const apiUrl = `${apiService?.protocol ?? 'https'}://${apiService?.endpoint ?? '-'}`
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -79,9 +76,7 @@ const ServiceList: FC<Props> = ({ projectRef }) => {
|
||||
readOnly
|
||||
disabled
|
||||
className="input-mono"
|
||||
value={`${IS_PLATFORM ? 'https' : PROJECT_ENDPOINT_PROTOCOL}://${
|
||||
apiConfig?.endpoint ?? '-'
|
||||
}`}
|
||||
value={apiUrl}
|
||||
descriptionText="A RESTful endpoint for querying and managing your database."
|
||||
layout="horizontal"
|
||||
/>
|
||||
|
||||
+1
-1
@@ -134,7 +134,7 @@ const CustomDomainVerify = ({ projectRef, customDomain, settings }: CustomDomain
|
||||
<DNSRecord
|
||||
type="CNAME"
|
||||
name={customDomain.hostname}
|
||||
value={settings?.autoApiService.app_config.endpoint ?? 'Loading...'}
|
||||
value={settings?.autoApiService.endpoint ?? 'Loading...'}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
Loaded 100 of 157 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user