Merge branch 'master' into chore/update-og-launch-week-2

This commit is contained in:
Paul Copplestone committed 2021-07-24 16:23:33 +08:00
commit 3d9cbab5aa
24 files changed
+949 -97

No files matched your search

+28 -3
View File
@@ -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:
@@ -49,9 +49,10 @@ services:
restart: always
environment:
PGRST_DB_URI: postgres://postgres:${POSTGRES_PASSWORD}@db:${POSTGRES_PORT}/postgres
PGRST_DB_SCHEMA: public
PGRST_DB_SCHEMA: public, storage
PGRST_DB_ANON_ROLE: anon
PGRST_JWT_SECRET: ${JWT_SECRET}
realtime:
container_name: supabase-realtime
image: supabase/realtime:latest
@@ -71,6 +72,30 @@ services:
# 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:
@@ -85,4 +110,4 @@ services:
POSTGRES_DB: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_PORT: ${POSTGRES_PORT}
POSTGRES_PORT: ${POSTGRES_PORT}
+10
View File
@@ -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:
+1
View File
@@ -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
@@ -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;
+25
View File
@@ -248,6 +248,31 @@ create policy "Only Blizzard staff can update leaderboard"
);
```
### Time to live for rows
Policies can also be used to implement TTL or time to live feature that you see in Instagram stories or Snapchat.
In the following example, rows of `stories` table are available only if they have been created within the last 24 hours.
```sql
-- 1. Create table
create table if not exists stories (
id uuid not null primary key DEFAULT uuid_generate_v4(),
created_at timestamp with time zone default timezone('utc' :: text, now()) not null,
content text not null
);
-- 2. Enable RLS
alter table stories
enable row level security;
-- 3. Create Policy
create policy "Stories are live for a day"
on stories
for select using (
created_at > (current_timestamp - interval '1 day')
);
```
### Advanced policies
Use the full power of SQL to build extremely advanced rules.
+7 -7
View File
@@ -116,7 +116,7 @@ import 'package:supabase/supabase.dart';
void main() async {
final client = SupabaseClient('supabaseUrl', 'supabaseKey');
// Sign up user with email and password
// Sign in user with email and password
final response = await client
.auth
.signIn(email: 'example@email.com', password: 'example-password');
@@ -170,7 +170,7 @@ import 'package:supabase/supabase.dart';
void main() async {
final client = SupabaseClient('supabaseUrl', 'supabaseKey');
// Sign up user with email and password
// Sign in user with email and magic link
final response = await client
.auth
.signIn(email: 'example@email.com');
@@ -270,7 +270,7 @@ import 'package:supabase/supabase.dart';
void main() async {
final client = SupabaseClient('supabaseUrl', 'supabaseKey');
// Sign up user with email and password
// Query tables and fetch related data in one round-trip, using select()
final response = await client
.from('countries')
.select('name')
@@ -320,7 +320,7 @@ import 'package:supabase/supabase.dart';
void main() async {
final client = SupabaseClient('supabaseUrl', 'supabaseKey');
// Sign up user with email and password
// When fetching data, use advanced filtering to only extract data, that you need.
final response = await client
.from('cities')
.select('name,country_id')
@@ -374,7 +374,7 @@ import 'package:supabase/supabase.dart';
void main() async {
final client = SupabaseClient('supabaseUrl', 'supabaseKey');
// Sign up user with email and password
// Create data easily, using insert()
final response = await client
.from('cities')
.insert([
@@ -430,7 +430,7 @@ import 'package:supabase/supabase.dart';
void main() async {
final client = SupabaseClient('supabaseUrl', 'supabaseKey');
// Sign up user with email and password
// Listen to realtime database changes, using subscribe()
final response = await client
.from('countries')
.on(SupabaseEventTypes.all, (payload) {
@@ -483,7 +483,7 @@ import 'package:supabase/supabase.dart';
void main() async {
final client = SupabaseClient('supabaseUrl', 'supabaseKey');
// Sign up user with email and password
// You can even listen to Row Level changes
final response = await client
.from('countries:id.eq.200')
.on(SupabaseEventTypes.update, (payload) {
+1
View File
@@ -56,6 +56,7 @@ Build a basic Todo List with Supabase and your favorite frontend framework:
- Flutter Supabase Authentication [Blog](https://www.sandromaglione.com/2021/04/24/flutter-supabase-authentication/)
- Supabase in 6 minutes [Video](https://www.youtube.com/watch?v=c8DNV9yl0mg)
- Let's build SupaAuth - Build an Authentication System using Supabase, Next.js and Typescript [6-part Blog Series](https://aalam.in/blog/supabase-auth-intro-setup-next)
- In-depth self-hosting guide using Nginx [Blog](https://dev.to/chronsyn/self-hosting-with-supabase-1aii)
### Example apps
+611
View File
@@ -0,0 +1,611 @@
---
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:
<img height="500px" src="/img/supabase-flutter-demo.png" alt="Supabase User Management example" />
### Github
Whenever you get stuck at any point, take a look at [this repo](https://github.com/dshukertjr/flutter_supabase_quickstart).
## 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.
<Tabs
defaultValue="UI"
values={[
{label: 'UI', value: 'UI'},
{label: 'SQL', value: 'SQL'},
]}>
<TabItem value="UI">
```sh
1. Go to the "SQL" section.
2. Click "User Management Starter".
3. Click "Run".
```
<video width="99%" muted playsInline controls="true">
<source src="/videos/sql-user-management-starter.mp4" type="video/mp4" muted playsInline />
</video>
</TabItem>
<TabItem value="SQL">
```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' );
```
</TabItem>
</Tabs>
### 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.
<Tabs
defaultValue="UI"
values={[
{label: 'UI', value: 'UI'}
]}>
<TabItem value="UI">
```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.
```
<video width="99%" muted playsInline controls="true">
<source src="/videos/api/api-url-and-key.mp4" type="video/mp4" muted playsInline />
</video>
</TabItem>
</Tabs>
## 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.4
```
Run `flutter pub get` to install the dependencies.
### Setup deep links
Now that we have the dependencies installed let's setup deep links so users who have logged in via magic link or OAuth can come back to the app.
```sh
1. Go to the "Authentication" section.
2. Click "Settings" in the sidebar.
3. Type `io.supabase.flutterquickstart://login-callback/` in the Additional Redirect URLs input field.
4. Hit save.
```
![Supabase console deep link setting](/img/deeplink-setting.png)
That is it on Supabase's end and the rest are platform specific settings:
For Android, add an intent-filter to enable deep linking:
```xml title="android/app/src/main/AndroidManifest.xml"
<manifest ...>
<!-- ... other tags -->
<application ...>
<activity ...>
<!-- ... other tags -->
<!-- Add this intent-filter for Deep Links -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- Accepts URIs that begin with YOUR_SCHEME://YOUR_HOST -->
<data
android:scheme="io.supabase.flutterquickstart"
android:host="login-callback" />
</intent-filter>
</activity>
</application>
</manifest>
```
For iOS add CFBundleURLTypes to enable deep linking:
```xml title="ios/Runner/Info.plist"
<!-- ... other tags -->
<plist>
<dict>
<!-- ... other tags -->
<!-- Add this array for Deep Links -->
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>io.supabase.flutterquickstart</string>
</array>
</dict>
</array>
<!-- ... other tags -->
</dict>
</plist>
```
For web:
There are no additional configurations.
### Main function
Now that we have deep links ready 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]',
authCallbackUrlHostname: 'login-callback',
);
runApp(MyApp());
}
```
### Set up AuthState
In order to handle deep links for Android and iOS, let's create a class that will do just that.
`supabase_flutter` plugin comes with `SupabaseAuthState` class where we can inherit from to react to various deep link events.
```dart title="lib/components/auth_state.dart"
import 'package:flutter/material.dart';
import 'package:supabase/supabase.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
class AuthState<T extends StatefulWidget> extends SupabaseAuthState<T> {
@override
void onUnauthenticated() {
Navigator.of(context).pushNamedAndRemoveUntil('/login', (route) => false);
}
@override
void onAuthenticated(Session session) {
Navigator.of(context).pushNamedAndRemoveUntil('/account', (route) => false);
}
@override
void onPasswordRecovery(Session session) {}
@override
void onErrorAuthenticating(String message) {
print('Error authenticating $message');
}
}
```
Let's also create a constant file to make it easier to use Supabase client.
```dart title="lib/utils/constants.dart"
import 'package:supabase_flutter/supabase_flutter.dart';
final supabase = Supabase.instance.client;
```
### Set up Splash Screen
Let's create a splash screen that will be shown to users right after they open the app.
This splash screen inherits `AuthState` to redirect users to the appropriate pages depending on their authentication state.
```dart title="lib/pages/splash_page.dart"
import 'package:flutter/material.dart';
import 'package:supabase_flutter_guide/components/auth_state.dart';
class SplashPage extends StatefulWidget {
const SplashPage({Key? key}) : super(key: key);
@override
_SplashPageState createState() => _SplashPageState();
}
class _SplashPageState extends AuthState<SplashPage> {
@override
void initState() {
recoverSupabaseSession();
super.initState();
}
@override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
}
```
### Set up a Login page
Let's create a Flutter widget to manage logins and sign ups.
We'll use Magic Links, so users can sign in with their email without using passwords.
This page will also inherit `AuthState` as it will handle user login.
```dart title="lib/pages/login_page.dart"
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:supabase_flutter_guide/components/auth_state.dart';
import 'package:supabase/supabase.dart';
import 'package:supabase_flutter_guide/utils/constants.dart';
class LoginPage extends StatefulWidget {
const LoginPage({Key? key}) : super(key: key);
@override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends AuthState<LoginPage> {
bool _isLoading = false;
late final TextEditingController _emailController;
Future<void> _signIn() async {
setState(() {
_isLoading = true;
});
final response = await supabase.auth.signIn(
email: _emailController.text,
options: AuthOptions(
redirectTo: kIsWeb
? null
: 'io.supabase.flutterquickstart://login-callback/'));
if (response.error != null) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(response.error!.message),
backgroundColor: Colors.red,
));
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Check your email for login link!')));
}
setState(() {
_emailController.clear();
_isLoading = false;
});
}
@override
void initState() {
_emailController = TextEditingController();
super.initState();
}
@override
void dispose() {
_emailController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Sign In')),
body: ListView(
padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 12),
children: [
const Text('Sign in via the magic link with your email below'),
const SizedBox(height: 18),
TextFormField(
controller: _emailController,
decoration: const InputDecoration(labelText: 'Email'),
),
const SizedBox(height: 18),
ElevatedButton(
onPressed: _isLoading ? null : _signIn,
child: Text(_isLoading ? 'Loading' : 'Send Magic Link'),
),
],
),
);
}
}
```
### Set up 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_page.dart`.
Note that this page will also inherit `AuthState` as it will handle user sign out.
```dart title="lib/pages/account_page.dart"
import 'package:flutter/material.dart';
import 'package:supabase_flutter_guide/components/auth_state.dart';
import 'package:supabase_flutter_guide/utils/constants.dart';
class AccountPage extends StatefulWidget {
const AccountPage({Key? key}) : super(key: key);
@override
_AccountPageState createState() => _AccountPageState();
}
class _AccountPageState extends AuthState<AccountPage> {
late final _usernameController = TextEditingController();
late final _websiteController = TextEditingController();
var _loading = false;
Future<void> _getProfile() async {
setState(() {
_loading = true;
});
final user = supabase.auth.user()!;
final response = await supabase
.from('profiles')
.select()
.eq('id', user.id)
.single()
.execute();
if (response.error != null && response.status != 406) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(response.error!.message)));
}
if (response.data != null) {
_usernameController.text = response.data!['username'] as String;
_websiteController.text = response.data!['website'] as String;
}
setState(() {
_loading = false;
});
}
Future<void> _updateProfile() async {
setState(() {
_loading = true;
});
final userName = _usernameController.text;
final website = _websiteController.text;
final user = supabase.auth.currentUser;
final updates = {
'id': user!.id,
'username': userName,
'website': website,
'updated_at': DateTime.now().toIso8601String(),
};
final response = await supabase.from('profiles').upsert(updates).execute();
if (response.error != null) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(response.error!.message),
backgroundColor: Colors.red,
));
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Successfully updated profile!')));
}
setState(() {
_loading = false;
});
}
Future<void> _signOut() async {
final response = await supabase.auth.signOut();
if (response.error != null) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(response.error!.message),
backgroundColor: Colors.red,
));
}
Navigator.of(context).pushReplacementNamed('/login');
}
@override
void initState() {
_getProfile();
super.initState();
}
@override
void dispose() {
_usernameController.dispose();
_websiteController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Profile')),
body: ListView(
padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 12),
children: [
TextFormField(
controller: _usernameController,
decoration: const InputDecoration(labelText: 'User Name'),
),
const SizedBox(height: 18),
TextFormField(
controller: _websiteController,
decoration: const InputDecoration(labelText: 'Website'),
),
const SizedBox(height: 18),
ElevatedButton(
onPressed: _updateProfile,
child: Text(_loading ? 'Saving...' : 'Update')),
const SizedBox(height: 18),
ElevatedButton(onPressed: _signOut, child: const Text('Sign Out')),
],
),
);
}
}
```
### Launch!
Now that we have all the components in place, let's update `lib/main.dart`:
```dart title="lib/main.dart"
import 'package:flutter/material.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:supabase_flutter_guide/pages/account_page.dart';
import 'package:supabase_flutter_guide/pages/login_page.dart';
import 'package:supabase_flutter_guide/pages/splash_page.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
Supabase.initialize(
url: '[YOUR_SUPABASE_URL]',
anonKey: '[YOUR_SUPABASE_ANNON_KEY]',
);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Supabase Flutter',
theme: ThemeData.dark().copyWith(
primaryColor: Colors.green,
accentColor: Colors.green,
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
onPrimary: Colors.white,
primary: Colors.green,
),
),
),
initialRoute: '/',
routes: <String, WidgetBuilder>{
'/': (_) => const SplashPage(),
'/login': (_) => const LoginPage(),
'/account': (_) => const AccountPage(),
},
);
}
}
```
Once that's done, run this in a terminal window to launch on Android or iOS:
```bash
flutter run
```
Or for web, run the following command to launch it on `localhost:3000`
```bash
flutter run -d web-server --web-hostname localhost --web-port 3000
```
And then open the browser to [localhost:3000](http://localhost:3000) and you should see the completed app.
<img height="500px" src="/img/supabase-flutter-demo.png" alt="Supabase User Management example" />
## 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)
+1
View File
@@ -89,6 +89,7 @@ module.exports = {
'guides/with-svelte',
'guides/with-vue-3',
'guides/with-angular',
'guides/with-flutter',
'guides/self-hosting',
],
},
+26
View File
@@ -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()'
Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

