Merge branch 'master' into feat/kotlin-docs

This commit is contained in:
Greg Richardson committed 2023-05-10 16:51:34 -06:00
commit 0bcef2e27b
117 files changed
+1500 -1472

No files matched your search

+16 -2
View File
@@ -11,9 +11,16 @@ jobs:
steps:
- name: Check out repo
uses: actions/checkout@v3
- name: Setup node
uses: actions/setup-node@v3
with:
node-version: 18
cache: 'npm'
- name: Download dependencies
run: npm ci
- name: Run prettier
run: |-
npx prettier -c 'apps/**/*.{js,jsx,ts,tsx,css,md,json}'
npx prettier -c 'apps/**/*.{js,jsx,ts,tsx,css,md,mdx,json}'
# [Joshen] Temp disable the following due to an issue
# https://github.com/EPMatt/reviewdog-action-prettier/issues/34
# uses: EPMatt/reviewdog-action-prettier@v1
@@ -42,9 +49,16 @@ jobs:
steps:
- name: Check out repo
uses: actions/checkout@v3
- name: Setup node
uses: actions/setup-node@v3
with:
node-version: 18
cache: 'npm'
- name: Download dependencies
run: npm ci
- name: Run prettier
run: |-
npx prettier -c 'i18n/**/*.{js,jsx,ts,tsx,css,md,json}'
npx prettier -c 'i18n/**/*.{js,jsx,ts,tsx,css,md,mdx,json}'
# uses: EPMatt/reviewdog-action-prettier@v1
# with:
# github_token: ${{ secrets.github_token }}
+3 -1
View File
@@ -5,5 +5,7 @@
"singleQuote": true,
"printWidth": 100,
"endOfLine": "lf",
"sqlKeywordCase": "lower"
"sqlKeywordCase": "lower",
"pluginSearchDirs": false,
"plugins": ["prettier-plugin-sql-cst"]
}
@@ -3,4 +3,4 @@
- Click on [`Providers`](https://app.supabase.com/project/_/auth/providers) under the Configuration section
- Click on **{props.provider}** from the accordion list to expand and turn **{props.provider} Enabled** to ON
- Enter your **{props.provider} Client ID** and **{props.provider} Client Secret** saved in the previous step
- Click `Save`
- Click `Save`
@@ -297,6 +297,10 @@ export const cli = {
items: [
{ name: 'Overview', url: '/guides/cli' },
{ name: 'Managing Environments', url: '/guides/cli/managing-environments' },
{
name: 'Using environment variables in config.toml',
url: '/guides/cli/using-environment-variables-in-config',
},
],
}
@@ -858,6 +862,10 @@ export const supabase_cli = {
items: [
{ name: 'Overview', url: '/guides/cli' },
{ name: 'Managing Environments', url: '/guides/cli/managing-environments' },
{
name: 'Using environment variables in config.toml',
url: '/guides/cli/using-environment-variables-in-config',
},
{
name: 'Reference',
url: undefined,
@@ -936,6 +944,10 @@ export const platform = {
name: 'HTTP and Project Issues',
url: '/guides/platform/troubleshooting',
},
{
name: 'High CPU Usage',
url: '/guides/platform/exhaust-cpu',
},
],
},
],
@@ -56,7 +56,7 @@ const TopNavBar: FC = () => {
}
return (
<nav className="h-[60px] border-b px-4 backdrop-blur backdrop-filter bg-white-1200 dark:bg-blackA-300">
<nav className="h-[60px] border-b px-4 backdrop-blur backdrop-filter bg-white-1200 bg-scale-200/90">
<div className="max-w-[1400px] grid grid-cols-12 mx-auto gap-4 px-5 h-full">
<div className="col-span-3 flex items-center">
<button className="mr-4 block stroke-2 lg:hidden" onClick={toggleMobileMenu}>
@@ -56,7 +56,7 @@ const TopNavBarRef: FC = () => {
}
return (
<nav className="h-[60px] border-b backdrop-blur backdrop-filter bg-white-1200 dark:bg-blackA-300">
<nav className="h-[60px] border-b backdrop-blur backdrop-filter bg-white-1200 dark:bg-scale-200/90">
<div className="px-5 max-w-7xl mx-auto flex gap-3 justify-between items-center h-full">
<div className={['lg:hidden'].join(' ')}>
<Link href="/">
@@ -89,7 +89,6 @@ The signIn() method has been deprecated in favor of more explicit method signatu
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
{/* prettier-ignore */}
```ts
const { user, error } = await supabase
.auth
@@ -98,7 +97,6 @@ const { user, error } = await supabase
</TabPanel>
<TabPanel id="2.0x" label="After">
{/* prettier-ignore */}
```ts
const {
data: { user },
@@ -128,7 +126,6 @@ const {
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
{/* prettier-ignore */}
```ts
const { error } = await supabase
.auth
@@ -137,7 +134,6 @@ const { error } = await supabase
</TabPanel>
<TabPanel id="2.0x" label="After">
{/* prettier-ignore */}
```ts
const { error } = await supabase
.auth
@@ -165,7 +161,6 @@ const { error } = await supabase
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
{/* prettier-ignore */}
```ts
const { error } = await supabase
.auth
@@ -174,7 +169,6 @@ const { error } = await supabase
</TabPanel>
<TabPanel id="2.0x" label="After">
{/* prettier-ignore */}
```ts
const { error } = await supabase
.auth
@@ -201,7 +195,6 @@ const { error } = await supabase
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
{/* prettier-ignore */}
```ts
const { error } = await supabase
.auth
@@ -210,7 +203,6 @@ const { error } = await supabase
</TabPanel>
<TabPanel id="2.0x" label="After">
{/* prettier-ignore */}
```ts
const { error } = await supabase
.auth
@@ -238,7 +230,6 @@ const { error } = await supabase
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
{/* prettier-ignore */}
```ts
const { error } = await supabase
.auth
@@ -248,16 +239,15 @@ const { error } = await supabase
</TabPanel>
<TabPanel id="2.0x" label="After">
{/* prettier-ignore */}
```ts
const { data, error } = await supabase
.auth
.signInWithOtp({ phone })
// After receiving a SMS with a OTP.
const { data, error } = await supabase
.auth
.verifyOtp({ phone, token })
.auth
.verifyOtp({ phone, token })
```
</TabPanel>
@@ -281,7 +271,6 @@ const { data, error } = await supabase
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
{/* prettier-ignore */}
```ts
const { data, error } = await supabase
.auth
@@ -291,7 +280,6 @@ const { data, error } = await supabase
</TabPanel>
<TabPanel id="2.0x" label="After">
{/* prettier-ignore */}
```ts
const { data, error } = await supabase
.auth
@@ -390,7 +378,6 @@ const { user } = session
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
{/* prettier-ignore */}
```ts
const { user, error } = await supabase
.auth
@@ -435,7 +422,6 @@ const { user, error } = supabase.auth.setAuth(access_token)
</TabPanel>
<TabPanel id="2.0x" label="After">
{/* prettier-ignore */}
```ts
const supabase = createClient(
SUPABASE_URL,
@@ -486,7 +472,6 @@ Previously, these methods return inserted/updated/deleted rows by default (which
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
{/* prettier-ignore */}
```ts
const { data, error } = await supabase
.from('my_table')
@@ -495,7 +480,6 @@ const { data, error } = await supabase
</TabPanel>
<TabPanel id="2.0x" label="After">
{/* prettier-ignore */}
```ts
const { data, error } = await supabase
.from('my_table')
@@ -524,7 +508,6 @@ const { data, error } = await supabase
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
{/* prettier-ignore */}
```ts
const { data, error } = await supabase
.from('my_table')
@@ -534,7 +517,6 @@ const { data, error } = await supabase
</TabPanel>
<TabPanel id="2.0x" label="After">
{/* prettier-ignore */}
```ts
const { data, error } = await supabase
.from('my_table')
@@ -564,7 +546,6 @@ const { data, error } = await supabase
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
{/* prettier-ignore */}
```ts
const { data, error } = await supabase
.from('my_table')
@@ -573,7 +554,6 @@ const { data, error } = await supabase
</TabPanel>
<TabPanel id="2.0x" label="After">
{/* prettier-ignore */}
```ts
const { data, error } = await supabase
.from('my_table')
@@ -602,7 +582,6 @@ const { data, error } = await supabase
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
{/* prettier-ignore */}
```ts
const { data, error } = await supabase
.from('my_table')
@@ -612,7 +591,6 @@ const { data, error } = await supabase
</TabPanel>
<TabPanel id="2.0x" label="After">
{/* prettier-ignore */}
```ts
const { data, error } = await supabase
.from('my_table')
@@ -14,9 +14,9 @@ hideTitle: true
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
The Supabase Auth Server (GoTrue) is a JSON Web Token (JWT)-based API for managing users and issuing access tokens.
The Supabase Auth Server (GoTrue) is a JSON Web Token (JWT)-based API for managing users and issuing access tokens.
GoTrue is an open-source API written in Golang, that acts as a self-standing API service for handling user registration and authentication for JAM projects. It's based on OAuth2 and JWT and handles user signup, authentication, and custom user data.
GoTrue is an open-source API written in Golang, that acts as a self-standing API service for handling user registration and authentication for JAM projects. It's based on OAuth2 and JWT and handles user signup, authentication, and custom user data.
</RefSubLayout.Details>
@@ -14,12 +14,12 @@ hideTitle: true
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
An S3 compatible object storage service that integrates with Postgres.
An S3 compatible object storage service that integrates with Postgres.
- Uses Postgres as it's datastore for storing metadata
- Authorization rules are written as Postgres Row Level Security policies
- Integrates with S3 as the storage backend (with more in the pipeline!)
- Extremely lightweight and performant
- Uses Postgres as it's datastore for storing metadata
- Authorization rules are written as Postgres Row Level Security policies
- Integrates with S3 as the storage backend (with more in the pipeline!)
- Extremely lightweight and performant
</RefSubLayout.Details>
+2 -2
View File
@@ -13,9 +13,9 @@ hideTitle: true
</div>
<div className="max-w-xl">
This reference documents every object and method available in Supabase's Swift library, [supabase-swift](https://github.com/supabase-community/supabase-swift). You can use supabase-swift to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
This reference documents every object and method available in Supabase's Swift library, [supabase-swift](https://github.com/supabase-community/supabase-swift). You can use supabase-swift to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
We also provide a [supabase](https://pub.dev/packages/supabase) package for non-Swift projects.
We also provide a [supabase](https://pub.dev/packages/supabase) package for non-Swift projects.
</div>
@@ -4,9 +4,6 @@ title: 'Initializing'
slug: initializing
---
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
## Flutter
For `supabase-flutter`, you will be using the static `initialize()` method on `Supabase` class.
@@ -30,8 +27,9 @@ final supabase = Supabase.instance.client;
### Call `initialize()` with custom headers
You can pass `headers` to initialize your Supabase client with customer headers.
You can pass `headers` to initialize your Supabase client with customer headers.
Here is an example of passing a custom auth header to Supabase client.
```dart title=main.dart
Future<void> main() async {
await Supabase.initialize(
@@ -4,9 +4,6 @@ title: 'Installing'
slug: installing
---
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
## Flutter
For Flutter project, you can use [supabase_flutter](https://pub.dev/packages/supabase_flutter) package.
@@ -5,9 +5,6 @@ slug: generating-types
custom_edit_url: https://github.com/supabase/supabase/edit/master/spec/supabase_js_v1.yml
---
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
Supabase will soon release native type generators that dump your database types for various languages. For now, we support TypeScript [through third-party tools](/docs/guides/database/api/generating-types).
## Usage with TypeScript
@@ -5,9 +5,6 @@ slug: initializing
custom_edit_url: https://github.com/supabase/supabase/edit/master/spec/supabase_js_v1.yml
---
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
You can initialize a new Supabase client using the `createClient()` method.
The Supabase client is your entrypoint to the rest of the Supabase functionality
@@ -117,7 +114,7 @@ const supabase = createClient('https://xyzcompany.supabase.co', 'public-anon-key
```
By default the API server points to the `public` schema. You can enable other database schemas within the Dashboard.
Go to `Settings > API > Schema` and add the schema which you want to expose to the API. You also need to grant `USAGE` on your new schema with the grants you desire, such as `SELECT, INSERT, UPDATE, DELETE`.
Go to `Settings > API > Schema` and add the schema which you want to expose to the API. You also need to grant `USAGE` on your new schema with the grants you desire, such as `SELECT, INSERT, UPDATE, DELETE`.
Note: each client connection can only access a single schema, so the code above can access the `other_schema` schema but cannot access the `public` schema.
@@ -5,9 +5,6 @@ slug: installing
custom_edit_url: https://github.com/supabase/supabase/edit/master/spec/supabase_js_v1.yml
---
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
All JavaScript libraries are built directly by the Supabase team.
Other languages are built by the community and supported by Supabase.
@@ -1,16 +1,12 @@
---
id: changing-timezones
title: "Changing Timezones"
title: 'Changing Timezones'
slug: changing-timezones
custom_edit_url: https://github.com/supabase/supabase/edit/master/web/spec/postgres.yml
---
import Tabs from '@theme/Tabs';
import TabsPanel from '@theme/TabsPanel';
Data types.
<Tabs
defaultActiveId="sql"
groupId="reference/postgres"
@@ -19,38 +15,22 @@ Data types.
<TabsPanel id="sql" label="sql">
```sql
alter database postgres
alter database postgres
set timezone to 'America/New_York';
```
</TabsPanel>
</Tabs>
## Notes
- View a full list of timezones on [Wikipedia](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones).
## Examples
### Change timezone
<Tabs
defaultActiveId="sql"
groupId="reference/postgres"
@@ -59,11 +39,10 @@ set timezone to 'America/New_York';
<TabsPanel id="sql" label="sql">
```sql
alter database postgres
alter database postgres
set timezone to 'America/New_York';
```
</TabsPanel>
</Tabs>
@@ -77,7 +56,6 @@ Get a full list of timezones supported by your database. This will return the fo
- `utc_offset`: Offset from UTC (positive means east of Greenwich)
- `is_dst`: True if currently observing daylight savings
<Tabs
defaultActiveId="sql"
groupId="reference/postgres"
@@ -87,11 +65,10 @@ Get a full list of timezones supported by your database. This will return the fo
```sql
select name, abbrev, utc_offset, is_dst
from pg_timezone_names()
from pg_timezone_names()
order by name;
```
</TabsPanel>
</Tabs>
@@ -108,12 +85,11 @@ Use `ilike` (case insensitive search) to find specific timezones.
<TabsPanel id="sql" label="sql">
```sql
select *
from pg_timezone_names()
select *
from pg_timezone_names()
where name ilike '%york%';
```
</TabsPanel>
</Tabs>
</Tabs>
+2 -29
View File
@@ -1,16 +1,12 @@
---
id: columns
title: "Columns"
title: 'Columns'
slug: columns
custom_edit_url: https://github.com/supabase/supabase/edit/master/web/spec/postgres.yml
---
import Tabs from '@theme/Tabs';
import TabsPanel from '@theme/TabsPanel';
Creating columns.
<Tabs
defaultActiveId="sql"
groupId="reference/postgres"
@@ -26,33 +22,14 @@ create table table_name (
);
```
</TabsPanel>
</Tabs>
## Examples
### During table creation
<Tabs
defaultActiveId="sql"
groupId="reference/postgres"
@@ -68,15 +45,12 @@ create table table_name (
);
```
</TabsPanel>
</Tabs>
### Create column
<Tabs
defaultActiveId="sql"
groupId="reference/postgres"
@@ -89,7 +63,6 @@ alter table new_table
add new_column text;
```
</TabsPanel>
</Tabs>
</Tabs>
@@ -1,16 +1,12 @@
---
id: connection-strings
title: "Connection Strings"
title: 'Connection Strings'
slug: connection-strings
custom_edit_url: https://github.com/supabase/supabase/edit/master/web/spec/postgres.yml
---
import Tabs from '@theme/Tabs';
import TabsPanel from '@theme/TabsPanel';
There are various ways to connect to your database, depending on the configuration of your Postgres instance and the tool which you are connecting with.
<Tabs
defaultActiveId="sql"
groupId="reference/postgres"
@@ -20,42 +16,27 @@ There are various ways to connect to your database, depending on the configurati
```bash
postgres://postgres:postgres@localhost:5432/postgres
# or
# or
postgresql://postgres:postgres@localhost:5432/postgres
```
</TabsPanel>
</Tabs>
## Notes
- [Official Documentation](https://www.postgresql.org/docs/current/libpq-connect.html).
- Avoid using special characters usernames and passwords. If you use special characters in a connection URL, you'll need to URL encode any special characters.
## Examples
### Basic connection string
If you're using a default setup, your postgres connection string will likely be in the format:
If you're using a default setup, your postgres connection string will likely be in the format:
`postgres://{user}:{password}@{host}:{port}/{database_name}`
<Tabs
defaultActiveId="sql"
groupId="reference/postgres"
@@ -65,11 +46,10 @@ If you're using a default setup, your postgres connection string will likely be
```bash
postgres://postgres:postgres@localhost:5432/postgres
# or
# or
postgresql://postgres:postgres@localhost:5432/postgres
```
</TabsPanel>
</Tabs>
@@ -89,7 +69,6 @@ See full [documentation](http://jdbc.postgresql.org/documentation/head/connect.h
jdbc:postgresql://{host}:{port}/{database_name}
```
</TabsPanel>
</Tabs>
@@ -109,7 +88,6 @@ See full [documentation](http://npgsql.projects.postgresql.org/docs/manual/UserM
Server=host;Port=5432;User Id=username;Password=secret;Database=database_name;
```
</TabsPanel>
</Tabs>
@@ -129,7 +107,6 @@ See full [documentation](http://php.net/manual/en/book.pgsql.php).
host=hostname port=5432 dbname=databasename user=username password=secret
```
</TabsPanel>
</Tabs>
</Tabs>
@@ -1,40 +1,16 @@
---
id: database-passwords
title: "Database Passwords"
title: 'Database Passwords'
slug: database-passwords
custom_edit_url: https://github.com/supabase/supabase/edit/master/web/spec/postgres.yml
---
import Tabs from '@theme/Tabs';
import TabsPanel from '@theme/TabsPanel';
Manage the passwords of your database users using any super user.
## Examples
### Password reset
<Tabs
defaultActiveId="sql"
groupId="reference/postgres"
@@ -43,11 +19,10 @@ Manage the passwords of your database users using any super user.
<TabsPanel id="sql" label="sql">
```sql
alter user postgres
alter user postgres
with password 'new_password';
```
</TabsPanel>
</Tabs>
</Tabs>
@@ -1,40 +1,16 @@
---
id: database-users
title: "Database Users"
title: 'Database Users'
slug: database-users
custom_edit_url: https://github.com/supabase/supabase/edit/master/web/spec/postgres.yml
---
import Tabs from '@theme/Tabs';
import TabsPanel from '@theme/TabsPanel';
Users and Roles are almost interchangeable.
## Examples
### Create New User
<Tabs
defaultActiveId="sql"
groupId="reference/postgres"
@@ -47,7 +23,6 @@ create user new_user
with password 'hello';
```
</TabsPanel>
</Tabs>
</Tabs>
+5 -9
View File
@@ -1,15 +1,11 @@
---
id: index
title: "Getting started"
title: 'Getting started'
slug: getting-started
custom_edit_url: https://github.com/supabase/supabase/edit/master/web/spec/postgres.yml
---
import Tabs from '@theme/Tabs';
import TabsPanel from '@theme/TabsPanel';
PostgreSQL, also known as Postgres, is a free and open-source relational
database management system emphasizing extensibility and SQL compliance.
It was originally named POSTGRES, referring to its origins as a successor
to the Ingres database developed at the University of California, Berkeley.
PostgreSQL, also known as Postgres, is a free and open-source relational
database management system emphasizing extensibility and SQL compliance.
It was originally named POSTGRES, referring to its origins as a successor
to the Ingres database developed at the University of California, Berkeley.
@@ -1,42 +1,19 @@
---
id: publications
title: "Publications"
title: 'Publications'
slug: publications
custom_edit_url: https://github.com/supabase/supabase/edit/master/web/spec/postgres.yml
---
import Tabs from '@theme/Tabs';
import TabsPanel from '@theme/TabsPanel';
Publications are a way of grouping changes generated from a table or a group of tables.
Publications are a way of grouping changes generated from a table or a group of tables.
These changes can then be sent to other systems (usually another Postgres database).
## Examples
### Create a Publication
This publication will contain all changes to all tables.
<Tabs
defaultActiveId="sql"
groupId="reference/postgres"
@@ -45,19 +22,16 @@ This publication will contain all changes to all tables.
<TabsPanel id="sql" label="sql">
```sql
create publication publication_name
create publication publication_name
for all tables;
```
</TabsPanel>
</Tabs>
### Create a Publication which listens to individual tables
<Tabs
defaultActiveId="sql"
groupId="reference/postgres"
@@ -66,19 +40,16 @@ for all tables;
<TabsPanel id="sql" label="sql">
```sql
create publication publication_name
create publication publication_name
for table table_one, table_two;
```
</TabsPanel>
</Tabs>
### Add tables to an existing publication
<Tabs
defaultActiveId="sql"
groupId="reference/postgres"
@@ -87,19 +58,16 @@ for table table_one, table_two;
<TabsPanel id="sql" label="sql">
```sql
alter publication publication_name
alter publication publication_name
add table table_name;
```
</TabsPanel>
</Tabs>
### Listens to inserts only
<Tabs
defaultActiveId="sql"
groupId="reference/postgres"
@@ -108,20 +76,17 @@ add table table_name;
<TabsPanel id="sql" label="sql">
```sql
create publication publication_name
create publication publication_name
for all tables
with (publish = 'insert');
```
</TabsPanel>
</Tabs>
### Listens to updates only
<Tabs
defaultActiveId="sql"
groupId="reference/postgres"
@@ -130,20 +95,17 @@ with (publish = 'insert');
<TabsPanel id="sql" label="sql">
```sql
create publication publication_name
create publication publication_name
for all tables
with (publish = 'update');
```
</TabsPanel>
</Tabs>
### Listens to deletions only
<Tabs
defaultActiveId="sql"
groupId="reference/postgres"
@@ -152,20 +114,17 @@ with (publish = 'update');
<TabsPanel id="sql" label="sql">
```sql
create publication publication_name
create publication publication_name
for all tables
with (publish = 'delete');
```
</TabsPanel>
</Tabs>
### Remove a Publication
<Tabs
defaultActiveId="sql"
groupId="reference/postgres"
@@ -177,7 +136,6 @@ with (publish = 'delete');
drop publication if exists publication_name;
```
</TabsPanel>
</Tabs>
@@ -186,7 +144,6 @@ drop publication if exists publication_name;
If you are planning to re-create a publication, it's best to do it in a transaction to ensure the operation succeeds.
<Tabs
defaultActiveId="sql"
groupId="reference/postgres"
@@ -195,16 +152,15 @@ If you are planning to re-create a publication, it's best to do it in a transact
<TabsPanel id="sql" label="sql">
```sql
begin;
begin;
-- remove the realtime publication
drop publication if exists publication_name;
drop publication if exists publication_name;
-- re-create the publication but don't enable it for any tables
create publication publication_name;
create publication publication_name;
commit;
```
</TabsPanel>
</Tabs>
</Tabs>
+4 -30
View File
@@ -1,19 +1,15 @@
---
id: schemas
title: "Schemas"
title: 'Schemas'
slug: schemas
custom_edit_url: https://github.com/supabase/supabase/edit/master/web/spec/postgres.yml
---
import Tabs from '@theme/Tabs';
import TabsPanel from '@theme/TabsPanel';
Schemas are like "folders". They help to keep your database organized.
Schemas are like "folders". They help to keep your database organized.
Schemas are particularly useful for security. You can set different permissions on each schema.
Schemas are particularly useful for security. You can set different permissions on each schema.
For example, you might want to use a `public` schema for user-facing data, and an `auth` schema for all logins and secured data.
<Tabs
defaultActiveId="sql"
groupId="reference/postgres"
@@ -25,36 +21,20 @@ For example, you might want to use a `public` schema for user-facing data, and a
create schema schema_name;
```
</TabsPanel>
</Tabs>
## Notes
- Schemas contain [tables](/docs/reference/postgres/tables), columns, triggers, functions, etc.
- Postgres comes with a `public` schema set up by default.
- It is best practice to use lowercase and underscores when naming schemas. For example: `schema_name`, not `Schema Name`.
## Examples
### Creating a schema
<Tabs
defaultActiveId="sql"
groupId="reference/postgres"
@@ -66,15 +46,12 @@ create schema schema_name;
create schema schema_name;
```
</TabsPanel>
</Tabs>
### Removing a schema
<Tabs
defaultActiveId="sql"
groupId="reference/postgres"
@@ -86,7 +63,6 @@ create schema schema_name;
drop schema if exists schema_name;
```
</TabsPanel>
</Tabs>
@@ -96,7 +72,6 @@ drop schema if exists schema_name;
Although it's not recommended, you can use uppercase and spaces when naming your schema by wrapping the name with double-quotes.
As a result, you will always need to use double-quotes when referencing your schema.
<Tabs
defaultActiveId="sql"
groupId="reference/postgres"
@@ -108,7 +83,6 @@ As a result, you will always need to use double-quotes when referencing your sch
create schema "Schema Name";
```
</TabsPanel>
</Tabs>
</Tabs>
+5 -35
View File
@@ -1,19 +1,15 @@
---
id: tables
title: "Tables"
title: 'Tables'
slug: tables
custom_edit_url: https://github.com/supabase/supabase/edit/master/web/spec/postgres.yml
---
import Tabs from '@theme/Tabs';
import TabsPanel from '@theme/TabsPanel';
Tables are similar to excel spreadsheets. They contain columns & rows of data. There are a few key differences from a spreadsheet however:
- Every column is a strict type of data. When you set up a column, you must define what "data type" it is.
- Tables can be joined together through relationships. For example you can have a "users" table, which is joined to a "teams" table.
<Tabs
defaultActiveId="sql"
groupId="reference/postgres"
@@ -28,7 +24,7 @@ create table table_name (
name text
);
# with schema
# with schema
create table schema_name.table_name (
id integer primary key,
data jsonb,
@@ -36,36 +32,20 @@ create table schema_name.table_name (
);
```
</TabsPanel>
</Tabs>
## Notes
- Tables contain columns, rows, triggers, comments,
- Tables contain columns, rows, triggers, comments,
- It is best practice to use lowercase and underscores when naming tables. For example: `table_name`, not `Table Name`.
- Tables belong to [schemas](/docs/reference/postgres/schemas). If you don't explicitly pass the schema, Postgres will assume that you want to create the table in the `public` schema.
## Examples
### Create table
<Tabs
defaultActiveId="sql"
groupId="reference/postgres"
@@ -80,7 +60,7 @@ create table table_name (
name text
);
# with schema
# with schema
create table schema_name.table_name (
id integer primary key,
data jsonb,
@@ -88,15 +68,12 @@ create table schema_name.table_name (
);
```
</TabsPanel>
</Tabs>
### Primary keys using multiple columns
<Tabs
defaultActiveId="sql"
groupId="reference/postgres"
@@ -113,15 +90,12 @@ create table table_name (
);
```
</TabsPanel>
</Tabs>
### Multiple foreign keys to the same table
<Tabs
defaultActiveId="sql"
groupId="reference/postgres"
@@ -135,15 +109,12 @@ alter table table_name
add constraint constraint_name_2 foreign key (column_2) references foreign_table(id);
```
</TabsPanel>
</Tabs>
### Delete a table
<Tabs
defaultActiveId="sql"
groupId="reference/postgres"
@@ -155,7 +126,6 @@ alter table table_name
delete table if exists table_name;
```
</TabsPanel>
</Tabs>
</Tabs>
+2 -1
View File
@@ -12,6 +12,7 @@ interface Props {
description?: string
hide_table_of_contents?: boolean
video?: string
tocVideo?: string
canonical?: string
}
children: any
@@ -74,7 +75,7 @@ const Layout: FC<Props> = (props: Props) => {
'thin-scrollbar overflow-y-auto sticky hidden md:block md:col-span-3 px-2',
].join(' ')}
>
<TableOfContents toc={props.toc} video={props.meta.video} />
<TableOfContents toc={props.toc} video={props.meta?.video} />
</div>
)}
</>
+3 -3
View File
@@ -201,7 +201,7 @@ const MobileMenuBackdrop = memo(function MobileMenuBackdrop() {
'left-0',
'right-0',
'z-10',
'backdrop-blur-sm backdrop-filter bg-white-1200 dark:bg-blackA-600',
'backdrop-blur-sm backdrop-filter bg-white-1200 dark:bg-scale-200/90',
mobileMenuOpen ? 'absolute h-full w-full top-0 left-0' : 'hidden h-0',
// always hide on desktop
'lg:hidden',
@@ -276,7 +276,7 @@ const NavContainer = memo(function NavContainer() {
'relative',
'w-auto',
'border-r overflow-auto h-screen',
'backdrop-blur backdrop-filter bg-white-1200 dark:bg-blackA-300',
'backdrop-blur backdrop-filter bg-white-1200 dark:bg-scale-200',
'flex flex-col',
].join(' ')}
>
@@ -332,7 +332,7 @@ const SiteLayout = ({ children }: PropsWithChildren<{}>) => {
className={[
'sticky transition-all top-0',
'z-10',
'backdrop-blur backdrop-filter bg-white-1200 dark:bg-blackA-300',
'backdrop-blur backdrop-filter bg-white-1200 dark:bg-scale-200',
].join(' ')}
>
<div className={['lg:hidden', 'px-5 ', 'border-b z-10'].join(' ')}>
+8 -1
View File
@@ -5,6 +5,7 @@ import Link from 'next/link'
import { useRouter } from 'next/router'
import { FC, useEffect, useRef, useState } from 'react'
import { IconExternalLink } from 'ui'
import ExpandableVideo from 'ui/src/components/ExpandableVideo/ExpandableVideo'
import components from '~/components'
import { highlightSelectedTocItem } from '~/components/CustomHTMLElements/CustomHTMLElements.utils'
import FooterHelpCallout, { FooterHelpCalloutType } from '~/components/FooterHelpCallout'
@@ -20,6 +21,7 @@ interface Props {
subtitle?: string
footerHelpType?: FooterHelpCalloutType
video?: string
tocVideo?: string
canonical?: string
}
children: any
@@ -35,7 +37,6 @@ const Layout: FC<Props> = (props) => {
const [tocList, setTocList] = useState([])
const { asPath } = useRouter()
const router = useRouter()
const EDIT_BUTTON_EXCLUDE_LIST = ['/404']
@@ -63,6 +64,7 @@ const Layout: FC<Props> = (props) => {
}, [])
const hasTableOfContents = tocList.length > 0
const tocVideoPreview = `http://img.youtube.com/vi/${props.meta.tocVideo}/0.jpg`
// page type, ie, Auth, Database, Storage etc
const ogPageType = asPath.split('/')[2]
@@ -157,6 +159,11 @@ const Layout: FC<Props> = (props) => {
].join(' ')}
>
<div className="border-l">
{props.meta?.tocVideo && !!tocVideoPreview && (
<div className="relative mb-6 pl-5">
<ExpandableVideo imgUrl={tocVideoPreview} videoId={props.meta.tocVideo} />
</div>
)}
<span className="block font-mono text-xs uppercase text-scale-1200 px-5 mb-6">
On this page
</span>
@@ -6,7 +6,13 @@ import SideBar from '~/components/Navigation/SideBar'
import TableOfContents from '~/components/TableOfContents'
interface Props {
meta: { title: string; description?: string; hide_table_of_contents?: boolean; video?: string }
meta: {
title: string
description?: string
hide_table_of_contents?: boolean
video?: string
tocVideo?: string
}
children: any
toc?: any
menuItems: any
+1
View File
@@ -60,6 +60,7 @@ const nextConfig = {
'user-images.githubusercontent.com',
'raw.githubusercontent.com',
'weweb-changelog.ghost.io',
'img.youtube.com',
],
},
experimental: {
+4
View File
@@ -32,6 +32,7 @@ Supabase provides a RESTful API using [PostgREST](https://postgrest.org/). This
It provides everything you need from a CRUD API at the URL `https://<project_ref>.supabase.co/rest/v1/`.
The REST interface is automatically reflected from your database's schema and supports:
- Basic CRUD operations (Create/Read/Update/Delete)
- Arbitrarily deep relationships among tables/views, functions that return table types can also nest related tables/views.
- Works with Postgres Views, Materialized Views and Foreign Tables
@@ -42,6 +43,7 @@ The REST interface is automatically reflected from your database's schema and su
The REST API resolves all requests to a single SQL statement leading to fast response times and high throughput.
Reference:
- [Docs](https://postgrest.org/)
- [Source Code](https://github.com/PostgREST/postgrest)
@@ -52,6 +54,7 @@ You can introspect and query the GraphQL API of an existing Supabase project wit
or navigate there manually at `API Docs > GraphQL > GraphiQL`.
The GraphQL interface is automatically reflected from your database's schema and supports:
- Basic CRUD operations (Create/Read/Update/Delete)
- Support for Tables, Views, Materialized Views, and Foreign Tables
- Arbitrarily deep relationships among tables/views
@@ -61,6 +64,7 @@ The GraphQL interface is automatically reflected from your database's schema and
The GraphQL API resolves all requests in a single round-trip leading to fast response times and high throughput.
Reference:
- [Docs](https://supabase.github.io/pg_graphql/)
- [Source Code](https://github.com/supabase/pg_graphql)
@@ -40,24 +40,25 @@ Let's use an example database that stores `countries` and `cities`:
<TabPanel id="SQL" label="SQL">
```sql
create table
countries ("id" serial primary key, "name" text);
create table countries (
"id" serial primary key,
"name" text
);
insert into
countries (id, name)
insert into countries
(id, name)
values
(1, 'United Kingdom'),
(2, 'United States');
create table
cities (
"id" serial primary key,
"name" text,
"country_id" int references "countries"
);
create table cities (
"id" serial primary key,
"name" text,
"country_id" int references "countries"
);
insert into
cities (name, country_id)
insert into cities
(name, country_id)
values
('London', 1),
('Manchester', 1),
@@ -130,18 +131,21 @@ GET https://[REF].supabase.co/rest/v1/countries?select=id,name,cities(id,name)
The Serverless APIs will detect many-to-many joins. For example, if you have a database which stored teams of users (where each user could belong to many teams):
```sql
create table
users ("id" serial primary key, "name" text);
create table users (
"id" serial primary key,
"name" text
);
create table
teams ("id" serial primary key, "team_name" text);
create table teams (
"id" serial primary key,
"team_name" text
);
create table
members (
"user_id" int references users,
"team_id" int references teams,
primary key(user_id, team_id)
);
create table members (
"user_id" int references users,
"team_id" int references teams,
primary key (user_id, team_id)
);
```
In these cases you don't need to explicitly define the joining table (members). If we wanted to fetch all the teams and the members in each team:
@@ -49,10 +49,8 @@ To confirm that safeupdate is enabled for API queries, run the following query:
select
usename,
useconfig
from
pg_shadow
where
usename = 'authenticator';
from pg_shadow
where usename = 'authenticator';
```
The expected value for `useconfig` should be:
+87 -9
View File
@@ -7,7 +7,7 @@ export const meta = {
video: 'https://www.youtube.com/v/em1cpOAXknM',
}
Supabase provides you with the option of adding captcha to your sign-in, sign-up, and password reset forms. This keeps your website safe from bots and malicious scripts. Supabase authentication has support for [hCaptcha](https://www.hcaptcha.com/).
Supabase provides you with the option of adding captcha to your sign-in, sign-up, and password reset forms. This keeps your website safe from bots and malicious scripts. Supabase authentication has support for [hCaptcha](https://www.hcaptcha.com/) and [Cloudflare Turnstile](https://www.cloudflare.com/products/turnstile/).
<div className="video-container">
<iframe
@@ -18,8 +18,15 @@ Supabase provides you with the option of adding captcha to your sign-in, sign-up
></iframe>
</div>
## Sign up for hCaptcha
## Sign up for Captcha
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="hcaptcha-1"
>
<TabPanel id="hcaptcha-1" label="HCaptcha">
Go to the [hCaptcha](https://www.hcaptcha.com/) website and sign up for an account. On the welcome page, copy the **Sitekey** and **Secret key**.
If you have already signed up and didn't copy this information from the welcome page, you can get the **Secret key** from the settings page.
@@ -34,17 +41,34 @@ In the Settings page, look for the **Sitekey** section and copy the key.
![sitekey_settings.png](/docs/img/guides/auth-captcha/sitekey_settings.png)
## Enable hCaptcha protection for your Supabase project
</TabPanel>
Navigate to the **[Authentication](https://app.supabase.com/project/_/settings/auth)** page in the Supabase Dashboard and find the **Enable hCaptcha protection** toggle under the **Security and Protection** section.
<TabPanel id="turnstile-1" label="Turnstile">
Go to the [Cloudflare website](https://dash.cloudflare.com/login) and sign up for an account. On the welcome page, head to the Turnstile section and add a new site. Create a site and take note of the **Sitekey** and **Secret Key** as shown below
![cloudflare_settings.png](/docs/img/guides/auth-captcha/cloudflare_settings.png)
</TabPanel>
</Tabs>
## Enable Captcha protection for your Supabase project
Navigate to the **[Authentication](https://app.supabase.com/project/_/settings/auth)** page in the Supabase Dashboard and find the **Enable Captcha protection** toggle under the **Security and Protection** section.
![supabase_auth_general_settings.png](/docs/img/guides/auth-captcha/supabase_auth_general_settings.png)
Enter your hCaptcha **Secret key** and click **Save**.
Enter your Captcha **Secret key** and click **Save**.
## Add the hCaptcha frontend component
## Add the Captcha frontend component
The frontend requires some changes to provide the captcha on-screen for the user. This example uses React and the hCaptcha React component, but hCaptcha can be used with any JavaScript framework.
The frontend requires some changes to provide the captcha on-screen for the user. This example uses React and the corresponding Captcha React component, but both Captcha providers can be used with any JavaScript framework.
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="hcaptcha-2"
>
<TabPanel id="hcaptcha-2" label="HCaptcha">
Install `@hcaptcha/react-hcaptcha` in your project as a dependency.
@@ -58,7 +82,7 @@ Now import the `HCaptcha` component from the `@hcaptcha/react-hcaptcha` library.
import HCaptcha from '@hcaptcha/react-hcaptcha'
```
Let’s create a empty state to store our `captchaToken`
Let's create a empty state to store our `captchaToken`
```jsx
const [captchaToken, setCaptchaToken] = useState()
@@ -75,7 +99,7 @@ We will pass it the sitekey we copied from the hCaptcha website as a property al
```jsx
<HCaptcha
sitekey="your-sitekey"
  onVerify={(token) => { setCaptchaToken(token) }
onVerify={(token) => { setCaptchaToken(token) }
/>
```
@@ -117,6 +141,60 @@ captcha.current.resetCaptcha()
In order to test that this works locally we will need to use something like [ngrok](https://ngrok.com/) or add an entry to your hosts file. You can read more about this in the [hCaptcha docs](https://docs.hcaptcha.com/#local-development).
</TabPanel>
<TabPanel id="turnstile-2" label="Turnstile">
The frontend requires some changes to provide the captcha on-screen for the user. This example uses React and the Turnstile React component, but Turnstile can be used with any JavaScript framework.
Install @marsidev/react-turnstile in your project as a dependency.
```bash
npm install @marsidev/react-turnstile
```
Now import the Turnstile component from the @marsidev/react-turnstile library.
```jsx
import { Turnstile } from '@marsidev/react-turnstile'
```
Let's create an empty state to store our `captchaToken`
```jsx
const [captchaToken, setCaptchaToken] = useState()
```
Now lets add the Cloudflare Turnstile component to the JSX section of our code
```html
<Turnstile />
```
We will pass it the sitekey we copied from the Cloudflare website as a property along with a `onVerify` property which takes a callback function. This callback function will have a token as one of its properties. Let's set the token in the state using `setCaptchaToken`
```jsx
<Turnstile
sitekey="your-sitekey"
onVerify={(token) => { setCaptchaToken(token) }
/>
```
We can now use the captchaToken we receive in our Supabase `signUp` function.
```jsx
await supabase.auth.signUp({
email,
password,
options: { captchaToken },
})
```
To test locally, you will need to add localhost to the domain whitelist as per the [Cloudflare docs](https://developers.cloudflare.com/turnstile/reference/testing/)
</TabPanel>
</Tabs>
Run the application and you should now be provided with a captcha challenge.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
@@ -94,7 +94,7 @@ The Auth component is currently shipped with the following views:
- Update password
- Forgotten password
We are planning on adding more views in the future. Follow along on that [repo](https://github.com/supabase/auth-ui).
We are planning on adding more views in the future. Follow along on that [repo](https://github.com/supabase/auth-ui).
## Customization
@@ -202,10 +202,7 @@ See the list of [tokens within a theme](https://github.com/supabase/auth-ui/blob
import { createClient } from '@supabase/supabase-js'
import { Auth } from '@supabase/auth-ui-react'
const supabase = createClient(
'<INSERT PROJECT URL>',
'<INSERT PROJECT ANON API KEY>'
)
const supabase = createClient('<INSERT PROJECT URL>', '<INSERT PROJECT ANON API KEY>')
const customTheme = {
default: {
@@ -239,7 +236,7 @@ const App = () => (
<Auth
supabaseClient={supabase}
theme="default" // can also be "dark" or "evenDarker"
appearance={{ theme: customTheme}}
appearance={{ theme: customTheme }}
/>
)
```
@@ -138,7 +138,7 @@ import { useRouter } from 'next/navigation'
const Context = createContext(undefined)
export default function SupabaseProvider({ children }) {
export default function SupabaseProvider({ children, session }) {
const [supabase] = useState(() => createBrowserSupabaseClient())
const router = useRouter()
@@ -155,7 +155,7 @@ export default function SupabaseProvider({ children }) {
}, [router, supabase])
return (
<Context.Provider value={{ supabase }}>
<Context.Provider value={{ supabase, session }}>
<>{children}</>
</Context.Provider>
)
@@ -188,13 +188,22 @@ import { useRouter } from 'next/navigation'
import type { SupabaseClient } from '@supabase/auth-helpers-nextjs'
import type { Database } from '@/lib/database.types'
type MaybeSession = Session | null
type SupabaseContext = {
supabase: SupabaseClient<Database>
session: MaybeSession
}
const Context = createContext<SupabaseContext | undefined>(undefined)
export default function SupabaseProvider({ children }: { children: React.ReactNode }) {
export default function SupabaseProvider({
children,
session,
}: {
children: React.ReactNode
session: MaybeSession
}) {
const [supabase] = useState(() => createBrowserSupabaseClient())
const router = useRouter()
@@ -211,7 +220,7 @@ export default function SupabaseProvider({ children }: { children: React.ReactNo
}, [router, supabase])
return (
<Context.Provider value={{ supabase }}>
<Context.Provider value={{ supabase, session }}>
<>{children}</>
</Context.Provider>
)
@@ -256,7 +265,10 @@ export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<SupabaseProvider>{children}</SupabaseProvider>
<SupabaseProvider session={session}>
<SupabaseListener serverAccessToken={session?.access_token} />
{children}
</SupabaseProvider>
</body>
</html>
)
@@ -282,7 +294,10 @@ export default function RootLayout({ children }: { children: React.ReactNode })
return (
<html lang="en">
<body>
<SupabaseProvider>{children}</SupabaseProvider>
<SupabaseProvider session={session}>
<SupabaseListener serverAccessToken={session?.access_token} />
{children}
</SupabaseProvider>
</body>
</html>
)
@@ -424,9 +424,7 @@ And then use the revalidator, inside the `onAuthStateChange` hook.
const { env, session } = useLoaderData()
const { revalidate } = useRevalidator()
const [supabase] = useState(() =>
createBrowserClient(env.SUPABASE_URL, env.SUPABASE_ANON_KEY)
)
const [supabase] = useState(() => createBrowserClient(env.SUPABASE_URL, env.SUPABASE_ANON_KEY))
const serverAccessToken = session?.access_token
@@ -464,7 +462,7 @@ useEffect(() => {
const {
data: { subscription },
} = supabase.auth.onAuthStateChange((event, session) => {
if (event !== "INITIAL_SESSION" && session?.access_token !== serverAccessToken) {
if (event !== 'INITIAL_SESSION' && session?.access_token !== serverAccessToken) {
// server and client are out of sync.
revalidate()
}
@@ -35,19 +35,16 @@ Create a server supabase client in a handle hook:
```ts title=src/hooks.server.ts
// src/hooks.server.ts
import {
PUBLIC_SUPABASE_URL,
PUBLIC_SUPABASE_ANON_KEY
} from '$env/static/public';
import { createSupabaseServerClient } from '@supabase/auth-helpers-sveltekit';
import type { Handle } from '@sveltejs/kit';
import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public'
import { createSupabaseServerClient } from '@supabase/auth-helpers-sveltekit'
import type { Handle } from '@sveltejs/kit'
export const handle: Handle = async ({ event, resolve }) => {
event.locals.supabase = createSupabaseServerClient({
supabaseUrl: PUBLIC_SUPABASE_URL,
supabaseKey: PUBLIC_SUPABASE_ANON_KEY,
event
});
event,
})
/**
* a little helper that is written for convenience so that instead
@@ -56,10 +53,10 @@ export const handle: Handle = async ({ event, resolve }) => {
*/
event.locals.getSession = async () => {
const {
data: { session }
} = await event.locals.supabase.auth.getSession();
return session;
};
data: { session },
} = await event.locals.supabase.auth.getSession()
return session
}
return resolve(event, {
/**
@@ -68,10 +65,10 @@ export const handle: Handle = async ({ event, resolve }) => {
* https://github.com/sveltejs/kit/issues/8061
*/
filterSerializedResponseHeaders(name) {
return name === 'content-range';
}
});
};
return name === 'content-range'
},
})
}
```
> Note that we are specifying filterSerializedResponseHeaders here. We need to tell SvelteKit that supabase needs the content-range header.
@@ -82,13 +79,13 @@ In order to make the session available to the UI (pages, layouts) we need to pas
```ts title=src/routes/+layout.server.ts
// src/routes/+layout.server.ts
import type { LayoutServerLoad } from './$types';
import type { LayoutServerLoad } from './$types'
export const load: LayoutServerLoad = async ({ locals: { getSession } }) => {
return {
session: await getSession()
};
};
session: await getSession(),
}
}
```
### Shared Load functions and pages
@@ -97,30 +94,27 @@ To be able to use Supabase in shared load functions and inside pages you need to
```ts
// src/routes/+layout.ts
import {
PUBLIC_SUPABASE_ANON_KEY,
PUBLIC_SUPABASE_URL
} from '$env/static/public';
import { createSupabaseLoadClient } from '@supabase/auth-helpers-sveltekit';
import type { LayoutLoad } from './$types';
import type { Database } from '../DatabaseDefinitions';
import { PUBLIC_SUPABASE_ANON_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public'
import { createSupabaseLoadClient } from '@supabase/auth-helpers-sveltekit'
import type { LayoutLoad } from './$types'
import type { Database } from '../DatabaseDefinitions'
export const load: LayoutLoad = async ({ fetch, data, depends }) => {
depends('supabase:auth');
depends('supabase:auth')
const supabase = createSupabaseLoadClient<Database>({
supabaseUrl: PUBLIC_SUPABASE_URL,
supabaseKey: PUBLIC_SUPABASE_ANON_KEY,
event: { fetch },
serverSession: data.session
});
serverSession: data.session,
})
const {
data: { session }
} = await supabase.auth.getSession();
data: { session },
} = await supabase.auth.getSession()
return { supabase, session };
};
return { supabase, session }
}
```
Access the client inside pages by `$page.data.supabase` or `data.supabase` when using `export let data: PageData`.
@@ -169,17 +163,17 @@ In order to get the most out of TypeScript and it's intellisense, you should imp
```ts
// src/app.d.ts
import { SupabaseClient, Session } from '@supabase/supabase-js';
import { Database } from './DatabaseDefinitions';
import { SupabaseClient, Session } from '@supabase/supabase-js'
import { Database } from './DatabaseDefinitions'
declare global {
namespace App {
interface Locals {
supabase: SupabaseClient<Database>;
getSession(): Promise<Session | null>;
supabase: SupabaseClient<Database>
getSession(): Promise<Session | null>
}
interface PageData {
session: Session | null;
session: Session | null
}
// interface Error {}
// interface Platform {}
@@ -193,24 +187,24 @@ For [row level security](https://supabase.com/docs/guides/auth/row-level-securit
```html
<script lang="ts">
import type { PageData } from './$types';
import type { PageData } from './$types'
export let data: PageData;
export let data: PageData
let loadedData = [];
let loadedData = []
async function loadData() {
const { data: result } = await data.supabase.from('test').select('*').limit(20);
loadedData = result;
const { data: result } = await data.supabase.from('test').select('*').limit(20)
loadedData = result
}
$: if (data.session) {
loadData();
loadData()
}
</script>
{#if data.session}
<p>client-side data fetching with RLS</p>
<pre>{JSON.stringify(loadedData, null, 2)}</pre>
<p>client-side data fetching with RLS</p>
<pre>{JSON.stringify(loadedData, null, 2)}</pre>
{/if}
```
@@ -219,10 +213,10 @@ For [row level security](https://supabase.com/docs/guides/auth/row-level-securit
```html
<!-- src/routes/profile/+page.svelte -->
<script lang="ts">
import type { PageData } from './$types';
import type { PageData } from './$types'
export let data: PageData;
$: ({ user, tableData } = data);
export let data: PageData
$: ({ user, tableData } = data)
</script>
<div>Protected content for {user.email}</div>
@@ -232,21 +226,21 @@ For [row level security](https://supabase.com/docs/guides/auth/row-level-securit
```ts
// src/routes/profile/+page.ts
import type { PageLoad } from './$types';
import { redirect } from '@sveltejs/kit';
import type { PageLoad } from './$types'
import { redirect } from '@sveltejs/kit'
export const load: PageLoad = async ({ parent }) => {
const { supabase, session } = await parent();
const { supabase, session } = await parent()
if (!session) {
throw redirect(303, '/');
throw redirect(303, '/')
}
const { data: tableData } = await supabase.from('test').select('*');
const { data: tableData } = await supabase.from('test').select('*')
return {
user: session.user,
tableData
};
};
tableData,
}
}
```
## Protecting API routes
@@ -255,21 +249,19 @@ Wrap an API Route to check that the user has a valid session. If they're not log
```ts
// src/routes/api/protected-route/+server.ts
import type { RequestHandler } from './$types';
import { json, error } from '@sveltejs/kit';
import type { RequestHandler } from './$types'
import { json, error } from '@sveltejs/kit'
export const GET: RequestHandler = async ({
locals: { supabase, getSession }
}) => {
const session = await getSession();
export const GET: RequestHandler = async ({ locals: { supabase, getSession } }) => {
const session = await getSession()
if (!session) {
// the user is not signed in
throw error(401, { message: 'Unauthorized' });
throw error(401, { message: 'Unauthorized' })
}
const { data } = await supabase.from('test').select('*');
const { data } = await supabase.from('test').select('*')
return json({ data });
};
return json({ data })
}
```
If you visit `/api/protected-route` without a valid session cookie, you will get a 401 response.
@@ -280,35 +272,35 @@ Wrap an Action to check that the user has a valid session. If they're not logged
```ts
// src/routes/posts/+page.server.ts
import type { Actions } from './$types';
import { error, fail } from '@sveltejs/kit';
import type { Actions } from './$types'
import { error, fail } from '@sveltejs/kit'
export const actions: Actions = {
createPost: async ({ request, locals: { supabase, getSession } }) => {
const session = await getSession();
const session = await getSession()
if (!session) {
// the user is not signed in
throw error(401, { message: 'Unauthorized' });
throw error(401, { message: 'Unauthorized' })
}
// we are save, let the user create the post
const formData = await request.formData();
const content = formData.get('content');
const formData = await request.formData()
const content = formData.get('content')
const { error: createPostError, data: newPost } = await supabase
.from('posts')
.insert({ content });
.insert({ content })
if (createPostError) {
return fail(500, {
supabaseErrorMessage: createPostError.message
});
supabaseErrorMessage: createPostError.message,
})
}
return {
newPost
};
}
};
newPost,
}
},
}
```
If you try to submit a form with the action `?/createPost` without a valid session cookie, you will get a 401 error response.
@@ -316,47 +308,47 @@ If you try to submit a form with the action `?/createPost` without a valid sessi
## Saving and deleting the session
```ts
import type { Actions } from './$types';
import { fail, redirect } from '@sveltejs/kit';
import { AuthApiError } from '@supabase/supabase-js';
import type { Actions } from './$types'
import { fail, redirect } from '@sveltejs/kit'
import { AuthApiError } from '@supabase/supabase-js'
export const actions: Actions = {
signin: async ({ request, locals: { supabase } }) => {
const formData = await request.formData();
const formData = await request.formData()
const email = formData.get('email') as string;
const password = formData.get('password') as string;
const email = formData.get('email') as string
const password = formData.get('password') as string
const { error } = await supabase.auth.signInWithPassword({
email,
password
});
password,
})
if (error) {
if (error instanceof AuthApiError && error.status === 400) {
return fail(400, {
error: 'Invalid credentials.',
values: {
email
}
});
email,
},
})
}
return fail(500, {
error: 'Server error. Try again later.',
values: {
email
}
});
email,
},
})
}
throw redirect(303, '/dashboard');
throw redirect(303, '/dashboard')
},
signout: async ({ locals: { supabase } }) => {
await supabase.auth.signOut();
throw redirect(303, '/');
}
};
await supabase.auth.signOut()
throw redirect(303, '/')
},
}
```
## Protecting multiple routes
@@ -366,33 +358,30 @@ protect multiple routes at once.
```ts
// src/hooks.server.ts
import type { RequestHandler } from './$types';
import { redirect, error } from '@sveltejs/kit';
import type { RequestHandler } from './$types'
import { redirect, error } from '@sveltejs/kit'
export const handle: Handle = async ({ event, resolve }) => {
// protect requests to all routes that start with /protected-routes
if (event.url.pathname.startsWith('/protected-routes')) {
const session = await event.locals.getSession();
const session = await event.locals.getSession()
if (!session) {
// the user is not signed in
throw redirect(303, '/');
throw redirect(303, '/')
}
}
// protect POST requests to all routes that start with /protected-posts
if (
event.url.pathname.startsWith('/protected-posts') &&
event.request.method === 'POST'
) {
const session = await event.locals.getSession();
if (event.url.pathname.startsWith('/protected-posts') && event.request.method === 'POST') {
const session = await event.locals.getSession()
if (!session) {
// the user is not signed in
throw error(303, '/');
throw error(303, '/')
}
}
return resolve(event);
};
return resolve(event)
}
```
## Migrate from 0.8.x to 0.9 [#migration]
@@ -425,19 +414,16 @@ export const supabaseClient = createClient(env.PUBLIC_SUPABASE_URL, env.PUBLIC_S
```js title=src/hooks.server.ts
// src/hooks.server.ts
import {
PUBLIC_SUPABASE_URL,
PUBLIC_SUPABASE_ANON_KEY
} from '$env/static/public';
import { createSupabaseServerClient } from '@supabase/auth-helpers-sveltekit';
import type { Handle } from '@sveltejs/kit';
import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public'
import { createSupabaseServerClient } from '@supabase/auth-helpers-sveltekit'
import type { Handle } from '@sveltejs/kit'
export const handle: Handle = async ({ event, resolve }) => {
event.locals.supabase = createSupabaseServerClient({
supabaseUrl: PUBLIC_SUPABASE_URL,
supabaseKey: PUBLIC_SUPABASE_ANON_KEY,
event
});
event,
})
/**
* a little helper that is written for convenience so that instead
@@ -446,10 +432,10 @@ export const handle: Handle = async ({ event, resolve }) => {
*/
event.locals.getSession = async () => {
const {
data: { session }
} = await event.locals.supabase.auth.getSession();
return session;
};
data: { session },
} = await event.locals.supabase.auth.getSession()
return session
}
return resolve(event, {
/**
@@ -458,10 +444,10 @@ export const handle: Handle = async ({ event, resolve }) => {
* https://github.com/sveltejs/kit/issues/8061
*/
filterSerializedResponseHeaders(name) {
return name === 'content-range';
}
});
};
return name === 'content-range'
},
})
}
```
</TabPanel>
@@ -469,7 +455,7 @@ export const handle: Handle = async ({ event, resolve }) => {
### Initialize the client [#migration-initialize-client]
In order to use the Supabase library in your client code you will need to setup a shared load function inside the root `+layout.ts` and create a `+layout.svelte` to handle our event listening for Auth events.
In order to use the Supabase library in your client code you will need to setup a shared load function inside the root `+layout.ts` and create a `+layout.svelte` to handle our event listening for Auth events.
<Tabs
scrollable
@@ -507,31 +493,28 @@ In order to use the Supabase library in your client code you will need to setup
```ts title=src/routes/+layout.ts
// src/routes/+layout.ts
import { invalidate } from '$app/navigation';
import {
PUBLIC_SUPABASE_ANON_KEY,
PUBLIC_SUPABASE_URL
} from '$env/static/public';
import { createSupabaseLoadClient } from '@supabase/auth-helpers-sveltekit';
import type { LayoutLoad } from './$types';
import type { Database } from '../DatabaseDefinitions';
import { invalidate } from '$app/navigation'
import { PUBLIC_SUPABASE_ANON_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public'
import { createSupabaseLoadClient } from '@supabase/auth-helpers-sveltekit'
import type { LayoutLoad } from './$types'
import type { Database } from '../DatabaseDefinitions'
export const load: LayoutLoad = async ({ fetch, data, depends }) => {
depends('supabase:auth');
depends('supabase:auth')
const supabase = createSupabaseLoadClient<Database>({
supabaseUrl: PUBLIC_SUPABASE_URL,
supabaseKey: PUBLIC_SUPABASE_ANON_KEY,
event: { fetch },
serverSession: data.session
});
serverSession: data.session,
})
const {
data: { session }
} = await supabase.auth.getSession();
data: { session },
} = await supabase.auth.getSession()
return { supabase, session };
};
return { supabase, session }
}
```
```svelte title=src/routes/+layout.svelte
@@ -605,17 +588,17 @@ declare namespace App {
```ts title=src/app.d.ts
// src/app.d.ts
import { SupabaseClient, Session } from '@supabase/supabase-js';
import { Database } from './DatabaseDefinitions';
import { SupabaseClient, Session } from '@supabase/supabase-js'
import { Database } from './DatabaseDefinitions'
declare global {
namespace App {
interface Locals {
supabase: SupabaseClient<Database>;
getSession(): Promise<Session | null>;
supabase: SupabaseClient<Database>
getSession(): Promise<Session | null>
}
interface PageData {
session: Session | null;
session: Session | null
}
// interface Error {}
// interface Platform {}
@@ -675,10 +658,10 @@ export const load: PageLoad = async (event) => {
```html title=src/routes/profile/+page.svelte
<!-- src/routes/profile/+page.svelte -->
<script lang="ts">
import type { PageData } from './$types';
import type { PageData } from './$types'
export let data: PageData;
$: ({ user, tableData } = data);
export let data: PageData
$: ({ user, tableData } = data)
</script>
<div>Protected content for {user.email}</div>
@@ -688,21 +671,21 @@ export const load: PageLoad = async (event) => {
```ts title=src/routes/profile/+page.ts
// src/routes/profile/+page.ts
import type { PageLoad } from './$types';
import { redirect } from '@sveltejs/kit';
import type { PageLoad } from './$types'
import { redirect } from '@sveltejs/kit'
export const load: PageLoad = async ({ parent }) => {
const { supabase, session } = await parent();
const { supabase, session } = await parent()
if (!session) {
throw redirect(303, '/');
throw redirect(303, '/')
}
const { data: tableData } = await supabase.from('test').select('*');
const { data: tableData } = await supabase.from('test').select('*')
return {
user: session.user,
tableData
};
};
tableData,
}
}
```
</TabPanel>
@@ -740,27 +723,24 @@ export const GET: RequestHandler = async (event) => {
```ts title=src/routes/api/protected-route/+server.ts
// src/routes/api/protected-route/+server.ts
import type { RequestHandler } from './$types';
import { json, error } from '@sveltejs/kit';
import type { RequestHandler } from './$types'
import { json, error } from '@sveltejs/kit'
export const GET: RequestHandler = async ({
locals: { supabase, getSession }
}) => {
const session = await getSession();
export const GET: RequestHandler = async ({ locals: { supabase, getSession } }) => {
const session = await getSession()
if (!session) {
// the user is not signed in
throw error(401, { message: 'Unauthorized' });
throw error(401, { message: 'Unauthorized' })
}
const { data } = await supabase.from('test').select('*');
const { data } = await supabase.from('test').select('*')
return json({ data });
};
return json({ data })
}
```
</TabPanel>
</Tabs>
## Migrate from 0.7.x to 0.8 [#migration-0-8]
### Set up the Supabase client [#migration-set-up-supabase-client-0-8]
@@ -98,7 +98,7 @@ export const meta = {
} = supabase.auth.onAuthStateChange((_event, session) => {
setSession(session)
})
return () => subscription.unsubscribe()
}, [])
@@ -36,7 +36,7 @@ Go to the [GitHub Developer Settings](https://github.com/settings/developers) pa
<SocialProviderSetup provider="GitHub" />
## Register a new OAuth application on Github.
## Register a new OAuth application on Github.
- Navigate to `Settings`/`Developer settings`/`OAuth Apps`
- Click `Register a new application`. If you've created an app before, click `New OAuth App` here.
@@ -105,17 +105,16 @@ A common use case with SSO is to use the UUID of the identity provider as the id
For example, let's say you have a table like:
```sql
CREATE TABLE
organization_settings (
-- the organization's unique ID
id UUID NOT NULL PRIMARY KEY,
-- the organization's SSO identity provider
sso_provider_id UUID UNIQUE,
-- name of the organization
name TEXT,
-- billing plan (paid, free, enterprise)
billing_plan TEXT
);
create table organization_settings (
-- the organization's unique ID
id uuid not null primary key,
-- the organization's SSO identity provider
sso_provider_id uuid unique,
-- name of the organization
name text,
-- billing plan (paid, free, enterprise)
billing_plan text
);
```
You can use the information present in the user's JWT to scope down which rows from this table the user can see, without doing any additional user management:
@@ -0,0 +1,35 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'using-environment-variables-in-config',
title: 'Using environment variables in config.toml',
description: 'How to use environment variables in config.toml with the Supabase CLI.',
}
The Supabase CLI is capable of utilizing environment variables stored in our project's root directory's `.env` file within the `config.toml` file.
We can reference the environment variable by using the `env()` function.
Inside of our `.env` file we add the environment variable as we normally would
```env
GITHUB_CLIENT_ID=""
GITHUB_SECRET=""
```
And then reference them inside of our `config.toml`
```toml
[auth.external.github]
enabled = true
client_id = "env(GITHUB_CLIENT_ID)"
secret = "env(GITHUB_SECRET)"
# Overrides the default auth redirectUrl.
redirect_uri = ""
```
These same environment variables will be referenced by the `supabase start` command from the Supabase CLI.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -12,13 +12,13 @@ extension which provides SQL access to
algorithms.
libsodium is a modern, easy-to-use software library for encryption,
decryption, signatures, password hashing, and more. It is a portable,
decryption, signatures, password hashing, and more. It is a portable,
cross-compilable, installable, and packageable fork of the
[NaCl](http://nacl.cr.yp.to/) library, with a compatible but extended
API to improve usability even further.
Its goal is to provide all of the core operations needed to build
higher-level cryptographic tools. Sodium is cross-platform and
higher-level cryptographic tools. Sodium is cross-platform and
cross-language. It runs on many compilers and operating systems,
including Windows (with MinGW or Visual Studio, x86 and x86_64), iOS,
and Android. JavaScript and WebAssembly versions are also available
@@ -33,12 +33,12 @@ pgsodium exposes the following libsodium APIs to SQL:
- [Generating Random Data](https://github.com/michelp/pgsodium/#generating-random-data)
- [Secret key cryptography](https://github.com/michelp/pgsodium/#secret-key-cryptography)
- [Authenticated encryption](https://github.com/michelp/pgsodium/#authenticated-encryption)
- [Authentication](https://github.com/michelp/pgsodium/#authentication)
- [Authenticated encryption](https://github.com/michelp/pgsodium/#authenticated-encryption)
- [Authentication](https://github.com/michelp/pgsodium/#authentication)
- [Public key cryptography](https://github.com/michelp/pgsodium/#public-key-cryptography)
- [Authenticated encryption](https://github.com/michelp/pgsodium/#authenticated-encryption-1)
- [Public key signatures](https://github.com/michelp/pgsodium/#public-key-signatures)
- [Sealed boxes](https://github.com/michelp/pgsodium/#sealed-boxes)
- [Authenticated encryption](https://github.com/michelp/pgsodium/#authenticated-encryption-1)
- [Public key signatures](https://github.com/michelp/pgsodium/#public-key-signatures)
- [Sealed boxes](https://github.com/michelp/pgsodium/#sealed-boxes)
- [Hashing](https://github.com/michelp/pgsodium/#hashing)
- [Password hashing](https://github.com/michelp/pgsodium/#password-hashing)
- [Key Derivation](https://github.com/michelp/pgsodium/#key-derivation)
@@ -15,6 +15,7 @@ Even though indexes improve query performance, the Postgres query planner may no
## Create an index
Let's take an example table:
```sql
create table persons (
id bigint generated by default as identity primary key,
@@ -25,6 +26,7 @@ create table persons (
deceased boolean
);
```
<Admonition>
All the queries in this guide can be run using the [SQL Editor](https://app.supabase.com/project/_/sql) in the Supabase Dashboard, or via `psql` if you're [connecting directly to the database](/docs/guides/database/connecting-to-postgres#direct-connections).
@@ -32,6 +34,7 @@ All the queries in this guide can be run using the [SQL Editor](https://app.supa
</Admonition>
We might want to frequently query users based on their age:
```sql
select name from persons where age = 32;
```
@@ -39,19 +42,22 @@ select name from persons where age = 32;
Without an index, Postgres will scan every row in the table to find equality matches on age.
You can verify this by doing an explain on the query:
```sql
explain select name from persons where age = 32;
```
Outputs:
```
Seq Scan on persons (cost=0.00..22.75 rows=x width=y)
Filter: (age = 32)
```
To add a simple B-Tree index you can run:
```sql
create index idx_persons_age on persons(age);
create index idx_persons_age on persons (age);
```
<Admonition type="caution">
@@ -73,7 +79,8 @@ You can see that in any large data set, traversing the index to locate a given v
If you are frequently querying a subset of rows then it may be more efficient to build a partial index. In our example, perhaps we only want to match on `age` where `deceased is false`. We could build a partial index:
```sql
create index idx_living_persons_age ON persons(age) where deceased is false;
create index idx_living_persons_age on persons (age)
where deceased is false;
```
## Ordering indexes
@@ -81,14 +88,13 @@ create index idx_living_persons_age ON persons(age) where deceased is false;
By default B-Tree indexes are sorted in ascending order, but sometimes you may want to provide a different ordering. Perhaps our application has a page featuring the top 10 oldest people. Here we would want to sort in descending order, and include `NULL` values last. For this we can use:
```sql
create index idx_persons_age_desc on persons(age desc nulls last);
create index idx_persons_age_desc on persons (age desc nulls last);
```
## Reindexing
After a while indexes can become stale and may need rebuilding. Postgres provides a `reindex` command for this, but due to Postgres locks being placed on the index during this process, you may want to make use of the `concurrent` keyword.
```sql
reindex index concurrently idx_persons_age;
```
@@ -103,4 +109,4 @@ Take note that `reindex` can be used inside a transaction, but `reindex [index/t
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
export default Page
@@ -4,6 +4,7 @@ export const meta = {
title: 'Build a User Management App with NextJS',
description: 'Learn how to use Supabase in your Next App.',
video: 'https://www.youtube.com/v/0Fs96oZ4se0',
tocVideo: '0Fs96oZ4se0',
}
<QuickstartIntro />
@@ -126,7 +127,7 @@ export default MyApp
Wrap your `pages/_app.tsx` component with the `SessionContextProvider` component:
```jsx lines=2,8 title=pages/_app.tsx
import '../styles/globals.css';
import '../styles/globals.css'
import { useState } from 'react'
import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'
import { SessionContextProvider, Session } from '@supabase/auth-helpers-react'
@@ -122,13 +122,13 @@ After a user is signed in we can allow them to edit their profile details and ma
Let's create a new component for that called `Account.tsx`.
```jsx title=src/Account.tsx
```tsx title=src/Account.tsx
import { AuthSession } from '@supabase/supabase-js'
import { Component, createEffect, createSignal } from 'solid-js'
import { supabase } from './supabaseClient'
interface Props {
session: AuthSession;
session: AuthSession
}
const Account: Component<Props> = ({ session }) => {
@@ -63,29 +63,26 @@ Add the code below to your `src/hooks.server.ts` to initialize the client on the
```ts title=src/hooks.server.ts
// src/hooks.server.ts
import {
PUBLIC_SUPABASE_URL,
PUBLIC_SUPABASE_ANON_KEY
} from '$env/static/public';
import { createSupabaseServerClient } from '@supabase/auth-helpers-sveltekit';
import type { Handle } from '@sveltejs/kit';
import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public'
import { createSupabaseServerClient } from '@supabase/auth-helpers-sveltekit'
import type { Handle } from '@sveltejs/kit'
export const handle: Handle = async ({ event, resolve }) => {
event.locals.supabase = createSupabaseServerClient({
supabaseUrl: PUBLIC_SUPABASE_URL,
supabaseKey: PUBLIC_SUPABASE_ANON_KEY,
event
});
event,
})
/**
* A convenience helper so we can just call await getSession() instead const { data: { session } } = await supabase.auth.getSession()
*/
event.locals.getSession = async () => {
const {
data: { session }
} = await event.locals.supabase.auth.getSession();
return session;
};
data: { session },
} = await event.locals.supabase.auth.getSession()
return session
}
return resolve(event, {
/**
@@ -94,10 +91,10 @@ export const handle: Handle = async ({ event, resolve }) => {
* https://github.com/sveltejs/kit/issues/8061
*/
filterSerializedResponseHeaders(name) {
return name === 'content-range';
}
});
};
return name === 'content-range'
},
})
}
```
If you are using TypeScript the compiler might complain about `event.locals.supabase` and `event.locals.getSession`, this can be fixed by updating your `src/app.d.ts` with the content below:
@@ -105,16 +102,16 @@ If you are using TypeScript the compiler might complain about `event.locals.supa
```ts title=src/app.d.ts
// src/app.d.ts
import { SupabaseClient, Session } from '@supabase/supabase-js';
import { SupabaseClient, Session } from '@supabase/supabase-js'
declare global {
namespace App {
interface Locals {
supabase: SupabaseClient;
getSession(): Promise<Session | null>;
supabase: SupabaseClient
getSession(): Promise<Session | null>
}
interface PageData {
session: Session | null;
session: Session | null
}
// interface Error {}
// interface Platform {}
@@ -126,11 +123,13 @@ Create a new `src/routes/+layout.server.ts` file to handle the session on the se
```ts title=src/routes/+layout.server.ts
// src/routes/+layout.server.ts
export const load = async ({ locals: { getSession } }) => {
import type { LayoutServerLoad } from './$types'
export const load: LayoutServerLoad = async ({ locals: { getSession } }) => {
return {
session: await getSession()
};
};
session: await getSession(),
}
}
```
> Start your dev server (`npm run dev`) in order to generate the `./$types` files we are referencing in our project.
@@ -139,29 +138,27 @@ Create a new `src/routes/+layout.ts` file to handle the session and the supabase
```ts title=src/routes/+layout.ts
// src/routes/+layout.ts
import { invalidate } from '$app/navigation';
import {
PUBLIC_SUPABASE_ANON_KEY,
PUBLIC_SUPABASE_URL
} from '$env/static/public';
import { createSupabaseLoadClient } from '@supabase/auth-helpers-sveltekit';
import { invalidate } from '$app/navigation'
import { PUBLIC_SUPABASE_ANON_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public'
import { createSupabaseLoadClient } from '@supabase/auth-helpers-sveltekit'
import type { LayoutLoad } from './$types'
export const load = async ({ fetch, data, depends }) => {
depends('supabase:auth');
export const load: LayoutLoad = async ({ fetch, data, depends }) => {
depends('supabase:auth')
const supabase = createSupabaseLoadClient({
supabaseUrl: PUBLIC_SUPABASE_URL,
supabaseKey: PUBLIC_SUPABASE_ANON_KEY,
event: { fetch },
serverSession: data.session
});
serverSession: data.session,
})
const {
data: { session }
} = await supabase.auth.getSession();
data: { session },
} = await supabase.auth.getSession()
return { supabase, session };
};
return { supabase, session }
}
```
Update your `src/routes/+layout.svelte`:
@@ -239,24 +236,26 @@ Add the `Auth` component to your home page
Create a `src/routes/+page.server.ts` file that will return our website url to be used in our `redirectTo` above.
> This is necessary because the current Supabase auth flow uses implicit grant which returns the tokens as part of the url fragment (#).
The redirect to a non server protected page will make sure that the client captures the url fragment as we cannot read these on the server
and send it over to the server. This all happens behind the scenes and isn't something you will need to do manually.
> This is necessary because the current Supabase auth flow uses implicit grant which returns the tokens as part of the url fragment (#).
> The redirect to a non server protected page will make sure that the client captures the url fragment as we cannot read these on the server
> and send it over to the server. This all happens behind the scenes and isn't something you will need to do manually.
```ts
// src/routes/+page.server.ts
import { redirect } from '@sveltejs/kit';
import { redirect } from '@sveltejs/kit'
import type { PageServerLoad } from './$types'
export const load = async ({ url, locals: { getSession } }) => {
const session = await getSession();
export const load: PageServerLoad = async ({ url, locals: { getSession } }) => {
const session = await getSession()
// if the user is already logged in return them to the account page
if (session) {
throw redirect(303, '/account');
}
// if the user is already logged in return them to the account page
if (session) {
throw redirect(303, '/account')
}
return { url: url.origin };
};
return { url: url.origin }
}
```
### Loading page
@@ -378,67 +377,68 @@ Now create the associated `src/routes/account/+page.server.ts` file that will ha
and handle all our form actions through the `actions` object.
```ts
import { fail, redirect } from '@sveltejs/kit';
import { fail, redirect } from '@sveltejs/kit'
import type { Actions, PageServerLoad } from './$types'
export const load = async ({ locals: { supabase, getSession } }) => {
const session = await getSession();
export const load = (async ({ locals: { supabase, getSession } }) => {
const session = await getSession()
if (!session) {
throw redirect(303, '/');
}
if (!session) {
throw redirect(303, '/')
}
const { data: profile } = await supabase
.from('profiles')
.select(`username, full_name, website, avatar_url`)
.eq('id', session.user.id)
.single();
const { data: profile } = await supabase
.from('profiles')
.select(`username, full_name, website, avatar_url`)
.eq('id', session.user.id)
.single()
return { session, profile };
};
return { session, profile }
}) satisfies PageServerLoad
export const actions = {
update: async ({ request, locals: { supabase, getSession } }) => {
const formData = await request.formData();
const fullName = formData.get('fullName') as string;
const username = formData.get('username') as string;
const website = formData.get('website') as string;
const avatarUrl = formData.get('avatarUrl') as string;
update: async ({ request, locals: { supabase, getSession } }) => {
const formData = await request.formData()
const fullName = formData.get('fullName') as string
const username = formData.get('username') as string
const website = formData.get('website') as string
const avatarUrl = formData.get('avatarUrl') as string
const session = await getSession();
const session = await getSession()
const { error } = await supabase.from('profiles').upsert({
id: session?.user.id,
full_name: fullName,
username,
website,
avatar_url: avatarUrl,
updated_at: new Date()
});
const { error } = await supabase.from('profiles').upsert({
id: session?.user.id,
full_name: fullName,
username,
website,
avatar_url: avatarUrl,
updated_at: new Date(),
})
if (error) {
return fail(500, {
fullName,
username,
website,
avatarUrl
});
}
if (error) {
return fail(500, {
fullName,
username,
website,
avatarUrl,
})
}
return {
fullName,
username,
website,
avatarUrl
};
},
signout: async ({ locals: { supabase, getSession } }) => {
const session = await getSession();
if (session) {
await supabase.auth.signOut();
throw redirect(303, '/');
}
}
};
return {
fullName,
username,
website,
avatarUrl,
}
},
signout: async ({ locals: { supabase, getSession } }) => {
const session = await getSession()
if (session) {
await supabase.auth.signOut()
throw redirect(303, '/')
}
},
} satisfies Actions
```
### Launch!
@@ -65,7 +65,7 @@ Optionally, update [src/style.css](https://raw.githubusercontent.com/supabase/su
### Set up a Login component
Set up an `src/components/Auth.vue` component to manage logins and sign ups. We'll use Magic Links, so users can sign in with their email without using passwords.
Set up an `src/components/Auth.vue` component to manage logins and sign ups. We'll use Magic Links, so users can sign in with their email without using passwords.
```vue title=/src/components/Auth.vue
<script setup>
+3 -1
View File
@@ -24,7 +24,9 @@ Daily Backups and PITR are mutually exclusive. If your project opts into using P
All Pro and Enterprise tier Supabase projects are backed up automatically on a daily basis. In terms of Recovery Point Objective (RPO), Daily Backups would be suitable for projects willing to lose up to 24 hours worth of data if disaster hits at the most inopportune time. If a lower RPO is required, enabling Point-in-Time Recovery should be considered.
<Admonition type="note">
For security purposes, passwords for custom roles are not stored in daily backups, and will not be found in downloadable files. As such, if you are restoring from a daily backup and are using custom roles, you will need to set their passwords once more following a completed restoration.
For security purposes, passwords for custom roles are not stored in daily backups, and will not be
found in downloadable files. As such, if you are restoring from a daily backup and are using
custom roles, you will need to set their passwords once more following a completed restoration.
</Admonition>
### Backup Process [#daily-backups-process]
@@ -0,0 +1,49 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'exhaust-cpu',
title: 'High CPU Usage',
description:
'Learn what high CPU usage could mean for your Supabase instance and what could have caused it.',
}
Learn what high CPU usage could mean for your Supabase instance and what could have caused it.
## The danger of high CPU usage
Every Supabase project runs in its dedicated virtual machine. Your instance will have a different set of hardware provisioned depending on your [compute add-on](https://supabase.com/docs/guides/platform/compute-add-ons). Your hardware may not be suitable for the intended workload and may experience high CPU usage.
High CPU usage could come with a range of issues:
- slower queries
- disruption of daily backup routines
- in rare cases, your instance may become unresponsive
Moreover, your instance might not be able to handle future traffic spikes if it already has a high CPU usage.
## Monitor your CPU
You can check your CPU usage directly on the Supabase Platform. For this go to database health in the reports section or [click here](https://app.supabase.com/project/_/reports/database) and select your project.
![CPU usage reported on Supabase dashboard](/docs/img/guides/platform/exhaust-cpu-report.png)
It is also possible to monitor your resources and set up alerts using Prometheus/Grafana. You can find a guide for this [here](https://supabase.com/docs/guides/platform/metrics).
## Common reasons for high CPU usage
Everything you do with your Supabase project requires compute. Hence, there can be many reasons for high CPU usage. Here are some common ones:
- **Query performance:** Queries that take a long time to complete (>1 second) as well as excessive amounts of querying can put a strain on the CPU. Check our guide on [examining query performance](https://supabase.com/docs/guides/platform/performance#examining-query-performance).
- **Missing indexes:** Your database might have to scan through a large amount of data to find the information it needs. Creating indexes helps your database find data faster. Learn more about indexes [here](https://supabase.com/docs/guides/database/postgres/indexes).
- **Unsuitable compute:** The instance size of your Supabase project might not be suitable for your application as you might have more traffic or run resource-intensive operations.
## Solving high CPU usage
There are two ways to solve high CPU:
1. **Optimize performance:** Get more out of your instance's resources by optimizing your usage. Have a look at our [performance tuning guide](https://supabase.com/docs/guides/platform/performance#examining-query-performance) and our [production readiness guide](https://supabase.com/docs/guides/platform/going-into-prod#performance).
2. **Upgrade your compute:** You can get a Compute Add-on for your project. Follow [this link](https://app.supabase.com/project/_/settings/billing/update/pro) and select your project to see your upgrade options.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
+1 -1
View File
@@ -112,7 +112,7 @@ To _permanently_ set a logging configuration (beyond a single session), execute
alter role postgres set pgaudit.log to 'function, write, ddl';
```
For user-traffic flowing through the [HTTP APIs](../../guides/database/api#rest-api-overview) powered by PostgREST, set your configuration values for the roles `anon` and `authenticated`. For traffic using the `service_role` key, set the configuration values for the role `service_role`.
For user-traffic flowing through the [HTTP APIs](../../guides/database/api#rest-api-overview) powered by PostgREST, set your configuration values for the roles `anon` and `authenticated`. For traffic using the `service_role` key, set the configuration values for the role `service_role`.
To reset system-wide settings, execute the following, then perform a fast reboot:
@@ -12,7 +12,10 @@ In some cases, access to new features require upgrading or migrating your Supaba
## Upgrade your project
<Admonition type="note">This is only available for projects on the Free plan. For projects on the Pro plan, please contact support for assistance with upgrading.</Admonition>
<Admonition type="note">
This is only available for projects on the Free plan. For projects on the Pro plan, please contact
support for assistance with upgrading.
</Admonition>
When you pause and restore a project, the restored database includes the latest features. This method _does_ include downtime, so be aware that your project will be inaccessible for a short period of time.
@@ -80,7 +83,7 @@ const NEW_PROJECT_SERVICE_KEY = 'new-project-service-key-yyy'
;(async () => {
const oldSupabaseRestClient = createClient(OLD_PROJECT_URL, OLD_PROJECT_SERVICE_KEY, {
db: {
schema: 'storage'
schema: 'storage',
},
})
const oldSupabaseClient = createClient(OLD_PROJECT_URL, OLD_PROJECT_SERVICE_KEY)
@@ -45,8 +45,8 @@ Your project will continue to operate after using all the free quota allowance f
This additional usage will be charged based on the line item's cost per unit outlined on the [pricing page](/pricing).
<Admonition type="note">
When your Spend Cap is **off**, we recommend monitoring your consumption and costs using the [Usage
section of your project](https://app.supabase.com/project/_/settings/billing/usage) in the
When your Spend Cap is **off**, we recommend monitoring your consumption and costs using the
[Usage section of your project](https://app.supabase.com/project/_/settings/billing/usage) in the
dashboard.
</Admonition>
@@ -25,8 +25,7 @@ const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY
const channel = supabase.channel('test')
channel.on('broadcast', { event: 'supa' }, (payload) => console.log(payload))
.subscribe()
channel.on('broadcast', { event: 'supa' }, (payload) => console.log(payload)).subscribe()
```
## Send Messages
@@ -69,7 +68,8 @@ const channel = supabase.channel('test', {
},
})
channel.on('broadcast', { event: 'supa' }, (payload) => console.log(payload))
channel
.on('broadcast', { event: 'supa' }, (payload) => console.log(payload))
.subscribe((status) => {
if (status === 'SUBSCRIBED') {
channel.send({
@@ -139,9 +139,7 @@ const channel = supabase
.subscribe()
```
<Admonition type="note">
This filter uses Postgres' `=`.
</Admonition>
<Admonition type="note">This filter uses Postgres' `=`.</Admonition>
### neq
@@ -165,9 +163,7 @@ const channel = supabase
.subscribe()
```
<Admonition type="note">
This filter uses Postgres' `!=`.
</Admonition>
<Admonition type="note">This filter uses Postgres' `!=`.</Admonition>
### lt
@@ -244,7 +240,8 @@ const channel = supabase
```
<Admonition type="note">
This filter uses Postgres' `>` so it works for non-numeric types but make sure to check the expected behavior of the compared data's type.
This filter uses Postgres' `>` so it works for non-numeric types but make sure to check the
expected behavior of the compared data's type.
</Admonition>
### gte
@@ -270,7 +267,8 @@ const channel = supabase
```
<Admonition type="note">
This filter uses Postgres' `>=` so it works for non-numeric types but make sure to check the expected behavior of the compared data's type.
This filter uses Postgres' `>=` so it works for non-numeric types but make sure to check the
expected behavior of the compared data's type.
</Admonition>
### in
+2 -1
View File
@@ -39,7 +39,8 @@ In the initial message, the client sends a message specifying the features they
```
<Admonition type="note">
The `in` filter has the format `COLUMN_NAME=in.(value1,value2,value3)`. However, other filters use the format `COLUMN_NAME=FILTER_NAME.value`.
The `in` filter has the format `COLUMN_NAME=in.(value1,value2,value3)`. However, other filters use
the format `COLUMN_NAME=FILTER_NAME.value`.
</Admonition>
In response, the server sends the Postgres configuration with a unique ID. With this ID, the client should route incoming changes to the appropriate callback.
@@ -118,7 +118,6 @@ Single-sign on allows enterprises to centrally manage accounts and access to app
A time-based one-time password is a one-time password generated at regular time intervals from a secret, usually from an application in a mobile device (e.g., Google Authenticator, 1Password).
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -5,7 +5,7 @@ export const meta = {
description: 'Migrate your Amazon RDS MySQL or MS SQL database to Supabase Postgres database.',
}
This guide aims to exhibit the process of transferring your Amazon RDS database from any of these engines Postgres, MySQL or MS SQL to Supabase's Postgres database. Although Amazon RDS is a favored managed database service provided by AWS, it may not suffice for all use cases. Supabase, on the other hand, provides an excellent free and open source option that encompasses all the necessary backend features to develop a product: a Postgres database, authentication, instant APIs, edge functions, real-time subscriptions, and storage.
This guide aims to exhibit the process of transferring your Amazon RDS database from any of these engines Postgres, MySQL or MS SQL to Supabase's Postgres database. Although Amazon RDS is a favored managed database service provided by AWS, it may not suffice for all use cases. Supabase, on the other hand, provides an excellent free and open source option that encompasses all the necessary backend features to develop a product: a Postgres database, authentication, instant APIs, edge functions, real-time subscriptions, and storage.
Supabase's core is Postgres, enabling the use of row-level security and providing access to over 40 Postgres extensions. By migrating from Amazon RDS to Supabase, you can leverage Postgres to its fullest potential and acquire all the features you need to complete your project.
@@ -33,7 +33,7 @@ Supabase's core is Postgres, enabling the use of row-level security and providin
## Migrate the database
The fastest way to migrate your database is with the Supabase migration tool on
[Google Colab](https://colab.research.google.com/github/mansueli/Supa-Migrate/blob/main/Amazon_RDS_to_Supabase.ipynb).
[Google Colab](https://colab.research.google.com/github/mansueli/Supa-Migrate/blob/main/Amazon_RDS_to_Supabase.ipynb).
Alternatively, you can use [pgloader](https://github.com/dimitri/pgloader), a flexible and powerful data migration tool that supports a wide range of source database engines, including MySQL and MS SQL, and migrates the data to a Postgres database. For databases using the Postgres engine, we recommend using the pg_dump and psql command line tools, which are included in a full PostgreSQL installation.
@@ -47,8 +47,8 @@ Alternatively, you can use [pgloader](https://github.com/dimitri/pgloader), a fl
1. Select the Dabase Engine from the Source database in the dropdown
1. Set the environment variables (`HOST`, `USER`, `SOURCE_DB`,`PASSWORD`, `SUPABASE_URL`, and `SUPABASE_PASSWORD`) in the Colab notebook.
2. Run the first two steps in [the notebook](https://colab.research.google.com/github/mansueli/Supa-Migrate/blob/main/Amazon_RDS_to_Supabase.ipynb) in order. The first sets engine and installs the necessary files.
3. Run the third step to start the migration. This will take a few minutes.
1. Run the first two steps in [the notebook](https://colab.research.google.com/github/mansueli/Supa-Migrate/blob/main/Amazon_RDS_to_Supabase.ipynb) in order. The first sets engine and installs the necessary files.
1. Run the third step to start the migration. This will take a few minutes.
</TabPanel>
<TabPanel id="MySQL" label="Migrate from MySQL with pgloader">
@@ -57,18 +57,19 @@ Alternatively, you can use [pgloader](https://github.com/dimitri/pgloader), a fl
Create a configuration file (e.g., config.load).
```sql
LOAD DATABASE
FROM mysql://USER:PASSWORD@HOST/SOURCE_DB
INTO postgres://postgres:password@db.xxxx.supabase.co:6543/postgres
ALTER SCHEMA 'public' OWNER TO 'postgres';
load database
from mysql://user:password@host/source_db
into postgres://postgres:password@db.xxxx.supabase.co:6543/postgres
alter schema 'public' owner to 'postgres';
set wal_buffers = '64MB', max_wal_senders = 0, statement_timeout = 0, work_mem to '2GB';
````
```
##Run the migration with pgloader
```bash
pgloader config.load
```
</TabPanel>
<TabPanel id="MS SQL" label="Migrate from MSSQL">
@@ -81,13 +82,14 @@ LOAD DATABASE
INTO postgres://postgres:password@db.xxxx.supabase.co:6543/postgres
ALTER SCHEMA 'public' OWNER TO 'postgres';
set wal_buffers = '64MB', max_wal_senders = 0, statement_timeout = 0, work_mem to '2GB';
````
```
##Run the migration with pgloader
```bash
pgloader config.load
```
</TabPanel>
</Tabs>
-1
View File
@@ -42,7 +42,6 @@ Note that CDNs might still evict your object from their cache if it has not been
The cache status of a particular request is sent in the `cf-cache-status` header. A cache status of `MISS` indicates that the CDN node did not have the object in its cache and had to ping the origin to get it. A cache status of `HIT` indicates that the object was sent directly from the CDN.
## Smart CDN Caching
<Admonition type="note">
@@ -77,7 +77,7 @@ For instance, if you use Chrome when viewing a jpeg image and using transformati
As a result, this will lower the bandwidth that you send to your users and your application will load much faster.
<Admonition type="note">
We currently only support WebP. AVIF support will come in the near future
We currently only support WebP. AVIF support will come in the near future
</Admonition>
**Disabling automatic optimisation:**
@@ -87,11 +87,11 @@ this is also supported in the JavaScript SDK starting from v2.2.0
```ts
await storage.from('bucket').download('image.jpeg', {
transform: {
width: 200,
height: 200,
format: 'origin',
},
transform: {
width: 200,
height: 200,
format: 'origin',
},
})
```
@@ -105,7 +105,9 @@ To get started, create a `supabase-image-loader.js` file in your NextJS project
const projectId = '' // your supabase project id
export default function supabaseLoader({ src, width, quality }) {
return `https://${projectId}.supabase.co/storage/v1/render/image/public/${src}?width=${width}&quality=${quality || 75}`
return `https://${projectId}.supabase.co/storage/v1/render/image/public/${src}?width=${width}&quality=${
quality || 75
}`
}
```
@@ -193,11 +195,11 @@ Under the hood we use the awesome [Imgproxy](https://imgproxy.net/)
Simply deploy an imgproxy container with the following configuration:
```yaml
imgproxy:
image: darthsim/imgproxy
environment:
- IMGPROXY_ENABLE_WEBP_DETECTION=true
- IMGPROXY_JPEG_PROGRESSIVE=true
imgproxy:
image: darthsim/imgproxy
environment:
- IMGPROXY_ENABLE_WEBP_DETECTION=true
- IMGPROXY_JPEG_PROGRESSIVE=true
```
Note: make sure that this service can only be reachable within an internal network and not exposed to the public internet
Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

+1 -1
View File
@@ -64,7 +64,7 @@ article h1 {
.table-of-contents-height {
max-height: calc(100vh - 3.75rem - 2rem);
top: calc(3.75rem + 1rem);
top: calc(4rem + 60px); // padding height + navbar height
}
.width-full {
@@ -17,7 +17,7 @@ Whenever you create a new database in Postgres, you are actually [basing it off
This database, `template1`, and another, called `template0`, are standard system databases that exist in every newly created database cluster. Don't believe me? Why not quickly [spin up a database](/docs/postgres/server/about) and see it for yourself with this query:
```sql
SELECT * FROM pg_database;
select * from pg_database;
```
In this post, we'll explore these template databases and see how we can make full use of their potential. We'll even look into creating a template database of our own.
+34 -30
View File
@@ -54,13 +54,14 @@ Creating a view consisting of all the three tables will look like this:
```sql
create view transcripts as
select
students.name,
students.type,
courses.title,
courses.code,
grades.result
from grades
select
students.name,
students.type,
courses.title,
courses.code,
grades.result
from
grades
left join students on grades.student_id = students.id
left join courses on grades.course_id = courses.id;
```
@@ -88,14 +89,15 @@ As a query becomes complex it becomes a hassle to call it. Especially when we ru
```sql
select
students.name,
students.type,
courses.title,
courses.code,
grades.result
from grades
left join students on grades.student_id = students.id
left join courses on grades.course_id = courses.id;
students.name,
students.type,
courses.title,
courses.code,
grades.result
from
grades
left join students on grades.student_id = students.id
left join courses on grades.course_id = courses.id;
```
We can run this instead:
@@ -112,14 +114,15 @@ Views ensure that the likelihood of mistakes decreases when repeatedly executing
```sql
select
students.name,
students.type,
courses.title,
courses.code,
grades.result
from grades
left join students on grades.student_id = students.id
left join courses on grades.course_id = courses.id
students.name,
students.type,
courses.title,
courses.code,
grades.result
from
grades
left join students on grades.student_id = students.id
left join courses on grades.course_id = courses.id
where courses.code != 'PG101';
```
@@ -143,13 +146,14 @@ Using our example above, a materialized view can be created like this:
```sql
create materialized view transcripts as
select
students.name,
students.type,
courses.title,
courses.code,
grades.result
from grades
select
students.name,
students.type,
courses.title,
courses.code,
grades.result
from
grades
left join students on grades.student_id = students.id
left join courses on grades.course_id = courses.id;
```
@@ -29,9 +29,10 @@ A review of [database normalization](https://ocw.mit.edu/courses/civil-and-envir
Postgres has tons of useful types beyond the basics, knowing how to use them will show you can leverage the true power of Postgres. JSONB for example can be incredibly useful for storing non-structured data, which you can query using syntax like:
{/* prettier-ignore */}
```sql
-- grades = {'geography': 'A', 'history': 'B', 'postgres': 'A++'}
SELECT * FROM students WHERE grades->>'geography' = 'A';
select * from students where grades->>'geography' = 'A';
```
### Know about namespacing with SCHEMAs
@@ -47,8 +48,7 @@ The art of indexing in Postgres could fill an entire book. In some circumstances
An index can be simple, for example, if your students table is most frequently queried on surname alone, you create an index:
```sql
CREATE INDEX idx_students_surname
ON students(surname);
create index idx_students_surname on students (surname);
```
The default index type used here is `btree` (you could have specified this as `USING btree`), but there are other types of indexes, such as `BRIN`, `GiST`, `GIN`, `hash`, and more. Readers wanting to go deeper may also want to explore [Partial](https://www.postgresql.org/docs/current/indexes-partial.html) or [Multicolumn](https://www.postgresql.org/docs/13/indexes-multicolumn.html) Indexes.
@@ -139,17 +139,18 @@ Whilst PL/pgSQL does have [loops](https://www.postgresql.org/docs/current/plpgsq
Common table expressions are temporary or intermediate result sets. They can make your queries more readable and even enable recursion. The typical form is:
{/* prettier-ignore */}
```sql
WITH ten_strumpers AS (
SELECT id, first_name
FROM students
WHERE surname = 'Strumper'
ORDER BY first_name
LIMIT 10
with ten_strumpers as (
select id, first_name
from students
where surname = 'Strumper'
order by first_name
limit 10
)
SELECT id
FROM ten_strumpers
WHERE first_name LIKE "S%";
select id
from ten_strumpers
where first_name like "S%";
```
If you can reason about most of the topics in this post then you'll be in a very strong position to impress with your answers to PostgreSQL interview questions. As with all programming topics however, the real learning starts when you put these things into practice. At Supabase we offer a very very fast (the fastest?) way to spin up a PostgreSQL database and start querying it, and our browser based SQL editor is getting more powerful every day.
@@ -120,7 +120,7 @@ from
'https://webhook.site/223c8a43-725b-4cbd-b1fe-d0da73353a6b', -- webhook URL
'{"hello": "world"}', -- payload
'application/json'
)
);
```
#### Scheduling the job
@@ -176,6 +176,7 @@ select * from cron.job_run_details;
To stop a running cron job, you can run:
{/* prettier-ignore */}
```sql
select cron.unschedule('webhook-every-minute'); -- pass the name of the cron job
```
@@ -13,10 +13,9 @@ date: '03-30-2021'
<div className="bg-gray-300 rounded-lg px-6 py-2 italic">
🆕 Storage has undergone significant enhancements with two major releases since its initial launch. Here is what is new:
- [Supabase Storage v2: Image resizing and Smart CDN](https://supabase.com/blog/storage-image-resizing-smart-cdn)
- [Supabase Storage v3: Resumable Uploads with support for 50GB files](https://supabase.com/blog/storage-v3-resumable-uploads)
- [Supabase Storage v2: Image resizing and Smart CDN](https://supabase.com/blog/storage-image-resizing-smart-cdn)
- [Supabase Storage v3: Resumable Uploads with support for 50GB files](https://supabase.com/blog/storage-v3-resumable-uploads)
</div>
@@ -165,8 +164,8 @@ You may be itching to get started tinkering around with our new Supabase storage
## What's next
- We currently support S3 and will be adding more storage backends. Vote for the storage backends you would like to see us implement [here](https://github.com/supabase/supabase/discussions/982).
- We will integrate our storage service with a Content Delivery Network. With a CDN, objects are cached on a global network. This leads to faster access times for your users around the world. **SHIPPED: [Supabase Storage v2: Image resizing and Smart CDN](https://supabase.com/blog/storage-image-resizing-smart-cdn#smart-cdn)
- We are working on transformations like resizing of images and automatic optimization of different media types. This makes it easy to embed Supabase objects directly in your websites and mobile applications without additional processing. **SHIPPED: [Supabase Storage v2: Image resizing and Smart CDN](https://supabase.com/blog/storage-image-resizing-smart-cdn#image-resizing)
- We will integrate our storage service with a Content Delivery Network. With a CDN, objects are cached on a global network. This leads to faster access times for your users around the world. \*\*SHIPPED: [Supabase Storage v2: Image resizing and Smart CDN](https://supabase.com/blog/storage-image-resizing-smart-cdn#smart-cdn)
- We are working on transformations like resizing of images and automatic optimization of different media types. This makes it easy to embed Supabase objects directly in your websites and mobile applications without additional processing. \*\*SHIPPED: [Supabase Storage v2: Image resizing and Smart CDN](https://supabase.com/blog/storage-image-resizing-smart-cdn#image-resizing)
- A better editor to make authoring policies easier and less error prone.
- Reach out to us if you would like to help out with adding storage support in one of the [community maintained client libraries](/docs/reference/javascript/installing).
@@ -59,7 +59,7 @@ To store sensor reading metric data, create a table consisting of the sensor des
create table sensor_range (
reading_id serial primary key,
metric_desc varchar(100),
metric_level valid_levels,
metric_level valid_levels,
metric_ts tsmultirange
);
```
@@ -67,27 +67,23 @@ create table sensor_range (
Now, insert some data into the table. Note that, the insert statement below provides two time ranges that are not contiguous.
```sql hideCopy
insert into sensor_range (
metric_desc,
metric_level,
metric_ts
)
values (
'Temperature',
'high',
'{[2021-11-01 6:00, 2021-11-01 10:00],[2021-11-05 14:00, 2021-11-05 20:00]}'
);
insert into sensor_range
(metric_desc, metric_level, metric_ts)
values
(
'Temperature',
'high',
'{[2021-11-01 6:00, 2021-11-01 10:00],[2021-11-05 14:00, 2021-11-05 20:00]}'
);
insert into sensor_range (
metric_desc,
metric_level,
metric_ts
)
values (
'Temperature',
'low',
'{[2021-11-01 10:00, 2021-11-01 12:00],[2021-11-05 21:00, 2021-11-05 22:00]}'
);
insert into sensor_range
(metric_desc, metric_level, metric_ts)
values
(
'Temperature',
'low',
'{[2021-11-01 10:00, 2021-11-01 12:00],[2021-11-05 21:00, 2021-11-05 22:00]}'
);
```
Query the data in the table to see the inserted data rows
+24 -24
View File
@@ -1,5 +1,5 @@
---
title: "Supabase Studio"
title: 'Supabase Studio'
description: "The same Dashboard that you're using on our Platform is now available for local development and Self-Hosting."
author: paul_copplestone
author_url: https://github.com/kiwicopple
@@ -15,26 +15,26 @@ date: '2021-11-30'
toc_depth: 2
---
Today we're releasing [Supabase Studio](https://github.com/supabase/supabase/tree/master/studio).
The same Dashboard that you're using on our Platform is now available for [Local Development](https://supabase.com/docs/guides/local-development)
Today we're releasing [Supabase Studio](https://github.com/supabase/supabase/tree/master/studio).
The same Dashboard that you're using on our Platform is now available for [Local Development](https://supabase.com/docs/guides/local-development)
and [Self-Hosting](https://supabase.com/docs/guides/hosting/overview).
## Background
Let's get the obvious question out of the way - why wasn't it already open source?
When Ant and I started Supabase the codebase was one large monorepo which contained everything from the dashboard to cloud infrastructure code to experimental code.
When Ant and I started Supabase the codebase was one large monorepo which contained everything from the dashboard to cloud infrastructure code to experimental code.
This is our preferred development setup - we like to keep all code in one place so that we have a single source of truth and tightly coupled CI workflows.
The original Dashboard really wasn't much except a couple of buttons which allowed our Alpha users to start and stop a Supabase project - back then only a PostgreSQL connection string.
Then time went on, and we started gaining traction - a lot faster than expected.
The nice thing about building for developers is that they are very vocal about the features they want to see next.
Then time went on, and we started gaining traction - a lot faster than expected.
The nice thing about building for developers is that they are very vocal about the features they want to see next.
And so we kept shipping - a SQL editor, a Table view, User management, auto-generated Docs, and everything else you can find on the Dashboard today.
We've been planning to make the Dashboard public for a long time now, starting with [Supabase UI](https://ui.supabase.com/),
[`supabase/grid`](https://github.com/supabase/grid), and a standalone [PR](https://github.com/supabase/supabase/pull/2281) for Supabase Studio.
But as feature requests kept rolling in it became a Sisyphean task to maintain separate code bases.
We've been planning to make the Dashboard public for a long time now, starting with [Supabase UI](https://ui.supabase.com/),
[`supabase/grid`](https://github.com/supabase/grid), and a standalone [PR](https://github.com/supabase/supabase/pull/2281) for Supabase Studio.
But as feature requests kept rolling in it became a Sisyphean task to maintain separate code bases.
After the last Launch Week, we decided to prioritize the Studio.
@@ -46,22 +46,23 @@ We investigated a few different approaches used in the open source world. There
- Keep two repos in sync using some mixture of git submodules or a tool like [copycat](https://github.com/atomix/copycat)
- Use the same codebase, abstracting platform-specific code behind an API and feature flags.
Our frontend [team](https://github.com/orgs/supabase/teams/frontend/members) experimented with the first two approaches where they plucked (injected, merged, and wrangled) code for our Platform.
Time-to-production become a lot slower, an unacceptable outcome at Supabase.
Our frontend [team](https://github.com/orgs/supabase/teams/frontend/members) experimented with the first two approaches where they plucked (injected, merged, and wrangled) code for our Platform.
Time-to-production become a lot slower, an unacceptable outcome at Supabase.
Eventually we decided to open source all the frontend code, a shared codebase for both the Platform and Self Hosting.
Eventually we decided to open source all the frontend code, a shared codebase for both the Platform and Self Hosting.
<details>
<summary>
<a>Special shout out</a>
</summary>
Sentry do a fantastic job of <a href="https://develop.sentry.dev/sentry-vs-getsentry/">documenting their strategy</a> for managing a shared codebase and served as a great model for Supabase.
<summary>
<a>Special shout out</a>
</summary>
Sentry do a fantastic job of <a href="https://develop.sentry.dev/sentry-vs-getsentry/">
documenting their strategy
</a> for managing a shared codebase and served as a great model for Supabase.
</details>
## Build in public
For the past week, the dashboard you've been using on the Platform has been deployed from our [main repository](https://github.com/supabase/supabase/tree/master/studio).
For the past week, the dashboard you've been using on the Platform has been deployed from our [main repository](https://github.com/supabase/supabase/tree/master/studio).
This fits one of our core philosophies at Supabase: do less. Less code to manage means less bugs. Fewer codebases means faster shipping.
@@ -69,7 +70,7 @@ This fits one of our core philosophies at Supabase: do less. Less code to manage
It also fits the development philosophy of every other part of Supabase: building in public, "warts and all".
This is a huge step forward for the community. Supabase users can now spot a bug, fix it in the open source repository,
This is a huge step forward for the community. Supabase users can now spot a bug, fix it in the open source repository,
and have it shipped to both the Platform and Self-Hosted environments - all in a few hours.
## Technical details
@@ -82,16 +83,16 @@ Studio is a Javascript application with a few key pieces of technology:
- [Next.js](https://nextjs.org/) - A frontend Javascript framework built with React.
- [Tailwind](https://tailwindcss.com/) - A utility-first CSS framework.
- [Supabase UI](https://ui.supabase.com/) - Our own component library.
- [Supabase UI](https://ui.supabase.com/) - Our own component library.
- [MobX](https://www.mobxjs.com/) - A state management library.
- A host of other [useful libraries](https://github.com/supabase/supabase/blob/master/studio/package.json), including [Radix UI](https://www.radix-ui.com), [Lottiefiles](https://lottiefiles.com/), [react-grid-layout](https://github.com/react-grid-layout/react-grid-layout), and [zxcvbn](https://github.com/dropbox/zxcvbn).
### What's included
Studio is designed to work with existing deployments - either the local hosted, docker setup, or our CLI.
It is not intended for managing the deployment and administration of projects - that's out of scope.
Studio is designed to work with existing deployments - either the local hosted, docker setup, or our CLI.
It is not intended for managing the deployment and administration of projects - that's out of scope.
The features exposed on Studio for existing deployments are limited to those which manage your database:
The features exposed on Studio for existing deployments are limited to those which manage your database:
- Table & SQL editors. (Saved queries are unavailable for now)
- Database management: Policies, roles, extensions, replication.
@@ -108,4 +109,3 @@ Check out the full instructions in our [Studio Readme](https://github.com/supaba
Our new open source Studio is also on Product Hunt right now. If you like what you see, please give it an upvote and a review.
[![Open Source Table Editor](https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=321226&theme=light)](https://www.producthunt.com/posts/open-source-postgresql-studio?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-open-source-postgresql-studio)
@@ -67,6 +67,7 @@ create policy todo_select_policy
which is equivalent to adding
{/* prettier-ignore */}
```sql hideCopy
select *
from todos
@@ -116,7 +117,7 @@ To maximize throughput, the query used to evaluate if a row is visible to a subs
For example:
```sql hideCopy
select exists(select 1 from some_table where id = 806);
select exists (select 1 from some_table where id = 806);
```
When more than one subscriber exists, the query is wrapped in a [prepared statement](https://www.postgresql.org/docs/13/sql-prepare.html) to remove the cost of the PostgreSQL
+9 -9
View File
@@ -18,9 +18,9 @@ toc_depth: 3
<div className="bg-gray-300 rounded-lg px-6 py-2 italic">
🆕 `pg_graphql` is now generally available and has undergone significant enhancements since this announcement. Here is what is new:
- [pg_graphql v1.0](https://supabase.com/blog/pg-graphql-v1)
- [New Features in pg_graphql v1.2](https://supabase.com/blog/whats-new-in-pg-graphql-v1-2)
- [pg_graphql v1.0](https://supabase.com/blog/pg-graphql-v1)
- [New Features in pg_graphql v1.2](https://supabase.com/blog/whats-new-in-pg-graphql-v1-2)
</div>
@@ -36,7 +36,6 @@ Today we're open sourcing [`pg_graphql`](https://github.com/supabase/pg_graphql)
Interested? You're [3 commands away](https://supabase.github.io/pg_graphql/quickstart/) from a live [GraphiQL](https://graphql-dotnet.github.io/docs/getting-started/graphiql/) demo.
## Motivation
The Supabase stack is centered around PostgreSQL as the single source of truth. All data, configuration, and security are housed in the database so any GraphQL solution needed to be equivalently SQL-centric.
@@ -85,11 +84,11 @@ As a limited example of how the reflection engine works, here's how it converts
```sql hideCopy
# schema.sql
create table account(
id serial primary key,
email varchar(255) not null,
created_at timestamp not null,
updated_at timestamp not null
create table account (
id serial primary key,
email varchar(255) not null,
created_at timestamp not null,
updated_at timestamp not null
);
```
@@ -179,6 +178,7 @@ We're opting to expose the function over HTTP through PostgREST but you could al
When it comes to APIs, performance counts. Here are some figures from [Apache Bench](https://www.tutorialspoint.com/apache_bench/apache_bench_quick_guide.htm) showing 2,205 requests/second on a 4 core machine with 16 GB of memory.
{/* prettier-ignore */}
```markdown hideCopy
Concurrency Level: 8
Time taken for tests: 3.628 seconds
+15 -12
View File
@@ -31,9 +31,9 @@ To demonstrate what we're working towards, the following example shows what we'l
```sql
-- create a table
create table public.members(
id int primary key,
name text not null
create table public.members (
id int primary key,
name text not null
);
-- Enable auditing on the new table
@@ -44,10 +44,15 @@ Produce some records to audit
```sql
-- create a new record
insert into public.members(id, name) values (1, 'foo');
insert into public.members
(id, name)
values
(1, 'foo');
-- edit the record
update public.members set name = 'bar' where id = 1;
update public.members
set name = 'bar'
where id = 1;
-- delete the record
delete from public.members;
@@ -56,7 +61,7 @@ delete from public.members;
Review the audit log
```sql
select * from audit.record_history
select * from audit.record_history;
```
```markdown
@@ -227,14 +232,12 @@ Finally, we index the `record_id` and `old_record_id` columns that contain these
```sql
-- index record_id for fast searching
create index record_version_record_id
on audit.record_version(record_id)
where record_id is not null;
create index record_version_record_id on audit.record_version (record_id)
where record_id is not null;
-- index old_record_id for fast searching
create index record_version_old_record_id
on audit.record_version(record_id)
where old_record_id is not null;
create index record_version_old_record_id on audit.record_version (record_id)
where old_record_id is not null;
```
### Enrollment
@@ -15,8 +15,8 @@ toc_depth: 3
🆕 `pg_graphql` has undergone significant enhancements since this announcement. Here is what is new:
- [pg_graphql v1.0](https://supabase.com/blog/pg-graphql-v1)
- [New Features in pg_graphql v1.2](https://supabase.com/blog/whats-new-in-pg-graphql-v1-2)
- [pg_graphql v1.0](https://supabase.com/blog/pg-graphql-v1)
- [New Features in pg_graphql v1.2](https://supabase.com/blog/whats-new-in-pg-graphql-v1-2)
</div>
@@ -67,11 +67,11 @@ GraphQL types and fields are reflected from the SQL schema:
For example:
```sql
create table "Account"(
"id" serial primary key,
"email" varchar(255) not null,
"createdAt" timestamp not null,
"updatedAt" timestamp not null
create table "Account" (
"id" serial primary key,
"email" varchar(255) not null,
"createdAt" timestamp not null,
"updatedAt" timestamp not null
);
```
@@ -152,12 +152,11 @@ For example, when selecting all comments for a blog post:
```sql
select
blog_posts.title,
comments.body as comment_body
blog_posts.title,
comments.body as comment_body
from
blog_posts
join
comments on blog_posts.id = comments.blog_post_id
blog_posts
join comments on blog_posts.id = comments.blog_post_id;
```
a SQL response would duplicate all data from the `blog_posts` table (title).
@@ -330,7 +329,6 @@ Didn't see the feature you're interested in? [Let us know](https://github.com/su
## More pg_graphql
- [Introducing pg_graphql: A GraphQL extension for PostgreSQL](https://supabase.com/blog/pg-graphql)
- [Introducing pg_graphql: A GraphQL extension for PostgreSQL](https://supabase.com/blog/pg-graphql)
- [pg_graphql v1.0](https://supabase.com/blog/pg-graphql-v1)
- [pg_graphql v1.2](https://supabase.com/blog/whats-new-in-pg-graphql-v1-2)
@@ -39,7 +39,6 @@ The next and third major version, announced today on new Supabase Realtime Day,
Presence is great for showing when your users are online. When a user connects or disconnects (even accidentally!), the server automatically detects this and let's everyone else know.
Presence is powered by [CRDTs](https://crdt.tech/) (Conflict-free Replicated Data Type) on a distributed cluster useful for storing synced data across nodes.
### Broadcast
![realtime-multiplayer-broadcast.png](/images/blog/launch-week-4/friday-realtime/realtime-multiplayer-broadcast.png)
@@ -50,7 +49,7 @@ In Realtime, connected users communicate with other subscribing & publishing to
### Extensions
In this version of Realtime we're re-architecting the way that we listen to PostgreSQL databases by opening the WebSocket functionality for general use.
Instead of only using WebSockets to broadcast database changes, we are now moving the PostgreSQL listener to an “extension” architecture. This opens up numerous new ways of extending the server.
Instead of only using WebSockets to broadcast database changes, we are now moving the PostgreSQL listener to an “extension” architecture. This opens up numerous new ways of extending the server.
Here are just a few that we've heard:
- Multiple Postgres: listen to multiple PostgreSQL databases at the same time.
@@ -14,13 +14,14 @@ toc_depth: 3
Python is an excellent choice when it comes to data science. With a wide selection of libraries and built-in analytics tools, you can crunch data with ease, analyze even the most complex datasets, and visualize your results in beautiful charts and graphs.
Supabase is backend-as-a-service built on top of PostgreSQL. It’s an excellent choice for building modern data-intensive apps and tooling.
Supabase is backend-as-a-service built on top of PostgreSQL. It's an excellent choice for building modern data-intensive apps and tooling.
Thanks to our incredible community, Supabase now has a powerful and open source [Python SDK](https://github.com/supabase-community/supabase-py). With Supabase and Python, you can automate tasks such as CRUD operations with only a few lines of code. This guide will first create a simple schema in Supabase, then we’ll use the Supabase Python SDK to show how you can load sample data.
Thanks to our incredible community, Supabase now has a powerful and open source [Python SDK](https://github.com/supabase-community/supabase-py). With Supabase and Python, you can automate tasks such as CRUD operations with only a few lines of code. This guide will first create a simple schema in Supabase, then we'll use the Supabase Python SDK to show how you can load sample data.
## Prerequisites
Before we dive in, let’s look at some prerequisites you'll need:
Before we dive in, let's look at some prerequisites you'll need:
- Python version > 3.7
- The SDK only supports version > 3.7. You can download a supported Python version from [here](https://www.python.org/downloads/).
- Python virtual environment
@@ -28,8 +29,8 @@ Before we dive in, let’s look at some prerequisites you'll need:
- Faker python package
- We will be using the [faker-commerce](https://pypi.org/project/faker-commerce/) package from the Faker library in Python to generate realistic sample data.
## Loading data into Supabase using Python
Supabase is built for developers, and you can [get started for free](https://app.supabase.com) using your existing Github account. Once your Supabase account is set up, you will access the Supabase dashboard. From here, go to All Project > New Project.
![screen shot of supabase dashboard](/images/blog/python-1/1.png)
@@ -42,7 +43,8 @@ Your project will spin up within 2 minutes.
## Creating tables in Supabase
In this example, we’ll be creating 2 tables in Supabase:
In this example, we'll be creating 2 tables in Supabase:
- Vendor (fields are vendor_name, vendor_location, and total_employees)
- Product (vendor_id as FK, product_name, price, and total orders)
@@ -54,7 +56,7 @@ Let us now begin creating the tables. Once you create a project, you will need t
![screen shot of supabase dashboard](/images/blog/python-1/4.png)
Now, you can create a table according to the defined schema.
Now, you can create a table according to the defined schema.
![screen shot of supabase dashboard](/images/blog/python-1/5.png)
Click on Save to create your vendor table. Similarly, create the product table.
@@ -98,7 +100,7 @@ So for our example we will set them up like this:
```bash
export SUPABASE_URL=<<the value under config > URL>>
export SUPABASE_KEY=<<the value present in Project API keys > anon public>>
export SUPABASE_KEY=<<the value present in Project API keys > anon public>>
export SUPABASE_SECRET_KEY=<<the value present in Project API keys > service_role secret>>
```
@@ -160,8 +162,9 @@ main()
```
To summarize what we have done using this code snippet:
- We have inserted 10 random vendors to the table.
- For each of the 10 vendors, we have inserted a number of different products
- For each of the 10 vendors, we have inserted a number of different products
## Reading the data stored in Supabase
@@ -170,16 +173,16 @@ Data can also be viewed directly from the Supabase dashboard. To do this, go to
![screen shot of supabase dashboard](/images/blog/python-1/11.png)
![screen shot of supabase dashboard](/images/blog/python-1/12.png)
Note: In case you cannot see any of the data, you should hit the Refresh button.
Note: In case you cannot see any of the data, you should hit the Refresh button.
## Conclusion
With Python, data loading into Supabase is easy. It just takes a few easy steps to get started with the Python SDK and Supabase. In the next part of this blog series, we will learn [how to visualize the data that we just loaded into Supabase using Metabase](/blog/visualizing-supabase-data-using-metabase). Stay tuned!
If you have any questions please reach out via [Twitter](https://twitter.com/supabase) or join our [Discord](https://discord.supabase.com).
## More Python and Supabase resources
- [supabase-py](https://github.com/supabase-community/supabase-py)
- [Slack Consolidate: a slackbot built with Python and Supabase](/blog/slack-consolidate-slackbot-to-consolidate-messages)
- [Supabase-py (Database) on Replit](https://replit.com/@Supabase/Supabase-py-Database?v=1)
@@ -100,10 +100,11 @@ Next, under the database, select "SupabaseDB".
We will be using the following SQL query:
```sql
SELECT "Vendor".vendor_name, product_name, "Vendor".total_employees
FROM "Product"
LEFT JOIN "Vendor" on "Product".vendor_id = "Vendor".vendor_id
WHERE "Vendor".total_employees
select "Vendor".vendor_name, product_name, "Vendor".total_employees
from
"Product"
left join "Vendor" on "Product".vendor_id = "Vendor".vendor_id
where "Vendor".total_employees;
```
This query should fetch us the vendor name and the product where the number of employees for a given vendor is less than 110.
@@ -266,7 +266,7 @@ class MyApp extends StatelessWidget {
When a user launches the app, we want to redirect those who have already signed in to the Chat page and those who have not signed in yet to the register page.
In order to achieve this, we will create a splash page, which is just a page with a preloader at the middle from the user, but takes care of fetching auth state and redirects users accordingly behind the scenes.
Within the `_redirect()` method, we are fetching the auth state and navigating the user accordingly.
Within the `_redirect()` method, we are fetching the auth state and navigating the user accordingly.
```dart title="lib/pages/splash_page.dart"
import 'package:flutter/material.dart';
@@ -443,17 +443,16 @@ A likely requirement from the original scenario that we've completely ignored is
In SQL:
```sql hideCopy
BEGIN;
begin;
CREATE TABLE posts_seen_by_users (
post_id bigint REFERENCES posts(id),
user_id bigint REFERENCES users(id),
seen_count bigint NOT NULL DEFAULT 0 CHECK (seen_count > 0),
PRIMARY KEY (post_id, user_id)
create table posts_seen_by_users (
post_id bigint references posts (id),
user_id bigint references users (id),
seen_count bigint not null default 0 check (seen_count > 0),
primary key (post_id, user_id)
);
COMMIT;
commit;
```
#### Caveats
@@ -192,15 +192,26 @@ Go to [SQL Editor](https://app.supabase.com/project/_/sql) and run insert comman
Inserting a public channel named #support-channel to be monitored:
```sql
INSERT INTO slack_channels(channel,channel_id,p_level,dest_channel, dest_channel_id, private)
VALUES ('support-channel','C0000ABC02DE','Support msgs', 'all_them_messages','C0000ABC02DF',0);
insert into slack_channels
(channel, channel_id, p_level, dest_channel, dest_channel_id, private)
values
('support-channel', 'C0000ABC02DE', 'Support msgs', 'all_them_messages', 'C0000ABC02DF', 0);
```
Inserting a **private** channel named #support-enterprise to be monitored:
```sql
INSERT INTO slack_channels(channel,channel_id,p_level,dest_channel, dest_channel_id, private)
VALUES ('support-enterprise','C0000ABC02DC','Enterprise Support msgs', 'all_them_messages','C0000ABC02DF',1);
insert into slack_channels
(channel, channel_id, p_level, dest_channel, dest_channel_id, private)
values
(
'support-enterprise',
'C0000ABC02DC',
'Enterprise Support msgs',
'all_them_messages',
'C0000ABC02DF',
1
);
```
Notes:
+1 -1
View File
@@ -14,7 +14,7 @@ video: https://www.youtube.com/v/iqZlPtl_b-I
<div className="bg-gray-300 rounded-lg p-6 italic">
<br /> ⚠️ UPDATED 20/10: supabase-js v2 is fully released 🥳<br />
<a href="https://supabase.com/docs/reference/javascript">Check the updated docs</a> and <a href="https://supabase.com/docs/reference/javascript/v1/upgrade-guide">migration guide</a>.
[Check the updated docs](https://supabase.com/docs/reference/javascript) and [migration guide](https://supabase.com/docs/reference/javascript/v1/upgrade-guide).
</div>
+26 -25
View File
@@ -39,6 +39,7 @@ The `text` format is used by default, which gives you the same output you’d ge
For `supabase-js`, you can get the execution plan using the `explain()` transform. This works for every `supabase-js` method, including `rpc()`. Here’s an example for `select()`.
{/* prettier-ignore */}
```jsx
const { data, error } = await supabase
.from('projects')
@@ -118,28 +119,28 @@ To avoid unnecessary JSON arrays in a query result, one-to-one relationships are
```sql
-- A unique constraint on a foreign key
create table country(
id serial primary key
, name text
create table country (
id serial primary key,
name text
);
create table capital(
id serial primary key
, name text
, country_id int unique
, foreign key (country_id) references country(id)
create table capital (
id serial primary key,
name text,
country_id int unique,
foreign key (country_id) references country (id)
);
-- or a primary key on a foreign key
create table country(
id serial primary key
, name text
create table country (
id serial primary key,
name text
);
create table capital(
id serial primary key
, name text
, foreign key (id) references country(id)
create table capital (
id serial primary key,
name text,
foreign key (id) references country (id)
);
```
@@ -254,20 +255,20 @@ Detecting join tables for many-to-many relationships has been working for many r
-- the primary key must include the foreign key columns
-- of the many-to-many ends
create table books(
id int primary key
, name text
create table books (
id int primary key,
name text
);
create table books_authors(
book_id int references books(id)
, author_id int references authors(id)
, primary key(book_id, author_id) -- this is now necessary
create table books_authors (
book_id int references books (id),
author_id int references authors (id),
primary key (book_id, author_id) -- this is now necessary
);
create table authors(
id int primary key
, name text
create table authors (
id int primary key,
name text
);
```
+6 -5
View File
@@ -147,11 +147,12 @@ Now you can encrypt table secrets with this new key by inserting its ID explicit
```sql
insert into vault.secrets
(secret, associated, key_id)
values (
'apikey_XaYrurzcquqhEdBjzfTzfwAZqpd',
'This is some different associated data.',
'f9f176eb-7069-4743-9403-582c04354ffc'
)
values
(
'apikey_XaYrurzcquqhEdBjzfTzfwAZqpd',
'This is some different associated data.',
'f9f176eb-7069-4743-9403-582c04354ffc'
)
returning *;
```
@@ -34,8 +34,8 @@ Along our jurney we will:
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
></iframe>
</div>
></iframe>
</div>
Watch the video version of the tutorial.
Since there are quite some code snippets we need I've put together the [full source code on Github](https://github.com/saimon24/supabase-trello-angular) so you can easily run the project yourself!
@@ -13,7 +13,7 @@ toc_depth: 2
During [Launch Week 5](https://supabase.com/launch-week), we shipped a lot of features, which was exciting, but what is more exciting is reviewing all the awesome hackathon submissions you all created for [Launch Week 5 Hackathon!](https://supabase.com/blog/launch-week-5-hackathon)
It is always tough to pick the winner, but this time around it was extra hard because of the amount of incredible projects: there were 46 submitted projects, making this our most popular hackathon yet. You will be able to view all of the submissions of this hackathon on [madewithsupabase.com](https://www.madewithsupabase.com/).
It is always tough to pick the winner, but this time around it was extra hard because of the amount of incredible projects: there were 46 submitted projects, making this our most popular hackathon yet. You will be able to view all of the submissions of this hackathon on [madewithsupabase.com](https://www.madewithsupabase.com/).
And now, without further ado, congratulations to the winners of Supabase Launch Week 5 Hackathon!
@@ -23,7 +23,7 @@ And now, without further ado, congratulations to the winners of Supabase Launch
[Supabase Cache Helpers](https://github.com/psteinroe/supabase-cache-helpers) - by [@psteinroe](https://twitter.com/psteinroe).
Supabase Cache Helpers is a caching utility library that works by turning your Supabase query into cache keys and provides methods for retrieving and mutating data on your Supabase database.
Supabase Cache Helpers is a caching utility library that works by turning your Supabase query into cache keys and provides methods for retrieving and mutating data on your Supabase database.
![Supabase Cache Helpers](/images/blog/lw5-hackathon-winners/supabase-cache-helpers.png)
@@ -31,7 +31,7 @@ Supabase Cache Helpers is a caching utility library that works by turning your S
[Super UI](https://github.com/pheralb/superui) - by [Pablo Hdez](https://twitter.com/pheralb_), [Nacho Aldama](https://twitter.com/srdrabx), [David Huertas](https://twitter.com/ikurotime) and [Juan Rojas](https://twitter.com/tmchein)
Super UI is a React UI component library for React, but it also lets you customize and create your own components from your browser.
Super UI is a React UI component library for React, but it also lets you customize and create your own components from your browser.
![Super UI](/images/blog/lw5-hackathon-winners/super-ui.png)
@@ -59,7 +59,7 @@ Vesta is an open source Google Keep alternative that helps you to save notes and
[Otentik Authenticator](https://github.com/riipandi/otentik-authenticator-desktop) - by [@riipandi](https://twitter.com/riipandi)
Otentik is a security desktop app to manage your 2-step verification tokens when logging into your online services so that you can complete your login flow within your computer.
Otentik is a security desktop app to manage your 2-step verification tokens when logging into your online services so that you can complete your login flow within your computer.
![Otentik Authenticator](/images/blog/lw5-hackathon-winners/otentik.png)
@@ -67,7 +67,7 @@ Otentik is a security desktop app to manage your 2-step verification tokens when
[Stripe Sync](https://github.com/lawrencecchen/stripe-sync) - by [@lawrencecchen](https://github.com/lawrencecchen)
Stripe Sync allows you to automatically sync your stripe data into your Supabase database using Edge Functions.
Stripe Sync allows you to automatically sync your stripe data into your Supabase database using Edge Functions.
![Stripe Sync](/images/blog/lw5-hackathon-winners/stripe-sync.png)
@@ -85,7 +85,7 @@ Play a classic hot dog eating contest with your friends! That Hot Dog Game uses
[Quizgrad (Quiz App)](https://github.com/mehulsatardekar/Quizgrad) - by [Mehul Satardekar](https://twitter.com/MehulSatardekar)
Quizgrad is a fun quiz app where you can compete against other players on quizzes from different categories.
Quizgrad is a fun quiz app where you can compete against other players on quizzes from different categories.
![Quizgrad (Quiz App)](/images/blog/lw5-hackathon-winners/quiz-grad.png)
@@ -113,7 +113,7 @@ A productivity manager application where you can leave a note right from your de
[RepoWatch](https://github.com/vvidday/repo-watch) - by [@vvidday](https://github.com/vvidday)
With RepoWatch, you can view any updates on across all the repositories in the Supabase GitHub org in realtime.
With RepoWatch, you can view any updates on across all the repositories in the Supabase GitHub org in realtime.
![RepoWatch](/images/blog/lw5-hackathon-winners/repo-watch.png)
@@ -127,7 +127,7 @@ CoWoFi is an application that helps managers, executives and the general public
## The Prizes
As promised in the [hackathon announcement article](https://supabase.com/blog/launch-week-5-hackathon), each members of the winners will receive a limited edition golden Supabase cap!
As promised in the [hackathon announcement article](https://supabase.com/blog/launch-week-5-hackathon), each members of the winners will receive a limited edition golden Supabase cap!
![Golden Supabase Cap](/images/blog/lw5-hackathon/cap.png)
@@ -2,7 +2,7 @@
title: Supabase Beta August 2022
description: Launch Week Special. See everything we shipped, plus winners of the Hackathon and the extended Community Highlights
author: ant_wilson
image: 2022-august/monthly-update-august-2022.jpg
image: 2022-august/monthly-update-august-2022.jpg
thumb: 2022-august/monthly-update-august-2022.jpg
tags:
- release-notes
@@ -18,7 +18,7 @@ This month the Beta Update is a Launch Week special. #SupaLaunchWeek 5 just happ
The Supabase CLI is now in v1.0 (including the ability to generate TypeScript types 🎉). We also released the Management API Beta, a REST API that opens the door to a whole new suite of integrations (Zapier, Terraform, Pulumi, you name it). Full programmatic control of your projects and orgs is on the way.
[Blog Post](https://supabase.com/blog/supabase-cli-v1-and-admin-api-beta)
[Blog Post](https://supabase.com/blog/supabase-cli-v1-and-admin-api-beta)
[Video Announcement](https://www.youtube.com/watch?v=OpPOaJI_Z28)
@@ -26,7 +26,7 @@ The Supabase CLI is now in v1.0 (including the ability to generate TypeScript ty
![Day 2 - supabase-js v2 Release Candidate](/images/blog/2022-august/supabase_js.png)
**[supabase-js v2](https://github.com/supabase/supabase-js)** focuses on "quality-of-life" improvements for developers and includes Type Support, new Auth Methods, async Auth overhaul, improvements for Edge Functions, and more. We couldn't have done this without our amazing Community, so thanks a lot to everyone who contributed.
**[supabase-js v2](https://github.com/supabase/supabase-js)** focuses on "quality-of-life" improvements for developers and includes Type Support, new Auth Methods, async Auth overhaul, improvements for Edge Functions, and more. We couldn't have done this without our amazing Community, so thanks a lot to everyone who contributed.
Try it out by running `npm i @supabase/supabase-js@rc`
@@ -84,8 +84,8 @@ We had a huge amount of open source submissions 🤯. The selection process was
The following changes to the Supabase Platform will take effect from September 11th at 7 pm PDT.
- HTTP API requests to Supabase will automatically be redirected to HTTPS.
- The API Key was passed to Supabase both in the `Authorization` header and in a separate `apiKey` header. This led to confusion among new users of Supabase who used the API directly. It is no longer required to send the anon key or service key via the `apiKey` header. If you are using Supabase via our client libraries, no change is required from your side.
- HTTP API requests to Supabase will automatically be redirected to HTTPS.
- The API Key was passed to Supabase both in the `Authorization` header and in a separate `apiKey` header. This led to confusion among new users of Supabase who used the API directly. It is no longer required to send the anon key or service key via the `apiKey` header. If you are using Supabase via our client libraries, no change is required from your side.
## Supabase Migration Guides
@@ -93,10 +93,10 @@ The following changes to the Supabase Platform will take effect from September 1
Our guides and tools make it super easy to migrate your projects to Supabase:
- [Firebase Auth Migration](https://supabase.com/docs/guides/migrations/firebase-auth)
- [Firestore Data Migration](https://supabase.com/docs/guides/migrations/firestore-data)
- [Firebase Storage Migration](https://supabase.com/docs/guides/migrations/firebase-storage)
- [Migrate from Heroku to Supabase](https://supabase.com/docs/guides/migrations/heroku)
- [Firebase Auth Migration](https://supabase.com/docs/guides/migrations/firebase-auth)
- [Firestore Data Migration](https://supabase.com/docs/guides/migrations/firestore-data)
- [Firebase Storage Migration](https://supabase.com/docs/guides/migrations/firebase-storage)
- [Migrate from Heroku to Supabase](https://supabase.com/docs/guides/migrations/heroku)
## Webinar: How Netlify and Supabase Enables “Supa” DX
@@ -104,8 +104,7 @@ Our guides and tools make it super easy to migrate your projects to Supabase:
Our friends from Netlify invited Ant to their new webinar series. He and Netlify's VP of Partnerships & Ecosystems, Steven Larsen, will show you how to:
- Deploy Supabase’s open source backend together with Netlify
- Deploy Supabase's open source backend together with Netlify
- Build User Management without handing over user data to any third parties
- Upload files and folders to the cloud without needing to tack on additional tools
@@ -115,20 +114,20 @@ Our friends from Netlify invited Ant to their new webinar series. He and Netlify
![Community](/images/blog/2022-june/community.jpg)
- Inian shared our journey with Cloudflare. [Video](https://twitter.com/Cloudflare/status/1557728943901675520)
- Supabase Tips: Introduction to Supabase Storage. [Video](https://www.youtube.com/watch?v=J9mTPY8rIXE)
- Zack DeRose playing around with Supabase and Nx. [Part 1](https://www.youtube.com/watch?v=OTh5GBBfr4E) | [Part 2](https://www.youtube.com/watch?v=_5gJi_xwpzk)
- A new guide showing how to combine Supabase and Directus. [Guide](https://supabase.com/docs/guides/integrations/directus)
- Supabase with Flutter course on raywenderlich. [Course](https://www.raywenderlich.com/33619647-supabase-with-flutter)
- Build a full-stack app with Next.js and Supabase on the LogRocket blog. [Tutorial](https://blog.logrocket.com/build-full-stack-app-next-js-supabase/)
- Supabase Crash Course by The Net Ninja. [Video Courses](https://www.youtube.com/watch?v=ydz7Dj5QHKY&list=PL4cUxeGkcC9hUb6sHthUEwG7r9VDPBMKO)
- How to build a Grocery Application with Webflow CMS using DhiWise. [Tutorial](https://dev.to/saloni137/how-to-build-a-grocery-application-with-webflow-cms-using-dhiwise-1a72)
- How To Create a Full Stack app with SolidJS, Supabase, and TailwindCSS [Video and Tutorial.](https://blog.chetanverma.com/how-to-create-a-full-stack-app-with-solidjs-supabase-and-tailwindcss)
- The WalletConnect Cloud now supports Sign in with Ethereum. [Announcement](https://twitter.com/TheHarryET/status/1559861021845643265).
- Building In Public: Cartta Tech Stack. [Article](https://dev.to/fvaldes33/building-in-public-cartta-tech-stack-5en0)
- Supabase + Vue 3 in 12 minutes. [Video](https://www.youtube.com/watch?v=YN32uVqAXw8&feature=emb_title)
- Inian shared our journey with Cloudflare. [Video](https://twitter.com/Cloudflare/status/1557728943901675520)
- Supabase Tips: Introduction to Supabase Storage. [Video](https://www.youtube.com/watch?v=J9mTPY8rIXE)
- Zack DeRose playing around with Supabase and Nx. [Part 1](https://www.youtube.com/watch?v=OTh5GBBfr4E) | [Part 2](https://www.youtube.com/watch?v=_5gJi_xwpzk)
- A new guide showing how to combine Supabase and Directus. [Guide](https://supabase.com/docs/guides/integrations/directus)
- Supabase with Flutter course on raywenderlich. [Course](https://www.raywenderlich.com/33619647-supabase-with-flutter)
- Build a full-stack app with Next.js and Supabase on the LogRocket blog. [Tutorial](https://blog.logrocket.com/build-full-stack-app-next-js-supabase/)
- Supabase Crash Course by The Net Ninja. [Video Courses](https://www.youtube.com/watch?v=ydz7Dj5QHKY&list=PL4cUxeGkcC9hUb6sHthUEwG7r9VDPBMKO)
- How to build a Grocery Application with Webflow CMS using DhiWise. [Tutorial](https://dev.to/saloni137/how-to-build-a-grocery-application-with-webflow-cms-using-dhiwise-1a72)
- How To Create a Full Stack app with SolidJS, Supabase, and TailwindCSS [Video and Tutorial.](https://blog.chetanverma.com/how-to-create-a-full-stack-app-with-solidjs-supabase-and-tailwindcss)
- The WalletConnect Cloud now supports Sign in with Ethereum. [Announcement](https://twitter.com/TheHarryET/status/1559861021845643265).
- Building In Public: Cartta Tech Stack. [Article](https://dev.to/fvaldes33/building-in-public-cartta-tech-stack-5en0)
- Supabase + Vue 3 in 12 minutes. [Video](https://www.youtube.com/watch?v=YN32uVqAXw8&feature=emb_title)
## We’re hiring
## We're hiring
Come join one of the fastest growing open source projects ever 🤗
@@ -13,9 +13,9 @@ toc_depth: 3
Primary keys are important. They uniquely identify rows of data in tables, and make it easy to fetch data. The job of a database is to archive and recall data and you're going to have a hard time finding data without a good primary key or a good index.
Sometimes it makes sense to use a [“natural key”](https://en.wikipedia.org/wiki/Natural_key) (like an `email` column in a `users` table) and sometimes it’s better to use a “[surrogate key”](https://en.wikipedia.org/wiki/Surrogate_key), a value made *for the purpose* of identifying a row (and has no other meaning).
Sometimes it makes sense to use a [“natural key”](https://en.wikipedia.org/wiki/Natural_key) (like an `email` column in a `users` table) and sometimes it's better to use a “[surrogate key”](https://en.wikipedia.org/wiki/Surrogate_key), a value made _for the purpose_ of identifying a row (and has no other meaning).
At first glance, the question of *which* primary key to use is easy! Just throw a `integer`/`serial` on there, right? Numeric IDs are cool, but what about random value IDs or [Universally Unique IDentifiers (UUIDs)](https://en.wikipedia.org/wiki/Uuid)?
At first glance, the question of _which_ primary key to use is easy! Just throw a `integer`/`serial` on there, right? Numeric IDs are cool, but what about random value IDs or [Universally Unique IDentifiers (UUIDs)](https://en.wikipedia.org/wiki/Uuid)?
Turns out the question of which identifier (and in this case, UUID) to use is complicated -- we're going to dive into some of the complexity and inherent trade-offs, and figure things out:
@@ -51,24 +51,24 @@ CREATE UNIQUE INDEX users_email_uniq ON users USING BTREE (email);
This looks great, but what should `id` be on new rows? I don't know -- maybe the application can figure it out? If they store some value in memory? That doesn't seem right.
Maybe we could figure out the next integer from what's in the table itself -- we just need to be able to "count" upwards. We *do* have all the `users` tables rows in there, so we should be able to do it:
Maybe we could figure out the next integer from what's in the table itself -- we just need to be able to "count" upwards. We _do_ have all the `users` tables rows in there, so we should be able to do it:
```sql
INSERT INTO users (id, email, name)
SELECT COUNT(*) + 1, 'new@example.com', 'new_user' FROM users;
insert into users
(id, email, name)
select count(*) + 1, 'new@example.com', 'new_user' from users;
```
After running that query, we can double check our results:
```sql
SELECT * FROM users;
select * from users;
```
| id | email | name |
|----|------------------|----------|
| `1` | `new@example.com` | `new user` |
| `2` | `new2@example.com` | `new user` |
| id | email | name |
| --- | ------------------ | ---------- |
| `1` | `new@example.com` | `new user` |
| `2` | `new2@example.com` | `new user` |
Using `COUNT(*)` in our query is not the most efficient (or even easiest) solution though, and hopefully it's clear why -- **counting a sequence of numbers for primary keys is a feature built in to Postgres**!
@@ -76,46 +76,45 @@ Using `COUNT(*)` in our query is not the most efficient (or even easiest) soluti
### `serial`/`bigserial`
`serial` is essentially a convenient macro for using [Postgres sequences](https://www.postgresql.org/docs/current/sql-createsequence.html), a database-managed auto-incrementing stream of `integer`.
`serial` is essentially a convenient macro for using [Postgres sequences](https://www.postgresql.org/docs/current/sql-createsequence.html), a database-managed auto-incrementing stream of `integer`.
Let's hear it from the docs:
> The data types `smallserial`, `serial` and `bigserial` are not true types, but merely a notational convenience for creating unique identifier columns (similar to the `AUTO_INCREMENT` property supported by some other databases).
>
Using a `serial` column to create the `users` table would look like this:
Using a `serial` column to create the `users` table would look like this:
```sql
CREATE TABLE users (
id serial PRIMARY KEY,
email citext NOT NULL CHECK (LENGTH(email) < 255),
name text NOT NULL
create table users (
id serial primary key,
email citext not null check (length(email) < 255),
name text not null
);
```
OK, now let's try inserting into it - we shouldn't have to specify `id`:
```sql
INSERT INTO users_serial (email, name)
VALUES ('user@example.com', 'new user');
insert into users_serial
(email, name)
values
('user@example.com', 'new user');
```
```sql
SELECT * FROM users;
```
| id | email | name |
|----|------------------|----------|
| `1` | `user@example.com` | `new user` |
```sql
(1 row)
select * from users;
```
| id | email | name |
| --- | ------------------ | ---------- |
| `1` | `user@example.com` | `new user` |
```sql
(1 row)
INSERT 0 1
```
It works, as you might expect - now the application doesn't have to *somehow magically know* the right ID to use when inserting.
It works, as you might expect - now the application doesn't have to _somehow magically know_ the right ID to use when inserting.
But what does `serial` actually do? Using a serial column is operationally similar to the following SQL:
@@ -127,7 +126,7 @@ CREATE TABLE tablename (
ALTER SEQUENCE tablename_colname_seq OWNED BY tablename.colname;
```
Back in application land, the `INSERT` statement returns, and provides the new `id` the database assigned our new row. Multiple application instances don't need to coordinate what ID to use -- they just *don't*, and find out from the database.
Back in application land, the `INSERT` statement returns, and provides the new `id` the database assigned our new row. Multiple application instances don't need to coordinate what ID to use -- they just _don't_, and find out from the database.
**We've taken a somewhat meandering path to get here, but this is the standard solution for most reasonable database schemas.**
@@ -135,7 +134,7 @@ Back in application land, the `INSERT` statement returns, and provides the new `
There are few issues with sequences:
- When writing automation that simply iterates through id values, note that `serial` columns can have gaps, even if you never `DELETE` (e.x. if an `INSERT` was rolled back — sequences live *outside* transactions).
- When writing automation that simply iterates through id values, note that `serial` columns can have gaps, even if you never `DELETE` (e.x. if an `INSERT` was rolled back — sequences live _outside_ transactions).
- When used from outside code `serial` may leak some data or give attackers an edge (e.x., if `yoursite.com/users/50` works, how about `yoursite.com/users/51`?).
- `serial` is PostgreSQL specific (i.e. not SQL standards compliant)
@@ -157,7 +156,7 @@ CREATE TABLE (
)
```
So how does *this* work? Postgres does the same thing under the covers -- it generates a `sequence`. As this syntax is standards compliant, it's generally recommended practice for DBAs going forward, for the sake of the realm.
So how does _this_ work? Postgres does the same thing under the covers -- it generates a `sequence`. As this syntax is standards compliant, it's generally recommended practice for DBAs going forward, for the sake of the realm.
**So `integer`s are great, but information leakage is still a problem. How do we fix that?** Well, make the numbers random, obviously.
@@ -166,8 +165,8 @@ So how does *this* work? Postgres does the same thing under the covers -- it gen
Let's say the application using the DB has some [Python](https://python.org) code like the following:
```python
from random import randrange
from models import User
from random import randrange
from models import User
MAX_RANDOM_USER_ID = 1_000_000_000
def create_user():
"""
@@ -178,11 +177,11 @@ def create_user():
db.save(user)
```
That *looks* good, but there's a problem -- [random](https://docs.python.org/3/library/random.html) is a *pseudorandom* generator.
That _looks_ good, but there's a problem -- [random](https://docs.python.org/3/library/random.html) is a _pseudorandom_ generator.
[Pseudo-random](https://en.wikipedia.org/wiki/Pseudorandomness) numbers are *not* what we want for ensuring user IDs cannot be easily guessed/collide. It's possible to get the exact same sequence of values out of a pseudo-random number generator by using the same *seed value*.
[Pseudo-random](https://en.wikipedia.org/wiki/Pseudorandomness) numbers are _not_ what we want for ensuring user IDs cannot be easily guessed/collide. It's possible to get the exact same sequence of values out of a pseudo-random number generator by using the same _seed value_.
Sometimes you *want* pseudo-random behavior (let's say for testing or fuzzing), but it's generally not desired for production systems that might run from a duplicated identical application image, since they *could* have weak pseudo-random seed initialization.
Sometimes you _want_ pseudo-random behavior (let's say for testing or fuzzing), but it's generally not desired for production systems that might run from a duplicated identical application image, since they _could_ have weak pseudo-random seed initialization.
### (Secure) Random Numeric IDs
@@ -203,9 +202,9 @@ def create_user():
Now we have a secure random value coming in for our user IDs. But having values like `583247` and `8923916` get generated are cool and all, but there are a few problems:
- These numbers are random *and* quite inscrutable
- These numbers are random _and_ quite inscrutable
- The keyspace is fairly small (maybe good for comments on a popular website, but not for IDs!)
- People can still *technically* check them all (the guessing space is 1 to `MAX_RANDOM_USER_ID`!)
- People can still _technically_ check them all (the guessing space is 1 to `MAX_RANDOM_USER_ID`!)
We need something better.
@@ -215,7 +214,7 @@ Along comes UUIDs -- you're probably used to seeing them now, values like this U
`468e8075-5815-4fe2-80d3-45a31827954b` .
They're *very* random (almost always generated with secure random sources), and while they're even worse for remembering, they're near impossible to practically guess -- the search space is just too large!
They're _very_ random (almost always generated with secure random sources), and while they're even worse for remembering, they're near impossible to practically guess -- the search space is just too large!
More importantly, UUIDs introduce methodology to the madness -- different versions of UUID are derived different ways -- combined with other sources of randomness or known values.
@@ -223,12 +222,12 @@ There are a lot of [versions of UUID](https://en.wikipedia.org/wiki/Universally_
### UUIDv1
[Version 1 UUIDs](https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_1_(date-time_and_MAC_address)) have three two components:
[Version 1 UUIDs](<https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_1_(date-time_and_MAC_address)>) have three two components:
- a 60 bit date-time (at nanosecond precision)
- a 48 bit [MAC address](https://en.wikipedia.org/wiki/MAC_address)
But where's the randomness? Well v1s assume that you *won't* generate a ton of values in the same nanosecond (and there are some extra bits reserved for differentiating even when you do), but another source is the MAC address. MAC addresses uniquely (usually) identify network cards -- which is a security risk -- and those bits can be made random.
But where's the randomness? Well v1s assume that you _won't_ generate a ton of values in the same nanosecond (and there are some extra bits reserved for differentiating even when you do), but another source is the MAC address. MAC addresses uniquely (usually) identify network cards -- which is a security risk -- and those bits can be made random.
Here's what a UUIDv1 looks like:
@@ -247,13 +246,13 @@ SELECT uuid_generate_v1mc();
uuid_generate_v1mc
--------------------------------------
dd1bbf10-0b47-11ed-80de-db48f6faaf86
(1 row)
```
### UUIDv4
Version 4 UUIDs use *all* the available bits for randomness -- *122 bits worth*!.
Version 4 UUIDs use _all_ the available bits for randomness -- _122 bits worth_!.
UUIDv4s look like this:
@@ -261,7 +260,7 @@ UUIDv4s look like this:
ce0b897d-03a0-4f54-8c97-41d29a325a23
```
These don't have a time component, but they don't have in time they make up for in randomness -- it is *very* unlikely for them to collide, so they make for excellent Global Unique IDentifiers ("GUID"s).
These don't have a time component, but they don't have in time they make up for in randomness -- it is _very_ unlikely for them to collide, so they make for excellent Global Unique IDentifiers ("GUID"s).
We can generate them in Postgres like this (with `uuid-ossp`):
@@ -271,7 +270,7 @@ SELECT gen_random_uuid();
uuid_generate_v4
--------------------------------------
6ca93dde-81d4-4ea0-bfe1-92ecb4d81ee4
(1 row)
```
@@ -283,12 +282,12 @@ This is a workable solution, but as you might expect, it's not that easy.
![thats-alot-of-uuids.png](/images/blog/primary-keys/uuids.png)
UUIDv1 and v4 were a start, but weren't enough for *many* companies out there. There are a couple shortcomings that plague both v1 and v2:
UUIDv1 and v4 were a start, but weren't enough for _many_ companies out there. There are a couple shortcomings that plague both v1 and v2:
- UUIDs are twice the size of `bigint`/`bigserial`
- UUIDv1s contain a time element but they're *not* lexicographically sortable (this means they `SORT` terribly, relative to `integer` or a `timestamp` column)
- UUIDv1s contain a time element but they're _not_ lexicographically sortable (this means they `SORT` terribly, relative to `integer` or a `timestamp` column)
- UUIDv1s are less random than UUIDv4, and can collide/overlap in close enough time intervals, at large scale
- UUIDv4s index *terribly*, as they're essentially random values (obviously, they `SORT` terribly as well)
- UUIDv4s index _terribly_, as they're essentially random values (obviously, they `SORT` terribly as well)
Many of the world's biggest companies generated UUIDs at speeds that made all of these deficiencies a problem.
@@ -316,7 +315,7 @@ Here's a quick list (from that IETF document):
That's... A lot of UUIDs. They're all slightly different, but the innovation was summed up by the IETF:
> An inspection of these implementations details the following trends that help define this standard:
>
>
> - Timestamps MUST be k-sortable. That is, values within or close to the same timestamp are ordered properly by sorting algorithms.
> - Timestamps SHOULD be big-endian with the most-significant bits of the time embedded as-is without reordering.
> - Timestamps SHOULD utilize millisecond precision and Unix Epoch as timestamp source. Although, there is some variation to this among implementations depending on the application requirements.
@@ -324,7 +323,7 @@ That's... A lot of UUIDs. They're all slightly different, but the innovation was
> - IDs MUST ensure proper embedded sequencing to facilitate sorting when multiple UUIDs are created during a given timestamp.
> - IDs MUST NOT require unique network identifiers as part of achieving uniqueness.
> - Distributed nodes MUST be able to create collision resistant Unique IDs without a consulting a centralized resource.
> **The IETF went on to introduce three 3 new types of UUIDs** that have these properties these companies were looking for: UUIDv6, UUIDv7, and UUIDv8.
> **The IETF went on to introduce three 3 new types of UUIDs** that have these properties these companies were looking for: UUIDv6, UUIDv7, and UUIDv8.
So what's the difference you ask?
@@ -332,19 +331,19 @@ So what's the difference you ask?
- **UUIDv7** - 36 bits of big endian unix timestamp (seconds since epoch + leapseconds w/ optional sub-second precision) + variable randomness up to 62 bits
- **UUIDv8** - variable size timestamp (32/48/60/64 bits) + variable size clock (8/12 bits) + variable randomness (54/62 bits)
It's not quite easy to work out what this all *means* but let's boil it down:
It's not quite easy to work out what this all _means_ but let's boil it down:
- All of these UUIDs sort properly (the "high bits" of time are first, like putting the year before the month -- "2022/07")
- UUIDv6 *requires* randomness
- UUIDv6 _requires_ randomness
- The data contained in the UUID can be variable (ex. UUIDv8), this means you can bytes that mean something else (ex. an encoding of the compute region you're running in)
Alright, done hearing about UUIDs? Let's get to the fun part.
## Benchmarking ID generation with `uuid-ossp` and `pg_idkit`
With the history lesson behind us, let’s benchmark these ID generation mechanisms against each other! For UUIDv1 and UUIDv4 we can use [uuid-ossp](https://www.postgresql.org/docs/current/uuid-ossp.html).
With the history lesson behind us, let's benchmark these ID generation mechanisms against each other! For UUIDv1 and UUIDv4 we can use [uuid-ossp](https://www.postgresql.org/docs/current/uuid-ossp.html).
Unfortunately, `uuid-ossp` isn't *quite* so advanced as to have many of these newer UUIDs we've been discussing, so we’ll pull in [pg_idkit](https://github.com/t3hmrman/pg_idkit) here.
Unfortunately, `uuid-ossp` isn't _quite_ so advanced as to have many of these newer UUIDs we've been discussing, so we'll pull in [pg_idkit](https://github.com/t3hmrman/pg_idkit) here.
[pg_idkit](https://github.com/t3hmrman/pg_idkit) is built with Rust, so it gives us access to the following ID generation crates:
@@ -362,7 +361,7 @@ Unfortunately, `uuid-ossp` isn't *quite* so advanced as to have many of these ne
For each type of UUID, we can test the following:
- **Generation speed:** **How fast can I generate IDs (let's say 1,000,000 of them)?
- **Generation speed:** \*\*How fast can I generate IDs (let's say 1,000,000 of them)?
- **Table & Index size:** How much larger do tables and associated indices get?
<aside>
@@ -382,17 +381,18 @@ Generation speed is pretty easy to test, we can enable `\\timing` mode on `psql`
SELECT COUNT(idkit_ksuid_generate()) FROM generate_series(1, 1000000);
```
Running all of the ID generation mechanisms on a *single core* of my machine (which happens to be an [Oryx Pro](https://system76.com/laptops/oryx)), the lowest of 5 runs for each ID looks like this:
Running all of the ID generation mechanisms on a _single core_ of my machine (which happens to be an [Oryx Pro](https://system76.com/laptops/oryx)), the lowest of 5 runs for each ID looks like this:
![Generation speed test](/images/blog/primary-keys/generation-speed-chart.jpg)
To be fair, **generation speed shouldn’t be a deal breaker** as it’s unlikely to be the bottle neck for most applications. That said, it is nice to have some data on where each ID generation mechanism lands.
To be fair, **generation speed shouldn't be a deal breaker** as it's unlikely to be the bottle neck for most applications. That said, it is nice to have some data on where each ID generation mechanism lands.
<aside>
ℹ️ Percona and CyberTec have some excellent posts on the topic:
[https://www.percona.com/blog/2014/12/19/store-uuid-optimized-way/](https://www.percona.com/blog/2014/12/19/store-uuid-optimized-way/)
[https://www.cybertec-postgresql.com/en/uuid-serial-or-identity-columns-for-postgresql-auto-generated-primary-keys/](https://www.cybertec-postgresql.com/en/uuid-serial-or-identity-columns-for-postgresql-auto-generated-primary-keys/)
</aside>
### Table & Index size
@@ -400,44 +400,44 @@ To be fair, **generation speed shouldn’t be a deal breaker** as it’s unlikel
We can check the size of our tables & related indices with this query (after running `VACUUM`):
```sql
SELECT
relname as table_name,
pg_size_pretty(pg_total_relation_size(relid)) As "Table Size",
pg_size_pretty(pg_indexes_size(relid)) as "Index Size",
pg_size_pretty(pg_relation_size(relid)) as "Total Size"
FROM pg_catalog.pg_statio_user_tables
ORDER BY pg_total_relation_size(relid) DESC;
select
relname as table_name,
pg_size_pretty(pg_total_relation_size(relid)) as "Table Size",
pg_size_pretty(pg_indexes_size(relid)) as "Index Size",
pg_size_pretty(pg_relation_size(relid)) as "Total Size"
from pg_catalog.pg_statio_user_tables
order by pg_total_relation_size(relid) desc;
```
Here are the sizes in tabular form:
![Sizes in tabular form](/images/blog/primary-keys/table-and-index-size-chart.jpg)
These numbers are *mostly* a reflection of the *length* of the default settings of `pg_idkit` but probably worth having in front of you anyway.
These numbers are _mostly_ a reflection of the _length_ of the default settings of `pg_idkit` but probably worth having in front of you anyway.
With this, we probably have enough information to make a decision (and a new library to generate our UUIDs with)!
## Which ID should you use?
As usual, *it depends* -- you didn't think it'd be that easy, did you?
As usual, _it depends_ -- you didn't think it'd be that easy, did you?
All I can offer are some general rules of thumb that hopefully work for you:
- `integer`s and `serial` have obvious benefits for simplicity, storage, and sortability. You *might* not want to expose them to the world though.
- `integer`s and `serial` have obvious benefits for simplicity, storage, and sortability. You _might_ not want to expose them to the world though.
- If you want the ultimate in collision avoidance UUIDv4 is OK
- UUIDv1 *could* have been great, *but* it doesn't lexicographically sort.
- The best time-based ID seems to be `xid`, with good performance *and* sort friendliness
- UUIDv1 _could_ have been great, _but_ it doesn't lexicographically sort.
- The best time-based ID seems to be `xid`, with good performance _and_ sort friendliness
- If you want to be a little more standards-oriented, UUID v6/v7
As usual, the best results will come from weighing all the options and finding what's best for your use-case, and doing appropriate testing on your data.
## Possible Improvements
We’ve done some good exploration so far, but here are some ideas for interesting use cases for `pg_idkit` and measuring the impact of ID generation using it.
We've done some good exploration so far, but here are some ideas for interesting use cases for `pg_idkit` and measuring the impact of ID generation using it.
### Usecase: Generating our `created_at` columns from our IDs
One interesting feature would be using at least partially time-based UUIDs for `created_at` columns -- we could save space by *virtualizing* our `created_at` columns:
One interesting feature would be using at least partially time-based UUIDs for `created_at` columns -- we could save space by _virtualizing_ our `created_at` columns:
```sql
-- At table creation
@@ -448,12 +448,12 @@ CREATE TABLE users (
);
-- An example query for a specific KSUID that uses created_at
SELECT *, idkit_ksuid_extract_timestamptz(id)
SELECT *, idkit_ksuid_extract_timestamptz(id)
FROM users
WHERE id = '0F755149A55730412B0AEC0E3B5B089C14B5B58D';
```
Ideally we could use the `GENERATED ALWAYS AS ( ... )` syntax for [generated columns](https://www.postgresql.org/docs/current/ddl-generated-columns.html) while creating the table, but as the time of this post *Postgres does not yet support virtual generated columns (only stored ones)*.
Ideally we could use the `GENERATED ALWAYS AS ( ... )` syntax for [generated columns](https://www.postgresql.org/docs/current/ddl-generated-columns.html) while creating the table, but as the time of this post _Postgres does not yet support virtual generated columns (only stored ones)_.
### Benchmarking: Measuring index fragmentation
@@ -469,7 +469,7 @@ Another great metric to measure might be performance of these indices on certain
In most code bases, simple `WHERE` queries with `SORT`s abound, and one of the big benefits of UUIDv6, UUIDv7 and the other alternatives is lexicographic sorting, after all.
Knowing *just how good* a certain ID generation method is at maintaining locality would be nice to know.
Knowing _just how good_ a certain ID generation method is at maintaining locality would be nice to know.
Creating and using a function like `idkit_uuidv1_extract_timestamptz` and using it in a [“functional index” (an index on an expression)](https://www.postgresql.org/docs/14/indexes-expressional.html) could resolve the sort unfriendliness of UUIDv1 as well!
@@ -488,4 +488,3 @@ Hopefully this article helps you head off some bikeshedding with your teammates
- [What are PostgreSQL Templates?](https://supabase.com/blog/postgresql-templates)
- [Realtime Postgres RLS on Supabase](https://supabase.com/blog/realtime-row-level-security-in-postgresql)
- [Implementing "seen by" functionality with Postgres](https://supabase.com/blog/seen-by-in-postgresql)
+1 -1
View File
@@ -101,7 +101,7 @@ See [source code](https://github.com/snaplet/postgres-browser/tree/main/packages
Next, we need to run the VM inside our browser. How? [WASM](https://en.wikipedia.org/wiki/WebAssembly).
We use [v86](https://github.com/copy/v86) to run our VM inside the browser. Our demo application is very simple - plain HTML and some basic styling.
See [source code](https://github.com/snaplet/postgres-wasm/tree/main/packages/runtime).
See [source code](https://github.com/snaplet/postgres-wasm/tree/main/packages/runtime).
### Network proxy
@@ -2,22 +2,23 @@
title: Supabase Beta September 2022
description: We were too focused on clearing out the backlog so we didn't ship anything new last month... or did we?!
author: ant_wilson
image: 2022-september/monthly-update-september-2022.jpg
image: 2022-september/monthly-update-september-2022.jpg
thumb: 2022-september/monthly-update-september-2022.jpg
tags:
- release-notes
date: '2022-10-05'
toc_depth: 3
---
For September we made a huge effort to focus on crushing our backlog and outstanding tickets amongst other things, here's the full list of updates for the month...
## Kaizen Week
Post Launch Week we did three subsequent weeks of [Kaizen](https://en.wikipedia.org/wiki/Kaizen), a term we use internally to deliver constant and incremental improvement. Each week had a different focus:
Post Launch Week we did three subsequent weeks of [Kaizen](https://en.wikipedia.org/wiki/Kaizen), a term we use internally to deliver constant and incremental improvement. Each week had a different focus:
- Week 1: QA and testing
- Week 2: Documentation
- Week 3: Issue Backlog and Automation
- Week 1: QA and testing
- Week 2: Documentation
- Week 3: Issue Backlog and Automation
It's pretty rare for a company to stop feature development altogether, but luckily we're just a bunch of developers so we all know the pain of technical debt. After 5 Launch Weeks, working on testing and backlogs feels like a bit of a relief.
@@ -39,11 +40,11 @@ We launched the new Auth UI on Product Hunt! Auth UI is a pre-built React compon
![Postgres WASM with Snaplet and Supabase](/images/blog/2022-september/postgres-wasm.jpg)
If you've ever wondered, "can I run Postgres inside a browser, using an embeddable Linux Virtual Machine?", wonder no longer. With our friends at [Snaplet](https://www.snaplet.dev/?utm_campaign=Beta%20update%20emails&utm_source=hs_email&utm_medium=email&_hsenc=p2ANqtz-8u0x9grbiydYqh56hC_8Dta4ZiTdWbW0eXWkVCnUEUGWWoVDFXCpWzJWImkLfM-lG-o2JX), we've released an open source Postgres WASM.
If you've ever wondered, "can I run Postgres inside a browser, using an embeddable Linux Virtual Machine?", wonder no longer. With our friends at [Snaplet](https://www.snaplet.dev/?utm_campaign=Beta%20update%20emails&utm_source=hs_email&utm_medium=email&_hsenc=p2ANqtz-8u0x9grbiydYqh56hC_8Dta4ZiTdWbW0eXWkVCnUEUGWWoVDFXCpWzJWImkLfM-lG-o2JX), we've released an open source Postgres WASM.
[Read the blog post](https://supabase.com/blog/postgres-wasm) <br/>
[Comment on Hacker News](https://news.ycombinator.com/item?id=33067962) <br/>
Visit the repo: [Snaplet](https://github.com/snaplet/postgres-wasm) | [Supabase](https://github.com/supabase-community/postgres-wasm)
Visit the repo: [Snaplet](https://github.com/snaplet/postgres-wasm) | [Supabase](https://github.com/supabase-community/postgres-wasm)
## Security updates
@@ -54,40 +55,39 @@ We're making some changes to the way the Dashboard interacts with your database.
## Quick product updates
- Edge functions free plan script size is bumped to 2 MB
- [functions-go](https://github.com/supabase-community/functions-go) was just contributed to supabase-community by [Zain Khan](https://twitter.com/Za1n_K)
- A new [guide](https://supabase.com/docs/guides/auth/auth-captcha) to add captcha to your sign-in, sign-up, and password reset forms
- New Postgres Extension for monitoring your PostgreSQL database network traffic: [pg_netstat](https://github.com/supabase/pg_netstat)
- Added [docs](https://supabase.com/docs/guides/storage-cdn) for how caching works in Supabase
- [functions-go](https://github.com/supabase-community/functions-go) was just contributed to supabase-community by [Zain Khan](https://twitter.com/Za1n_K)
- A new [guide](https://supabase.com/docs/guides/auth/auth-captcha) to add captcha to your sign-in, sign-up, and password reset forms
- New Postgres Extension for monitoring your PostgreSQL database network traffic: [pg_netstat](https://github.com/supabase/pg_netstat)
- Added [docs](https://supabase.com/docs/guides/storage-cdn) for how caching works in Supabase
## Migrate from Heroku to Supabase
![Migrate from Heroku to Supabase](/images/blog/2022-september/migrate-from-heroku-to-supabase.jpg)
In case you missed it, Heroku is shutting down their free tier. We're big fans of Heroku and take a lot of inspiration from what they've built. Supabase is compatible with Heroku's PostgreSQL product (because we're just Postgres too), and if you have a free project running on Heroku we've created a [tool to help migrate](https://migrate.supabase.com/) to our free tier.
In case you missed it, Heroku is shutting down their free tier. We're big fans of Heroku and take a lot of inspiration from what they've built. Supabase is compatible with Heroku's PostgreSQL product (because we're just Postgres too), and if you have a free project running on Heroku we've created a [tool to help migrate](https://migrate.supabase.com/) to our free tier.
## Made with Supabase
![Made with Supabase - Supabase Schema](/images/blog/2022-september/madewithsupabase.jpg)
[Supabase Schema](https://supabase-schema.vercel.app/): An open source, free, login-less and secured Supabase Schema Visualizer. Simply plug in your URL and anon key and see a visual representation of your database schema! Built by the amazing [Zernonia](https://twitter.com/zernonia) using Vue 3, Viti, and WindiCSS.
[Supabase Schema](https://supabase-schema.vercel.app/): An open source, free, login-less and secured Supabase Schema Visualizer. Simply plug in your URL and anon key and see a visual representation of your database schema! Built by the amazing [Zernonia](https://twitter.com/zernonia) using Vue 3, Viti, and WindiCSS.
## Extended Community Highlights
![Community](/images/blog/2022-june/community.jpg)
- Building a viral application to visualize train routes. [Blog](https://vercel.com/blog/building-a-viral-application-to-visualize-train-routes)
- Building a viral application to visualize train routes. [Blog](https://vercel.com/blog/building-a-viral-application-to-visualize-train-routes)
- OTP Authentication with Supabase and Twilio in React. [Tutorial](https://refine.dev/blog/supabase-twilio-otp-authentication-in-react/)
- Join Vercel and us for [Next.js Conf](https://nextjs.org/conf).
- Building $1K - $10K MRR Micro SaaS around Supabase. [Blog](https://microsaasidea.substack.com/p/micro-saas-products-around-supabase)
- Watch How Netlify and Supabase Enables "Supa" DX. [Webinar](https://www.netlify.com/resources/webinars/how-netlify-supabase-enables-supa-dx/)
- Postgres database functions are your next favorite feature! [Blog](https://mirror.xyz/0x7D15080A13c8128dBAf90a2c2326058b5c1D5eac/MaiOq5m1xSa9QfMuiLYq4bJdA0owWoTpOZNlQv76_so)
- Create a blog with Vue, Tailwindcss & Supabase. [Part 1](https://youtube.com/watch?v=0NB9lgFcOT4) and [Part 2](https://youtube.com/watch?v=UP2mHUpcv6g)
- Join Vercel and us for [Next.js Conf](https://nextjs.org/conf).
- Building $1K - $10K MRR Micro SaaS around Supabase. [Blog](https://microsaasidea.substack.com/p/micro-saas-products-around-supabase)
- Watch How Netlify and Supabase Enables "Supa" DX. [Webinar](https://www.netlify.com/resources/webinars/how-netlify-supabase-enables-supa-dx/)
- Postgres database functions are your next favorite feature! [Blog](https://mirror.xyz/0x7D15080A13c8128dBAf90a2c2326058b5c1D5eac/MaiOq5m1xSa9QfMuiLYq4bJdA0owWoTpOZNlQv76_so)
- Create a blog with Vue, Tailwindcss & Supabase. [Part 1](https://youtube.com/watch?v=0NB9lgFcOT4) and [Part 2](https://youtube.com/watch?v=UP2mHUpcv6g)
- Flutter Supabase Authentication. [Tutorial](https://www.sandromaglione.com/techblog/flutter-supabase-authentication-complete-tutorial)
- Vue 3 + Pinia + Supabase Auth (Email). [Tutorial](https://towardsdev.com/vue-3-pinia-supabase-auth-email-1228f5f8502e)
- Power up your ClickUp skills with Supabase, Figma, and DhiWise in a day. [Tutorial](https://dev.to/saloni137/power-up-your-clickup-skills-with-supabase-figma-and-dhiwise-in-a-day-3pak)
- Creating an Email Authentication using Supabase in React. [Tutorial](https://dev.to/surajondev/creating-an-email-authentication-using-supabase-in-react-5883)
## Meme Zone
If you made it this far in the blog post you deserve a treat. [Follow us on Twitter](https://twitter.com/supabase) for more.
@@ -78,13 +78,9 @@ With nothing but the raw data in your database, we can actually do some _basic_
```sql
select
*
from
movies
where
title like '%Avengers%'
or
overview like '%Avengers%';
*
from movies
where title like '%Avengers%' or overview like '%Avengers%';
```
The `LIKE` [operator](https://www.postgresql.org/docs/current/functions-matching.html#FUNCTIONS-LIKE) as used in the query above will match _only_ the term "Avengers", but it will match it in any part of a given row's `title` or `overview`.
@@ -17,21 +17,21 @@ This article is the second part of the Flutter tutorial series. During the serie
In this article, I will show you how you can make a secure chat application by introducing authentication and authorization to the basic chat app that we created [previously](https://supabase.com/blog/flutter-tutorial-building-a-chat-app).
We will use [Supabase](https://supabase.com/) to store the chat data and will use its [auth feature](https://supabase.com/auth) to provide a secure chat room feature.
Supabase lets us build a secure database where we can control who can access what part of the database, so your users’ data is secured without worrying about the backend.
Supabase lets us build a secure database where we can control who can access what part of the database, so your users' data is secured without worrying about the backend.
## What we created in the previous article
Before we jump in, let’s go over what we built in the [previous article](blog/flutter-tutorial-building-a-chat-app), because we will be building on top of it. If you have not gone through it, I recommend you to go check it out.
Before we jump in, let's go over what we built in the [previous article](blog/flutter-tutorial-building-a-chat-app), because we will be building on top of it. If you have not gone through it, I recommend you to go check it out.
In the previous article, we created a basic real-time chat application. Users will register or sign in using an email address and password. Once they are signed in, they are taken to a chat page, where they can view and send messages to everyone in the app. There are no Chat rooms, and everyone’s messages were sent to the same chat room.
In the previous article, we created a basic real-time chat application. Users will register or sign in using an email address and password. Once they are signed in, they are taken to a chat page, where they can view and send messages to everyone in the app. There are no Chat rooms, and everyone's messages were sent to the same chat room.
You can also find a complete code example [here](https://github.com/supabase-community/flutter-chat/tree/with_auth) to follow along.
You can also find a complete code example [here](https://github.com/supabase-community/flutter-chat/tree/with_auth) to follow along.
![Chat app without authorization](/images/blog/flutter-chat-auth/chat-app-screenshot.png)
## Overview of the final app
The app will allow us to have 1 on 1 chat with other users in the app. To enable this, we will introduce a new rooms page. The rooms page serves two purposes here, one is to initiate a conversation with other users, and the other is to display existing chat rooms. At the top of the app, we see a list of other users’ icons. A user can tap the icon to start a 1 on 1 conversation. Below the icons, there is a list of rooms that the user is a part of.
The app will allow us to have 1 on 1 chat with other users in the app. To enable this, we will introduce a new rooms page. The rooms page serves two purposes here, one is to initiate a conversation with other users, and the other is to display existing chat rooms. At the top of the app, we see a list of other users' icons. A user can tap the icon to start a 1 on 1 conversation. Below the icons, there is a list of rooms that the user is a part of.
![Chat app without authorization](/images/blog/flutter-chat-auth/rooms-and-chat-page.png)
@@ -52,7 +52,7 @@ flutter_bloc: ^8.0.0
Since the app has evolved, we also need to update our table schema. In order to store rooms data, we will add a rooms table. We will also modify the messages table to add a foreign key constraint to the rooms table so that we can tell which message belongs to which room.
We will also introduce a `create_new_room` function, which is a [database function](https://supabase.com/docs/guides/database/functions) that handles chat room creation. It knows to create a new room if a chat room with the two users does not exist yet, or to just return the room ID if it already exists.
We will also introduce a `create_new_room` function, which is a [database function](https://supabase.com/docs/guides/database/functions) that handles chat room creation. It knows to create a new room if a chat room with the two users does not exist yet, or to just return the room ID if it already exists.
```sql
-- *** Table definitions ***
@@ -89,7 +89,7 @@ create or replace function create_new_room(other_user_id uuid) returns uuid as $
with rooms_with_profiles as (
select room_id, array_agg(profile_id) as participants
from room_participants
group by room_id
group by room_id
)
select room_id
into new_room_id
@@ -119,7 +119,7 @@ $$ language plpgsql security definer;
### Setup deep links
Something we skipped in the previous article was sending confirmation emails to users when they signup. Since today is about security, let's properly send confirmation emails to people who signup.
Something we skipped in the previous article was sending confirmation emails to users when they signup. Since today is about security, let's properly send confirmation emails to people who signup.
When we send confirmation emails, the users need to be brought back to the app somehow.
Since supabase_flutter has a mechanism to detect and handle deep links, we will register a `io.supabase.chat://login` as our deep link for the app and bring the users back after confirming their email address.
@@ -184,9 +184,9 @@ And that is it for deep link configuration.
### Step1: Create rooms page
The rooms page will load two types of data, recently added users and a list of rooms that the user belongs to. We will be using bloc to load these two types of data and display them on the rooms page.
The rooms page will load two types of data, recently added users and a list of rooms that the user belongs to. We will be using bloc to load these two types of data and display them on the rooms page.
Let’s start out by creating states for the rooms page.
Let's start out by creating states for the rooms page.
The rooms page would have four different states, loading, loaded, empty, and error. We will display different UI on the rooms page depending on what state it is.
Create a `lib/cubit/rooms/rooms_state.dart` file and paste the following code.
You may see some errors, but we will take care of them in the next step.
@@ -223,8 +223,8 @@ class RoomsError extends RoomState {
```
Now that we have the states defined, we will create rooms_cubit.
A [cubit](https://bloclibrary.dev/#/coreconcepts?id=cubit) is a class within the flutter_bloc library where we will make requests to Supabase to get the data and transform them into states and emit them to the UI widgets.
Let's create a `lib/cubit/rooms/rooms_cubit.dart` file and complete the cubit.
A [cubit](https://bloclibrary.dev/#/coreconcepts?id=cubit) is a class within the flutter_bloc library where we will make requests to Supabase to get the data and transform them into states and emit them to the UI widgets.
Let's create a `lib/cubit/rooms/rooms_cubit.dart` file and complete the cubit.
```dart title=lib/cubit/rooms/rooms_cubit.dart
import 'dart:async';
@@ -369,9 +369,9 @@ class RoomCubit extends Cubit<RoomState> {
}
```
Now that we have the states and cubit to power our rooms page, it’s time to create the `RoomsPage`.
Now that we have the states and cubit to power our rooms page, it's time to create the `RoomsPage`.
We have two list views, one horizontal list view to display other users, and one vertical list views with list tiles representing each room that the user is a part of.
We have two list views, one horizontal list view to display other users, and one vertical list views with list tiles representing each room that the user is a part of.
We will create a `lib/pages/rooms_page.dart` file with the following content.
```dart title=lib/pages/rooms_page.dart
@@ -670,7 +670,7 @@ class ChatCubit extends Cubit<ChatState> {
}
```
Chat cubit is pretty simple. It sets a real-time listener to the database using the stream method and emits an empty state if there are no messages in the room, or emits a loaded state if there are messages.
Chat cubit is pretty simple. It sets a real-time listener to the database using the stream method and emits an empty state if there are no messages in the room, or emits a loaded state if there are messages.
Because we are using cubit, we need to modify the MessagesPage widget as well.
Open `lib/pages/chat_page.dart` and let's update it.
@@ -873,7 +873,7 @@ class _ChatBubble extends StatelessWidget {
### Step 3: Implementing Authentication in Flutter
Because we have modified the setting of our Supabase to send a confirmation email, we need to make some modifications to the register page and login page as well.
Because we have modified the setting of our Supabase to send a confirmation email, we need to make some modifications to the register page and login page as well.
The main change is how we handle navigation.
Previously, we were able to navigate the user to `ChatPage` right after sign-in was complete.
@@ -1054,9 +1054,9 @@ class _RegisterPageState extends State<RegisterPage> {
```
Login page becomes more simple.
All it is doing is taking a user’s email and password and logging them in.
All it is doing is taking a user's email and password and logging them in.
It is not doing any navigation whatsoever.
This is because `LoginPage` is navigated on top of `RegisterPage`, the auth state listener on `RegisterPage` is still active, and therefore can take care of the navigation.
This is because `LoginPage` is navigated on top of `RegisterPage`, the auth state listener on `RegisterPage` is still active, and therefore can take care of the navigation.
```dart title=lib/pages/login_page.dart
import 'package:flutter/material.dart';
@@ -1202,7 +1202,7 @@ class SplashPageState extends State<SplashPage> {
```
Finally we implement those `ProfilesCubit` that you saw here and there throughout the code.
This cubit will act as in memory cache of all the profiles data so that the app does not have to go fetch the same profiles every single time it needs it.
This cubit will act as in memory cache of all the profiles data so that the app does not have to go fetch the same profiles every single time it needs it.
Create `profiles_state.dart` and `profiles_cubit.dart` under `lib/cubits/` and add the following code.
```dart title=lib/cubits/profiles_state.dart
@@ -1301,7 +1301,7 @@ class MyApp extends StatelessWidget {
### Step 4: Authorization with Row Level Security (RLS)
At this point, we seemingly have a complete app, except if we open the app right now, we will see every user’s room with all the messages that have ever been sent within the app by any user. This is because we have not set up Row Level Security yet. [Row Level Security](https://supabase.com/docs/guides/auth/row-level-security) is a mechanism in PostgreSQL that allows developers to define who can perform what operation on a row-by-row basis. There are two ways we can define Row Level Security policies in Supabase, with the GUI or through SQL, but we will set them using SQL today. Let’s run the following SQL to set the security policy.
At this point, we seemingly have a complete app, except if we open the app right now, we will see every user's room with all the messages that have ever been sent within the app by any user. This is because we have not set up Row Level Security yet. [Row Level Security](https://supabase.com/docs/guides/auth/row-level-security) is a mechanism in PostgreSQL that allows developers to define who can perform what operation on a row-by-row basis. There are two ways we can define Row Level Security policies in Supabase, with the GUI or through SQL, but we will set them using SQL today. Let's run the following SQL to set the security policy.
```sql
-- Returns true if the signed in user is a participant of the room
@@ -1340,9 +1340,9 @@ create policy "Users can insert messages on rooms they are in."
on public.messages for insert with check (is_room_participant(room_id) and profile_id = auth.uid());
```
Notice that we have created a handy `is_room_participant` function that will return whether a particular user is a participant or not in a specific room.
Notice that we have created a handy `is_room_participant` function that will return whether a particular user is a participant or not in a specific room.
With the Row Level Security policies set up, our application is complete. We now have a real-time chat application with proper authentication and authorization in place.
With the Row Level Security policies set up, our application is complete. We now have a real-time chat application with proper authentication and authorization in place.
## Conclusions/ Future Improvements
@@ -11,29 +11,30 @@ date: '2022-11-24'
toc_depth: 3
---
It's a tough decision for any developer starting a new project. Should you store your data in a standard, time-tested SQL database, or go with one of the newer NoSQL document-based databases? This seemingly simple decision can literally make or break your project down the line. Choose correctly and structure your data well, and you may sail smoothly into production and watch your app take off. Make the wrong choice and you could be headed for nightmares (and maybe even some major re-writes) before your app ever makes it out the door.
It's a tough decision for any developer starting a new project. Should you store your data in a standard, time-tested SQL database, or go with one of the newer NoSQL document-based databases? This seemingly simple decision can literally make or break your project down the line. Choose correctly and structure your data well, and you may sail smoothly into production and watch your app take off. Make the wrong choice and you could be headed for nightmares (and maybe even some major re-writes) before your app ever makes it out the door.
## Simplicity vs Power
There are tradeoffs with both SQL and NoSQL solutions. Typically it's easier to get started with NoSQL data structures, especially when the data is complex or hierarchical. You can just take a JSON data object from your front-end code and throw it in the database and be done with it. But later when you need to access that data to answer some basic business questions, it's much more difficult. A SQL solution makes it easier to gather data and draw conclusions down the line. Let's look at an example:
There are tradeoffs with both SQL and NoSQL solutions. Typically it's easier to get started with NoSQL data structures, especially when the data is complex or hierarchical. You can just take a JSON data object from your front-end code and throw it in the database and be done with it. But later when you need to access that data to answer some basic business questions, it's much more difficult. A SQL solution makes it easier to gather data and draw conclusions down the line. Let's look at an example:
Each day I track the food I eat, along with the number of calories in each item:
| Day | Food Item | Calories | Meal |
|--------|-----------|----------|------|
| 01 Jan | Apple | 72 | Breakfast |
| 01 Jan | Oatmeal | 146 | Breakfast |
| 01 Jan | Sandwich | 445 | Lunch |
| 01 Jan | Chips | 280 | Lunch |
| 01 Jan | Cookie | 108 | Lunch |
| 01 Jan | Mixed Nuts | 175 | Snack |
| 01 Jan | Pasta/Sauce | 380 | Dinner |
| 01 Jan | Garlic Bread | 200 | Dinner |
| 01 Jan | Broccoli | 32 | Dinner |
| Day | Food Item | Calories | Meal |
| ------ | ------------ | -------- | --------- |
| 01 Jan | Apple | 72 | Breakfast |
| 01 Jan | Oatmeal | 146 | Breakfast |
| 01 Jan | Sandwich | 445 | Lunch |
| 01 Jan | Chips | 280 | Lunch |
| 01 Jan | Cookie | 108 | Lunch |
| 01 Jan | Mixed Nuts | 175 | Snack |
| 01 Jan | Pasta/Sauce | 380 | Dinner |
| 01 Jan | Garlic Bread | 200 | Dinner |
| 01 Jan | Broccoli | 32 | Dinner |
I also track the number of cups of water I drink and when I drink them:
| Day | Time | Cups |
|--------|-------|------|
| ------ | ----- | ---- |
| Jan 01 | 08:15 | 1 |
| Jan 01 | 09:31 | 1 |
| Jan 01 | 10:42 | 2 |
@@ -45,94 +46,96 @@ I also track the number of cups of water I drink and when I drink them:
And finally, I track my exercise:
| Day | Time | Duration | Exercise |
|--------|-------|----------|----------|
| Jan 01 | 11:02 | 0.5 | Walking |
| Jan 02 | 09:44 | 0.75 | Bicycling |
| Jan 02 | 17:00 | 0.25 | Walking |
| Day | Time | Duration | Exercise |
| ------ | ----- | -------- | --------- |
| Jan 01 | 11:02 | 0.5 | Walking |
| Jan 02 | 09:44 | 0.75 | Bicycling |
| Jan 02 | 17:00 | 0.25 | Walking |
For each day I also track my current weight along with any notes for the day:
| Day | Weight | Notes |
|--------|--------|-------|
| Jan 01 | 172.6 | This new diet is awesome! |
| Jan 14 | 170.2 | Not sure all this is worth it. |
| Jan 22 | 169.8 | Jogged past a McDonald's today. It was hard. |
| Day | Weight | Notes |
| ------ | ------ | -------------------------------------------------- |
| Jan 01 | 172.6 | This new diet is awesome! |
| Jan 14 | 170.2 | Not sure all this is worth it. |
| Jan 22 | 169.8 | Jogged past a McDonald's today. It was hard. |
| Feb 01 | 168.0 | I feel better, but sure miss all that greasy food. |
## Gathering All That Data
That's a lot of different data that needs to be gathered, stored, retrieved, and later analyzed. It's organized simply and easily, but the number of records varies from day to day. On any given day I may have zero or more entries for food, water, and exercise, and I may have zero or one entry for weight & notes.
That's a lot of different data that needs to be gathered, stored, retrieved, and later analyzed. It's organized simply and easily, but the number of records varies from day to day. On any given day I may have zero or more entries for food, water, and exercise, and I may have zero or one entry for weight & notes.
In my app, I gather all the data for a single day on one page, to make it easier for my users. So, I get a JSON object for each day that looks like this:
In my app, I gather all the data for a single day on one page, to make it easier for my users. So, I get a JSON object for each day that looks like this:
```json
{ "date": "2022-01-01",
"weight": 172.6,
"notes": "This new diet is awesome!",
{
"date": "2022-01-01",
"weight": 172.6,
"notes": "This new diet is awesome!",
"food": [
{ "title": "Apple", "calories": 72, "meal": "Breakfast"},
{ "title": "Oatmeal", "calories": 146, "meal": "Breakfast"},
{ "title": "Sandwich", "calories": 445, "meal": "Lunch"},
{ "title": "Chips", "calories": 280, "meal": "Lunch"},
{ "title": "Cookie", "calories": 108, "meal": "Lunch"},
{ "title": "Mixed Nuts", "calories": 175, "meal": "Snack"},
{ "title": "Pasta/Sauce", "calories": 380, "meal": "Dinner"},
{ "title": "Garlic Bread", "calories": 200, "meal": "Dinner"},
{ "title": "Broccoli", "calories": 32, "meal": "Dinner"}
{ "title": "Apple", "calories": 72, "meal": "Breakfast" },
{ "title": "Oatmeal", "calories": 146, "meal": "Breakfast" },
{ "title": "Sandwich", "calories": 445, "meal": "Lunch" },
{ "title": "Chips", "calories": 280, "meal": "Lunch" },
{ "title": "Cookie", "calories": 108, "meal": "Lunch" },
{ "title": "Mixed Nuts", "calories": 175, "meal": "Snack" },
{ "title": "Pasta/Sauce", "calories": 380, "meal": "Dinner" },
{ "title": "Garlic Bread", "calories": 200, "meal": "Dinner" },
{ "title": "Broccoli", "calories": 32, "meal": "Dinner" }
],
"water": [
{"time": "08:15", "qty": 1},
{"time": "09:31", "qty": 1},
{"time": "10:42", "qty": 2},
{"time": "10:42", "qty": 2},
{"time": "12:07", "qty": 1},
{"time": "14:58", "qty": 1},
{"time": "17:15", "qty": 1},
{"time": "18:40", "qty": 1},
{"time": "19:05", "qty": 1}
{ "time": "08:15", "qty": 1 },
{ "time": "09:31", "qty": 1 },
{ "time": "10:42", "qty": 2 },
{ "time": "10:42", "qty": 2 },
{ "time": "12:07", "qty": 1 },
{ "time": "14:58", "qty": 1 },
{ "time": "17:15", "qty": 1 },
{ "time": "18:40", "qty": 1 },
{ "time": "19:05", "qty": 1 }
],
"exercise": [
{"time": "11:02", "duration": 0.5, "type": "Walking"}
]
"exercise": [{ "time": "11:02", "duration": 0.5, "type": "Walking" }]
}
```
## Saving the Data
Once we've gathered all the data for a day, we need to store it in our database. In a NoSQL database, this can be a pretty easy process, as we can just create a record (document) for a specific user for a specific date and throw document into a collection and we're done. With SQL, we have some structure we have to work within, and in this case it looks like 4 separate tables: food, water, exercise, and notes. We'd want to do 4 separate inserts here, one for each table. If we don't have data for a specific table (say no exercise was recorded today) then we can skip that table.
Once we've gathered all the data for a day, we need to store it in our database. In a NoSQL database, this can be a pretty easy process, as we can just create a record (document) for a specific user for a specific date and throw document into a collection and we're done. With SQL, we have some structure we have to work within, and in this case it looks like 4 separate tables: food, water, exercise, and notes. We'd want to do 4 separate inserts here, one for each table. If we don't have data for a specific table (say no exercise was recorded today) then we can skip that table.
If you're using SQL to store this data, you might want to save each table's data as it's entered in your data entry form (and not wait until all the data is entered.) Or you might want to create a database function that takes all the JSON data, parses it, and writes it to all the related tables in a single transaction. There's a lot of ways to handle this, but suffice it to say this: it's a bit more complicated than saving the data in a NoSQL database.
If you're using SQL to store this data, you might want to save each table's data as it's entered in your data entry form (and not wait until all the data is entered.) Or you might want to create a database function that takes all the JSON data, parses it, and writes it to all the related tables in a single transaction. There's a lot of ways to handle this, but suffice it to say this: it's a bit more complicated than saving the data in a NoSQL database.
### Retrieving the Data
If we want to display all the data for a single day, it's pretty much the same. With NoSQL you can grab the data for the user's day and then use it in your application. Nice! With SQL we need to query 4 tables to get all the data (or we could use a function to get it all in a single call.) Of course, when displaying the data, we'd need to first break up our JSON data into pieces that are needed by each section of our dashboard screen, and you could argue that it's simpler to map each SQL table with the dashboard section on the screen, but that's a trivial point.
If we want to display all the data for a single day, it's pretty much the same. With NoSQL you can grab the data for the user's day and then use it in your application. Nice! With SQL we need to query 4 tables to get all the data (or we could use a function to get it all in a single call.) Of course, when displaying the data, we'd need to first break up our JSON data into pieces that are needed by each section of our dashboard screen, and you could argue that it's simpler to map each SQL table with the dashboard section on the screen, but that's a trivial point.
### Analyzing the Data
Now that we've saved the data and we can retrieve it and display it, let's use it for some analysis. *Let's display a graph of how many total calories I've eaten over the past month.* With SQL, this is a simple task:
Now that we've saved the data and we can retrieve it and display it, let's use it for some analysis. _Let's display a graph of how many total calories I've eaten over the past month._ With SQL, this is a simple task:
```sql
select
date, sum(calories) as total_calories
from food_log
group by date
where user_id = 'xyz'
and day between '2022-01-01' and '2022-01-31'
select
date,
sum(calories) as total_calories
from food_log
group by date
where user_id = 'xyz' and day between '2022-01-01' and '2022-01-31'
order by date;
```
Bam! Done! Now we can send those results to our graphing library and make a nice pretty picture of my eating habits.
Bam! Done! Now we can send those results to our graphing library and make a nice pretty picture of my eating habits.
But if we've stored this data in NoSQL, it gets a little more complicated. We'll need to:
But if we've stored this data in NoSQL, it gets a little more complicated. We'll need to:
- grab all the data for the user for the month
- parse each day's data to get the food log information
- loop through each day and total the calories
- send the aggregate data to our graphing module
If this is something we're going to do regularly, it makes sense to calculate the total calories for each day and store it in the day's document so we can get at that data faster. But that requires more work up front, and we still need to pull the data for each day and parse out that calorie total first. And if we update the data we still need to recalculate things and update that total. Eventually we'll want to do that with the water and exercise totals as well. The code will eventually start to get longer and more complex.
If this is something we're going to do regularly, it makes sense to calculate the total calories for each day and store it in the day's document so we can get at that data faster. But that requires more work up front, and we still need to pull the data for each day and parse out that calorie total first. And if we update the data we still need to recalculate things and update that total. Eventually we'll want to do that with the water and exercise totals as well. The code will eventually start to get longer and more complex.
### SQL and NoSQL Together - FTW
Let's see how we can use the power of SQL together with the ease-of-use of NoSQL in the same database to make this all a bit easier. We'll create a table for each day of data (for each user) and store the basic fields such as `weight` and `notes` first. Then we'll just throw the `food_log`, `water_log`, and `exercise_log` fields in a `JSONB` field.
Let's see how we can use the power of SQL together with the ease-of-use of NoSQL in the same database to make this all a bit easier. We'll create a table for each day of data (for each user) and store the basic fields such as `weight` and `notes` first. Then we'll just throw the `food_log`, `water_log`, and `exercise_log` fields in a `JSONB` field.
```sql
CREATE TABLE calendar (
@@ -145,21 +148,23 @@ CREATE TABLE calendar (
water_log jsonb,
exercise_log jsonb
);
-- (Optional) - create a foreign key relationship for the user_id field
-- (Optional) - create a foreign key relationship for the user_id field
ALTER TABLE ONLY calendar
ADD CONSTRAINT calendar_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id);
```
Now let's insert some data into the table. PostgreSQL offers both JSON and JSONB fields, and since the latter are more optimized by the database and much faster for query processing, we’ll almost always want to use JSONB. We’ll use JSONB fields for `food_log`, `water_log`, and `exercise_log` and just dump the data we got from our app right into those fields as a string:
Now let's insert some data into the table. PostgreSQL offers both JSON and JSONB fields, and since the latter are more optimized by the database and much faster for query processing, we’ll almost always want to use JSONB. We’ll use JSONB fields for `food_log`, `water_log`, and `exercise_log` and just dump the data we got from our app right into those fields as a string:
```sql
insert into calendar (date, user_id, weight, notes, food_log, water_log, exercise_log)
values (
'2022-01-01',
'xyz',
172.6,
'This new diet is awesome!',
'[
insert into calendar
(date, user_id, weight, notes, food_log, water_log, exercise_log)
values
(
'2022-01-01',
'xyz',
172.6,
'This new diet is awesome!',
'[
{ "title": "Apple", "calories": 72, "meal": "Breakfast"},
{ "title": "Oatmeal", "calories": 146, "meal": "Breakfast"},
{ "title": "Sandwich", "calories": 445, "meal": "Lunch"},
@@ -170,7 +175,7 @@ values (
{ "title": "Garlic Bread", "calories": 200, "meal": "Dinner"},
{ "title": "Broccoli", "calories": 32, "meal": "Dinner"}
]',
'[
'[
{"time": "08:15", "qty": 1},
{"time": "09:31", "qty": 1},
{"time": "10:42", "qty": 2},
@@ -181,33 +186,33 @@ values (
{"time": "18:40", "qty": 1},
{"time": "19:05", "qty": 1}
]',
'[
'[
{"time": "11:02", "duration": 0.5, "type": "Walking"}
]'
);
);
```
While that's a big insert statement, it sure beats doing inserts on 4 separate tables. With all those food entries and water log entries, we would have had to made 1 entry in the main table, then 9 food_log entries, 9 water_log entries, and one exercise_log entry for a total of 20 database records. We've wrapped that into a single record.
While that's a big insert statement, it sure beats doing inserts on 4 separate tables. With all those food entries and water log entries, we would have had to made 1 entry in the main table, then 9 food_log entries, 9 water_log entries, and one exercise_log entry for a total of 20 database records. We've wrapped that into a single record.
## But How Do We Query This Data?
Great, we're collecting the data now, and it's easy to insert the data into the database. Editing the data isn't too bad either because we're just downloading the data to the client, updating the JSON field(s) as needed, and throwing them back into the database. Not too hard. But how can I query this data? What about that task from before? *Let's display a graph of how many total calories I've eaten over the past month.*
Great, we're collecting the data now, and it's easy to insert the data into the database. Editing the data isn't too bad either because we're just downloading the data to the client, updating the JSON field(s) as needed, and throwing them back into the database. Not too hard. But how can I query this data? What about that task from before? _Let's display a graph of how many total calories I've eaten over the past month._
In this case, that data is stored inside the `food_log` field inside the `calendar` table. If only PostgreSQL had a way of converting JSONB arrays into individual database records (recordsets). Well, it does! The `jsonb_array_elements` function will do this for us, allowing to create a simple table we can use to calculate our caloric intake.
In this case, that data is stored inside the `food_log` field inside the `calendar` table. If only PostgreSQL had a way of converting JSONB arrays into individual database records (recordsets). Well, it does! The `jsonb_array_elements` function will do this for us, allowing to create a simple table we can use to calculate our caloric intake.
Here's some SQL to turn that `food_log` array into individual output records:
{/* prettier-ignore */}
```sql
select
select
user_id,
date,
jsonb_array_elements(food_log)->>'title' as title,
jsonb_array_elements(food_log)->'calories' as calories,
jsonb_array_elements(food_log)->'meal' as meal
from calendar
where user_id = 'xyz'
and date between
'2022-01-01' and '2022-01-31';
from calendar
where user_id = 'xyz'
and date between '2022-01-01' and '2022-01-31';
```
This returns a table that looks like this:
@@ -233,14 +238,15 @@ If we want to `sum` the calories to get some totals, we can't have a JSON object
- `(jsonb_array_elements(food_log)->'calories')::INTEGER as calories` this returns an INTEGER
Now we can't just throw the `sum` operator on this to get the total calories by day. If we try this:
Now we can't just throw the `sum` operator on this to get the total calories by day. If we try this:
{/* prettier-ignore */}
```sql
select
select
date,
sum((jsonb_array_elements(food_log)->'calories')::integer) as total_calories
from calendar where user_id = 'xyz'
and date between '2022-01-01' and '2022-01-31'
from calendar where user_id = 'xyz'
and date between '2022-01-01' and '2022-01-31'
group by date;
```
@@ -248,30 +254,31 @@ we get an error back from PostgreSQL: **Failed to run sql query: aggregate funct
Instead, we need to think of this as a set of building blocks, where our first SQL statement returns a table:
{/* prettier-ignore */}
```sql
select
select
date,
(jsonb_array_elements(food_log)->'calories')::integer as calories
from calendar where user_id = 'xyz'
and date between
'2022-01-01' and '2022-01-31';
from calendar where user_id = 'xyz'
and date between '2022-01-01' and '2022-01-31';
```
Now we can take that "table" statement, throw some (parenthesis) around it, and query **it**:
{/* prettier-ignore */}
```sql
with data as
(
select
date,
(jsonb_array_elements(food_log)->'calories')::integer as calories
from calendar
where user_id = 'xyz'
and date between
'2022-01-01' and '2022-01-31'
)
select date, sum(calories) from
data group by date;
(
select
date,
(jsonb_array_elements(food_log)->'calories')::integer as calories
from calendar
where user_id = 'xyz'
and date between '2022-01-01' and '2022-01-31'
)
select date, sum(calories)
from data
group by date;
```
This gives us exactly what we want:
@@ -283,39 +290,43 @@ This gives us exactly what we want:
If we add more data for the rest of the days of the month, we'll have all the data we need for a beautiful graph.
### Searching the Data
What if we want to answer the question: *How many calories were in the garlic bread I ate last month?* This data is stored inside the `food_log` field in the `calendar` table. We can use the same type of query we used before to "flatten" the `food_log` data so we can search it.
What if we want to answer the question: _How many calories were in the garlic bread I ate last month?_ This data is stored inside the `food_log` field in the `calendar` table. We can use the same type of query we used before to "flatten" the `food_log` data so we can search it.
To get every item I ate during the month of January, we can use:
{/* prettier-ignore */}
```sql
select
select
date,
jsonb_array_elements(food_log)->>'title' as title,
(jsonb_array_elements(food_log)->'calories')::integer as calories
from calendar
where user_id = 'xyz'
and date between
'2022-01-01' and '2022-01-31'
from calendar
where user_id = 'xyz'
and date between '2022-01-01' and '2022-01-31'
```
Now to search for the **garlic bread** we can just put (parenthesis) around this to make a "table" and then search for the item we want:
{/* prettier-ignore */}
```sql
with my_food as
with my_food as
(
select
date,
jsonb_array_elements(food_log)->>'title' as title,
(jsonb_array_elements(food_log)->'calories')::integer as calories
from calendar
where user_id = 'xyz'
and date between
'2022-01-01' and '2022-01-31'
)
select title, calories
from my_food
select
date,
jsonb_array_elements(food_log)->>'title' as title,
(jsonb_array_elements(food_log)->'calories')::integer as calories
from calendar
where user_id = 'xyz'
and date between '2022-01-01' and '2022-01-31'
)
select
title,
calories
from my_food
where title = 'Garlic Bread';
```
which gives us:
| title | calories |
@@ -324,7 +335,7 @@ which gives us:
## Conclusion
If we take a little time to study the [JSON Functions and Operators](https://www.postgresql.org/docs/9.5/functions-json.html) that PostgreSQL offers, we can turn Postgres into an easy-to-use NoSQL database that still retains all the power of SQL. This gives us a super easy way to store our complex JSON data coming from our application code in our database. Then we can use powerful SQL capabilities to analyze and present that data in our application. It's the best of both worlds!
If we take a little time to study the [JSON Functions and Operators](https://www.postgresql.org/docs/9.5/functions-json.html) that PostgreSQL offers, we can turn Postgres into an easy-to-use NoSQL database that still retains all the power of SQL. This gives us a super easy way to store our complex JSON data coming from our application code in our database. Then we can use powerful SQL capabilities to analyze and present that data in our application. It's the best of both worlds!
## More Postgres resources
@@ -334,4 +345,3 @@ If we take a little time to study the [JSON Functions and Operators](https://www
- [Partial data dumps using Postgres Row Level Security](https://supabase.com/blog/partial-postgresql-data-dumps-with-rls)
- [Postgres Views](https://supabase.com/blog/postgresql-views)
- [Realtime Postgres RLS on Supabase](https://supabase.com/blog/realtime-row-level-security-in-postgresql)
@@ -206,12 +206,10 @@ The new label indicates which column is to be associated with the secret, and th
Now that you have TCE setup for a table, it's easy to use by simply inserting data into the table, and querying that data by looking at its generated view. The view is named `decrypted_<table_name>` and by default is in the same schema as your table:
```sql
INSERT INTO credit_cards (
credit_card_number,
account_id
)
VALUES
('1234-5678-8765-4321', 123);
insert into credit_cards
(credit_card_number, account_id)
values
('1234-5678-8765-4321', 123);
```
Now that you have inserted data, look at the table and notice how the credit card number is encrypted. This is the data that is stored on disk, the encrypted card number, the key id, and the account id, **but the key itself is not stored**. This means if someone gets a backup or dump of your database, they cannot decrypt the credit card number, they do not have the key, only the key ID:
@@ -14,7 +14,7 @@ We're always trying to innovate at Supabase, so when we started planning [Launch
## What is the Content Storm?
The idea was to get many different external content creators as possible to create Supabase-related content and *publish everything on the same day*. We contacted some well known Youtubers, community members, makers building with Supabase, the SupaSquad, and generally anyone within the Supabase community. We wanted this to be an opportunity for them to build their personal brand or promote their products and startups.
The idea was to get many different external content creators as possible to create Supabase-related content and _publish everything on the same day_. We contacted some well known Youtubers, community members, makers building with Supabase, the SupaSquad, and generally anyone within the Supabase community. We wanted this to be an opportunity for them to build their personal brand or promote their products and startups.
## The aftermath of the storm
@@ -53,6 +53,6 @@ More than 30 creators participated in creating content in different formats and
- [Twitter thread showing how to build a web app](https://twitter.com/thisisisheanesu/status/1600257146792181766) by Isheanesu.
- [This service will run your entire app](https://www.instagram.com/reel/Cl1KWbtDGMR/) by Simon Baker (@allthecode).
It's a lot! There are tons of great resources, going through everything would take a couple of days, so we recommend bookmarking this page! If you like the content, make sure to give feedback to the author and follow them on social.
It's a lot! There are tons of great resources, going through everything would take a couple of days, so we recommend bookmarking this page! If you like the content, make sure to give feedback to the author and follow them on social.
And don't miss Launch Week 6, we've been working hard to make it our best Launch Week yet 🚀. Get your tickets [here](https://supabase.com/launch-week).
@@ -11,7 +11,6 @@ date: '2022-12-09'
toc_depth: 3
---
The official Supabase Hackathon starts on Friday 9th December at 8am Pacific Time and ends Monday 19th December at 00:01am.
[Launch Week](https://supabase.com/launch-week) is about to kick off and we're running a hackathon in parallel.
@@ -32,7 +31,7 @@ See all the [winners from all the previous hackathons](https://supabase.com/blog
- There are 5 prize categories.
- Best overall project will win $1500 in GitHub sponsorships, and a Supabase Keyboard for each team member.
- Most fun/interesting, best Flutter project, and best storage project will all win a limited edition Hackathon swag kit for each team member!
- Our friends at [Deno](https://deno.land/) will be judging a special guest category: Best Edge Functions Project! They will be providing some [special swag](https://deno.com/blog/edge-functions-supabase-launch-week-6-hackathon) for the winners!
- Our friends at [Deno](https://deno.land/) will be judging a special guest category: Best Edge Functions Project! They will be providing some [special swag](https://deno.com/blog/edge-functions-supabase-launch-week-6-hackathon) for the winners!
## Details
@@ -47,13 +46,12 @@ See all the [winners from all the previous hackathons](https://supabase.com/blog
There are 5 chances to win, there will be prizes for:
- Best Overall Project ($1500 in GitHub Sponsorship & Supabase Keyboards)
- to be paid as 3x$500 GitHub Sponsorships (over 3 months)
- to be paid as 3x$500 GitHub Sponsorships (over 3 months)
- Most Fun/Interesting (Swag kit)
- Best Flutter Project (Swag kit)
- Best Storage Project (Swag kit)
- Best Edge Functions Project (Deno Swag kit)
There will be winner and runner-up prizes for each category. Every team member on winning/runner-up teams gets a supaverified swag kit.
![Swag kit](/images/blog/lw6-hackathon/swagkit.png)
@@ -107,7 +105,6 @@ Don't forget to checkout the new feature's being announced as part of [Launch We
- [Previous Hackathon Prize Winners](https://supabase.com/blog/launch-week-5-hackathon-winners)
- [Who We Hire at Supabase - Founders Chat](https://youtu.be/-BG9XptyCKI)
### Additional Info
- Any intellectual property developed during the hackathon will belong to the team that developed it. We expect that each team will have an agreement between themselves regarding the IP, but this is not required
@@ -125,4 +122,3 @@ Don't forget to checkout the new feature's being announced as part of [Launch We
- [Point in Time Recovery is now available](https://supabase.com/blog/postgres-point-in-time-recovery)
- [Custom Domain Names are now available](https://supabase.com/blog/custom-domain-names)
- [Wrap Up: everything we shipped](https://supabase.com/blog/launch-week-6-wrap-up)
+18 -16
View File
@@ -13,20 +13,25 @@ date: '2022-12-14'
toc_depth: 3
video: https://www.youtube.com/v/He7LI2mv9v0
---
Today, we’re releasing Multi Factor Authentication for everyone.
Additionally, in preparation for releasing SAML, we're "dogfooding" the feature with the introduction of Single Sign On (SSO) on our dashboard. Contact us at [growth@supabase.com](mailto:growth@supabase.com) if you want to [enable this on your Enterprise plan](https://supabase.com/docs/guides/platform/sso).
<div className="video-container">
<iframe
src="https://www.youtube-nocookie.com/embed/He7LI2mv9v0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe
src="https://www.youtube-nocookie.com/embed/He7LI2mv9v0"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
></iframe>
</div>
## What is MFA?
Multi-factor authentication (MFA), sometimes called two-factor authentication (2FA), adds an additional layer of security to your application by letting you verify users’ identity through extra steps. This typically consists of something you know, like a password, and something you have, like an authenticator application. We built MFA in response to customer requests - developers wanted enhanced security - be it for compliance, client requirements, or simply for peace of mind. As such, we started by building MFA support for Time-Based One Time Passwords (TOTP).
## An Overview of TOTP
[TOTP](https://www.rfc-editor.org/rfc/rfc6238) works by generating a unique, one-time password that is valid for a limited amount of time, usually 30 seconds or less. This password is generated using a shared secret key that is known only to the device and Supabase Auth, along with the current time. To exchange the shared secret key, a user scans a QR code generated by the server in order to establish a connection. The QR code can be represented by a URI which conforms to the [Google Authenticator Key URI format](https://github.com/google/google-authenticator/wiki/Key-Uri-Format):
@@ -35,31 +40,30 @@ Multi-factor authentication (MFA), sometimes called two-factor authentication (2
otpauth://totp/supabase.io:j@supacats.io?algorithm=SHA1&digits=6&issuer=supabase.io&period=30&secret=BFSXQHFB2BGAZIOQWCDBJUF7B54A52JQ
```
The first portion of `otpauth://totp/supabase.io` describes the protocol and issuer while `j@supacats.io` refers to the user. The remaining parameters refer to specifics around OTP generation. In this case, the OTP code is generated using a SHA1 hash of the secret combined with the timestamp and the OTP code is valid for `30s`
The first portion of `otpauth://totp/supabase.io` describes the protocol and issuer while `j@supacats.io` refers to the user. The remaining parameters refer to specifics around OTP generation. In this case, the OTP code is generated using a SHA1 hash of the secret combined with the timestamp and the OTP code is valid for `30s`
In the event that the user faces difficulties entering a QR code the user can also opt to manually type the secret into the authenticator device.
## MFA Flows: Enrollment and Verification
An MFA flow can be broken into two key steps: Enrollment and Verification. During the *Enrollment* process Supabase Auth exchanges a randomly generated secret with the user’s authenticator application. During the *Verification* process, the device makes use of the timestamp together with the secret to produce a six digit code that the server can verify.
An MFA flow can be broken into two key steps: Enrollment and Verification. During the _Enrollment_ process Supabase Auth exchanges a randomly generated secret with the user’s authenticator application. During the _Verification_ process, the device makes use of the timestamp together with the secret to produce a six digit code that the server can verify.
![Flowchart of an MFA flow](/images/blog/lw6-auth/mfa-flow.png)
*Enrollment*
_Enrollment_
To generate a QR code, call the `/enroll` endpoint which returns an SVG encoded QR and the secret. Thereafter, create a challenge by calling the `/challenge` endpoint. Once the user has entered the six digit TOTP code generated by their authenticator app, call the`/verify` endpoint with the corresponding factor and challenge details.
To generate a QR code, call the `/enroll` endpoint which returns an SVG encoded QR and the secret. Thereafter, create a challenge by calling the `/challenge` endpoint. Once the user has entered the six digit TOTP code generated by their authenticator app, call the`/verify` endpoint with the corresponding factor and challenge details.
You might wonder: why the need for the "challenge" step? This step creates an interval between MFA initiation and the action of making a verification. This is useful in cases like Yubikey authentication where a user might need to request a challenge before placing their finger on the device.
![MFA Enrollment Flow](/images/blog/lw6-auth/enrollment_flow.png)
*Overview of Enrollment Flow*
_Overview of Enrollment Flow_
**Verification**
On subsequent logins attempts, redirect a user to an MFA verification after they have completed the conventional sign in process. On the verification page, wait for the user to enter the six digit OTP code from the authenticator application and then call the`/challenge` endpoint followed by the `/verify` endpoint. If a correct code is submitted, a JWT will be created with a few additional fields.
On subsequent logins attempts, redirect a user to an MFA verification after they have completed the conventional sign in process. On the verification page, wait for the user to enter the six digit OTP code from the authenticator application and then call the`/challenge` endpoint followed by the `/verify` endpoint. If a correct code is submitted, a JWT will be created with a few additional fields.
![MFA Verification Flow](/images/blog/lw6-auth/verify_flow.png)
*Overview of Verification Flow*
_Overview of Verification Flow_
## Enforcement via Row Level Security
@@ -87,7 +91,7 @@ Using MFA without enforcing it is like buying an expensive door and never lockin
],
// ...
}
````
```
The information encoded in these claims can be used for both full enforcement and partial enforcement across database queries.
@@ -99,7 +103,7 @@ create policy "Enforce MFA for all end users."
using ( auth.jwt()->>'aal' = 'aal2' );
```
*Enforce MFA for all end users*
_Enforce MFA for all end users_
```sql
create policy "Allow access on table only if user has gone through MFA"
@@ -118,20 +122,18 @@ create policy "Allow access on table only if user has gone through MFA"
));
```
*Enforce MFA for selected users*
_Enforce MFA for selected users_
Note that both RLS policies are restrictive. By default, overlapping policies in PostgreSQL are permissive rather than restrictive. This means that RLS policies are combined with an `OR` clause and only one policy needs to pass in order for a row to be operated on. Therefore, we set RLS policies as restrictive to enforce the checks from multiple policies.
Be mindful of your user’s preference, though. If a user has enabled MFA, they are expecting a higher level of security for their account. Consequently, we recommend that developers enforce MFA across all operations if a user has MFA enabled. You can check out [our MFA guide](https://supabase.com/docs/guides/auth/auth-mfa) for more details about MFA enforcement.
## What's Next
For starters, we are looking to support WebAuthn and FIDO2 compliant devices such as Yubikeys. We also hope to allow users to receive email notifications when selected MFA actions are triggered. If you have MFA requirements which are not covered here feel free to write to us at support[at]supabase.io .
We are grateful to our early MFA users for the support and feedback provided throughout this period. In particular, we would like to thank [Fabian Beer](https://madebyfabian.com), [Cogram](https://www.cogram.com), and [Happl](https://happl.com) whose detailed feedback helped to shape our implementation. We would also like to specially thank the community behind the [pquerna/otp](https://github.com/pquerna/otp) and [ajstarks/svgo](https://github.com/ajstarks/svgo) libraries - their work is indispensable to this implementation.
## More Launch Week 6
- [Day 1: New Supabase Docs, built with Next.js](https://supabase.com/blog/new-supabase-docs-built-with-nextjs)
@@ -143,14 +143,11 @@ You can even join remote data with your local tables to enrich existing operatio
```sql
select
users.id,
count(order_history.event)
count(order_history.event)
from
snowflake.order_history
join
auth.users on auth.users.id = snowflake.order_history.user_id
where
order_history.event = 'purchase' and
order_history.user_id = '<some_user_id>';
snowflake.order_history
join auth.users on auth.users.id = snowflake.order_history.user_id
where order_history.event = 'purchase' and order_history.user_id = '<some_user_id>';
```
We can either run these queries on demand or, for better query performance, we can run them in the background (using something like [pg_cron](https://supabase.com/docs/guides/database/extensions/pgcron)), and materialize the data into a local table.
@@ -127,7 +127,7 @@ Mobbin use Supabase Auth to power their [UI/UX library of Mobile and Web screens
### Topaz
[Topaz](https://topaz.network/) is one of the fastest growing NFT networks and chose supabase as a backend to ensure they won't lose customers during peak traffic periods.
[Topaz](https://topaz.network/) is one of the fastest growing NFT networks and chose supabase as a backend to ensure they won't lose customers during peak traffic periods.
### Repeat.dev
@@ -184,7 +184,7 @@ Check out Jon's brand new, entirely free course on egghead to learn all about th
### Base Jump
[usebasejump.com](https://usebasejump.com/) is "The fastest way to launch Supabase apps with NextJS". Basejump is open source. Contributions are both awesome and encouraged!
[usebasejump.com](https://usebasejump.com/) is "The fastest way to launch Supabase apps with NextJS". Basejump is open source. Contributions are both awesome and encouraged!
### supastarter
+2 -2
View File
@@ -96,12 +96,12 @@ While v1.0 is an important milestone for stability, there's plenty of room to ex
- `startsWith` for the `String` type
- nestable `and`/ `or` blocks
- Support for user-defined functions
- Support for views
- Support for views
Longer term we look forward to experimenting with more ambitions features like an API for migrations, integrations with 3rd-party services through [supabase/wrappers](https://github.com/supabase/wrappers), and a scalable solution for subscriptions.
## More pg_graphql
- [Introducing pg_graphql: A GraphQL extension for PostgreSQL](https://supabase.com/blog/pg-graphql)
- [Introducing pg_graphql: A GraphQL extension for PostgreSQL](https://supabase.com/blog/pg-graphql)
- [GraphQL is now available in Supabase](https://supabase.com/blog/graphql-now-available)
- [pg_graphql v1.0](https://supabase.com/blog/pg-graphql-v1)
@@ -91,7 +91,7 @@ Two key features to level up your projects are now available: Custom domains all
It was great to see so many high-quality projects. The decision wasn't easy, but in the end, the panel of judges chose Depulso as the winner of the Best Overall Project.
Congratulations 👏 to [@_iamkarthikeyan](https://twitter.com/_iamkarthikeyan) and [@SindhuMansi](https://twitter.com/SindhuMansi) who get each a Supabase Darkmode Keyboard and the $1500 Github sponsorship!
Congratulations 👏 to [@\_iamkarthikeyan](https://twitter.com/_iamkarthikeyan) and [@SindhuMansi](https://twitter.com/SindhuMansi) who get each a Supabase Darkmode Keyboard and the $1500 Github sponsorship!
- [Full list of Winners](https://supabase.com/blog/launch-week-6-hackathon-winners)
- [See all the submissions](https://www.madewithsupabase.com/launch-week-6)
@@ -14,7 +14,7 @@ toc_depth: 3
A new PostgreSQL extension is now available in Supabase: [`pgvector`](https://github.com/pgvector/pgvector), an open-source vector similarity search.
The exponential progress of AI functionality over the past year has inspired many new real world applications. One specific challenge has been the ability to store and query _embeddings_ at scale.
The exponential progress of AI functionality over the past year has inspired many new real world applications. One specific challenge has been the ability to store and query _embeddings_ at scale.
In this post we'll explain what embeddings are, why we might want to use them, and how we can store and query them in PostgreSQL using `pgvector`.
## What are embeddings?
@@ -92,7 +92,7 @@ Next let's create a table to store our documents and their embeddings:
create table documents (
id bigserial primary key,
content text,
embedding vector (1536)
embedding vector(1536)
);
```
@@ -145,7 +145,7 @@ Once your table starts to grow with embeddings, you will likely want to add an i
Each distance operator requires a different type of index. We expect to order by cosine distance, so we need `vector_cosine_ops` index. A good starting number of lists is 4 \* sqrt(table_rows):
```sql
create index on documents
create index on documents
using ivfflat (embedding vector_cosine_ops)
with (lists = 100);
```
Loaded 100 of 117 files, more files were not shown because too many files have changed in this diff. Show more