Merge branch 'master' of github.com:supabase/supabase into dev/add-c#-documentation

This commit is contained in:
Joseph Schultz committed 2023-01-27 08:49:59 -06:00
commit 9cfd93bae4
132 files changed
+2757 -1850

No files matched your search

+19 -7
View File
@@ -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
+20
View File
@@ -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
: []
+2
View File
@@ -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) => (
+7
View File
@@ -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,
+4
View File
@@ -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',
-161
View File
@@ -1,161 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
title: 'Overview',
description: 'An overview of Supabase\'s architecture and product principles.',
}
Supabase is open source. We choose open source tools which are scalable and make them simple to use.
Supabase is not a 1-to-1 mapping of Firebase. While we are building many of the features that Firebase offers, we are not going about it the same way:
our technological choices are quite different; everything we use is open source; and wherever possible, we use and support existing tools rather than developing from scratch.
Most notably, we use Postgres rather than a NoSQL store. This choice was deliberate. We believe that no other database offers the functionality required to compete with Firebase,
while maintaining the scalability required to go beyond it.
## Architecture
Each Supabase project consists of several tools:
![Supabase Architecture](/docs/img/supabase-architecture.png)
### PostgreSQL (Database)
PostgreSQL is the core of Supabase. We do not abstract the PostgreSQL database — you can access it and use it with full privileges. We simply provide tools which makes PostgreSQL as easy to use as Firebase.
- Official Docs: [postgresql.org/docs](https://www.postgresql.org/docs/current/index.html)
- Source code: [github.com/postgres/postgres](https://github.com/postgres/postgres) (mirror)
- License: [PostgreSQL Licence](https://www.postgresql.org/about/licence/)
- Language: C
### Studio (Dashboard)
An open source Dashboard for managing your database and services.
- Official Docs: [Supabase docs](/docs)
- Source code: [github.com/supabase/supabase](https://github.com/supabase/supabase/tree/master/studio)
- License: [Apache 2](https://github.com/supabase/supabase/blob/master/LICENSE)
- Language: TypeScript
### GoTrue (Auth)
A JWT-based API for managing users and issuing access tokens. This integrates with PostgreSQL's Row Level Security and the API servers.
- Official Docs: [Supabase Auth reference docs](/docs/reference/auth)
- Source code: [github.com/supabase/gotrue](https://github.com/supabase/gotrue)
- License: [MIT](https://github.com/supabase/gotrue/blob/master/LICENSE)
- Language: Go
### PostgREST (API)
A standalone web server that turns your PostgreSQL database directly into a RESTful API.
We use this with our [pg_graphql](https://github.com/supabase/pg_graphql) extension to provide a GraphQL API.
- Official Docs: [postgrest.org](https://postgrest.org/)
- Source code: [github.com/PostgREST/postgrest](https://github.com/PostgREST/postgrest)
- License: [MIT](https://github.com/PostgREST/postgrest/blob/main/LICENSE)
- Language: Haskel
### Realtime (API & multiplayer)
A scalable websocket engine for managing user Presence, broadcasting messages, and streaming database changes.
- Official Docs: [Supabase Realtime docs](/docs/guides/realtime)
- Source code: [github.com/supabase/realtime](https://github.com/supabase/realtime)
- License: [Apache 2](https://github.com/supabase/realtime/blob/main/LICENSE)
- Language: Elixir
### Storage API (large file storage)
An S3-compatible object storage service that stores metadata in Postgres.
- Official Docs: [Supabase Storage reference docs](/docs/reference/storage)
- Source code: [github.com/supabase/storage-api](https://github.com/supabase/storage-api)
- License: [Apache 2.0](https://github.com/supabase/storage-api/blob/master/LICENSE)
- Language: NodeJS / TypeScript
### Deno (Edge Functions)
A modern runtime for JavaScript and TypeScript.
- Official Docs: [deno.land](https://deno.land/)
- Source code: [github.com/denoland/deno](https://github.com/denoland/deno)
- License: [MIT](https://github.com/denoland/deno/blob/main/LICENSE.md)
- Language: TypeScript / Rust
### postgres-meta (Database management)
A RESTful API for managing your Postgres. Fetch tables, add roles, and run queries.
- Official Docs: [supabase.github.io/postgres-meta](https://supabase.github.io/postgres-meta/)
- Source code: [github.com/supabase/postgres-meta](https://github.com/supabase/postgres-meta)
- License: [Apache 2.0](https://github.com/supabase/postgres-meta/blob/master/LICENSE)
- Language: NodeJS / TypeScript
### PgBouncer
A lightweight connection pooler for PostgreSQL. This is useful for connecting to Postgres when using Serverless functions.
- Official Docs: [pgbouncer.org](http://www.pgbouncer.org/)
- Source code: [pgbouncer/pgbouncer](https://github.com/pgbouncer/pgbouncer)
- License: [ISC](https://github.com/pgbouncer/pgbouncer/blob/master/COPYRIGHT)
- Language: C
### Kong (API Gateway)
A cloud-native API gateway, built on top of Nginx.
- Official Docs: [docs.konghq.com](https://docs.konghq.com/)
- Source code: [github.com/kong/kong](https://github.com/kong/kong)
- License: [Apache 2.0](https://github.com/Kong/kong/blob/master/LICENSE)
- Language: Lua
## Product Principles
It is our goal to provide an architecture that any large-scale company would design for themselves,
and then provide tooling around that architecture that is easy-to-use for indie-developers and small teams.
We use a series of principles to ensure that scalability and usability are never mutually exclusive:
### Everything works in isolation
Each system must work as a standalone tool with as few moving parts as possible.
The litmus test for this is: "Can a user run this product with nothing but a Postgres database?"
### Everything is integrated
Supabase is composable. Even though every product works in isolation, each product on the platform needs to 10x the other products.
For integration, each tool should expose an API and Webhooks.
### Everything is extensible
We're deliberate about adding a new tool, and prefer instead to extend an existing one.
This is the opposite of many cloud providers whose product offering expands into niche use-cases. We provide _primitives_ for developers, which allow them to achieve any goal.
Less, but better.
### Everything is portable
To avoid lock-in, we make it easy to migrate in and out. Our cloud offering is compatible with our self-hosted product.
We use existing standards to increase portability (like pg_dump an CSV files). If a new standard emerges which competes with a "Supabase" approach, we will deprecate the approach in favor of the standard.
This forces us compete on experience. We aim to be the best Postgres hosting service.
### Play the long game
We sacrifice short-term wins for long-term gains. For example, it is tempting to run a fork of Postgres with additional functionality which only our customers need.
Instead, we prefer to support efforts to upstream missing functionality so that the entire community benefits. This has the additional benefit of ensuring portability and longevity.
### Build for developers
"Developers" are a specific profile of user: they are _builders_.
When assessing impact as a function of effort, developers have a large efficiency due to the type of products and systems they can build.
As the profile of a developer changes over time, Supabase will continue to evolve the product to fit this evolving profile.
### Support existing tools
Supabase supports existing tools and communities wherever possible. Supabase is more like a "community of communities" - each tool typically has its own community
which we work with.
Open source is something we approach [collaboratively](https://supabase.com/blog/supabase-series-b#giving-back): we employ maintainers, sponsor projects, invest in businesses, and develop our own open source tools.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
+1 -1
View File
@@ -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)
@@ -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
@@ -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.
+1 -1
View File
@@ -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!
-14
View File
@@ -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
-159
View 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
+17 -7
View File
@@ -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>
-77
View File
@@ -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
View File
Whitespace-only changes.
+29 -6
View File
@@ -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>
)
}
-724
View File
@@ -1,724 +0,0 @@
import Layout from '~/layouts/tutorials/TutorialLayout'
export const meta = {
title: 'Creating a user management dashboard with NextJS and Supabase',
author: 'Rich Haines',
video: 'https://www.youtube-nocookie.com/embed/0Fs96oZ4se0',
}
In this 25-minute guide, we’ll be building a collaborative user managment app using NextJS and Supabase.
As users add and move rectangles in a canvas, changes will be automatically synced and persisted, allowing for a canvas that updates in real-time across clients.
Users will also be able to see other users selections, and undo and redo actions.
![Supabase User Management example](/docs/img/user-management-demo.png)
### GitHub
Should you get stuck while working through the guide, refer to [this repo](https://github.com/supabase/supabase/tree/master/examples/user-management/nextjs-ts-user-management).
## Building the App
Let's start building the Next.js app from scratch.
### Initialize a Next.js app
We can use [`create-next-app`](https://nextjs.org/docs/getting-started) to initialize
an app called `supabase-nextjs`:
<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
View File
@@ -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,
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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>
-7
View File
@@ -1,7 +0,0 @@
node_modules/
.vercel
.next
.env.local
.env.production
.env.dev
.env.*
+2 -4
View File
@@ -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
+1766 -302
View File
File diff suppressed because it is too large. Load diff
+3
View File
@@ -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"
}
}
+37 -5
View File
@@ -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
+6
View File
@@ -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",
-22
View File
@@ -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.
-7
View File
@@ -1,7 +0,0 @@
node_modules/
.vercel
.next
.env.local
.env.production
.env.dev
.env.*
+1 -3
View File
@@ -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
View File
@@ -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"
/>
@@ -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...'}
/>
)}
@@ -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,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'
@@ -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,6 +1,6 @@
import { find, isUndefined, isEqual, isNull } from 'lodash'
import { Dictionary } from 'components/grid'
import {
import type {
PostgresColumn,
PostgresRelationship,
PostgresTable,
@@ -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,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,4 +1,4 @@
import { PostgresTable, PostgresColumn } from '@supabase/postgres-meta'
import type { PostgresTable, PostgresColumn } from '@supabase/postgres-meta'
interface Props {
table: PostgresTable
@@ -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,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'
@@ -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,4 +1,4 @@
import { PostgresRelationship } from '@supabase/postgres-meta'
import type { PostgresRelationship } from '@supabase/postgres-meta'
export interface JsonEditValue {
column: string
@@ -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,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'
@@ -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,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,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,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