mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
Merge branch 'master' of github.com:supabase/supabase into dev/add-c#-documentation
This commit is contained in:
132 files changed
+2757
-1850
No files matched your search
+19
-7
@@ -1,8 +1,20 @@
|
||||
node_modules/
|
||||
.vercel
|
||||
.next
|
||||
.env.local
|
||||
.env.production
|
||||
.env.dev
|
||||
.env.*
|
||||
# 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
|
||||
.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();
|
||||
|
||||
```
|
||||
@@ -107,7 +107,7 @@ const home = [
|
||||
level: 'reference_python',
|
||||
},
|
||||
{
|
||||
label: 'Tools Reference',
|
||||
label: 'Tools',
|
||||
},
|
||||
{
|
||||
label: 'Management API',
|
||||
@@ -118,7 +118,7 @@ const home = [
|
||||
{
|
||||
label: 'Supabase CLI',
|
||||
icon: '/img/icons/menu/reference-cli',
|
||||
href: '/reference/cli/introduction',
|
||||
href: '/guides/cli',
|
||||
level: 'reference_javascript',
|
||||
},
|
||||
],
|
||||
|
||||
@@ -55,6 +55,8 @@ export const gettingstarted = {
|
||||
{ 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: [] },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -492,6 +494,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',
|
||||
@@ -596,26 +617,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: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@@ -108,6 +108,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')
|
||||
@@ -132,6 +135,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
|
||||
@@ -171,6 +177,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
|
||||
@@ -199,6 +206,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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
@@ -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
|
||||
@@ -77,7 +77,7 @@ export const meta = {
|
||||
<StepHikeCompact.Step step={4}>
|
||||
<StepHikeCompact.Details title="Create the Supabase client">
|
||||
|
||||
Create a `/src/lib` directory of 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).
|
||||
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>
|
||||
|
||||
@@ -146,7 +146,7 @@ export const meta = {
|
||||
<StepHikeCompact.Step step={6}>
|
||||
<StepHikeCompact.Details title="Start the app">
|
||||
|
||||
Start the app, go to http://localhost:5173 in a browser, and open the browser console and you should see the list of countries.
|
||||
Start the app and go to http://localhost:5173 in a browser and you should see the list of countries.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -530,6 +530,10 @@ export class AccountComponent implements OnInit {
|
||||
}
|
||||
```
|
||||
|
||||
### Storage management
|
||||
|
||||
<StorageManagement />
|
||||
|
||||
## Next steps
|
||||
|
||||
At this stage you have a fully functional application!
|
||||
|
||||
@@ -500,6 +500,10 @@ Now you will need to run the prebuild command to get the application working on
|
||||
expo prebuild
|
||||
```
|
||||
|
||||
### Storage management
|
||||
|
||||
<StorageManagement />
|
||||
|
||||
## Next steps
|
||||
|
||||
At this stage you have a fully functional application!
|
||||
|
||||
@@ -793,6 +793,10 @@ class _AccountPageState extends State<AccountPage> {
|
||||
}
|
||||
```
|
||||
|
||||
### Storage management
|
||||
|
||||
<StorageManagement />
|
||||
|
||||
Congratulations, that is it! You have now built a fully functional user management app using Flutter and Supabase!
|
||||
|
||||
## See also
|
||||
|
||||
@@ -522,6 +522,10 @@ template: `
|
||||
`
|
||||
```
|
||||
|
||||
### Storage management
|
||||
|
||||
<StorageManagement />
|
||||
|
||||
## Next steps
|
||||
|
||||
At this stage you have a fully functional application!
|
||||
|
||||
@@ -495,6 +495,10 @@ return (
|
||||
<Avatar url={profile.avatar_url} onUpload={updateProfile}></Avatar>
|
||||
```
|
||||
|
||||
### Storage management
|
||||
|
||||
<StorageManagement />
|
||||
|
||||
## Next steps
|
||||
|
||||
At this stage you have a fully functional application!
|
||||
|
||||
@@ -555,6 +555,10 @@ export default defineComponent({
|
||||
</script>
|
||||
```
|
||||
|
||||
### Storage management
|
||||
|
||||
<StorageManagement />
|
||||
|
||||
## Next steps
|
||||
|
||||
At this stage you have a fully functional application!
|
||||
|
||||
@@ -718,6 +718,10 @@ return (
|
||||
)
|
||||
```
|
||||
|
||||
### Storage management
|
||||
|
||||
<StorageManagement />
|
||||
|
||||
## Next steps
|
||||
|
||||
At this stage you have a fully functional application!
|
||||
|
||||
@@ -420,6 +420,10 @@ And then we can add the widget to the Account page:
|
||||
|
||||
That is it! You should now be able to upload a profile photo to Supabase Storage.
|
||||
|
||||
### Storage management
|
||||
|
||||
<StorageManagement />
|
||||
|
||||
## Next steps
|
||||
|
||||
At this stage you have a fully functional application!
|
||||
|
||||
@@ -397,6 +397,10 @@ return (
|
||||
)
|
||||
```
|
||||
|
||||
### Storage management
|
||||
|
||||
<StorageManagement />
|
||||
|
||||
## Next steps
|
||||
|
||||
At this stage you have a fully functional application!
|
||||
|
||||
@@ -771,6 +771,10 @@ return (
|
||||
)
|
||||
```
|
||||
|
||||
### Storage management
|
||||
|
||||
<StorageManagement />
|
||||
|
||||
## Next steps
|
||||
|
||||
At this stage you have a fully functional application!
|
||||
|
||||
@@ -415,6 +415,10 @@ return (
|
||||
)
|
||||
```
|
||||
|
||||
### Storage management
|
||||
|
||||
<StorageManagement />
|
||||
|
||||
## Next steps
|
||||
|
||||
At this stage you have a fully functional application!
|
||||
|
||||
@@ -368,6 +368,10 @@ And then we can add the widget to the Account page:
|
||||
</form>
|
||||
```
|
||||
|
||||
### Storage management
|
||||
|
||||
<StorageManagement />
|
||||
|
||||
## Next steps
|
||||
|
||||
At this stage you have a fully functional application!
|
||||
|
||||
@@ -434,6 +434,10 @@ And then we can add the widget to the Account page:
|
||||
</form>
|
||||
```
|
||||
|
||||
### Storage management
|
||||
|
||||
<StorageManagement />
|
||||
|
||||
## Next steps
|
||||
|
||||
At this stage you have a fully functional application!
|
||||
|
||||
@@ -379,6 +379,10 @@ And then we can add the widget to the Account page:
|
||||
</template>
|
||||
```
|
||||
|
||||
### Storage management
|
||||
|
||||
<StorageManagement />
|
||||
|
||||
## Next steps
|
||||
|
||||
At this stage you have a fully functional application!
|
||||
|
||||
@@ -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
|
||||
@@ -61,7 +61,7 @@ export const meta = {
|
||||
<div className="hidden">
|
||||
<Button size="small">Get started</Button>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-3 md:gap-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-3 md:gap-6">
|
||||
<Link href={`/guides/getting-started/quickstarts/reactjs`} passHref>
|
||||
<a className="no-underline">
|
||||
<IconPanel title="ReactJS" icon="/docs/img/icons/react-icon" />
|
||||
@@ -69,18 +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>
|
||||
<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,7 +9,7 @@ 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)})" ]
|
||||
@@ -27,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}
|
||||
|
||||
@@ -211,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
|
||||
|
||||
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",
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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={
|
||||
|
||||
@@ -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...'}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
+2
-2
@@ -26,7 +26,7 @@ const CustomDomainsConfigureHostname = () => {
|
||||
const { data: settings } = useProjectSettingsQuery({ projectRef: ref })
|
||||
|
||||
const FORM_ID = 'custom-domains-form'
|
||||
const endpoint = settings?.autoApiService.app_config.endpoint ?? undefined
|
||||
const endpoint = settings?.autoApiService.endpoint
|
||||
const canConfigureCustomDomain = checkPermissions(PermissionAction.UPDATE, 'projects')
|
||||
|
||||
const verifyCNAME = async (domain: string): Promise<boolean> => {
|
||||
@@ -123,7 +123,7 @@ const CustomDomainsConfigureHostname = () => {
|
||||
'your custom domain'
|
||||
)}
|
||||
, resolving to{' '}
|
||||
{endpoint !== undefined ? (
|
||||
{endpoint ? (
|
||||
<code className="text-xs">{endpoint}</code>
|
||||
) : (
|
||||
"your project's API URL"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { FC } from 'react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { PostgresTable } from '@supabase/postgres-meta'
|
||||
import type { PostgresTable } from '@supabase/postgres-meta'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
import { checkPermissions, useStore } from 'hooks'
|
||||
import ProductEmptyState from 'components/to-be-cleaned/ProductEmptyState'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { FC } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { Button, IconAlertCircle, IconLock } from 'ui'
|
||||
import { PostgresPolicy, PostgresTable } from '@supabase/postgres-meta'
|
||||
import type { PostgresPolicy, PostgresTable } from '@supabase/postgres-meta'
|
||||
|
||||
import { useStore } from 'hooks'
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import React, { FC } from 'react'
|
||||
import { isNil } from 'lodash'
|
||||
import { Select } from 'ui'
|
||||
import { PostgresType } from '@supabase/postgres-meta'
|
||||
import type { PostgresType } from '@supabase/postgres-meta'
|
||||
|
||||
import InputWithSuggestions from './InputWithSuggestions'
|
||||
import { POSTGRES_DATA_TYPES } from '../SidePanelEditor.constants'
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ import { FC, useEffect, useState } from 'react'
|
||||
import { isUndefined, isEmpty } from 'lodash'
|
||||
import { Dictionary } from 'components/grid'
|
||||
import { Checkbox, SidePanel, Input, Button, IconExternalLink, Toggle } from 'ui'
|
||||
import {
|
||||
import type {
|
||||
PostgresColumn,
|
||||
PostgresExtension,
|
||||
PostgresRelationship,
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { find, isUndefined, isEqual, isNull } from 'lodash'
|
||||
import { Dictionary } from 'components/grid'
|
||||
import {
|
||||
import type {
|
||||
PostgresColumn,
|
||||
PostgresRelationship,
|
||||
PostgresTable,
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ import { Button, IconArrowRight } from 'ui'
|
||||
import { ColumnField } from '../SidePanelEditor.types'
|
||||
import InformationBox from 'components/ui/InformationBox'
|
||||
import { getForeignKeyUIState } from './ColumnEditor.utils'
|
||||
import { PostgresRelationship } from '@supabase/postgres-meta'
|
||||
import type { PostgresRelationship } from '@supabase/postgres-meta'
|
||||
|
||||
interface Props {
|
||||
column: ColumnField
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import React, { FC, ReactNode } from 'react'
|
||||
import { IconCalendar, IconType, IconHash, Listbox, IconToggleRight, Input } from 'ui'
|
||||
import { PostgresType } from '@supabase/postgres-meta'
|
||||
import type { PostgresType } from '@supabase/postgres-meta'
|
||||
import { POSTGRES_DATA_TYPES, POSTGRES_DATA_TYPE_OPTIONS } from '../SidePanelEditor.constants'
|
||||
import { PostgresDataTypeOption } from '../SidePanelEditor.types'
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { PostgresTable, PostgresColumn } from '@supabase/postgres-meta'
|
||||
import type { PostgresTable, PostgresColumn } from '@supabase/postgres-meta'
|
||||
|
||||
interface Props {
|
||||
table: PostgresTable
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@ import React, { FC, useEffect, useState } from 'react'
|
||||
import { get, find, isEmpty, sortBy } from 'lodash'
|
||||
import { Dictionary } from 'components/grid'
|
||||
import { SidePanel, Input, Listbox, IconHelpCircle } from 'ui'
|
||||
import { PostgresTable, PostgresColumn } from '@supabase/postgres-meta'
|
||||
import type { PostgresTable, PostgresColumn } from '@supabase/postgres-meta'
|
||||
|
||||
import ActionBar from '../ActionBar'
|
||||
import { ForeignKey } from './ForeignKeySelector.types'
|
||||
@@ -71,7 +71,7 @@ const ForeignKeySelector: FC<Props> = ({
|
||||
setSelectedForeignKey({
|
||||
schema: table.schema,
|
||||
table: table.name,
|
||||
column: table.columns.length > 0 ? table.columns[0].name : undefined,
|
||||
column: table.columns?.length ? table.columns[0].name : undefined,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import React, { FC, useEffect, useState } from 'react'
|
||||
import { SidePanel, IconLoader, IconXCircle } from 'ui'
|
||||
import { PostgresRelationship } from '@supabase/postgres-meta'
|
||||
import type { PostgresRelationship } from '@supabase/postgres-meta'
|
||||
|
||||
import ActionBar from '../ActionBar'
|
||||
import InputField from './InputField'
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ import { isUndefined, partition, isEmpty } from 'lodash'
|
||||
import { SidePanel } from 'ui'
|
||||
import { Dictionary } from 'components/grid'
|
||||
import { Query } from 'components/grid/query/Query'
|
||||
import { PostgresTable } from '@supabase/postgres-meta'
|
||||
import type { PostgresTable } from '@supabase/postgres-meta'
|
||||
|
||||
import { useStore } from 'hooks'
|
||||
import ActionBar from '../ActionBar'
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { PostgresRelationship } from '@supabase/postgres-meta'
|
||||
import type { PostgresRelationship } from '@supabase/postgres-meta'
|
||||
|
||||
export interface JsonEditValue {
|
||||
column: string
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import dayjs from 'dayjs'
|
||||
import { find, isUndefined, compact, isEqual, omitBy, isNull, isString } from 'lodash'
|
||||
import { Dictionary } from 'components/grid'
|
||||
import { PostgresTable } from '@supabase/postgres-meta'
|
||||
import type { PostgresTable } from '@supabase/postgres-meta'
|
||||
|
||||
import { uuidv4, minifyJSON, tryParseJson } from 'lib/helpers'
|
||||
import { RowField } from './RowEditor.types'
|
||||
@@ -15,7 +15,7 @@ export const generateRowFields = (
|
||||
// @ts-ignore
|
||||
const primaryKeyColumns = primary_keys.map((key) => key.name)
|
||||
|
||||
return table.columns.map((column) => {
|
||||
return table.columns!.map((column) => {
|
||||
const value = isUndefined(row)
|
||||
? ''
|
||||
: DATETIME_TYPES.includes(column.format)
|
||||
|
||||
@@ -2,7 +2,7 @@ import { FC, useState } from 'react'
|
||||
import { find, isEmpty, isUndefined } from 'lodash'
|
||||
import { Query, Dictionary } from 'components/grid'
|
||||
import { Modal } from 'ui'
|
||||
import { PostgresRelationship, PostgresTable, PostgresColumn } from '@supabase/postgres-meta'
|
||||
import type { PostgresRelationship, PostgresTable, PostgresColumn } from '@supabase/postgres-meta'
|
||||
|
||||
import { useStore } from 'hooks'
|
||||
import { RowEditor, ColumnEditor, TableEditor } from '.'
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { PostgresRelationship } from '@supabase/postgres-meta'
|
||||
import type { PostgresRelationship } from '@supabase/postgres-meta'
|
||||
|
||||
export interface CreateColumnPayload {
|
||||
tableId: number
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { FC } from 'react'
|
||||
import { isUndefined } from 'lodash'
|
||||
import { Checkbox, Input, IconX, IconMenu, Popover, IconLink, IconSettings, Button } from 'ui'
|
||||
import { PostgresType } from '@supabase/postgres-meta'
|
||||
import type { PostgresType } from '@supabase/postgres-meta'
|
||||
|
||||
import { ColumnField } from '../SidePanelEditor.types'
|
||||
import ColumnType from '../ColumnEditor/ColumnType'
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ import React, { FC, useState } from 'react'
|
||||
import { partition, isEmpty, isUndefined } from 'lodash'
|
||||
import { Alert, Button, IconEdit, IconHelpCircle, IconKey, IconTrash, IconExternalLink } from 'ui'
|
||||
|
||||
import {
|
||||
import type {
|
||||
PostgresTable,
|
||||
PostgresColumn,
|
||||
PostgresRelationship,
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { FC } from 'react'
|
||||
import { PostgresTable } from '@supabase/postgres-meta'
|
||||
import type { PostgresTable } from '@supabase/postgres-meta'
|
||||
|
||||
interface Props {
|
||||
schema: string
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import { FC, useEffect, useState } from 'react'
|
||||
import { isUndefined, isEmpty } from 'lodash'
|
||||
import { Badge, Checkbox, SidePanel, Input, Alert } from 'ui'
|
||||
import { PostgresTable, PostgresType } from '@supabase/postgres-meta'
|
||||
import type { PostgresTable, PostgresType } from '@supabase/postgres-meta'
|
||||
|
||||
import { useStore } from 'hooks'
|
||||
import ActionBar from '../ActionBar'
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { some } from 'lodash'
|
||||
import { PostgresColumn, PostgresTable } from '@supabase/postgres-meta'
|
||||
import type { PostgresColumn, PostgresTable } from '@supabase/postgres-meta'
|
||||
|
||||
import { ImportContent, TableField } from './TableEditor.types'
|
||||
import { DEFAULT_COLUMNS } from './TableEditor.constants'
|
||||
|
||||
@@ -2,7 +2,7 @@ import { FC, useRef, useEffect, useState } from 'react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { useRouter } from 'next/router'
|
||||
import { find, isUndefined } from 'lodash'
|
||||
import { PostgresColumn, PostgresTable } from '@supabase/postgres-meta'
|
||||
import type { PostgresColumn, PostgresTable } from '@supabase/postgres-meta'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
|
||||
import { SchemaView } from 'types'
|
||||
@@ -92,7 +92,7 @@ const TableGridEditor: FC<Props> = ({
|
||||
? parseSupaTable(
|
||||
{
|
||||
table: selectedTable as PostgresTable,
|
||||
columns: (selectedTable as PostgresTable).columns,
|
||||
columns: (selectedTable as PostgresTable).columns ?? [],
|
||||
primaryKeys: (selectedTable as PostgresTable).primary_keys,
|
||||
relationships: (selectedTable as PostgresTable).relationships,
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Project } from 'types'
|
||||
import { ProductMenuGroup } from 'components/ui/ProductMenu/ProductMenu.types'
|
||||
import { useFlag } from 'hooks'
|
||||
import { IS_PLATFORM } from 'lib/constants'
|
||||
|
||||
export const generateDatabaseMenu = (project?: Project): ProductMenuGroup[] => {
|
||||
const ref = project?.ref ?? 'default'
|
||||
@@ -62,12 +63,16 @@ export const generateDatabaseMenu = (project?: Project): ProductMenuGroup[] => {
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
name: 'Backups',
|
||||
key: 'backups',
|
||||
url: `/project/${ref}/database/backups/scheduled`,
|
||||
items: [],
|
||||
},
|
||||
...(IS_PLATFORM
|
||||
? [
|
||||
{
|
||||
name: 'Backups',
|
||||
key: 'backups',
|
||||
url: `/project/${ref}/database/backups/scheduled`,
|
||||
items: [],
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
Loaded 100 of 132 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user