From abebd998599cfc718797a56d2a74254e7f7a1f59 Mon Sep 17 00:00:00 2001
From: dshukertjr <18113850+dshukertjr@users.noreply.github.com>
Date: Mon, 19 Jul 2021 22:25:43 +0900
Subject: [PATCH 01/17] feat: Created with-flutter.mdx and started the guide
---
web/docs/guides/with-flutter.mdx | 560 +++++++++++++++++++++++++++++++
1 file changed, 560 insertions(+)
create mode 100644 web/docs/guides/with-flutter.mdx
diff --git a/web/docs/guides/with-flutter.mdx b/web/docs/guides/with-flutter.mdx
new file mode 100644
index 00000000000..c4749a062ce
--- /dev/null
+++ b/web/docs/guides/with-flutter.mdx
@@ -0,0 +1,560 @@
+---
+id: with-flutter
+title: "Quickstart: Flutter"
+description: Learn how to use Supabase in your Flutter App.
+---
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+## Intro
+
+This example provides the steps to build a simple user management app (from scratch!) using Supabase and Flutter. It includes:
+
+- Supabase [Database](/docs/guides/database): a Postgres database for storing your user data.
+- Supabase [Auth](/docs/guides/auth): users can sign in with magic links (no passwords, only email).
+- Supabase [Storage](/docs/guides/storage): users can upload a photo.
+- [Row Level Security](/docs/guides/auth#row-level-security): data is protected so that individuals can only access their own data.
+- Instant [APIs](/docs/guides/api): APIs will be automatically generated when you create your database tables.
+
+By the end of this guide you'll have an app which allows users to login and update some basic profile details:
+
+
+
+
+
+## Project set up
+
+Before we start building we're going to set up our Database and API. This is as simple as starting a new Project in Supabase
+and then creating a "schema" inside the database.
+
+### Create a project
+
+1. Go to [app.supabase.io](https://app.supabase.io).
+1. Click on "New Project".
+1. Enter your project details.
+1. Wait for the new database to launch.
+
+
+### Set up the database schema
+
+Now we are going to set up the database schema. We can use the "User Management Starter" quickstart in the SQL Editor,
+or you can just copy/paste the SQL from below and run it yourself.
+
+
+
+
+```sh
+1. Go to the "SQL" section.
+2. Click "User Management Starter".
+4. Click "Run".
+```
+
+
+
+
+
+
+```sql
+-- Create a table for public "profiles"
+create table profiles (
+ id uuid references auth.users not null,
+ updated_at timestamp with time zone,
+ username text unique,
+ avatar_url text,
+ website text,
+
+ primary key (id),
+ unique(username),
+ constraint username_length check (char_length(username) >= 3)
+);
+
+alter table profiles enable row level security;
+
+create policy "Public profiles are viewable by everyone."
+ on profiles for select
+ using ( true );
+
+create policy "Users can insert their own profile."
+ on profiles for insert
+ with check ( auth.uid() = id );
+
+create policy "Users can update own profile."
+ on profiles for update
+ using ( auth.uid() = id );
+
+-- Set up Realtime!
+begin;
+ drop publication if exists supabase_realtime;
+ create publication supabase_realtime;
+commit;
+alter publication supabase_realtime add table profiles;
+
+-- Set up Storage!
+insert into storage.buckets (id, name)
+values ('avatars', 'avatars');
+
+create policy "Avatar images are publicly accessible."
+ on storage.objects for select
+ using ( bucket_id = 'avatars' );
+
+create policy "Anyone can upload an avatar."
+ on storage.objects for insert
+ with check ( bucket_id = 'avatars' );
+
+```
+
+
+
+
+
+### Get the API Keys
+
+
+Now that you've created some database tables, you are ready to insert data using the auto-generated API.
+We just need to get the URL and `anon` key from the API settings.
+
+
+
+
+```sh
+1. Go to the "Settings" section.
+2. Click "API" in the sidebar.
+3. Find your API URL in this page.
+4. Find your "anon" key on this page.
+```
+
+
+
+
+
+
+## Building the App
+
+Let's start building the Flutter app from scratch.
+
+### Initialize a Flutter app
+
+We can use [`flutter create`](https://flutter.dev/docs/get-started/test-drive) to initialize
+an app called `supabase_quickstart`:
+
+```bash
+flutter create supabase_quickstart
+```
+
+Then let's install the only additional dependency: [supabase-flutter](https://github.com/supabase/supabase-flutter)
+
+Open the app and copy and paste the following inside `pubspec.yaml` under `dependencies`.
+
+```yaml
+supabase_flutter: ^0.0.3
+```
+
+Run `flutter pub get` to install the dependencies.
+
+Now that we have the dependencies installed let's initialize the Supabase client inside our `main` function with the API credentials that you copied [earlier](#get-the-api-keys).
+These variables will be exposed on the app, and that's completely fine since we have
+[Row Level Security](/docs/guides/auth#row-level-security) enabled on our Database.
+
+```dart title="lib/main.dart"
+void main() {
+ WidgetsFlutterBinding.ensureInitialized();
+
+ Supabase.initialize(
+ url: YOUR_SUPABASE_URL,
+ anonKey: YOUR_SUPABASE_ANNON_KEY,
+ );
+ runApp(MyApp());
+}
+```
+
+
+
+### Set up a Login widget
+
+Let's create a Flutter widget to manage logins and sign ups.
+
+Let's set up a React component to manage logins and sign ups.
+We'll use Magic Links, so users can sign in with their email without using passwords.
+
+```jsx title="components/Auth.js"
+import { useState } from 'react'
+import { supabase } from '../utils/supabaseClient'
+
+export default function Auth() {
+ const [loading, setLoading] = useState(false)
+ const [email, setEmail] = useState('')
+
+ const handleLogin = async (email) => {
+ try {
+ setLoading(true)
+ const { error } = await supabase.auth.signIn({ email })
+ if (error) throw error
+ alert('Check your email for the login link!')
+ } catch (error) {
+ alert(error.error_description || error.message)
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ return (
+
+
+
Supabase + Next.js
+
Sign in via magic link with your email below
+
+ setEmail(e.target.value)}
+ />
+
+
+
+
+
+
+ )
+}
+```
+
+### Account page
+
+After a user is signed in we can allow them to edit their profile details and manage their account.
+
+Let's create a new component for that called `Account.js`.
+
+```jsx title="components/Account.js"
+import { useState, useEffect } from 'react'
+import { supabase } from '../utils/supabaseClient'
+
+export default function Account({ session }) {
+ const [loading, setLoading] = useState(true)
+ const [username, setUsername] = useState(null)
+ const [website, setWebsite] = useState(null)
+ const [avatar_url, setAvatarUrl] = useState(null)
+
+ useEffect(() => {
+ getProfile()
+ }, [session])
+
+ async function getProfile() {
+ try {
+ setLoading(true)
+ const user = supabase.auth.user()
+
+ let { data, error, status } = await supabase
+ .from('profiles')
+ .select(`username, website, avatar_url`)
+ .eq('id', user.id)
+ .single()
+
+ if (error && status !== 406) {
+ throw error
+ }
+
+ if (data) {
+ setUsername(data.username)
+ setWebsite(data.website)
+ setAvatarUrl(data.avatar_url)
+ }
+ } catch (error) {
+ alert(error.message)
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ async function updateProfile({ username, website, avatar_url }) {
+ try {
+ setLoading(true)
+ const user = supabase.auth.user()
+
+ const updates = {
+ id: user.id,
+ username,
+ website,
+ avatar_url,
+ updated_at: new Date(),
+ }
+
+ let { error } = await supabase.from('profiles').upsert(updates, {
+ returning: 'minimal', // Don't return the value after inserting
+ })
+
+ if (error) {
+ throw error
+ }
+ } catch (error) {
+ alert(error.message)
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ return (
+
+
+
+
+
+
+
+ setUsername(e.target.value)}
+ />
+
+
+
+ setWebsite(e.target.value)}
+ />
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+```
+
+
+
+### Launch!
+
+Now that we have all the components in place, let's update `pages/index.js`:
+
+```jsx title="pages/index.js"
+import { useState, useEffect } from 'react'
+import { supabase } from '../utils/supabaseClient'
+import Auth from '../components/Auth'
+import Account from '../components/Account'
+
+export default function Home() {
+ const [session, setSession] = useState(null)
+
+ useEffect(() => {
+ setSession(supabase.auth.session())
+
+ supabase.auth.onAuthStateChange((_event, session) => {
+ setSession(session)
+ })
+ }, [])
+
+ return (
+
+ {!session ? : }
+
+ )
+}
+```
+
+Once that's done, run this in a terminal window:
+
+```bash
+npm run dev
+```
+
+And then open the browser to [localhost:3000](http://localhost:3000) and you should see the completed app.
+
+
+
+
+## Bonus: Profile photos
+
+Every Supabase project is configured with [Storage](/docs/guides/storage) for managing large files like
+photos and videos.
+
+### Create an upload widget
+
+Let's create an avatar for the user so that they can upload a profile photo.
+We can start by creating a new component:
+
+```jsx title="components/Avatar.js"
+import { useEffect, useState } from 'react'
+import { supabase } from '../utils/supabaseClient'
+
+export default function Avatar({ url, size, onUpload }) {
+ const [avatarUrl, setAvatarUrl] = useState(null)
+ const [uploading, setUploading] = useState(false)
+
+ useEffect(() => {
+ if (url) downloadImage(url)
+ }, [url])
+
+ async function downloadImage(path) {
+ try {
+ const { data, error } = await supabase.storage.from('avatars').download(path)
+ if (error) {
+ throw error
+ }
+ const url = URL.createObjectURL(data)
+ setAvatarUrl(url)
+ } catch (error) {
+ console.log('Error downloading image: ', error.message)
+ }
+ }
+
+
+ async function uploadAvatar(event) {
+ try {
+ setUploading(true)
+
+ if (!event.target.files || event.target.files.length === 0) {
+ throw new Error('You must select an image to upload.')
+ }
+
+ const file = event.target.files[0]
+ const fileExt = file.name.split('.').pop()
+ const fileName = `${Math.random()}.${fileExt}`
+ const filePath = `${fileName}`
+
+ let { error: uploadError } = await supabase.storage
+ .from('avatars')
+ .upload(filePath, file)
+
+ if (uploadError) {
+ throw uploadError
+ }
+
+ onUpload(filePath)
+ } catch (error) {
+ alert(error.message)
+ } finally {
+ setUploading(false)
+ }
+ }
+
+ return (
+
+ {avatarUrl ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+ )
+}
+```
+
+
+### Add the new widget
+
+And then we can add the widget to the Account page:
+
+
+```jsx title="components/Account.js"
+// Import the new component
+import Avatar from './Avatar'
+
+// ...
+
+return (
+
+ {/* Add to the body */}
+ {
+ setAvatarUrl(url)
+ updateProfile({ username, website, avatar_url: url })
+ }}
+ />
+ {/* ... */}
+
+)
+
+```
+
+## Next steps
+
+At this stage you have a fully functional application!
+
+- Got a question? [Ask here](https://github.com/supabase/supabase/discussions).
+- Sign in: [app.supabase.io](https://app.supabase.io)
From 7d4b72f63778055ca9e8fdc707db3261dd3c28b9 Mon Sep 17 00:00:00 2001
From: Inian Parameshwaran
Date: Wed, 31 Mar 2021 16:32:03 +0530
Subject: [PATCH 02/17] Adds storage server to self hosting setup
---
docker/README.md | 15 ++--
docker/docker-compose.yml | 108 +++++++++++-------------
docker/dockerfiles/kong/kong.yml | 10 +++
docker/dockerfiles/postgres/Dockerfile | 1 +
docker/postgres/storage-schema.sql | 111 +++++++++++++++++++++++++
5 files changed, 181 insertions(+), 64 deletions(-)
create mode 100644 docker/postgres/storage-schema.sql
diff --git a/docker/README.md b/docker/README.md
index 4b0f64e1546..041594405a7 100644
--- a/docker/README.md
+++ b/docker/README.md
@@ -3,7 +3,10 @@
You can run Supabase on your local machine or a server using `docker-compose`.
For a more secure setup to use on a server (e.g. using HTTPS), have a look at the *supabase-traefik* setup.
-## Configuration
+Update the docker-compose file with your valid AWS_SECRET_ACCESS_KEY and AWS_ACCESS_KEY_ID in the storage section. The key must have permissions to `s3:PutObject , s3:GetObject, s3:DeleteObject` in the bucket you have chosen.
+In production, PGRST_JWT_SECRET and derived keys from the JWT secret like ANON_KEY need to be changed to different values.
+
+You can run Supabase on your local machine using `docker-compose`:
Add your passwords to the .env file.
For better customization and security, please make sure you read the [self-hosting guide](https://supabase.io/docs/guides/self-hosting#running-supabase).
@@ -27,7 +30,9 @@ const supabase = createClient(SUPABASE_URL, SUPABASE_KEY)
Accessing the services directly:
- Kong: http://localhost:8000
- - GoTrue: http://localhost:8000/auth/v1/?apikey=
- - PostgREST: http://localhost:8000/rest/v1/?apikey=
- - Realtime: http://localhost:8000/realtime/v1/?apikey=
-- Postgres: http://localhost:5432
\ No newline at end of file
+ - GoTrue: http://localhost:8000/auth/v1/
+ - PostgREST: http://localhost:8000/rest/v1/
+ - Realtime: http://localhost:8000/realtime/v1/
+ - Supabase: http://localhost:8000/storage/v1
+- Postgres: http://localhost:5432
+
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
index 194050ee191..513e427a129 100644
--- a/docker/docker-compose.yml
+++ b/docker/docker-compose.yml
@@ -17,67 +17,57 @@ services:
- ${AUTH_PORT}
depends_on:
- db
- environment:
- GOTRUE_JWT_SECRET: ${JWT_SECRET}
- GOTRUE_JWT_EXP: 3600
- GOTRUE_JWT_DEFAULT_GROUP_NAME: authenticated
- GOTRUE_DB_DRIVER: postgres
- DB_NAMESPACE: auth
- API_EXTERNAL_URL: localhost
- GOTRUE_API_HOST: 0.0.0.0
- PORT: ${AUTH_PORT}
-
- GOTRUE_SMTP_HOST: ${SMTP_HOST}
- GOTRUE_SMTP_PORT: ${SMTP_PORT}
- GOTRUE_SMTP_USER: ${SMTP_USER}
- GOTRUE_SMTP_PASS: ${SMTP_PASS}
-
- GOTRUE_DISABLE_SIGNUP: 'false'
- GOTRUE_SITE_URL: localhost
- GOTRUE_MAILER_AUTOCONFIRM: 'true'
- GOTRUE_LOG_LEVEL: DEBUG
- GOTRUE_OPERATOR_TOKEN: ${OPERATOR_TOKEN}
- DATABASE_URL: 'postgres://postgres:${POSTGRES_PASSWORD}@db:${POSTGRES_PORT}/postgres?sslmode=disable'
-
- rest:
- container_name: supabase-rest
- image: postgrest/postgrest:latest
- ports:
- - ${REST_PORT}:3000
- depends_on:
+ restart: always
+ environment:
+ PGRST_DB_URI: postgres://postgres:postgres@db:5432/postgres
+ PGRST_DB_SCHEMA: public, storage
+ PGRST_DB_ANON_ROLE: postgres
+ PGRST_JWT_SECRET: super-secret-jwt-token-with-at-least-32-characters-long
+ realtime:
+ image: supabase/realtime:latest
+ ports:
+ - '4000:4000'
+ depends_on:
- db
- restart: always
- environment:
- PGRST_DB_URI: postgres://postgres:${POSTGRES_PASSWORD}@db:${POSTGRES_PORT}/postgres
- PGRST_DB_SCHEMA: public
- PGRST_DB_ANON_ROLE: anon
- PGRST_JWT_SECRET: ${JWT_SECRET}
- realtime:
- container_name: supabase-realtime
- image: supabase/realtime:latest
- ports:
- - ${REALTIME_PORT}
- depends_on:
+ restart: on-failure
+ environment:
+ DB_HOST: db
+ DB_NAME: postgres
+ DB_USER: postgres
+ DB_PASSWORD: postgres
+ DB_PORT: 5432
+ PORT: 4000
+ HOSTNAME: localhost
+ # Disable JWT Auth locally. The JWT_SECRET will be ignored.
+ SECURE_CHANNELS: 'false'
+ JWT_SECRET: super-secret-jwt-token-with-at-least-32-characters-long
+ storage:
+ image: supabase/storage-api:v0.1.4
+ ports:
+ - '5000:5000'
+ depends_on:
- db
- restart: on-failure
- environment:
- DB_HOST: db
- DB_NAME: postgres
- DB_USER: postgres
- DB_PASSWORD: ${POSTGRES_PASSWORD}
- DB_PORT: ${POSTGRES_PORT}
- PORT: ${REALTIME_PORT}
- HOSTNAME: localhost
- # Disable JWT Auth locally. The JWT_SECRET will be ignored.
- SECURE_CHANNELS: 'false'
- JWT_SECRET: ${JWT_SECRET}
- db:
- container_name: supabase-db
- build:
- context: ./dockerfiles/postgres
- ports:
- - ${POSTGRES_PORT}:${POSTGRES_PORT}
- command:
+ - rest
+ restart: on-failure
+ environment:
+ ANON_KEY: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTYxMzUzMTk4NSwiZXhwIjoxOTI5MTA3OTg1fQ.ReNhHIoXIOa-8tL1DO3e26mJmOTnYuvdgobwIYGzrLQ
+ SERVICE_KEY: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoic2VydmljZV9yb2xlIiwiaWF0IjoxNjEzNTMxOTg1LCJleHAiOjE5MjkxMDc5ODV9.FhK1kZdHmWdCIEZELt0QDCw6FIlCS8rVmp4RzaeI2LM
+ PROJECT_REF: bjwdssmqcnupljrqypxz # can be any random string
+ REGION: us-east-1 # region where your bucket is located
+ POSTGREST_URL: http://rest:3000
+ GLOBAL_S3_BUCKET: supa-storage-testing # name of s3 bucket where you want to store objects
+ PGRST_JWT_SECRET: super-secret-jwt-token-with-at-least-32-characters-long
+ DATABASE_URL: postgres://postgres:postgres@db:5432/postgres
+ PGOPTIONS: "-c search_path=storage"
+ AWS_ACCESS_KEY_ID: replace-with-your-aws-key
+ AWS_SECRET_ACCESS_KEY: replace-with-your-aws-secret
+ FILE_SIZE_LIMIT: 52428800
+ db:
+ build:
+ context: ./postgres
+ ports:
+ - 5432:5432
+ command:
- postgres
- -c
- wal_level=logical
diff --git a/docker/dockerfiles/kong/kong.yml b/docker/dockerfiles/kong/kong.yml
index 1adfb5c8c58..659d804783e 100644
--- a/docker/dockerfiles/kong/kong.yml
+++ b/docker/dockerfiles/kong/kong.yml
@@ -39,6 +39,16 @@ services:
- name: key-auth
config:
hide_credentials: true
+- name: storage-v1
+ _comment: 'Storage: /storage/v1/* -> http://storage-api:5000/*'
+ url: http://storage:5000/
+ routes:
+ - name: storage-v1-all
+ strip_path: true
+ paths:
+ - /storage/v1/
+ plugins:
+ - name: cors
consumers:
- username: 'anon-key'
keyauth_credentials:
diff --git a/docker/dockerfiles/postgres/Dockerfile b/docker/dockerfiles/postgres/Dockerfile
index cbec306808c..cd03b29b241 100644
--- a/docker/dockerfiles/postgres/Dockerfile
+++ b/docker/dockerfiles/postgres/Dockerfile
@@ -2,6 +2,7 @@ FROM supabase/postgres:0.13.0
COPY 00-initial-schema.sql /docker-entrypoint-initdb.d/00-initial-schema.sql
COPY auth-schema.sql /docker-entrypoint-initdb.d/auth-schema.sql
+COPY storage-schema.sql /docker-entrypoint-initdb.d/storage-schema.sql
# Build time defaults
ARG build_POSTGRES_DB=postgres
diff --git a/docker/postgres/storage-schema.sql b/docker/postgres/storage-schema.sql
new file mode 100644
index 00000000000..c879c6b6234
--- /dev/null
+++ b/docker/postgres/storage-schema.sql
@@ -0,0 +1,111 @@
+CREATE SCHEMA IF NOT EXISTS storage AUTHORIZATION postgres;
+
+grant usage on schema storage to postgres, anon, authenticated, service_role;
+alter default privileges in schema storage grant all on tables to postgres, anon, authenticated, service_role;
+alter default privileges in schema storage grant all on functions to postgres, anon, authenticated, service_role;
+alter default privileges in schema storage grant all on sequences to postgres, anon, authenticated, service_role;
+
+DROP TABLE IF EXISTS "storage"."buckets";
+CREATE TABLE "storage"."buckets" (
+ "id" text not NULL,
+ "name" text NOT NULL,
+ "owner" uuid,
+ "created_at" timestamptz DEFAULT now(),
+ "updated_at" timestamptz DEFAULT now(),
+ CONSTRAINT "buckets_owner_fkey" FOREIGN KEY ("owner") REFERENCES "auth"."users"("id"),
+ PRIMARY KEY ("id")
+);
+CREATE UNIQUE INDEX "bname" ON "storage"."buckets" USING BTREE ("name");
+
+DROP TABLE IF EXISTS "storage"."objects";
+CREATE TABLE "storage"."objects" (
+ "id" uuid NOT NULL DEFAULT extensions.uuid_generate_v4(),
+ "bucket_id" text,
+ "name" text,
+ "owner" uuid,
+ "created_at" timestamptz DEFAULT now(),
+ "updated_at" timestamptz DEFAULT now(),
+ "last_accessed_at" timestamptz DEFAULT now(),
+ "metadata" jsonb,
+ CONSTRAINT "objects_bucketId_fkey" FOREIGN KEY ("bucket_id") REFERENCES "storage"."buckets"("id"),
+ CONSTRAINT "objects_owner_fkey" FOREIGN KEY ("owner") REFERENCES "auth"."users"("id"),
+ PRIMARY KEY ("id")
+);
+CREATE UNIQUE INDEX "bucketid_objname" ON "storage"."objects" USING BTREE ("bucket_id","name");
+CREATE INDEX name_prefix_search ON storage.objects(name text_pattern_ops);
+
+ALTER TABLE storage.objects ENABLE ROW LEVEL SECURITY;
+
+CREATE OR REPLACE FUNCTION storage.foldername(name text)
+ RETURNS text[]
+ LANGUAGE plpgsql
+AS $function$
+DECLARE
+_parts text[];
+BEGIN
+ select string_to_array(name, '/') into _parts;
+ return _parts[1:array_length(_parts,1)-1];
+END
+$function$;
+
+CREATE OR REPLACE FUNCTION storage.filename(name text)
+ RETURNS text
+ LANGUAGE plpgsql
+AS $function$
+DECLARE
+_parts text[];
+BEGIN
+ select string_to_array(name, '/') into _parts;
+ return _parts[array_length(_parts,1)];
+END
+$function$;
+
+CREATE OR REPLACE FUNCTION storage.extension(name text)
+ RETURNS text
+ LANGUAGE plpgsql
+AS $function$
+DECLARE
+_parts text[];
+_filename text;
+BEGIN
+ select string_to_array(name, '/') into _parts;
+ select _parts[array_length(_parts,1)] into _filename;
+ return split_part(_filename, '.', 2);
+END
+$function$;
+
+CREATE OR REPLACE FUNCTION storage.search(prefix text, bucketname text, limits int DEFAULT 100, levels int DEFAULT 1, offsets int DEFAULT 0)
+ RETURNS TABLE (
+ name text,
+ id uuid,
+ updated_at TIMESTAMPTZ,
+ created_at TIMESTAMPTZ,
+ last_accessed_at TIMESTAMPTZ,
+ metadata jsonb
+ )
+ LANGUAGE plpgsql
+AS $function$
+DECLARE
+_bucketId text;
+BEGIN
+ select buckets."id" from buckets where buckets.name=bucketname limit 1 into _bucketId;
+ return query
+ with files_folders as (
+ select ((string_to_array(objects.name, '/'))[levels]) as folder
+ from objects
+ where objects.name ilike prefix || '%'
+ and bucket_id = _bucketId
+ GROUP by folder
+ limit limits
+ offset offsets
+ )
+ select files_folders.folder as name, objects.id, objects.updated_at, objects.created_at, objects.last_accessed_at, objects.metadata from files_folders
+ left join objects
+ on prefix || files_folders.folder = objects.name
+ where objects.id is null or objects.bucket_id=_bucketId;
+END
+$function$;
+
+GRANT ALL PRIVILEGES ON SCHEMA storage TO postgres;
+GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA storage TO postgres;
+GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA storage TO postgres;
\ No newline at end of file
From 635dab60dcdc8115a6a72d7ee58580cf7e5d4225 Mon Sep 17 00:00:00 2001
From: Inian
Date: Tue, 20 Jul 2021 21:17:02 +0800
Subject: [PATCH 03/17] storage working with a file system backend
---
docker/README.md | 15 +-
docker/docker-compose.yml | 135 +++++++++++-------
.../postgres/storage-schema.sql | 0
3 files changed, 90 insertions(+), 60 deletions(-)
rename docker/{ => dockerfiles}/postgres/storage-schema.sql (100%)
diff --git a/docker/README.md b/docker/README.md
index 041594405a7..4b0f64e1546 100644
--- a/docker/README.md
+++ b/docker/README.md
@@ -3,10 +3,7 @@
You can run Supabase on your local machine or a server using `docker-compose`.
For a more secure setup to use on a server (e.g. using HTTPS), have a look at the *supabase-traefik* setup.
-Update the docker-compose file with your valid AWS_SECRET_ACCESS_KEY and AWS_ACCESS_KEY_ID in the storage section. The key must have permissions to `s3:PutObject , s3:GetObject, s3:DeleteObject` in the bucket you have chosen.
-In production, PGRST_JWT_SECRET and derived keys from the JWT secret like ANON_KEY need to be changed to different values.
-
-You can run Supabase on your local machine using `docker-compose`:
+## Configuration
Add your passwords to the .env file.
For better customization and security, please make sure you read the [self-hosting guide](https://supabase.io/docs/guides/self-hosting#running-supabase).
@@ -30,9 +27,7 @@ const supabase = createClient(SUPABASE_URL, SUPABASE_KEY)
Accessing the services directly:
- Kong: http://localhost:8000
- - GoTrue: http://localhost:8000/auth/v1/
- - PostgREST: http://localhost:8000/rest/v1/
- - Realtime: http://localhost:8000/realtime/v1/
- - Supabase: http://localhost:8000/storage/v1
-- Postgres: http://localhost:5432
-
+ - GoTrue: http://localhost:8000/auth/v1/?apikey=
+ - PostgREST: http://localhost:8000/rest/v1/?apikey=
+ - Realtime: http://localhost:8000/realtime/v1/?apikey=
+- Postgres: http://localhost:5432
\ No newline at end of file
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
index 513e427a129..a8d17f1138f 100644
--- a/docker/docker-compose.yml
+++ b/docker/docker-compose.yml
@@ -17,57 +17,92 @@ services:
- ${AUTH_PORT}
depends_on:
- db
- restart: always
- environment:
- PGRST_DB_URI: postgres://postgres:postgres@db:5432/postgres
- PGRST_DB_SCHEMA: public, storage
- PGRST_DB_ANON_ROLE: postgres
- PGRST_JWT_SECRET: super-secret-jwt-token-with-at-least-32-characters-long
- realtime:
- image: supabase/realtime:latest
- ports:
- - '4000:4000'
- depends_on:
+ environment:
+ GOTRUE_JWT_SECRET: ${JWT_SECRET}
+ GOTRUE_JWT_EXP: 3600
+ GOTRUE_JWT_DEFAULT_GROUP_NAME: authenticated
+ GOTRUE_DB_DRIVER: postgres
+ DB_NAMESPACE: auth
+ API_EXTERNAL_URL: localhost
+ GOTRUE_API_HOST: 0.0.0.0
+ PORT: ${AUTH_PORT}
+
+ GOTRUE_SMTP_HOST: ${SMTP_HOST}
+ GOTRUE_SMTP_PORT: ${SMTP_PORT}
+ GOTRUE_SMTP_USER: ${SMTP_USER}
+ GOTRUE_SMTP_PASS: ${SMTP_PASS}
+
+ GOTRUE_DISABLE_SIGNUP: 'false'
+ GOTRUE_SITE_URL: localhost
+ GOTRUE_MAILER_AUTOCONFIRM: 'true'
+ GOTRUE_LOG_LEVEL: DEBUG
+ GOTRUE_OPERATOR_TOKEN: ${OPERATOR_TOKEN}
+ DATABASE_URL: 'postgres://postgres:${POSTGRES_PASSWORD}@db:${POSTGRES_PORT}/postgres?sslmode=disable'
+
+ rest:
+ container_name: supabase-rest
+ image: postgrest/postgrest:latest
+ ports:
+ - ${REST_PORT}:3000
+ depends_on:
- db
- restart: on-failure
- environment:
- DB_HOST: db
- DB_NAME: postgres
- DB_USER: postgres
- DB_PASSWORD: postgres
- DB_PORT: 5432
- PORT: 4000
- HOSTNAME: localhost
- # Disable JWT Auth locally. The JWT_SECRET will be ignored.
- SECURE_CHANNELS: 'false'
- JWT_SECRET: super-secret-jwt-token-with-at-least-32-characters-long
- storage:
- image: supabase/storage-api:v0.1.4
- ports:
- - '5000:5000'
- depends_on:
+ restart: always
+ environment:
+ PGRST_DB_URI: postgres://postgres:${POSTGRES_PASSWORD}@db:${POSTGRES_PORT}/postgres
+ PGRST_DB_SCHEMA: public, storage
+ PGRST_DB_ANON_ROLE: anon
+ PGRST_JWT_SECRET: ${JWT_SECRET}
+
+ realtime:
+ container_name: supabase-realtime
+ image: supabase/realtime:latest
+ ports:
+ - ${REALTIME_PORT}
+ depends_on:
- db
- - rest
- restart: on-failure
- environment:
- ANON_KEY: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTYxMzUzMTk4NSwiZXhwIjoxOTI5MTA3OTg1fQ.ReNhHIoXIOa-8tL1DO3e26mJmOTnYuvdgobwIYGzrLQ
- SERVICE_KEY: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoic2VydmljZV9yb2xlIiwiaWF0IjoxNjEzNTMxOTg1LCJleHAiOjE5MjkxMDc5ODV9.FhK1kZdHmWdCIEZELt0QDCw6FIlCS8rVmp4RzaeI2LM
- PROJECT_REF: bjwdssmqcnupljrqypxz # can be any random string
- REGION: us-east-1 # region where your bucket is located
- POSTGREST_URL: http://rest:3000
- GLOBAL_S3_BUCKET: supa-storage-testing # name of s3 bucket where you want to store objects
- PGRST_JWT_SECRET: super-secret-jwt-token-with-at-least-32-characters-long
- DATABASE_URL: postgres://postgres:postgres@db:5432/postgres
- PGOPTIONS: "-c search_path=storage"
- AWS_ACCESS_KEY_ID: replace-with-your-aws-key
- AWS_SECRET_ACCESS_KEY: replace-with-your-aws-secret
- FILE_SIZE_LIMIT: 52428800
- db:
- build:
- context: ./postgres
- ports:
- - 5432:5432
- command:
+ restart: on-failure
+ environment:
+ DB_HOST: db
+ DB_NAME: postgres
+ DB_USER: postgres
+ DB_PASSWORD: ${POSTGRES_PASSWORD}
+ DB_PORT: ${POSTGRES_PORT}
+ PORT: ${REALTIME_PORT}
+ HOSTNAME: localhost
+ # Disable JWT Auth locally. The JWT_SECRET will be ignored.
+ SECURE_CHANNELS: 'false'
+ JWT_SECRET: ${JWT_SECRET}
+
+ storage:
+ image: supabase/storage-api:v0.8.3
+ ports:
+ - '5000:5000'
+ depends_on:
+ - db
+ - rest
+ restart: on-failure
+ environment:
+ ANON_KEY: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzdXBhYmFzZSIsImlhdCI6MTYwMzk2ODgzNCwiZXhwIjoxNjkwNDQzMTM1LCJhdWQiOiIiLCJzdWIiOiIiLCJSb2xlIjoiYW5vbiIsImp0aSI6ImQ1N2ZkMjFlLTNkYjQtNDk3YS1hNGViLTI4ZDY3Yzg2NjEwMiJ9.YySz_BoYxYco0b2aP3iBMt-dVUqC6RpnQiSb4Zt-L-o
+ SERVICE_KEY: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzdXBhYmFzZSIsImlhdCI6MTYwMzk2ODgzNCwiZXhwIjoxNjkwNDQzMTM1LCJhdWQiOiIiLCJyb2xlIjoic2VydmljZV9yb2xlIiwianRpIjoiOTk4MzJiYmItNDdhNS00NzJhLTgxMDQtYzY4ZGVjZDQyZDVlIn0._DToQdxDcSLsp3tPpacsxswv0p1KRti0QOTmnbfs5JY
+ PROJECT_REF: bjwdssmqcnupljrqypxz # can be any random string
+ POSTGREST_URL: http://rest:3000
+ PGRST_JWT_SECRET: ${JWT_SECRET}
+ DATABASE_URL: postgres://postgres:${POSTGRES_PASSWORD}@db:${POSTGRES_PORT}/postgres
+ PGOPTIONS: "-c search_path=storage"
+ FILE_SIZE_LIMIT: 52428800
+ REGION: us-east-1 # region where your bucket is located
+ GLOBAL_S3_BUCKET: supa-storage-testing # name of s3 bucket where you want to store objects
+ # AWS_ACCESS_KEY_ID: replace-with-your-aws-key
+ # AWS_SECRET_ACCESS_KEY: replace-with-your-aws-secret
+ STORAGE_BACKEND: file
+ FILE_STORAGE_BACKEND_PATH: /var/data/storage
+ db:
+ container_name: supabase-db
+ build:
+ context: ./dockerfiles/postgres
+ ports:
+ - ${POSTGRES_PORT}:${POSTGRES_PORT}
+ command:
- postgres
- -c
- wal_level=logical
@@ -75,4 +110,4 @@ services:
POSTGRES_DB: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
- POSTGRES_PORT: ${POSTGRES_PORT}
+ POSTGRES_PORT: ${POSTGRES_PORT}
\ No newline at end of file
diff --git a/docker/postgres/storage-schema.sql b/docker/dockerfiles/postgres/storage-schema.sql
similarity index 100%
rename from docker/postgres/storage-schema.sql
rename to docker/dockerfiles/postgres/storage-schema.sql
From 424483fac4ae467f8093a71798539e44e5729ca3 Mon Sep 17 00:00:00 2001
From: Inian
Date: Tue, 20 Jul 2021 21:17:48 +0800
Subject: [PATCH 04/17] storage depends on nightly version of postgrest
else match call for update is not working
---
docker/docker-compose.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
index a8d17f1138f..e0c4af21e53 100644
--- a/docker/docker-compose.yml
+++ b/docker/docker-compose.yml
@@ -41,7 +41,7 @@ services:
rest:
container_name: supabase-rest
- image: postgrest/postgrest:latest
+ image: postgrest/postgrest:nightly-2021-03-05-19-03-d3a8b5f
ports:
- ${REST_PORT}:3000
depends_on:
From ffdde846aadfd0fbb52b0eb34fb3b3da30953a34 Mon Sep 17 00:00:00 2001
From: Beng Eu
Date: Wed, 21 Jul 2021 12:12:55 +0800
Subject: [PATCH 05/17] Document from.upload() and from.update() workaround for
React Native
---
web/spec/supabase.yml | 26 ++++++++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/web/spec/supabase.yml b/web/spec/supabase.yml
index b6e3a05e9cc..45f4e397478 100644
--- a/web/spec/supabase.yml
+++ b/web/spec/supabase.yml
@@ -986,6 +986,7 @@ pages:
- Policy permissions required:
- `buckets` permissions: none
- `objects` permissions: `insert`
+ - For React Native, using either `Blob`, `File` or `FormData` does not work as intended. Upload file using `ArrayBuffer` from base64 file data instead, see example below.
examples:
- name: Upload file
isSpotlight: true
@@ -1000,6 +1001,18 @@ pages:
upsert: false
})
```
+ - name: Upload file using `ArrayBuffer` from base64 file data
+ js: |
+ ```js
+ import {decode} from 'base64-arraybuffer'
+
+ const { data, error } = await supabase
+ .storage
+ .from('avatars')
+ .upload('public/avatar1.png', decode('base64FileData'), {
+ contentType: 'image/png'
+ })
+ ```
storage.from.update():
title: 'from.update()'
@@ -1008,6 +1021,7 @@ pages:
- Policy permissions required:
- `buckets` permissions: none
- `objects` permissions: `update` and `select`
+ - For React Native, using either `Blob`, `File` or `FormData` does not work as intended. Update file using `ArrayBuffer` from base64 file data instead, see example below.
examples:
- name: Update file
isSpotlight: true
@@ -1022,6 +1036,18 @@ pages:
upsert: false
})
```
+ - name: Update file using `ArrayBuffer` from base64 file data
+ js: |
+ ```js
+ import {decode} from 'base64-arraybuffer'
+
+ const { data, error } = await supabase
+ .storage
+ .from('avatars')
+ .update('public/avatar1.png', decode('base64FileData'), {
+ contentType: 'image/png'
+ })
+ ```
storage.from.move():
title: 'from.move()'
From 0254f0865c901efed19739ac02e1a4db75a5e44d Mon Sep 17 00:00:00 2001
From: dshukertjr <18113850+dshukertjr@users.noreply.github.com>
Date: Thu, 22 Jul 2021 08:47:16 +0900
Subject: [PATCH 06/17] chore: Finished up with flutter quick start for the
most part
---
web/docs/guides/with-flutter.mdx | 692 +++++++++++++++-------------
web/static/img/deeplink-setting.png | Bin 0 -> 88204 bytes
2 files changed, 375 insertions(+), 317 deletions(-)
create mode 100644 web/static/img/deeplink-setting.png
diff --git a/web/docs/guides/with-flutter.mdx b/web/docs/guides/with-flutter.mdx
index c4749a062ce..b111e7a4356 100644
--- a/web/docs/guides/with-flutter.mdx
+++ b/web/docs/guides/with-flutter.mdx
@@ -71,7 +71,7 @@ values={[
```sh
1. Go to the "SQL" section.
2. Click "User Management Starter".
-4. Click "Run".
+3. Click "Run".
```