Merge branch 'master' into docs/tutorials

This commit is contained in:
dng authored and GitHub committed 2023-01-27 17:33:20 -08:00
commit 7b479e48fc
157 files changed
+3295 -2042

No files matched your search

+19 -6
View File
@@ -1,7 +1,20 @@
node_modules/
# Root folder of this turbo monorepo
# Doesn't send useless context to Docker daemon
.git
.github
about
apps
docker
examples
i18n
**/node_modules
spec
supabase
tests
**/.next
.vercel
.next
.env.local
.env.production
.env.dev
.env.*
.env.*
.dockerignore
studio/Dockerfile
+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();
```
@@ -101,7 +101,7 @@ const home = [
level: 'reference_python',
},
{
label: 'Tools Reference',
label: 'Tools',
},
{
label: 'Management API',
@@ -112,7 +112,7 @@ const home = [
{
label: 'Supabase CLI',
icon: '/img/icons/menu/reference-cli',
href: '/reference/cli/introduction',
href: '/guides/cli',
level: 'reference_javascript',
},
],
@@ -48,6 +48,9 @@ export const gettingstarted = {
{ name: 'React', url: '/guides/getting-started/quickstarts/reactjs', items: [] },
{ name: 'NextJS', url: '/guides/getting-started/quickstarts/nextjs', items: [] },
{ name: 'Flutter', url: '/guides/getting-started/quickstarts/flutter', items: [] },
{ name: 'SvelteKit', url: '/guides/getting-started/quickstarts/sveltekit', items: [] },
{ name: 'SolidJS', url: '/guides/getting-started/quickstarts/solidjs', items: [] },
{ name: 'Vue', url: '/guides/getting-started/quickstarts/vue', items: [] },
],
},
{
@@ -485,6 +488,25 @@ export const storage = {
],
}
export const supabase_cli = {
icon: 'reference-cli',
title: 'Supabase CLI',
url: '/guides/cli',
items: [
{ name: 'Overview', url: '/guides/cli', items: [] },
{ name: 'Local Development', url: '/guides/cli/local-development', items: [] },
{ name: 'Managing Environments', url: '/guides/cli/managing-environments', items: [] },
{
name: 'Reference',
url: undefined,
items: [
{ name: 'Commands', url: '/reference/cli/introduction', items: [] },
{ name: 'Configuration', url: '/reference/cli/config', items: [] },
],
},
],
}
export const platform = {
icon: 'platform',
label: 'Platform',
@@ -589,26 +611,6 @@ export const resources = {
},
],
},
{
name: 'Supabase CLI',
items: [
{
name: 'Overview',
url: '/guides/resources/supabase-cli',
items: [],
},
{
name: 'Local Development',
url: '/guides/resources/supabase-cli/local-development',
items: [],
},
{
name: 'Managing Environments',
url: '/guides/resources/supabase-cli/managing-environments',
items: [],
},
],
},
],
}
@@ -105,6 +105,9 @@ const NavigationMenu = () => {
case url.includes(`/docs/guides/integrations`) && url:
menuState.setMenuLevelId('integrations')
break
case url.includes(`/docs/guides/cli`) && url:
menuState.setMenuLevelId('supabase_cli')
break
// JS v1
case url.includes(`/docs/reference/javascript/v1`) && url:
menuState.setMenuLevelId('reference_javascript_v1')
@@ -125,6 +128,9 @@ const NavigationMenu = () => {
case url.includes(`/docs/reference/python`) && url:
menuState.setMenuLevelId('reference_python_v2')
break
case url.includes(`/docs/reference/cli/config`) && url:
menuState.setMenuLevelId('supabase_cli')
break
case url.includes(`/docs/reference/cli`) && url:
menuState.setMenuLevelId('reference_cli')
break
@@ -164,6 +170,7 @@ const NavigationMenu = () => {
const isFunctionsActive = 'functions' === level
const isRealtimeActive = 'realtime' === level
const isStorageActive = 'storage' === level
const issupabase_cliActive = 'supabase_cli' === level
const isPlatformActive = 'platform' === level
const isResourcesActive = 'resources' === level
const isSelfHosting = 'self_hosting' === level
@@ -191,6 +198,7 @@ const NavigationMenu = () => {
<NavigationMenuGuideList id={'functions'} active={isFunctionsActive} />
<NavigationMenuGuideList id={'realtime'} active={isRealtimeActive} />
<NavigationMenuGuideList id={'storage'} active={isStorageActive} />
<NavigationMenuGuideList id={'supabase_cli'} active={issupabase_cliActive} />
<NavigationMenuGuideList id={'platform'} active={isPlatformActive} />
<NavigationMenuGuideList id={'resources'} active={isResourcesActive} />
<NavigationMenuGuideList id={'self_hosting'} active={isSelfHosting} />
@@ -179,7 +179,7 @@ const Content: React.FC<INavigationMenuRefList> = ({ id, lib, commonSections, sp
if (!sections) console.error('no common sections imported')
const menu = NavItems[id]
console.log(menu)
//console.log(menu)
const databaseFunctions = find(sections, { title: 'Database' })
? find(sections, { title: 'Database' }).items
: []
+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,
+1 -1
View File
@@ -17,7 +17,7 @@ hideTitle: true
The Supabase CLI provides tools to develop your project locally and deploy to the Supabase Platform.
The CLI is still under development, but it contains all the functionality for working with your Supabase projects and the Supabase Platform.
- Run Supabase locally: [`supabase start`](/docs/reference/cli/usage#supabase-start)
- Run Supabase locally: [`supabase init`](/docs/reference/cli/usage#supabase-init) and [`supabase start`](/docs/reference/cli/usage#supabase-start)
- Manage database migrations: [`supabase migration`](/docs/reference/cli/usage#supabase-migration)
- CI/CD for releasing to production: [`supabase db push`](/docs/reference/cli/usage#supabase-db-push)
- Manage your Supabase projects: [`supabase projects`](/docs/reference/cli/usage#supabase-projects)
-50
View File
@@ -1,50 +0,0 @@
---
id: initializing
title: 'Initializing'
slug: initializing
---
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
Initialize Supabase with the static `initialize()` method on `Supabase` class.
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
size="small"
type="underlined"
defaultActiveId="flutter"
>
<TabPanel id="flutter" label="Flutter">
```dart main.dart
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Supabase.initialize(
url: 'https://xyzcompany.supabase.co',
anonKey: 'public-anon-key',
);
runApp(MyApp());
}
```
</TabPanel>
<TabPanel id="dart" label="Other Dart Project">
```dart
final supabase = SupabaseClient(
'https://xyzcompany.supabase.co',
'public-anon-key',
);
```
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
+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
@@ -140,7 +140,7 @@ export async function middleware(req) {
<TabPanel id="ts" label="TypeScript">
Create a new `/app/middleware.js` file and populate with the following:
Create a new `/app/middleware.ts` file and populate with the following:
```tsx title="middleware.ts"
import { createMiddlewareSupabaseClient } from '@supabase/auth-helpers-nextjs'
@@ -1,7 +1,6 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'cli',
title: 'Supabase CLI',
description:
'The Supabase CLI provides tools to develop your project locally and deploy to the Supabase Platform.',
@@ -111,7 +110,8 @@ brew upgrade supabase
## See also
- [Supabase CLI Reference](/docs/reference/cli/usage)
- [Supabase CLI Reference](/docs/reference/cli/introduction)
- [Supabase CLI Configuration](/docs/reference/cli/config)
- [Local Development](/docs/guides/cli/local-development)
- [Managing Environments](/docs/guides/cli/managing-environments)
@@ -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
+1 -1
View File
@@ -77,7 +77,7 @@ You must define the "data type" when you create a column.
### Data types
Every column is a predefined type. PostgreSQL provides many [default types](https://www.postgresql.org/docs/current/datatype.html), and you can even design your own (or use extensions)
if the default types don't fit your needs.
if the default types don't fit your needs. You can use any data type that Postgres supports via the SQL editor. We only support a subset of these in the Table Editor in an effort to keep the experience simple for people with less experience with databases.
<details>
<summary>Show/Hide default data types</summary>
@@ -6,6 +6,15 @@ export const meta = {
description: 'Using Import Maps.',
}
<div class="video-container">
<iframe
src="https://www.youtube-nocookie.com/embed/ILr3cneZuFk"
frameBorder="1"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>
</div>
Since [Supabase CLI](https://supabase.com/docs/guides/resources/supabase-cli) version `1.33.0` [import maps](https://deno.land/manual@v1.29.2/node/import_maps) can be used with Supabase Edge Functions.
[Import maps](https://github.com/WICG/import-maps#the-import-map) is a web-platform standard that allows you to use bare specifiers with Deno without having to install the Node.js package locally.
+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
@@ -0,0 +1,166 @@
import Layout from '~/layouts/DefaultGuideLayout'
import StepHikeCompact from '~/components/StepHikeCompact'
export const meta = {
title: 'Use Supabase with SvelteKit',
subtitle:
'Learn how to create a Supabase project, add some sample data to your database, and query the data from a SvelteKit app.',
breadcrumb: 'Framework Quickstarts',
}
<StepHikeCompact>
<StepHikeCompact.Step step={1}>
<StepHikeCompact.Details title="Set up a Supabase project with sample data">
[Create a new project](https://app.supabase.com) in the Supabase Dashboard.
After your project is ready, create a table in your Supabase database using the [SQL Editor](https://app.supabase.com/project/_/sql) in the Dashboard. Use the following SQL statement to create a `countries` table with some sample data.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```sql SQL_EDITOR
-- Create the table
CREATE TABLE countries (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL
);
-- Insert some sample data into the table
INSERT INTO countries (name) VALUES ('United States');
INSERT INTO countries (name) VALUES ('Canada');
INSERT INTO countries (name) VALUES ('Mexico');
````
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={2}>
<StepHikeCompact.Details title="Create a SvelteKit app">
Create a SvelteKit app using the `npm create` command.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```bash Terminal
npm create svelte@latest myapp
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={3}>
<StepHikeCompact.Details title="Install the Supabase client library">
The fastest way to get started is to use the `supabase-js` client library which provides a convenient interface for working with Supabase from a SvelteKit app.
Navigate to the SvelteKit app and install `supabase-js`.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```bash Terminal
cd my-app && npm install @supabase/supabase-js
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={4}>
<StepHikeCompact.Details title="Create the Supabase client">
Create a `/src/lib` directory in your SvelteKit app, create a file called `supabaseClient.js` and add the following code to initialize the Supabase client with your [project URL and public API (anon) key](https://app.supabase.com/project/_/settings/api).
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```js src/lib/supabaseClient.js
import { createClient } from '@supabase/supabase-js'
export const supabase = createClient('https://<project>.supabase.co', '<your-anon-key>')
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={5}>
<StepHikeCompact.Details title="Query data from the app">
Use `load` method to fetch the data server-side and display the query results as a simple list.
Create `+page.server.js` file in the `routes` directory with the following code.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```js src/routes/+page.server.js
import { supabase } from "$lib/supabaseClient";
export async function load() {
const { data } = await supabase.from("countries").select();
return {
countries: data ?? [],
};
}
```
</StepHikeCompact.Code>
<StepHikeCompact.Details title="">
Replace the existing content in your `+page.svelte` file in the `routes` directory with the following code.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```svelte src/routes/+page.svelte
<script>
export let data;
let { countries } = data;
$: ({ countries } = data);
</script>
<ul>
{#each countries as country}
<li>{country.name}</li>
{/each}
</ul>
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={6}>
<StepHikeCompact.Details title="Start the app">
Start the app and go to http://localhost:5173 in a browser and you should see the list of countries.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```bash Terminal
npm run dev
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
</StepHikeCompact>
export const Page = ({ children }) => <Layout meta={meta} children={children} hideToc={true} />
export default Page
@@ -0,0 +1,153 @@
import Layout from '~/layouts/DefaultGuideLayout'
import StepHikeCompact from '~/components/StepHikeCompact'
export const meta = {
title: 'Use Supabase with Vue',
subtitle:
'Learn how to create a Supabase project, add some sample data to your database, and query the data from a Vue app.',
breadcrumb: 'Framework Quickstarts',
}
<StepHikeCompact>
<StepHikeCompact.Step step={1}>
<StepHikeCompact.Details title="Set up a Supabase project with sample data">
[Create a new project](https://app.supabase.com) in the Supabase Dashboard.
After your project is ready, create a table in your Supabase database using the [SQL Editor](https://app.supabase.com/project/_/sql) in the Dashboard. Use the following SQL statement to create a `countries` table with some sample data.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```sql SQL_EDITOR
-- Create the table
CREATE TABLE countries (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL
);
-- Insert some sample data into the table
INSERT INTO countries (name) VALUES ('United States');
INSERT INTO countries (name) VALUES ('Canada');
INSERT INTO countries (name) VALUES ('Mexico');
````
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={2}>
<StepHikeCompact.Details title="Create a Vue app">
Create a Vue app using the `npm init` command.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```sh Terminal
npm init vue@latest my-app
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={3}>
<StepHikeCompact.Details title="Install the Supabase client library">
The fastest way to get started is to use the `supabase-js` client library which provides a convenient interface for working with Supabase from a Vue app.
Navigate to the Vue app and install `supabase-js`.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```bash Terminal
cd my-app && npm install @supabase/supabase-js
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={4}>
<StepHikeCompact.Details title="Create the Supabase client">
Create a `/src/lib` directory in your Vue app, create a file called `supabaseClient.js` and add the following code to initialize the Supabase client with your [project URL and public API (anon) key](https://app.supabase.com/project/_/settings/api).
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```js src/lib/supabaseClient.js
import { createClient } from '@supabase/supabase-js'
export const supabase = createClient('https://<project>.supabase.co', '<your-anon-key>')
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={5}>
<StepHikeCompact.Details title="Query data from the app">
Replace the existing content in your `App.vue` file with the following code.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```vue src/App.vue
<script setup>
import { ref, onMounted } from 'vue'
import { supabase } from './lib/supabaseClient'
const countries = ref([])
async function getCountries() {
const { data } = await supabase.from('countries').select()
countries.value = data
}
onMounted(() => {
getCountries()
})
</script>
<template>
<ul>
<li v-for="country in countries" :key="country.id">{{ country.name }}</li>
</ul>
</template>
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={6}>
<StepHikeCompact.Details title="Start the app">
Start the app and go to http://localhost:5173 in a browser and you should see the list of countries.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```bash Terminal
npm run dev
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
</StepHikeCompact>
export const Page = ({ children }) => <Layout meta={meta} children={children} hideToc={true} />
export default Page
@@ -525,6 +525,10 @@ export class AccountComponent implements OnInit {
}
```
### Storage management
<StorageManagement />
At this stage you have a fully functional application!
export const Page = ({ children }) => <Layout meta={meta} children={children} />
@@ -498,6 +498,10 @@ Now you will need to run the prebuild command to get the application working on
expo prebuild
```
### Storage management
<StorageManagement />
At this stage you have a fully functional application!
export const Page = ({ children }) => <Layout meta={meta} children={children} />
@@ -792,6 +792,10 @@ class _AccountPageState extends State<AccountPage> {
}
```
### Storage management
<StorageManagement />
Congratulations, you've built a fully functional user management app using Flutter and Supabase!
## See also
@@ -521,6 +521,10 @@ template: `
`
```
### Storage management
<StorageManagement />
At this stage you have a fully functional application!
## See also
@@ -494,6 +494,10 @@ return (
<Avatar url={profile.avatar_url} onUpload={updateProfile}></Avatar>
```
### Storage management
<StorageManagement />
At this stage you have a fully functional application!
export const Page = ({ children }) => <Layout meta={meta} children={children} />
@@ -554,6 +554,10 @@ export default defineComponent({
</script>
```
### Storage management
<StorageManagement />
At this stage you have a fully functional application!
export const Page = ({ children }) => <Layout meta={meta} children={children} />
@@ -717,6 +717,10 @@ return (
)
```
### Storage management
<StorageManagement />
At this stage you have a fully functional application!
## See also
@@ -417,6 +417,10 @@ And then we can add the widget to the Account page:
</script>
```
### Storage management
<StorageManagement />
That is it! You should now be able to upload a profile photo to Supabase Storage and you have a fully functional application.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
@@ -396,6 +396,10 @@ return (
)
```
### Storage management
<StorageManagement />
At this stage you have a fully functional application!
export const Page = ({ children }) => <Layout meta={meta} children={children} />
@@ -621,6 +621,10 @@ return (
)
```
### Storage management
<StorageManagement />
At this stage you have a fully functional application!
## See also
@@ -414,6 +414,10 @@ return (
)
```
### Storage management
<StorageManagement />
At this stage you have a fully functional application!
export const Page = ({ children }) => <Layout meta={meta} children={children} />
@@ -367,6 +367,10 @@ And then we can add the widget to the Account page:
</form>
```
### Storage management
<StorageManagement />
At this stage you have a fully functional application!
export const Page = ({ children }) => <Layout meta={meta} children={children} />
@@ -433,6 +433,10 @@ And then we can add the widget to the Account page:
</form>
```
### Storage management
<StorageManagement />
At this stage you have a fully functional application!
export const Page = ({ children }) => <Layout meta={meta} children={children} />
@@ -378,6 +378,10 @@ And then we can add the widget to the Account page:
</template>
```
### Storage management
<StorageManagement />
At this stage you have a fully functional application!
export const Page = ({ children }) => <Layout meta={meta} children={children} />
+33 -26
View File
@@ -18,7 +18,7 @@ Once created, the pipeline backfills all your historical data from Firestore and
Before you begin, you'll need:
- An [Estuary account](https://go.estuary.dev/sign-up).
- An Estuary account. [Head to the web app to start for free](https://dashboard.estuary.dev).
- For your Firestore database:
@@ -31,36 +31,35 @@ Before you begin, you'll need:
You'll start by creating a **capture**, a task in Flow that connects to your data source system: in this case, Firestore. This process will create one or more data **collections**, backed by a real-time data lake.
1. Go to the Flow web application at [dashboard.estuary.dev](http://dashboard.estuary.dev) and sign in using the credentials provided by your Estuary account manager.
1. Go to the [**Captures** tab](https://dashboard.estuary.dev/captures) of the Flow web app and choose **New Capture**.
2. Click the **Captures** tab and choose **New Capture**.
3. Locate and select the **Google Firestore** card.
2. Locate and select the **Google Firestore** card.
A form appears with the properties required for a Firestore capture.
4. Set a name for your capture.
3. Set a name for your capture.
Click inside the **Name** field to generate a drop-down menu of available **prefixes** and select one (likely, this will be the name of your organization). Append a unique capture name after the `/` to create the full name, for example, `acmeCo/myFirestoreCapture`.
5. Fill out the required properties for Firestore.
4. Fill out the required properties for Firestore.
- **Database**: Flow can autodetect the database name, but you may optionally specify it here. This is helpful if the service account used has access to multiple Firebase projects. Your database name usually follows the format `projects/$PROJECTID/databases/(default)`.
- **Credentials**: The JSON service account key created per the prerequisites.
**Database**: Flow can autodetect the database name, but you may optionally specify it here. This is helpful if the service account used has access to multiple Firebase projects. Your database name usually follows the format `projects/$PROJECTID/databases/(default)`.
**Credentials**: The JSON service account key created per the prerequisites.
6. Click **Discover Endpoint**.
5. Click **Next**.
Flow uses the provided configuration to initiate a connection with Firestore. It generates a capture specification and details of the collections that it will create, once published.
Flow uses the provided configuration to initiate a connection with Firestore. It maps each collection in the Firestore database to a Flow collection.
7. Use the **Specification Editor** to view the [JSON schemas](https://docs.estuary.dev/concepts/schemas/) for each collection and make sure they are formatted correctly for your needs. If they're not, you can edit them.
6. Optionally, use the **Collection Selector** to remove any collections you don't need to migrate to Supabase.
8. Click **Save and publish**.
7. Click **Save and Publish**.
You'll see a notification when the capture publishes successfully.
You'll see a notification when the capture publishes successfully.
The data currently in your Firestore database has been captured, and future updates to it will be captured continuously.
The data currently in your Firestore database has been captured to Flow, and future updates to it will be captured continuously.
Click **Materialize Collections** to continue.
Click **Materialize Collections** to continue.
## Step 2: Materialize your collections to Postgres
@@ -72,23 +71,31 @@ A form appears with the properties required for a Postgres materialization.
2. Choose a unique name for your materialization like you did when naming your capture; for example, `acmeCo/mySupabaseMaterialization`.
3. Fill out the required properties for PostgreSQL. You can find most of these in Supabase by going to the **Settings** section and clicking **Database**.
3. Fill out the required properties for Postgres. You can find most of these in Supabase by going to the **Settings** section and clicking **Database**.
- **Address**: Format at `<host>:<port>`.
- **User**: Usually, this is `postgres`.
- **Password**: The password you set when you created your Supabase project.
**Address**: Format at `<host>:<port>`.
**User**: Usually, this is `postgres`.
**Password**: The password you set when you created your Supabase project.
4. Scroll down to view the **Collection Selector** and fill in the **Table** field for each collection.
4. Click **Next**.
The collections you just created have already been selected, but you must provide names for the tables to which they'll be materialized.
Flow initiates a connection with the database and the **Collection Selector** expands.
It's populated with your collections from Firestore, each mapped to a Postgres table.
5. Click **Discover Endpoint**.
5. For each collection, apply a stricter JSON schema.
This ensure that the less-structured Firestore data will be written to a Postgres table in the correct shape.
Flow uses the provided configuration to initiate a connection to your Supabase Postgres database and generate a specification.
In the Collection Selector, choose a collection and click its **Specification** tab.
Click **Schema Inference**. Flow scans the data in your collection and infers a new schema to use for materialization.
Review the new schema and click **Apply Inferred Schema**.
6. Click **Save and Publish**. You'll see a notification when the full materialization publishes successfully.
6. Click **Save and Publish**. You'll see a notification when the materialization publishes successfully.
Your Firestore collections are copied to tables in Supabase. As long as you leave the capture and materialation running, any changes to the Firestore data will be reflected in Supabase in milliseconds.
Your Firestore collections are copied to tables in Supabase. As long as you leave the capture and materialation running, any changes to the Firestore data will be reflected in Supabase in milliseconds.
## Resources
@@ -14,7 +14,18 @@ export const meta = {
- Emails
- In-app notifications
This guide steps you through how you can integrate OneSignal with Supabase to create a seamless cloud messaging experience for your users using Database webhooks and edge functions through a simple Next.js application.
Here is William giving us the overview of how OneSignal can work with Supabase to send notifications to your users.
<div className="video-container">
<iframe
src="https://www.youtube-nocookie.com/embed/mw0DLwItue4"
frameBorder="1"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>
</div>
In this guide, we will build a similar app and steps you through how you can integrate OneSignal with Supabase to create a seamless cloud messaging experience for your users using Database webhooks and edge functions through a simple Next.js application.
![Entity Diagram](/docs/img/guides/integrations/onesignal/diagram.png)
@@ -19,15 +19,11 @@ This value is also reported in the [database settings page](https://app.supabase
## Database storage management
Your project will be changed to read-only mode once it reaches 95% of your subscription quota, preventing further writes to your project.
In read-only mode, clients will encounter errors such as `cannot execute INSERT in a read-only transaction`.
Supabase uses network-attached storage to balance performance with scalability. For Pro and Enterprise projects, database storage expands ~1.5x automatically (e.g., 8GB -> 12GB) when you reach 90% of the database storage quota. Automatic database storage expansion can only occur once every six hours. Pro projects can store up to 1024TB.
Regular operation (read-write mode) is automatically re-enabled once database storage usage falls under 95%.
All projects enter read-only mode when you reach 95% of the database storage quota. In read-only mode, clients will encounter errors such as `cannot execute INSERT in a read-only transaction`. Regular operation (read-write mode) is automatically re-enabled once usage is below 95% of the database storage quota.
If you are on Pro or Enterprise, database storage expands automatically, however you may also encounter this error if fill the database faster than it can expand.
Supabase uses network-attached storage, which balances performance with scalability. Pro projects can store up to 1024TB.
If you intend to grow beyond this, reach out to our team to learn more about our Enterprise tier.
If you need more than 1024TB of database storage or require multiple storage expansions in a short period of time, [contact us](https://app.supabase.com/support/new) to learn more about the Enterprise plan.
### Increasing available database storage
@@ -121,7 +121,7 @@ channel.subscribe(async (status) => {
})
```
## Track and display which users are online
## Track and Display Which Users Are Online
[Presence](/docs/guides/realtime#presence) stores and synchronize shared state across clients. The `sync` event is triggered whenever the shared state changes. The `join` event is triggered when new clients join the channel and `leave` event is triggered when clients leave.
-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
+19 -4
View File
@@ -69,13 +69,28 @@ export const meta = {
</Link>
<Link href={`/guides/getting-started/quickstarts/nextjs`} passHref>
<a className="no-underline">
<IconPanel title="NextJS" icon="/docs/img/icons/nextjs-icon" />
</a>
<IconPanel title="NextJS" icon="/docs/img/icons/nextjs-icon" />
</a>
</Link>
<Link href={`/guides/getting-started/quickstarts/flutter`} passHref>
<a className="no-underline">
<IconPanel title="Flutter" icon="/docs/img/icons/flutter-icon" />
</a>
<IconPanel title="Flutter" icon="/docs/img/icons/flutter-icon" />
</a>
</Link>
<Link href={`/guides/getting-started/quickstarts/sveltekit`} passHref>
<a className="no-underline">
<IconPanel title="SvelteKit" icon="/docs/img/icons/svelte-icon" />
</a>
</Link>
<Link href={`/guides/getting-started/quickstarts/solidjs`} passHref>
<a className="no-underline">
<IconPanel title="SolidJS" icon="/docs/img/icons/solidjs-icon" />
</a>
</Link>
<Link href={`/guides/getting-started/quickstarts/vue`} passHref>
<a className="no-underline">
<IconPanel title="Vue" icon="/docs/img/icons/vuejs-icon" />
</a>
</Link>
</div>
</div>
-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.*
+28 -5
View File
@@ -9,8 +9,13 @@ version: "3.8"
services:
studio:
container_name: supabase-studio
image: supabase/studio:20221214-4eecc99
image: supabase/studio:20230127-123d704
restart: unless-stopped
healthcheck:
test: [ "CMD", "node", "-e", "require('http').get('http://localhost:3000/api/profile', (r) => {if (r.statusCode !== 200) throw new Error(r.statusCode)})" ]
timeout: 5s
interval: 5s
retries: 3
ports:
- ${STUDIO_PORT}:3000/tcp
environment:
@@ -22,8 +27,6 @@ services:
SUPABASE_URL: http://kong:8000
SUPABASE_PUBLIC_URL: ${SUPABASE_PUBLIC_URL}
# Kept for backwards compatibility with studio:0.22.08
SUPABASE_REST_URL: ${API_EXTERNAL_URL}/rest/v1/
SUPABASE_ANON_KEY: ${ANON_KEY}
SUPABASE_SERVICE_KEY: ${SERVICE_ROLE_KEY}
@@ -51,6 +54,11 @@ services:
depends_on:
db: # Disable this if you are using an external Postgres database
condition: service_healthy
healthcheck:
test: [ "CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:9999/health" ]
timeout: 5s
interval: 5s
retries: 3
restart: unless-stopped
environment:
GOTRUE_API_HOST: 0.0.0.0
@@ -109,6 +117,11 @@ services:
depends_on:
db: # Disable this if you are using an external Postgres database
condition: service_healthy
healthcheck:
test: [ "CMD", "bash", "-c", "printf \\0 > /dev/tcp/localhost/4000" ]
timeout: 5s
interval: 5s
retries: 3
restart: unless-stopped
environment:
PORT: 4000
@@ -139,6 +152,11 @@ services:
condition: service_started
imgproxy:
condition: service_started
healthcheck:
test: [ "CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:5000/status" ]
timeout: 5s
interval: 5s
retries: 3
restart: unless-stopped
environment:
ANON_KEY: ${ANON_KEY}
@@ -161,6 +179,11 @@ services:
imgproxy:
container_name: supabase-imgproxy
image: darthsim/imgproxy:v3.11
healthcheck:
test: [ "CMD", "imgproxy", "health" ]
timeout: 5s
interval: 5s
retries: 3
environment:
IMGPROXY_BIND: ":5001"
IMGPROXY_LOCAL_FILESYSTEM_ROOT: /
@@ -170,7 +193,7 @@ services:
meta:
container_name: supabase-meta
image: supabase/postgres-meta:v0.52.1
image: supabase/postgres-meta:v0.59.0
depends_on:
db: # Disable this if you are using an external Postgres database
condition: service_healthy
@@ -186,7 +209,7 @@ services:
# Comment out everything below this point if you are using an external Postgres database
db:
container_name: supabase-db
image: supabase/postgres:14.1.0.89
image: supabase/postgres:14.1.0.106
healthcheck:
test: pg_isready -U postgres -h localhost
interval: 5s
@@ -6,7 +6,7 @@ router
.get('/oak-server', (context) => {
context.response.body = 'This is an example Oak server running on Edge Functions!'
})
.post('/oak-server/greet', (context) => {
.post('/oak-server/greet', async (context) => {
// Note: request body will be streamed to the function as chunks, set limit to 0 to fully read it.
const result = context.request.body({ type: 'json', limit: 0 })
const body = await result.value
+1 -1
View File
@@ -27,7 +27,7 @@
За пълната документация посетете [supabase.com/docs](https://supabase.com/docs)
За да видите как да допринасяте, посетете [Getting Started](./DEVELOPERS.md)
За да видите как да допринасяте, посетете [Getting Started](../DEVELOPERS.md)
## Общност & поддръжка
+1 -1
View File
@@ -27,7 +27,7 @@
Katso täydelliset dokumentaatiot osoitteesta [supabase.com/docs](https://supabase.com/docs)
Katso ohjeet osallistumiseen vierailemalla [Aloitus](./DEVELOPERS.md)
Katso ohjeet osallistumiseen vierailemalla [Aloitus](../DEVELOPERS.md)
## Yhteisö ja tuki
+1 -1
View File
@@ -23,7 +23,7 @@
Pour une documentation complète, visitez [supabase.com/docs](https://supabase.com/docs)
Pour voir comment Contribuer, visitez [Getting Started](./DEVELOPERS.md)
Pour voir comment Contribuer, visitez [Getting Started](../DEVELOPERS.md)
## Communauté et Support
+1 -1
View File
@@ -27,7 +27,7 @@
詳しいドキュメントは[supabase.com/docs](https://supabase.com/docs)をご覧ください。
コントリビュート方法は[Getting Started](./DEVELOPERS.md)をご覧ください。
コントリビュート方法は[Getting Started](../DEVELOPERS.md)をご覧ください。
## コミュニティとサポート
+2 -2
View File
@@ -27,7 +27,7 @@
Para documentação completa, visite [supabase.com/docs](https://supabase.com/docs)
Para ver como Contribuir, visite [Primeiros Passos](./DEVELOPERS.md)
Para ver como Contribuir, visite [Primeiros Passos](../DEVELOPERS.md)
## Comunidade & Suporte
@@ -142,7 +142,7 @@ Nossa biblioteca de cliente é modular. Cada sub-biblioteca é uma implementaç
<td>-</td>
<td><a href="https://github.com/supabase-community/gotrue-java" target="_blank" rel="noopener noreferrer">gotrue-java</a></td>
<td>-</td>
<td>-</td>
<td><a href="https://github.com/supabase-community/storage-java" target="_blank" rel="noopener noreferrer">storage-java</a></td>
<td>-</td>
</tr>
<tr>
+1 -1
View File
@@ -27,7 +27,7 @@
Za punu dokumentaciju, posetite [supabase.com/docs](https://supabase.com/docs)
Da vidite kako možete doprineti razvoju, posetite [Getting Started](./DEVELOPERS.md)
Da vidite kako možete doprineti razvoju, posetite [Getting Started](../DEVELOPERS.md)
## Zajednica & Podrška
+1 -1
View File
@@ -27,7 +27,7 @@
Để có tài liệu đầy đủ, hãy truy cập [supabase.com/docs](https://supabase.com/docs)
Để biết cách Đóng góp, hãy truy cập [Bắt đầu](./DEVELOPERS.md)
Để biết cách Đóng góp, hãy truy cập [Bắt đầu](../DEVELOPERS.md)
## Hỗ trợ & Cộng đồng
+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",
+2 -1
View File
@@ -677,7 +677,7 @@
},
{
"id": "subscribe",
"title": "Subscribe to database changes",
"title": "Subscribe to channel",
"slug": "subscribe",
"product": "realtime",
"type": "function"
@@ -847,6 +847,7 @@
},
{
"title": "Misc",
"excludes": ["reference_dart_v1", "reference_dart_v0"],
"items": [
{
+11 -11
View File
@@ -5010,8 +5010,8 @@ functions:
})
.subscribe()
```
- id: listening-to-a-specific-table
name: Listening to a specific table
- id: listen-to-a-specific-table
name: Listen to a specific table
js: |
```js
supabase
@@ -5021,8 +5021,8 @@ functions:
})
.subscribe()
```
- id: listening-to-inserts
name: Listening to inserts
- id: listen-to-inserts
name: Listen to inserts
js: |
```js
supabase
@@ -5032,8 +5032,8 @@ functions:
})
.subscribe()
```
- id: listening-to-updates
name: Listening to updates
- id: listen-to-updates
name: Listen to updates
description: |
By default, Supabase will send only the updated record. If you want to receive the previous values as well you can
enable full replication for the table you are listening to:
@@ -5050,8 +5050,8 @@ functions:
})
.subscribe()
```
- id: listening-to-deletes
name: Listening to deletes
- id: listen-to-deletes
name: Listen to deletes
description: |
By default, Supabase does not send deleted records. If you want to receive the deleted record you can
enable full replication for the table you are listening too:
@@ -5068,8 +5068,8 @@ functions:
})
.subscribe()
```
- id: listening-to-multiple-events
name: Listening to multiple events
- id: listen-to-multiple-events
name: Listen to multiple events
description: You can chain listeners if you want to listen to multiple events for each table.
js: |
```js
@@ -5080,7 +5080,7 @@ functions:
.subscribe()
```
- id: listening-to-row-level-changes
name: Listening to row level changes
name: Listen to row level changes
description: You can listen to individual rows using the format `{table}:{col}=eq.{val}` - where `{col}` is the column name, and `{val}` is the value which you want to match.
notes: |
- ``eq`` filter works with all database types as under the hood, it's casting both the filter value and the database value to the correct type and then comparing them.
+13 -13
View File
@@ -700,8 +700,8 @@ functions:
})
.subscribe();
```
- id: listening-to-a-specific-table
name: Listening to a specific table
- id: listen-to-a-specific-table
name: Listen to a specific table
code: |
```dart
final mySubscription = supabase
@@ -711,8 +711,8 @@ functions:
})
.subscribe();
```
- id: listening-to-inserts
name: Listening to inserts
- id: listen-to-inserts
name: Listen to inserts
code: |
```dart
final mySubscription = supabase
@@ -722,8 +722,8 @@ functions:
})
.subscribe();
```
- id: listening-to-updates
name: Listening to updates
- id: listen-to-updates
name: Listen to updates
description: |
By default, Supabase will send only the updated record. If you want to receive the previous values as well you can
enable full replication for the table you are listening too:
@@ -740,8 +740,8 @@ functions:
})
.subscribe();
```
- id: listening-to-deletes
name: Listening to deletes
- id: listen-to-deletes
name: Listen to deletes
description: |
By default, Supabase does not send deleted records. If you want to receive the deleted record you can
enable full replication for the table you are listening too:
@@ -758,8 +758,8 @@ functions:
})
.subscribe();
```
- id: listening-to-multiple-events
name: Listening to multiple events
- id: listen-to-multiple-events
name: Listen to multiple events
description: You can chain listeners if you want to listen to multiple events for each table.
code: |
```dart
@@ -770,7 +770,7 @@ functions:
.subscribe();
```
- id: listening-to-row-level-changes
name: Listening to row level changes
name: Listen to row level changes
description: You can listen to individual rows using the format `{table}:{col}=eq.{val}` - where `{col}` is the column name, and `{val}` is the value which you want to match.
code: |
```dart
@@ -816,8 +816,8 @@ functions:
- `stream()` will emit the initial data as well as any further change on the database as `Stream` of `List<Map<String, dynamic>>` by combining Postgrest and Realtime.
- Takes a list of primary key columns as its argument.
examples:
- id: listening-to-a-specific-table
name: Listening to a specific table
- id: listen-to-a-specific-table
name: Listen to a specific table
isSpotlight: true
code: |
```dart
+48 -13
View File
@@ -16,6 +16,41 @@ info:
version: '0.0.1'
functions:
- id: initializing
title: 'Initializing'
$ref: '@supabase/supabase-js.index.SupabaseClient.constructor'
description: |
You can initialize Supabase with the static `initialize()` method of `Supabase` class.
The Supabase client is your entrypoint to the rest of the Supabase functionality
and is the easiest way to interact with everything we offer within the Supabase ecosystem.
examples:
- id: flutter-initialize
name: For Flutter
code: |
```dart
Future<void> main() async {
await Supabase.initialize(
url: 'https://xyzcompany.supabase.co',
anonKey: 'public-anon-key',
);
runApp(MyApp());
}
// Get a reference your Supabase client
final supabase = Supabase.instance.client;
```
- id: for-other-dart-projects
name: For other Dart projects
code: |
```dart
final supabase = SupabaseClient(
'https://xyzcompany.supabase.co',
'public-anon-key',
);
```
- id: sign-up
title: 'signUp()'
description: |
@@ -696,8 +731,8 @@ functions:
},
).subscribe();
```
- id: listening-to-a-specific-table
name: Listening to a specific table
- id: listen-to-a-specific-table
name: Listen to a specific table
code: |
```dart
supabase.channel('public:countries').on(
@@ -708,8 +743,8 @@ functions:
},
).subscribe();
```
- id: listening-to-inserts
name: Listening to inserts
- id: listen-to-inserts
name: Listen to inserts
code: |
```dart
supabase.channel('public:countries').on(
@@ -720,8 +755,8 @@ functions:
},
).subscribe();
```
- id: listening-to-updates
name: Listening to updates
- id: listen-to-updates
name: Listen to updates
description: |
By default, Supabase will send only the updated record. If you want to receive the previous values as well you can
enable full replication for the table you are listening too:
@@ -739,8 +774,8 @@ functions:
},
).subscribe();
```
- id: listening-to-deletes
name: Listening to deletes
- id: listen-to-deletes
name: Listen to deletes
description: |
By default, Supabase does not send deleted records. If you want to receive the deleted record you can
enable full replication for the table you are listening too:
@@ -758,8 +793,8 @@ functions:
},
).subscribe();
```
- id: listening-to-multiple-events
name: Listening to multiple events
- id: listen-to-multiple-events
name: Listen to multiple events
description: You can chain listeners if you want to listen to multiple events for each table.
code: |
```dart
@@ -774,7 +809,7 @@ functions:
}).subscribe();
```
- id: listening-to-row-level-changes
name: Listening to row level changes
name: Listen to row level changes
description: You can listen to individual rows using the format `{table}:{col}=eq.{val}` - where `{col}` is the column name, and `{val}` is the value which you want to match.
code: |
```dart
@@ -841,8 +876,8 @@ functions:
- `stream()` will emit the initial data as well as any further change on the database as `Stream` of `List<Map<String, dynamic>>` by combining Postgrest and Realtime.
- Takes a list of primary key columns as its argument.
examples:
- id: listening-to-a-specific-table
name: Listening to a specific table
- id: listen-to-a-specific-table
name: Listen to a specific table
isSpotlight: true
code: |
```dart
+12 -12
View File
@@ -1138,8 +1138,8 @@ functions:
})
.subscribe()
```
- id: listening-to-a-specific-table
name: Listening to a specific table
- id: listen-to-a-specific-table
name: Listen to a specific table
code: |
```js
const mySubscription = supabase
@@ -1149,8 +1149,8 @@ functions:
})
.subscribe()
```
- id: listening-to-inserts
name: Listening to inserts
- id: listen-to-inserts
name: Listen to inserts
code: |
```js
const mySubscription = supabase
@@ -1160,8 +1160,8 @@ functions:
})
.subscribe()
```
- id: listening-to-updates
name: Listening to updates
- id: listen-to-updates
name: Listen to updates
description: |
By default, Supabase will send only the updated record. If you want to receive the previous values as well you can
enable full replication for the table you are listening too:
@@ -1178,8 +1178,8 @@ functions:
})
.subscribe()
```
- id: listening-to-deletes
name: Listening to deletes
- id: listen-to-deletes
name: Listen to deletes
description: |
By default, Supabase does not send deleted records. If you want to receive the deleted record you can
enable full replication for the table you are listening too:
@@ -1196,8 +1196,8 @@ functions:
})
.subscribe()
```
- id: listening-to-multiple-events
name: Listening to multiple events
- id: listen-to-multiple-events
name: Listen to multiple events
description: You can chain listeners if you want to listen to multiple events for each table.
code: |
```js
@@ -1208,7 +1208,7 @@ functions:
.subscribe()
```
- id: listening-to-row-level-changes
name: Listening to row level changes
name: Listen to row level changes
description: You can listen to individual rows using the format `{table}:{col}=eq.{val}` - where `{col}` is the column name, and `{val}` is the value which you want to match.
notes: |
- ``eq`` filter works with all database types as under the hood, it's casting both the filter value and the database value to the correct type and then comparing them.
@@ -1272,7 +1272,7 @@ functions:
name: List buckets
isSpotlight: true
code: |
```js
```ts
const { data, error } = await supabase
.storage
.listBuckets()
+106 -20
View File
@@ -156,7 +156,7 @@ functions:
password: 'example-password',
})
```
- id: sign-up-with-additional-user-metadata.
- id: sign-up-with-additional-user-metadata
name: Sign up with additional user metadata
isSpotlight: false
code: |
@@ -174,6 +174,22 @@ functions:
}
)
```
- id: sign-up-with-redirect
name: Sign up with a redirect URL
description: |
- See [redirect URLs and wildcards](/docs/guides/auth/overview#redirect-urls-and-wildcards) to add additional redirect URLs to your project.
code: |
```js
const { data, error } = await supabase.auth.signUp(
{
email: 'example@email.com',
password: 'example-password',
options: {
emailRedirectTo: 'https://example.com/welcome'
}
}
)
```
- id: sign-in-with-password
title: 'signInWithPassword()'
$ref: '@supabase/gotrue-js.GoTrueClient.signInWithPassword'
@@ -217,6 +233,7 @@ functions:
- If you're using phone, you can configure whether you want the user to receive a OTP.
- The magic link's destination URL is determined by the [`SITE_URL`](/docs/reference/auth/config#site_url).
- See [redirect URLs and wildcards](/docs/guides/auth/overview#redirect-urls-and-wildcards) to add additional redirect URLs to your project.
- Magic links and OTPs share the same implementation. To send users a one-time code instead of a magic link, [modify the magic link email template](https://app.supabase.com/project/_/auth/templates) to include `{{ .Token }}` instead of `{{ .ConfirmationURL }}`.
examples:
- id: sign-in-with-email
name: Sign in with email
@@ -3980,42 +3997,111 @@ functions:
- When a delete occurs, the contents of old_record will be broadcast to all subscribers to that table so ensure that each table's replica identity only contains information that is safe to expose publicly.
- The channel name must exactly match the schema/table/filter you want to listen to separated by colons. See below examples for additional context.
examples:
- id: listen-to-broadcast
name: Listen to broadcast messages
isSpotlight: true
code: |
```js
supabase
.channel('any')
.on('broadcast', { event: 'cursor-pos' }, payload => {
console.log('Cursor position received!', payload)
})
.subscribe((status) => {
if (status === 'SUBSCRIBED') {
channel.send({
type: 'broadcast',
event: 'cursor-pos',
payload: { x: Math.random(), y: Math.random() },
})
}
})
```
- id: listen-to-presence-sync
name: Listen to presence sync
isSpotlight: true
code: |
```js
const channel = supabase.channel('any')
channel
.on('presence', { event: 'sync' }, () => {
console.log('Synced presence state: ', channel.presenceState())
})
.subscribe(async (status) => {
if (status === 'SUBSCRIBED') {
await channel.track({ online_at: new Date().toISOString() })
}
})
```
- id: listen-to-presence-join
name: Listen to presence join
isSpotlight: true
code: |
```js
const channel = supabase.channel('any')
channel
.on('presence', { event: 'join' }, ({ newPresences }) => {
console.log('Newly joined presences: ', newPresences)
})
.subscribe(async (status) => {
if (status === 'SUBSCRIBED') {
await channel.track({ online_at: new Date().toISOString() })
}
})
```
- id: listen-to-presence-leave
name: Listen to presence leave
isSpotlight: true
code: |
```js
const channel = supabase.channel('any')
channel
.on('presence', { event: 'leave' }, ({ leftPresences }) => {
console.log('Newly left presences: ', leftPresences)
})
.subscribe(async (status) => {
if (status === 'SUBSCRIBED') {
await channel.track({ online_at: new Date().toISOString() })
await channel.untrack()
}
})
```
- id: listen-to-all-database-changes
name: Listen to all database changes
isSpotlight: true
code: |
```js
supabase
.channel('*')
.channel('any')
.on('postgres_changes', { event: '*', schema: '*' }, payload => {
console.log('Change received!', payload)
})
.subscribe()
```
- id: listening-to-a-specific-table
name: Listening to a specific table
- id: listen-to-a-specific-table
name: Listen to a specific table
code: |
```js
supabase
.channel('public:countries')
.channel('any')
.on('postgres_changes', { event: '*', schema: 'public', table: 'countries' }, payload => {
console.log('Change received!', payload)
})
.subscribe()
```
- id: listening-to-inserts
name: Listening to inserts
- id: listen-to-inserts
name: Listen to inserts
code: |
```js
supabase
.channel('public:countries')
.channel('any')
.on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'countries' }, payload => {
console.log('Change received!', payload)
})
.subscribe()
```
- id: listening-to-updates
name: Listening to updates
- id: listen-to-updates
name: Listen to updates
description: |
By default, Supabase will send only the updated record. If you want to receive the previous values as well you can
enable full replication for the table you are listening to:
@@ -4026,14 +4112,14 @@ functions:
code: |
```js
supabase
.channel('public:countries')
.channel('any')
.on('postgres_changes', { event: 'UPDATE', schema: 'public', table: 'countries' }, payload => {
console.log('Change received!', payload)
})
.subscribe()
```
- id: listening-to-deletes
name: Listening to deletes
- id: listen-to-deletes
name: Listen to deletes
description: |
By default, Supabase does not send deleted records. If you want to receive the deleted record you can
enable full replication for the table you are listening too:
@@ -4044,32 +4130,32 @@ functions:
code: |
```js
supabase
.channel('public:countries')
.channel('any')
.on('postgres_changes', { event: 'DELETE', schema: 'public', table: 'countries' }, payload => {
console.log('Change received!', payload)
})
.subscribe()
```
- id: listening-to-multiple-events
name: Listening to multiple events
- id: listen-to-multiple-events
name: Listen to multiple events
description: You can chain listeners if you want to listen to multiple events for each table.
code: |
```js
supabase
.channel('public:countries')
.channel('any')
.on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'countries' }, handleRecordInserted)
.on('postgres_changes', { event: 'DELETE', schema: 'public', table: 'countries' }, handleRecordDeleted)
.subscribe()
```
- id: listening-to-row-level-changes
name: Listening to row level changes
name: Listen to row level changes
description: You can listen to individual rows using the format `{table}:{col}=eq.{val}` - where `{col}` is the column name, and `{val}` is the value which you want to match.
notes: |
- ``eq`` filter works with all database types as under the hood, it's casting both the filter value and the database value to the correct type and then comparing them.
code: |
```js
supabase
.channel('public:countries:id=eq.200')
.channel('any')
.on('postgres_changes', { event: 'UPDATE', schema: 'public', table: 'countries', filter: 'id=eq.200' }, handleRecordUpdated)
.subscribe()
```
@@ -4127,7 +4213,7 @@ functions:
name: List buckets
isSpotlight: true
code: |
```js
```ts
const { data, error } = await supabase
.storage
.listBuckets()
-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'
@@ -37,6 +37,8 @@ const ExitSurvey: FC<Props> = ({ freeTier, subscription, onSelectBack }) => {
// Tracking submitting state separately outside of form component cause of
// the additional dynamic confirmation modal that we're doing
const [isSubmitting, setIsSubmitting] = useState(false)
const [isSubmittingDowngradeModal, setIsSubmittingDowngradeModal] = useState(false)
const [isSuccessful, setIsSuccessful] = useState(false)
const [showConfirmModal, setShowConfirmModal] = useState(false)
const [subscriptionPreview, setSubscriptionPreview] = useState<SubscriptionPreview>()
@@ -123,6 +125,7 @@ const ExitSurvey: FC<Props> = ({ freeTier, subscription, onSelectBack }) => {
try {
setIsSubmitting(true)
setIsSubmittingDowngradeModal(true)
// Trigger subscription downgrade
const tier = freeTier.prices[0].id
@@ -175,6 +178,7 @@ const ExitSurvey: FC<Props> = ({ freeTier, subscription, onSelectBack }) => {
})
} finally {
setIsSubmitting(false)
setIsSubmittingDowngradeModal(false)
}
}
@@ -293,7 +297,10 @@ const ExitSurvey: FC<Props> = ({ freeTier, subscription, onSelectBack }) => {
visible={showConfirmModal}
size="large"
header="Downgrading project to free"
onCancel={() => setShowConfirmModal(false)}
onCancel={() => {
setShowConfirmModal(false)
setIsSubmitting(false)
}}
>
<div className="space-y-4 py-4">
<Modal.Content>
@@ -314,14 +321,21 @@ const ExitSurvey: FC<Props> = ({ freeTier, subscription, onSelectBack }) => {
<Modal.Separator />
<Modal.Content>
<div className="flex items-center gap-2">
<Button block type="default" onClick={() => setShowConfirmModal(false)}>
<Button
block
type="default"
onClick={() => {
setShowConfirmModal(false)
setIsSubmitting(false)
}}
>
Cancel
</Button>
<Button
block
htmlType="submit"
loading={isSubmitting}
disabled={isSubmitting}
loading={isSubmittingDowngradeModal}
disabled={isSubmittingDowngradeModal}
onClick={() => downgradeProject()}
>
Confirm
@@ -1,6 +1,6 @@
import { FC, useEffect, useState } from 'react'
import { Button, Input, Form, Modal, Listbox, IconPlus, IconDatabase } from 'ui'
import { PostgresExtension, PostgresSchema } from '@supabase/postgres-meta'
import type { PostgresExtension, PostgresSchema } from '@supabase/postgres-meta'
import { useStore } from 'hooks'
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
@@ -1,7 +1,7 @@
import { FC, useState } from 'react'
import { observer } from 'mobx-react-lite'
import { Badge, Toggle } from 'ui'
import { PostgresPublication, PostgresTable } from '@supabase/postgres-meta'
import type { PostgresPublication, PostgresTable } from '@supabase/postgres-meta'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { checkPermissions, useStore } from 'hooks'
@@ -1,7 +1,7 @@
import { FC, useState } from 'react'
import { observer } from 'mobx-react-lite'
import { Button, Input, IconChevronLeft, IconSearch, IconAlertCircle } from 'ui'
import { PostgresPublication } from '@supabase/postgres-meta'
import type { PostgresPublication } from '@supabase/postgres-meta'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { checkPermissions, useStore } from 'hooks'
@@ -5,7 +5,7 @@ import { Input, Button, IconSearch, IconPlus, IconChevronLeft, IconEdit3, IconTr
import { useStore } from 'hooks'
import Table from 'components/to-be-cleaned/Table'
import NoSearchResults from 'components/to-be-cleaned/NoSearchResults'
import { PostgresTable } from '@supabase/postgres-meta'
import type { PostgresTable } from '@supabase/postgres-meta'
interface Props {
selectedTable: PostgresTable
@@ -25,11 +25,11 @@ const ColumnList: FC<Props> = ({
const { meta } = useStore()
const [filterString, setFilterString] = useState<string>('')
const columns =
filterString.length === 0
(filterString.length === 0
? selectedTable.columns
: selectedTable.columns.filter((column: any) => column.name.includes(filterString))
: selectedTable.columns?.filter((column: any) => column.name.includes(filterString))) ?? []
const isLocked = meta.excludedSchemas.includes(selectedTable?.schema ?? '')
const isLocked = meta.excludedSchemas.includes(selectedTable.schema ?? '')
return (
<>
@@ -18,7 +18,7 @@ import { partition } from 'lodash'
import { useStore } from 'hooks'
import Table from 'components/to-be-cleaned/Table'
import NoSearchResults from 'components/to-be-cleaned/NoSearchResults'
import { PostgresTable, PostgresSchema } from '@supabase/postgres-meta'
import type { PostgresTable, PostgresSchema } from '@supabase/postgres-meta'
interface Props {
selectedSchema: string
@@ -1,6 +1,6 @@
import { useRouter } from 'next/router'
import { Button, IconTerminal, IconMaximize2, IconMinimize2, IconBookOpen, IconCode } from 'ui'
import { useProjectSettings } from 'hooks'
import { useProjectSettingsQuery } from 'data/config/project-settings-query'
import { useAccessTokens } from 'hooks/queries/useAccessTokens'
import { Commands } from './Functions.types'
import CommandRender from 'components/interfaces/Functions/CommandRender'
@@ -18,17 +18,17 @@ const TerminalInstructions: FC<Props> = ({ closable = false }) => {
const { ui } = useStore()
const { tokens } = useAccessTokens()
const { services } = useProjectSettings(ref as string | undefined)
const { data: settings } = useProjectSettingsQuery({ projectRef: ref as string })
const [showInstructions, setShowInstructions] = useState(!closable)
// Get the API service
const API_SERVICE_ID = 1
const apiService = (services ?? []).find((x: any) => x.app.id == API_SERVICE_ID)
const apiKeys = apiService?.service_api_keys ?? []
const anonKey = apiKeys.find((x: any) => x.name === 'anon key')?.api_key
const apiService = settings?.autoApiService
const anonKey = apiService?.service_api_keys.find((x: any) => x.name === 'anon key')
? apiService.defaultApiKey
: undefined
const endpoint = apiService?.app_config.endpoint ?? ''
const endpoint = apiService?.endpoint ?? ''
const endpointSections = endpoint.split('.')
const functionsEndpoint = [
...endpointSections.slice(0, 1),
@@ -5,9 +5,8 @@ import { Input, IconLoader, IconAlertCircle } from 'ui'
import { JwtSecretUpdateStatus } from '@supabase/shared-types/out/events'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { checkPermissions, useJwtSecretUpdateStatus, useProjectSettings } from 'hooks'
import { DEFAULT_PROJECT_API_SERVICE_ID, IS_PLATFORM } from 'lib/constants'
import { PROJECT_ENDPOINT_PROTOCOL } from 'pages/api/constants'
import { useProjectSettingsQuery } from 'data/config/project-settings-query'
import { checkPermissions, useJwtSecretUpdateStatus } from 'hooks'
import Snippets from 'components/to-be-cleaned/Docs/Snippets'
import Panel from 'components/ui/Panel'
import SimpleCodeBlock from 'components/to-be-cleaned/SimpleCodeBlock'
@@ -23,10 +22,10 @@ const APIKeys = () => {
const [selectedLanguage, setSelectedLanguage] = useState(availableLanguages[0])
const {
services,
data: settings,
isError: isProjectSettingsError,
isLoading: isProjectSettingsLoading,
} = useProjectSettings(ref as string | undefined)
} = useProjectSettingsQuery({ projectRef: ref as string })
const {
jwtSecretUpdateStatus,
@@ -37,8 +36,7 @@ const APIKeys = () => {
const canReadAPIKeys = checkPermissions(PermissionAction.READ, 'service_api_keys')
// Get the API service
const apiService = (services ?? []).find((x: any) => x.app.id == DEFAULT_PROJECT_API_SERVICE_ID)
const apiConfig = apiService?.app_config ?? {}
const apiService = settings?.autoApiService
const apiKeys = apiService?.service_api_keys ?? []
// API keys should not be empty. However it can be populated with a delay on project creation
@@ -46,7 +44,7 @@ const APIKeys = () => {
const isNotUpdatingJwtSecret =
jwtSecretUpdateStatus === undefined || jwtSecretUpdateStatus === JwtSecretUpdateStatus.Updated
const apiUrl = `${IS_PLATFORM ? 'https' : PROJECT_ENDPOINT_PROTOCOL}://${apiConfig.endpoint}`
const apiUrl = `${apiService?.protocol ?? 'https'}://${apiService?.endpoint ?? '-'}`
const anonKey = apiKeys.find((key: any) => key.tags === 'anon')
const clientInitSnippet: any = Snippets.init(apiUrl)
@@ -111,7 +109,7 @@ const APIKeys = () => {
<div className="space-y-2">
<p className="text-sm">API Key</p>
<div className="flex items-center space-x-1 -ml-1">
{anonKey.tags?.split(',').map((x: any, i: number) => (
{anonKey?.tags?.split(',').map((x: any, i: number) => (
<code key={`${x}${i}`} className="text-xs">
{x}
</code>
@@ -121,7 +119,7 @@ const APIKeys = () => {
</div>
}
copy={canReadAPIKeys && isNotUpdatingJwtSecret}
reveal={anonKey.tags !== 'anon' && canReadAPIKeys && isNotUpdatingJwtSecret}
reveal={anonKey?.tags !== 'anon' && canReadAPIKeys && isNotUpdatingJwtSecret}
value={
!canReadAPIKeys
? 'You need additional permissions to view API keys'
@@ -129,7 +127,7 @@ const APIKeys = () => {
? 'JWT secret update failed, new API key may have issues'
: jwtSecretUpdateStatus === JwtSecretUpdateStatus.Updating
? 'Updating JWT secret...'
: anonKey.api_key
: apiService?.defaultApiKey
}
onChange={() => {}}
descriptionText={
@@ -613,10 +613,8 @@ values
sql: `
--
-- For use with:
-- https://github.com/supabase/supabase/tree/master/examples/todo-list/nextjs-todo-list or
-- https://github.com/supabase/supabase/tree/master/examples/todo-list/react-todo-list or
-- https://github.com/supabase/supabase/tree/master/examples/todo-list/sveltejs-todo-list or
-- https://github.com/supabase/supabase/tree/master/examples/todo-list/vue3-ts-todo-list
-- https://github.com/supabase/examples-archive/tree/main/supabase-js-v1/todo-list
--
create table todos (
@@ -3,18 +3,15 @@ import { useRouter } from 'next/router'
import { JwtSecretUpdateError, JwtSecretUpdateStatus } from '@supabase/shared-types/out/events'
import { IconAlertCircle, Input } from 'ui'
import {
useStore,
useProjectSettings,
useProjectPostgrestConfig,
useJwtSecretUpdateStatus,
} from 'hooks'
import { configKeys } from 'data/config/keys'
import { useQueryClient } from '@tanstack/react-query'
import { useProjectSettingsQuery } from 'data/config/project-settings-query'
import { useStore, useProjectPostgrestConfig, useJwtSecretUpdateStatus } from 'hooks'
import Panel from 'components/ui/Panel'
import PostgrestConfig from './PostgrestConfig'
import { DisplayApiSettings } from 'components/ui/ProjectSettings'
import { JWT_SECRET_UPDATE_ERROR_MESSAGES } from './API.constants'
import { PROJECT_ENDPOINT_PROTOCOL } from 'pages/api/constants'
import { IS_PLATFORM } from 'lib/constants'
import JWTSettings from './JWTSettings'
@@ -26,8 +23,9 @@ const ServiceList: FC<Props> = ({ projectRef }) => {
const { ui } = useStore()
const router = useRouter()
const { ref } = router.query
const queryClient = useQueryClient()
const { services, isError, mutateSettings } = useProjectSettings(ref as string | undefined)
const { data: settings, isError } = useProjectSettingsQuery({ projectRef: ref as string })
const { mutateConfig } = useProjectPostgrestConfig(ref as string | undefined)
const { jwtSecretUpdateError, jwtSecretUpdateStatus }: any = useJwtSecretUpdateStatus(ref)
@@ -41,7 +39,7 @@ const ServiceList: FC<Props> = ({ projectRef }) => {
switch (jwtSecretUpdateStatus) {
case Updated:
mutateConfig()
mutateSettings()
queryClient.invalidateQueries({ queryKey: configKeys.settings(projectRef) })
ui.setNotification({ category: 'success', message: 'Successfully updated JWT secret' })
break
case Failed:
@@ -57,9 +55,8 @@ const ServiceList: FC<Props> = ({ projectRef }) => {
}, [jwtSecretUpdateStatus])
// Get the API service
const API_SERVICE_ID = 1
const apiService = services ? services.find((x: any) => x.app.id == API_SERVICE_ID) : {}
const apiConfig = apiService?.app_config
const apiService = settings?.autoApiService
const apiUrl = `${apiService?.protocol ?? 'https'}://${apiService?.endpoint ?? '-'}`
return (
<div>
@@ -79,9 +76,7 @@ const ServiceList: FC<Props> = ({ projectRef }) => {
readOnly
disabled
className="input-mono"
value={`${IS_PLATFORM ? 'https' : PROJECT_ENDPOINT_PROTOCOL}://${
apiConfig?.endpoint ?? '-'
}`}
value={apiUrl}
descriptionText="A RESTful endpoint for querying and managing your database."
layout="horizontal"
/>
@@ -134,7 +134,7 @@ const CustomDomainVerify = ({ projectRef, customDomain, settings }: CustomDomain
<DNSRecord
type="CNAME"
name={customDomain.hostname}
value={settings?.autoApiService.app_config.endpoint ?? 'Loading...'}
value={settings?.autoApiService.endpoint ?? 'Loading...'}
/>
)}
Loaded 100 of 157 files, more files were not shown because too many files have changed in this diff. Show more