@@ -35,7 +35,7 @@ One of the best things about building Supabase is the community. We're even luck
### Where?
Blog post coming soon. Follow our [Twitter](twitter.com/supabase) to get notified immediately.
Blog post coming soon. Follow our [Twitter](https://twitter.com/supabase) to get notified immediately.
## Tuesday 27 July: Auth v2
@@ -51,7 +51,7 @@ What's cooler than Arnie in the 90's? Passwordless logins. We've [already](https
### Where?
Blog post coming soon. Follow our [Twitter](twitter.com/supabase) to get notified immediately.
Blog post coming soon. Follow our [Twitter](https://twitter.com/supabase) to get notified immediately.
## Wednesday 28 July: Storage v2
@@ -67,7 +67,7 @@ We shipped [Version 1 of Supabase Storage](https://supabase.io/blog/2021/03/30/s
### Where?
Blog post coming soon. Follow our [Twitter](twitter.com/supabase) to get notified immediately.
Blog post coming soon. Follow our [Twitter](https://twitter.com/supabase) to get notified immediately.
## Thursday 29 July: Dashboard v2
@@ -83,7 +83,7 @@ Who needs a futuristic [Hoverboard from 2015](https://www.youtube.com/watch?v=NR
### Where?
Blog post coming soon. Follow our [Twitter](twitter.com/supabase) to get notified immediately.
Blog post coming soon. Follow our [Twitter](https://twitter.com/supabase) to get notified immediately.
## Friday 30 July: One more thing
@@ -99,4 +99,4 @@ We all know that the best [SeQueLs](https://www.reddit.com/r/SequelMemes/) are a
### Where?
Blog post coming soon. Follow our [Twitter](twitter.com/supabase) to get notified immediately.
Blog post coming soon. Follow our [Twitter](https://twitter.com/supabase) to get notified immediately.
+1 -1
View File
@@ -41,7 +41,7 @@ const CaseStudies = () => {
</a>
))}
</div>
<Benchmark />
{/* <Benchmark /> */}
</SectionContainer>
)
}
+9
View File
@@ -72,6 +72,15 @@ function ExampleCard(props: any) {
</a>
</Link>
)}
{!props.demo_url && (
<Link href={props.repo_url} as={props.repo_url}>
<a target="_blank">
<Button size="tiny" type="default" iconRight={<IconArrowUpRight />}>
View Code
</Button>
</a>
</Link>
)}
</Space>
</div>
</div>
+1 -1
View File
@@ -66,7 +66,7 @@ const Developers = () => {
<div className="col-span-6">
<div className="m-3 mx-6">
<Typography.Text type="secondary">Latest announcements</Typography.Text>
<ul className="mt-6 space-y-16">
<ul className="mt-6 space-y-3 pb-6">
{AnnouncementsData.map((caseStudy: any, idx: number) => (
<li className="flow-root" key={`flyout_case_${idx}`}>
<a
+14 -11
View File
@@ -2,7 +2,7 @@ import React, { useState } from 'react'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { Button, Badge } from '@supabase/ui'
import { Button, Badge, IconGitHub } from '@supabase/ui'
import FlyOut from '~/components/UI/FlyOut'
import Transition from 'lib/Transition'
@@ -239,19 +239,15 @@ const Nav = (props: Props) => {
</a>
</div>
</div>
<div className="hidden lg:flex items-center sm:space-x-4">
<div className="hidden lg:flex items-center sm:space-x-3">
<a href="https://github.com/supabase/supabase" target="_blank">
<Button type="default" icon={<IconGitHub />}>
GitHub
</Button>
</a>
<a href="https://app.supabase.io/api/login">
<Button>Start your project</Button>
</a>
<a
href="https://app.supabase.io/api/login"
className={`
items-center px-1 border-b-2 border-transparent text-sm font-medium
text-gray-500 hover:text-gray-700 hidden lg:block dark:text-dark-100 dark:hover:text-white
`}
>
Sign in
</a>
</div>
</div>
{/* <div className="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0"></div> */}
@@ -330,6 +326,13 @@ const Nav = (props: Props) => {
>
Pricing
</a>
<a
href="https://github.com/supabase/supabase"
target="_blank"
className="block pl-3 pr-4 py-2 text-base font-medium text-gray-600 hover:text-gray-800 hover:bg-gray-50 dark:hover:bg-dark-600 hover:border-gray-300 dark:text-white"
>
GitHub
</a>
</div>
<div className="p-3">
<p className="mb-6 text-sm text-gray-400">Products available:</p>
+19 -3
View File
@@ -1,12 +1,28 @@
[
{
"type": "Announcement",
"title": "Beta launch",
"description": "We're excited to announce we have moved from Alpha to Beta",
"title": "Launch Week II: The SQL",
"description": "We're launching a product every day for one week in July.",
"imgUrl": "images/case-study-monitoro.jpg",
"logoUrl": "images/logos/monitoro.png",
"organization": "Monitoro",
"url": "/beta",
"url": "/blog/2021/07/22/supabase-launch-week-sql",
"postMeta": {
"name": "Paul Copplestone",
"avatarUrl": "https://avatars0.githubusercontent.com/u/10214025?v=4",
"publishDate": "Mar 16, 2020",
"readLength": 6
},
"ctaText": "View case study"
},
{
"type": "Announcement",
"title": "Supabase Storage",
"description": "Supabase launches Storage leveraging Postgres.",
"imgUrl": "images/case-study-monitoro.jpg",
"logoUrl": "images/logos/monitoro.png",
"organization": "Monitoro",
"url": "/blog/2021/03/30/supabase-storage",
"postMeta": {
"name": "Paul Copplestone",
"avatarUrl": "https://avatars0.githubusercontent.com/u/10214025?v=4",
+4 -4
View File
@@ -14,13 +14,13 @@
{
"name": "Local emulator",
"description": "Develop locally and push to production when you're ready",
"badge": "Q1 2021",
"url": "https://supabasesubscribe.vercel.app/subscription/localEmulator"
"badge": "",
"url": "https://supabase.io/docs/guides/self-hosting"
},
{
"name": "Supabase CLI",
"description": "Manage Supabase projects from your local machine",
"badge": "Q2 2021",
"url": "https://supabasesubscribe.vercel.app/subscription/commandLine"
"badge": "",
"url": "https://github.com/supabase/cli"
}
]
+36 -36
View File
@@ -1,21 +1,9 @@
[
{
"text": "Been playing with @supabase_io and so far I'm liking it. I ❤️ you get full access to the database. It's very quick to get up and running, seems a nice fit for prototypes or smaller apps",
"url": "https://twitter.com/mattegreer/status/1341765395133415425",
"handle": "mattegreer",
"img_url": "/images/twitter-profiles/IgXniSVk_400x400.jpg"
},
{
"text": "Reply enjoying building on @supabase_io for a project with @lasnindrek - allows incredible development speed when bashing together an MVP",
"url": "https://twitter.com/geordidearns/status/1344433863804788737",
"handle": "geordidearns",
"img_url": "/images/twitter-profiles/vppimY3M_400x400.jpg"
},
{
"text": "OKAY OKAY FINE I'LL LOOK AT SUPABASE PROPERLY",
"url": "https://twitter.com/benbarbersmith/status/1369758528764776453",
"handle": "benbarbersmith",
"img_url": "/images/twitter-profiles/Jqle_tE6_400x400.jpg"
"text": "DB/Backend: @supabase_io Reason: Takes care of Auth, DB hosting, API. Open source, easy to eject into something else. I love SQL. Cloud IDE is incredible for productivity.",
"url": "https://twitter.com/JessetheSibley/status/1369678185508593673",
"handle": "JessetheSibley",
"img_url": "/images/twitter-profiles/aNx9O0lH_400x400.jpg"
},
{
"text": "Been trying out this new stack. Loving it so far. Next.js (Frontend) + Stitches.js (Styling) + Radix (Primitives & Icons) + Supabase (Backend) + Vercel (Deployment) 😍 Thank you @vercel @Modulz @supabase_io for this amazing DX.",
@@ -24,16 +12,28 @@
"img_url": "/images/twitter-profiles/mZAIoZM1_400x400.jpg"
},
{
"text": "DB/Backend: @supabase_io Reason: Takes care of Auth, DB hosting, API. Open source, easy to eject into something else. I love SQL. Cloud IDE is incredible for productivity.",
"url": "https://twitter.com/JessetheSibley/status/1369678185508593673",
"handle": "JessetheSibley",
"img_url": "/images/twitter-profiles/aNx9O0lH_400x400.jpg"
"text": "OKAY OKAY FINE I'LL LOOK AT SUPABASE PROPERLY",
"url": "https://twitter.com/benbarbersmith/status/1369758528764776453",
"handle": "benbarbersmith",
"img_url": "/images/twitter-profiles/Jqle_tE6_400x400.jpg"
},
{
"text": "Looking for a Firebase alternative? Consider using the open-source @supabase_io. It's as simple as Firebase but I found the dashboard UX more intuitive and hey it's OSS! It's in beta so you can use it for free rn.",
"url": "https://twitter.com/mneelansh15/status/1368171468153118721",
"handle": "mneelansh15",
"img_url": "/images/twitter-profiles/6XKH3Rm6_400x400.jpg"
"text": "@supabase_io is amazing, super easy to get up and running quickly + they have multiple examples with various frameworks (chat app, auth, etc)",
"url": "https://twitter.com/ssw1ss/status/1366515168260337671",
"handle": "ssw1ss",
"img_url": "/images/twitter-profiles/4mD0C5Wk_400x400.jpg"
},
{
"text": "Been playing with @supabase_io and so far I'm liking it. I ❤️ you get full access to the database. It's very quick to get up and running, seems a nice fit for prototypes or smaller apps",
"url": "https://twitter.com/mattegreer/status/1341765395133415425",
"handle": "mattegreer",
"img_url": "/images/twitter-profiles/IgXniSVk_400x400.jpg"
},
{
"text": "Using @supabase_io with SWR by @vercel is something else 🔥",
"url": "https://twitter.com/eunjae_lee/status/1369788285317419009",
"handle": "eunjae_lee",
"img_url": "/images/twitter-profiles/eGjSYbQd_400x400.jpg"
},
{
"text": "big props to the @supabase_io team - open source is a two way street and they’re super responsive & community oriented",
@@ -41,6 +41,12 @@
"handle": "_mateomorris",
"img_url": "/images/twitter-profiles/1FyEWRE8_400x400.jpg"
},
{
"text": "Enjoying building on @supabase_io for a project with @lasnindrek - allows incredible development speed when bashing together an MVP",
"url": "https://twitter.com/geordidearns/status/1344433863804788737",
"handle": "geordidearns",
"img_url": "/images/twitter-profiles/vppimY3M_400x400.jpg"
},
{
"text": "Been actively working on Quill. It's been rapid, thanks to @supabase_io and @tailwindui",
"url": "https://twitter.com/quill_web/status/1369061125157961734",
@@ -53,12 +59,6 @@
"handle": "MartinOrszulik",
"img_url": "/images/twitter-profiles/ext7ggBo_400x400.jpg"
},
{
"text": "Using @supabase_io with SWR by @vercel is something else Fire",
"url": "https://twitter.com/eunjae_lee/status/1369788285317419009",
"handle": "eunjae_lee",
"img_url": "/images/twitter-profiles/eGjSYbQd_400x400.jpg"
},
{
"text": "Another month, another impressive @supabase_io release. It’s rare to see a startup ship in public so consistently.",
"url": "https://twitter.com/yuris/status/1366770633401491456",
@@ -71,12 +71,6 @@
"handle": "swyx",
"img_url": "/images/twitter-profiles/qhvO9V6x_400x400.jpg"
},
{
"text": "@supabase_io is amazing, super easy to get up and running quickly + they have multiple examples with various frameworks (chat app, auth, etc)",
"url": "https://twitter.com/ssw1ss/status/1366515168260337671",
"handle": "ssw1ss",
"img_url": "/images/twitter-profiles/4mD0C5Wk_400x400.jpg"
},
{
"text": "This weekend I made a personal record 🥇 on the less time spent creating an application with social login / permissions, database, cdn, infinite scaling, git push to deploy and for free. Thanks to @supabase_io and @vercel",
"url": "https://twitter.com/jperelli/status/1366195769657720834",
@@ -107,6 +101,12 @@
"handle": "prakis",
"img_url": "/images/twitter-profiles/eRvqp-t0_400x400.jpg"
},
{
"text": "Looking for a Firebase alternative? Consider using the open-source @supabase_io. It's as simple as Firebase but I found the dashboard UX more intuitive and hey it's OSS! It's in beta so you can use it for free rn.",
"url": "https://twitter.com/mneelansh15/status/1368171468153118721",
"handle": "mneelansh15",
"img_url": "/images/twitter-profiles/6XKH3Rm6_400x400.jpg"
},
{
"text": "Me using @supabase_io for the first time right now 🤯",
"url": "https://twitter.com/nasiscoe/status/1365140856035024902",
+9 -15
View File
@@ -118,20 +118,15 @@ function AuthPage() {
</div>
<img src={`${basePath}/images/product/auth/gitlab-icon.svg`} width={21} />
<img src={`${basePath}/images/product/auth/bitbucket-icon.svg`} width={21} />
<img src={`${basePath}/images/product/auth/twitter-icon.svg`} width={21} />
</Space>
</p>
<Typography.Title level={4}>All the social providers</Typography.Title>
<Typography.Text>
<p className="text-base lg:text-lg">
Enable social logins with the click of a button. Google, Facebook, Github, Azure,
Gitlab and Bitbucket.
<p className="text-base">
Enable social logins with the click of a button. Google, Facebook, GitHub, Azure,
Gitlab, Twitter, Discord, and Bitbucket.
</p>
<Space>
<img src={`${basePath}/images/product/auth/twitter-icon.svg`} width={21} />
<p className="m-0">
<span className="text-gray-800 dark:text-white">Twitter</span> coming soon
</p>
</Space>
</Typography.Text>
</div>
<div className="mb-10 lg:mb-0 col-span-12 lg:col-span-3 lg:col-start-5">
@@ -142,10 +137,10 @@ function AuthPage() {
</Typography.Text>
<Typography.Title level={4}>Fully integrated</Typography.Title>
<Typography.Text>
<p className="text-base lg:text-lg">
Incredibly simple Auth, without a single external authentication service.
<p className="text-base">
Incredibly simple Auth, without a single external authentication service. Built-in
Authentication, Authorization, and User Management.
</p>
<p>Built-in Authentication, Authorization, and User Management.</p>
</Typography.Text>
</div>
<div className="col-span-12 lg:col-span-3 lg:col-start-9">
@@ -156,11 +151,10 @@ function AuthPage() {
</Typography.Text>
<Typography.Title level={4}>Own your data</Typography.Title>
<Typography.Text>
<p className="text-base lg:text-lg">
<p className="text-base">
User data stored in your Supabase database so you never have to worry about 3rd
party privacy issues.
party privacy issues. Host your data in 8 different locations.
</p>
<p>Host your data in 8 different regions.</p>
</Typography.Text>
</div>
</div>
+3
View File
@@ -117,6 +117,9 @@ const Hero = () => {
<p className="text-2xl">
Today, we're moving to <span className="text-brand-700">Beta</span>
</p>
<time itemProp="datePublished" dateTime="2020-12-03" className="opacity-50 text-sm">
Published December 3rd, 2020
</time>
</div>
</div>
</div>
+5 -1
View File
@@ -155,7 +155,11 @@ function Database() {
<div className="col-span-12 lg:col-span-3 lg:col-start-9">
<Typography.Text>
<p className="mb-4">
<IconShuffle />
<Space>
<ProductIcon icon={Solutions['database'].icon} />
<IconX />
<ProductIcon icon={'M13 10V3L4 14h7v7l9-11h-7z'} />
</Space>
</p>
</Typography.Text>
<Typography.Title level={4}>Realtime enabled</Typography.Title>
+22 -10
View File
@@ -35,7 +35,7 @@ function StoragePage() {
const meta_title = 'Storage | Store any digital content'
const meta_description =
'An open source Object store service with unlimited scalability, for any file type.'
'An open source Object store with unlimited scalability, for any file type.'
return (
<>
@@ -64,7 +64,7 @@ function StoragePage() {
</span>,
]}
subheader={[
'An open source Object store service with unlimited scalability, for any file type.',
'An open source Object store with unlimited scalability, for any file type.',
'With custom policies and permissions that are familiar and easy to implement.',
]}
image={[
@@ -116,8 +116,14 @@ function StoragePage() {
</div>
<div className="mb-10 lg:mb-0 col-span-12 lg:col-span-3 lg:col-start-5">
<Typography.Text>
<p className="mb-4">
<IconBarChart />
<p>
<Space>
<ProductIcon
icon={
'M16 8v8m-4-5v5m-4-2v2m-2 4h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z'
}
/>
</Space>
</p>
</Typography.Text>
<Typography.Title level={4}>Lightning fast</Typography.Title>
@@ -133,8 +139,14 @@ function StoragePage() {
</div>
<div className="col-span-12 lg:col-span-3 lg:col-start-9">
<Typography.Text>
<p className="mb-4">
<IconCheckSquare />
<p>
<Space>
<ProductIcon
icon={
'M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z'
}
/>
</Space>
</p>
</Typography.Text>
<Typography.Title level={4}>Dependable</Typography.Title>
@@ -201,11 +213,11 @@ function StoragePage() {
</p>,
]}
footer={[
<div className="grid grid-cols-12 gap-8 lg:gap-0 xl:gap-16 mt-8">
<div className="grid grid-cols-12 gap-8 lg:gap-0 xl:gap-16 my-8">
<div className="col-span-6 lg:col-span-12 lg:mb-8 xl:mb-0 xl:col-span-4">
<FeatureColumn
icon={<IconWifi />}
title="CDN integration"
title="CDN"
text="Serve from the edge to reduce latency."
/>
<Badge color="blue">Coming soon</Badge>
@@ -213,7 +225,7 @@ function StoragePage() {
<div className="col-span-6 lg:col-span-12 xl:col-span-4">
<FeatureColumn
icon={<IconShuffle />}
title="Auto transformation & optimization"
title="Transformations"
text="Resize and compress your media before you serve it."
/>
<Badge color="blue">Coming soon</Badge>
@@ -243,7 +255,7 @@ function StoragePage() {
with Supabase Auth
</Typography.Title>
<Typography.Text>
<p className="text-lg">
<p className="">
Using Postgres Row Level Security to create Object access rules.
</p>
<p>