Merge pull request #1 from supabase/feature/marketing-content-update

Feature/marketing content update
This commit is contained in:
Jonathan Summers-Muir authored and GitHub committed 2021-03-26 18:02:00 +08:00
commit fccbc8d610
212 files changed
+14235 -17468

No files matched your search

+14 -30
View File
@@ -1,6 +1,3 @@
<p align="center">
<img width="300" src="https://gitcdn.xyz/repo/supabase/supabase/master/web/static/supabase-light.svg"/>
</p>
@@ -9,7 +6,7 @@
# Supabase
[Supabase](https://supabase.io) is an open source Firebase alternative. We're building the features of Firebase using enterprise-grade open source tools.
[Supabase](https://supabase.io) is an open source Firebase alternative. We're building the features of Firebase using enterprise-grade open source tools.
- [x] Hosted Postgres Database
- [x] Realtime subscriptions
@@ -29,7 +26,6 @@ For full documentation, visit [supabase.io/docs](https://supabase.io/docs)
- [GitHub Issues](https://github.com/supabase/supabase/issues). Best for: bugs and errors you encounter using Supabase.
- [Email Support](https://supabase.io/docs/support#business-support). Best for: problems with your database or infrastructure.
## Status
- [x] Alpha: We are testing Supabase with a closed set of customers
@@ -39,13 +35,9 @@ For full documentation, visit [supabase.io/docs](https://supabase.io/docs)
We are currently in Public Beta. Watch "releases" of this repo to get notified of major updates.
<kbd><img src="https://gitcdn.link/repo/supabase/supabase/master/web/static/watch-repo.gif" alt="Watch this repo"/></kbd>
<kbd><img src="https://gitcdn.xyz/repo/supabase/supabase/master/web/static/watch-repo.gif" alt="Watch this repo"/></kbd>
----
---
## How it works
@@ -55,41 +47,33 @@ Supabase is a combination of open source tools. We’re building the features of
Supabase is a [hosted platform](https://app.supabase.io). You can sign up and start using Supabase without installing anything. We are still creating the local development experience - this is now our core focus, along with platform stability.
![Architecture](https://supabase.io/assets/images/supabase-architecture-0a162cd9b23053a55074d7dda5b6c4ad.png)
- [PostgreSQL](https://www.postgresql.org/) is an object-relational database system with over 30 years of active development that has earned it a strong reputation for reliability, feature robustness, and performance.
- [Realtime](https://github.com/supabase/realtime) is an Elixir server that allows you to listen to PostgreSQL inserts, updates, and deletes using websockets. Supabase listens to Postgres' built-in replication functionality, converts the replication byte stream into JSON, then broadcasts the JSON over websockets.
- [Realtime](https://github.com/supabase/realtime) is an Elixir server that allows you to listen to PostgreSQL inserts, updates, and deletes using websockets. Supabase listens to Postgres' built-in replication functionality, converts the replication byte stream into JSON, then broadcasts the JSON over websockets.
- [PostgREST](http://postgrest.org/) is a web server that turns your PostgreSQL database directly into a RESTful API
- [postgres-meta](https://github.com/supabase/postgres-meta) is a RESTful API for managing your Postgres, allowing you to fetch tables, add roles, and run queries etc.
- [GoTrue](https://github.com/netlify/gotrue) is an SWT based API for managing users and issuing SWT tokens.
- [Kong](https://github.com/Kong/kong) is a cloud-native API gateway.
#### Client libraries
Our client library is modular. Each sub-library is a standalone implementation for a single external system. This is one of the ways we support existing tools.
- **`supabase-{lang}`**: Combines libraries and adds enrichments.
- `postgrest-{lang}`: Client library to work with [PostgREST](https://github.com/postgrest/postgrest)
- `realtime-{lang}`: Client library to work with [Realtime](https://github.com/supabase/realtime)
- `gotrue-{lang}`: Client library to work with [GoTrue](https://github.com/netlify/gotrue)
- `postgrest-{lang}`: Client library to work with [PostgREST](https://github.com/postgrest/postgrest)
- `realtime-{lang}`: Client library to work with [Realtime](https://github.com/supabase/realtime)
- `gotrue-{lang}`: Client library to work with [GoTrue](https://github.com/netlify/gotrue)
| Repo | Official | Community |
|-----------------------|--------------------------------------------------|------------------|
| **`supabase-{lang}`** | [`JS`](https://github.com/supabase/supabase-js) | [`C#`](https://github.com/supabase/supabase-csharp) \| [`Dart`](https://github.com/supabase/supabase-dart) \| [`Python`](https://github.com/supabase/supabase-py) \| `Rust` |
| `postgrest-{lang}` | [`JS`](https://github.com/supabase/postgrest-js) | [`C#`](https://github.com/supabase/postgrest-csharp) \| [`Dart`](https://github.com/supabase/postgrest-dart) \| [`Python`](https://github.com/supabase/postgrest-py) \| [`Rust`](https://github.com/supabase/postgrest-rs) |
| `realtime-{lang}` | [`JS`](https://github.com/supabase/realtime-js) | [`C#`](https://github.com/supabase/realtime-csharp) \| [`Dart`](https://github.com/supabase/realtime-dart) \| [`Python`](https://github.com/supabase/realtime-py) \| `Rust` |
| `gotrue-{lang}` | [`JS`](https://github.com/supabase/gotrue-js) | [`C#`](https://github.com/supabase/gotrue-csharp) \| [`Dart`](https://github.com/supabase/gotrue-dart) \| [`Python`](https://github.com/supabase/gotrue-py) \| `Rust` |
| Repo | Official | Community |
| --------------------- | ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`supabase-{lang}`** | [`JS`](https://github.com/supabase/supabase-js) | [`C#`](https://github.com/supabase/supabase-csharp) \| [`Dart`](https://github.com/supabase/supabase-dart) \| [`Python`](https://github.com/supabase/supabase-py) \| `Rust` |
| `postgrest-{lang}` | [`JS`](https://github.com/supabase/postgrest-js) | [`C#`](https://github.com/supabase/postgrest-csharp) \| [`Dart`](https://github.com/supabase/postgrest-dart) \| [`Python`](https://github.com/supabase/postgrest-py) \| [`Rust`](https://github.com/supabase/postgrest-rs) |
| `realtime-{lang}` | [`JS`](https://github.com/supabase/realtime-js) | [`C#`](https://github.com/supabase/realtime-csharp) \| [`Dart`](https://github.com/supabase/realtime-dart) \| [`Python`](https://github.com/supabase/realtime-py) \| `Rust` |
| `gotrue-{lang}` | [`JS`](https://github.com/supabase/gotrue-js) | [`C#`](https://github.com/supabase/gotrue-csharp) \| [`Dart`](https://github.com/supabase/gotrue-dart) \| [`Python`](https://github.com/supabase/gotrue-py) \| `Rust` |
----
---
## Sponsors
[![New Sponsor](https://user-images.githubusercontent.com/10214025/90518111-e74bbb00-e198-11ea-8f88-c9e3c1aa4b5b.png)](https://github.com/sponsors/supabase)
+1 -1
View File
@@ -2,7 +2,7 @@
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://unpkg.com/@supabase/supabase-js"></script>
<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js"></script>
<script src="./index.js"></script>
<link rel="stylesheet" type="text/css" href="./index.css">
</head>
+3 -3
View File
@@ -1,9 +1,9 @@
var SUPABASE_URL = '<supabase url - retrieve from supabase dashboard>'
var SUPABASE_URL = 'https://ernhobnpmmupjnmxpfbt.supabase.co'
var SUPABASE_KEY =
'<client key - retrieve from supabase dashboard>'
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTYxMzA5ODU0NCwiZXhwIjoxOTI4Njc0NTQ0fQ.Z9bRrfaL2oGhSuyBckFcdcnRIJDelWJ1II98OnEtLO0'
var supabase = Supabase.createClient(SUPABASE_URL, SUPABASE_KEY)
var supabase = supabase.createClient(SUPABASE_URL, SUPABASE_KEY)
window.userToken = null
document.addEventListener('DOMContentLoaded', function (event) {
@@ -0,0 +1,2 @@
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_KEY=your-anon-key
+51 -1
View File
@@ -1 +1,51 @@
# Live Tracker Map [COMING SOON]
# Live Tracker Map
Build with supabase-js and leafletjs. [[Demo App]](https://realtime-map.vercel.app)
<p align="center">
<kbd>
<img src="https://i.imgur.com/WoMRKwo.gif" alt="Demo"/>
</kbd>
</p>
## Test with your own Supabase Project
Note: you have to enable `DISABLE EMAIL CONFIRMATIONS` under your Supabase project settings.
#### Database schema
Go to [app.supabase.io](https://app.supabase.io/), create a new organisation and project if you haven't had one.
Run this sql query to create required tables.
```sql
-- Locations
CREATE TABLE public.locations (
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
latitude numeric NOT NULL,
longitude numeric NOT NULL,
user_id uuid REFERENCES auth.users NOT NULL
);
ALTER TABLE public.locations ENABLE ROW LEVEL SECURITY;
COMMENT ON table public.locations IS 'Individual locations sent by each user.';
CREATE POLICY "Allow logged-in read access" on public.locations USING ( auth.role() = 'authenticated' );
CREATE POLICY "Allow individual insert access" on public.locations FOR INSERT WITH CHECK ( auth.uid() = user_id );
```
#### Setup env vars
Next, copy the `.env.local.example` file in this directory to `.env.local` (which will be ignored by Git):
```bash
cp .env.local.example .env.local
```
Then set each variable on `.env.local`:
- `NEXT_PUBLIC_SUPABASE_URL` should be the **API URL**
- `NEXT_PUBLIC_SUPABASE_KEY` should be the **anon** key
You can get these values from your project dashboard at [app.supabase.io](https://app.supabase.io/).
The **anon** key is your client-side API key. It allows "anonymous access" to your database, until the user has logged in. Once they have logged in, the keys will switch to the user's own login token. This enables row level security for your data.
> **_NOTE_**: The `service_role` key has full access to your data, bypassing any security policies. These keys have to be kept secret and are meant to be used in server environments and never on a client or browser.
@@ -1,91 +0,0 @@
# Live Tracker Map [WIP]
Build with supabase auth, realtime and leafletjs. [[Demo App]](https://realtime-map-v2.vercel.app/)
**This `archive` project doesn't use official supabase-js and gotrue-js libs.**
<p align="center">
<kbd>
<img src="https://media.giphy.com/media/iDU80ngpsSddc0ObGI/giphy.gif" alt="Demo"/>
</kbd>
</p>
## How to use
#### Clone this folder
```bash
git clone https://github.com/supabase/supabase
cd supabase/examples/live-tracker-map
```
#### Install dependencies
```bash
npm install
```
#### Start the app
```bash
# Open a terminal and run:
npm run dev
```
Visit http://localhost:3000 and start testing!
## Test with your own Supabase Project
#### Database schema
Go to [app.supabase.io](https://app.supabase.io/), create a new organisation and project if you haven't had one.
Run this sql query to create required tables.
```sql
-- USERS
CREATE TYPE public.user_role AS ENUM ('DRIVER', 'MANAGER');
CREATE TABLE public.users (
id uuid NOT NULL PRIMARY KEY, -- UUID from auth.users
username text,
role user_role DEFAULT 'DRIVER'::public.user_role
);
COMMENT ON table public.users IS 'Profile data for each user.';
COMMENT ON column public.users.id IS 'References the internal Supabase Auth user.';
ALTER TABLE public.users REPLICA IDENTITY FULL; -- Send "previous data" on change
ALTER TABLE public.users ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Allow logged-in read access" on public.users FOR SELECT USING ( auth.role() = 'authenticated' );
CREATE POLICY "Allow individual insert access" on public.users FOR INSERT WITH CHECK ( auth.uid() = id );
CREATE POLICY "Allow individual update access" on public.users FOR UPDATE USING ( auth.uid() = id );
-- LOCATIONS
CREATE TABLE public.locations (
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
latitude numeric NOT NULL,
longitude numeric NOT NULL,
user_id uuid REFERENCES public.users NOT NULL
);
ALTER TABLE public.locations REPLICA IDENTITY FULL; -- Send "previous data" on change
ALTER TABLE public.locations ENABLE ROW LEVEL SECURITY;
COMMENT ON table public.locations IS 'Individual locations sent by each user.';
CREATE POLICY "Allow logged-in read access" on public.locations USING ( auth.role() = 'authenticated' );
CREATE POLICY "Allow individual insert access" on public.locations FOR INSERT WITH CHECK ( auth.uid() = user_id );
```
#### Setup env vars
Next, copy the `.env.local.example` file in this directory to `.env.local` (which will be ignored by Git):
```bash
cp .env.local.example .env.local
```
Then set each variable on `.env.local`:
- `NEXT_PUBLIC_SUPABASE_ENDPOINT` should be the **API URL**
- `NEXT_PUBLIC_SUPABASE_APIKEY` should be the **anon** key
You can get these values from your project dashboard at [app.supabase.io](https://app.supabase.io/).
The **anon** key is your client-side API key. It allows "anonymous access" to your database, until the user has logged in. Once they have logged in, the keys will switch to the user's own login token. This enables row level security for your data.
> **_NOTE_**: The `service_role` key has full access to your data, bypassing any security policies. These keys have to be kept secret and are meant to be used in server environments and never on a client or browser.
@@ -1,81 +0,0 @@
import { useState, useEffect, useRef } from 'react'
import { MapContainer, TileLayer, Polyline } from 'react-leaflet'
import { RoundToFixDecimals } from "lib/Utils"
import TextLog from "components/TextLog"
function randomColor() {
var array = ["black", "magenta", "blue", "indigo", "green", "blueviolet"];
return array[Math.floor(Math.random() * array.length)];
}
function MapView({ supabase, center, zoom }) {
const [log, setLog] = useState(undefined)
const [positions, setPositions] = useState([])
const [userPositionsDict, setUserPositionsDict] = useState([])
const mySubscription = useRef(false)
useEffect(() => {
let newLog = `Start listerning...`
newLog += positions.map(item => { return `\nuser_id=${item.user_id} lat=${RoundToFixDecimals(item.lat)} long=${RoundToFixDecimals(item.lng)}` })
setLog(newLog)
}, [positions])
useEffect(() => {
// Listen to INSERT event on locations table
mySubscription.current = supabase
.from('locations')
.on('INSERT', payload => {
console.log('Change received!', payload)
const { new: newItem } = payload
const { id, user_id, latitude, longitude } = newItem
let userPositions = userPositionsDict[user_id]
if (userPositions) userPositions.data = [...userPositions.data, { id, user_id, lat: latitude, lng: longitude }]
else userPositions = { color: randomColor(), data: [{ id, user_id, lat: latitude, lng: longitude }] }
setPositions([...positions, { id, user_id, lat: latitude, lng: longitude }])
setUserPositionsDict({ ...userPositionsDict, [user_id]: userPositions })
})
.subscribe()
return () => {
if (mySubscription.current) supabase.removeSubscription(mySubscription.current)
}
}, [supabase, positions, setPositions, userPositionsDict, setUserPositionsDict])
function drawPolylines() {
// console.log(userPositionsDict)
function drawPolyline(color, data) {
const polyline = data.map(item => {
if (!item) return
const { lat, lng } = item
return [lat, lng]
})
return <Polyline pathOptions={{ color: color }} positions={polyline} />
}
let result = Object.keys(userPositionsDict).map(key => {
const item = userPositionsDict[key]
if (!item) return
const { color, data } = item
return drawPolyline(color, data)
})
return result
}
return (
<div className="map-view">
<MapContainer center={center} zoom={zoom || 15} scrollWheelZoom={false}>
<TileLayer
attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
{drawPolylines()}
</MapContainer>
<TextLog log={log} />
</div>
)
}
export default MapView
@@ -1,95 +0,0 @@
import React, { useState, useEffect } from 'react'
import { auth } from 'lib/Store'
/**
* Sign in with username and password
*
* @param {String} role DRIVER/MANAGER. default as DRIVER
*/
export default function SignIn({ role = "DRIVER" }) {
const [formData, updateFormData] = useState({ role });
const [action, setAction] = useState(null);
function onChange(e) {
updateFormData({
...formData,
// Trimming any whitespace
[e.target.name]: e.target.value.trim()
});
};
async function postAndWait(url, data, options = {}) {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
body: JSON.stringify(data),
...options,
})
const body = await response.json()
return body
}
async function onSubmit(event) {
event.preventDefault();
try {
if (action === "SIGNUP") {
const signupResponse = await postAndWait(`/api/auth/signup`, formData)
await auth.loginWithRefreshToken(signupResponse.refresh_token, true)
window.location.reload();
} else if (action === "LOGIN") {
const loginResponse = await postAndWait(`/api/auth/login`, formData)
await auth.loginWithRefreshToken(loginResponse.refresh_token, true)
window.location.reload();
}
} catch (error) {
console.log('error', error)
alert("Authentication failed. Please check your input.")
}
}
return (
<form className="container" onSubmit={onSubmit}>
<label>
Email
<input name="email" type='email' onChange={onChange} />
</label>
<label>
Password
<input name="password" type='password' onChange={onChange} />
</label>
<button type='submit' onClick={() => setAction("SIGNUP")}>Sign up</button>
<button type='submit' onClick={() => setAction("LOGIN")}>Login</button>
<style jsx>{`
.container {
min-width: 20rem;
padding: 0 0.5rem;
display: flex;
flex-direction: column;
}
label {
display: flex;
flex-direction: column;
margin-bottom: 1rem;
}
input {
margin-top: 0.5rem;
padding: 0.5rem;
font-size: 1rem;
}
button {
margin-top: 0.5rem;
padding: 0.5rem;
font-size: 1rem;
}
`}</style>
</form>
)
}
@@ -1,14 +0,0 @@
import { createClient } from "lib/supabase/index"
import GoTrue from "lib/gotrue/index";
// Create a single supabase client for interacting with your database
export const supabase = createClient(process.env.NEXT_PUBLIC_SUPABASE_ENDPOINT, process.env.NEXT_PUBLIC_SUPABASE_APIKEY);
export const auth = new GoTrue({
APIUrl: `${process.env.NEXT_PUBLIC_SUPABASE_ENDPOINT}/auth/v1`,
audience: '',
setCookie: true,
});
const user = auth.currentUser();
if (user) supabase.setAccessToken(user.token.access_token)
@@ -1,5 +0,0 @@
import { createContext } from 'react'
const UserContext = createContext()
export default UserContext
@@ -1,39 +0,0 @@
export default class Admin {
constructor(user) {
this.user = user;
}
// Return a list of all users in an audience
listUsers(aud) {
return this.user._request('/admin/users', {
method: 'GET',
audience: aud,
});
}
getUser(user) {
return this.user._request(`/admin/users/${user.id}`);
}
updateUser(user, attributes = {}) {
return this.user._request(`/admin/users/${user.id}`, {
method: 'PUT',
body: JSON.stringify(attributes),
});
}
createUser(email, password, attributes = {}) {
attributes.email = email;
attributes.password = password;
return this.user._request('/admin/users', {
method: 'POST',
body: JSON.stringify(attributes),
});
}
deleteUser(user) {
return this.user._request(`/admin/users/${user.id}`, {
method: 'DELETE',
});
}
}
@@ -1,144 +0,0 @@
import API, { JSONHTTPError } from 'micro-api-client';
import User from './user';
const HTTPRegexp = /^http:\/\//;
const defaultApiURL = `/.netlify/identity`;
export default class GoTrue {
constructor({ APIUrl = defaultApiURL, audience = '', setCookie = false } = {}) {
if (APIUrl.match(HTTPRegexp)) {
console.warn(
'Warning:\n\nDO NOT USE HTTP IN PRODUCTION FOR GOTRUE EVER!\nGoTrue REQUIRES HTTPS to work securely.',
);
}
if (audience) {
this.audience = audience;
}
this.setCookie = setCookie;
this.api = new API(APIUrl);
}
_request(path, options = {}) {
options.headers = options.headers || {};
options.headers['apikey'] = process.env.NEXT_PUBLIC_SUPABASE_APIKEY;
const aud = options.audience || this.audience;
if (aud) {
options.headers['X-JWT-AUD'] = aud;
}
return this.api.request(path, options).catch((err) => {
if (err instanceof JSONHTTPError && err.json) {
if (err.json.msg) {
err.message = err.json.msg;
} else if (err.json.error) {
err.message = `${err.json.error}: ${err.json.error_description}`;
}
}
return Promise.reject(err);
});
}
settings() {
return this._request('/settings');
}
signup(email, password, data) {
return this._request('/signup', {
method: 'POST',
body: JSON.stringify({ email, password, data }),
});
}
login(email, password, remember) {
this._setRememberHeaders(remember);
return this._request('/token?grant_type=password', {
method: 'POST',
body: JSON.stringify({ email, password }),
}).then((response) => {
User.removeSavedSession();
return this.createUser(response, remember);
});
}
loginWithRefreshToken(refreshToken, remember) {
this._setRememberHeaders(remember);
return this._request('/token?grant_type=refresh_token', {
method: 'POST',
body: JSON.stringify({ refresh_token: refreshToken }),
}).then((response) => {
User.removeSavedSession();
return this.createUser(response, remember);
});
}
loginExternalUrl(provider) {
return `${this.api.apiURL}/authorize?provider=${provider}`;
}
confirm(token, remember) {
this._setRememberHeaders(remember);
return this.verify('signup', token, remember);
}
requestPasswordRecovery(email) {
return this._request('/recover', {
method: 'POST',
body: JSON.stringify({ email }),
});
}
recover(token, remember) {
this._setRememberHeaders(remember);
return this.verify('recovery', token, remember);
}
acceptInvite(token, password, remember) {
this._setRememberHeaders(remember);
return this._request('/verify', {
method: 'POST',
body: JSON.stringify({ token, password, type: 'signup' }),
}).then((response) => this.createUser(response, remember));
}
acceptInviteExternalUrl(provider, token) {
return `${this.api.apiURL}/authorize?provider=${provider}&invite_token=${token}`;
}
createUser(tokenResponse, remember = false) {
this._setRememberHeaders(remember);
const user = new User(this.api, tokenResponse, this.audience);
return user.getUserData().then((user) => {
if (remember) {
user._saveSession();
}
return user;
});
}
currentUser() {
const user = User.recoverSession(this.api);
user && this._setRememberHeaders(user._fromStorage);
return user;
}
verify(type, token, remember) {
this._setRememberHeaders(remember);
return this._request('/verify', {
method: 'POST',
body: JSON.stringify({ token, type }),
}).then((response) => this.createUser(response, remember));
}
_setRememberHeaders(remember) {
if (this.setCookie) {
this.api.defaultHeaders = this.api.defaultHeaders || {};
this.api.defaultHeaders['X-Use-Cookie'] = remember ? '1' : 'session';
}
}
}
if (typeof window !== 'undefined') {
window.GoTrue = GoTrue;
}
@@ -1,223 +0,0 @@
import API, { JSONHTTPError } from 'micro-api-client';
import Admin from './admin';
const ExpiryMargin = 60 * 1000;
const storageKey = 'gotrue.user';
const refreshPromises = {};
let currentUser = null;
const forbiddenUpdateAttributes = { api: 1, token: 1, audience: 1, url: 1 };
const forbiddenSaveAttributes = { api: 1 };
const isBrowser = () => typeof window !== 'undefined';
export default class User {
constructor(api, tokenResponse, audience) {
this.api = api;
this.url = api.apiURL;
this.audience = audience;
this._processTokenResponse(tokenResponse);
currentUser = this;
}
static removeSavedSession() {
isBrowser() && localStorage.removeItem(storageKey);
}
static recoverSession(apiInstance) {
if (currentUser) {
return currentUser;
}
const json = isBrowser() && localStorage.getItem(storageKey);
if (json) {
try {
const data = JSON.parse(json);
const { url, token, audience } = data;
if (!url || !token) {
return null;
}
const api = apiInstance || new API(url, {});
return new User(api, token, audience)._saveUserData(data, true);
} catch (ex) {
console.error(new Error(`Gotrue-js: Error recovering session: ${ex}`));
return null;
}
}
return null;
}
get admin() {
return new Admin(this);
}
update(attributes) {
return this._request('/user', {
method: 'PUT',
body: JSON.stringify(attributes),
}).then((response) => {
return this._saveUserData(response)._refreshSavedSession();
});
}
jwt(forceRefresh) {
const token = this.tokenDetails();
if (token === null || token === undefined) {
return Promise.reject(new Error(`Gotrue-js: failed getting jwt access token`));
}
const { expires_at, refresh_token, access_token } = token;
if (forceRefresh || expires_at - ExpiryMargin < Date.now()) {
return this._refreshToken(refresh_token);
}
return Promise.resolve(access_token);
}
logout() {
return this._request('/logout', { method: 'POST' })
.then(this.clearSession.bind(this))
.catch(this.clearSession.bind(this));
}
_refreshToken(refresh_token) {
if (refreshPromises[refresh_token]) {
return refreshPromises[refresh_token];
}
return (refreshPromises[refresh_token] = this.api
.request('/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `grant_type=refresh_token&refresh_token=${refresh_token}`,
})
.then((response) => {
delete refreshPromises[refresh_token];
this._processTokenResponse(response);
this._refreshSavedSession();
return this.token.access_token;
})
.catch((error) => {
delete refreshPromises[refresh_token];
this.clearSession();
return Promise.reject(error);
}));
}
_request(path, options = {}) {
options.headers = options.headers || {};
options.headers['apikey'] = process.env.NEXT_PUBLIC_SUPABASE_APIKEY;
const aud = options.audience || this.audience;
if (aud) {
options.headers['X-JWT-AUD'] = aud;
}
return this.jwt().then((token) => {
return this.api
.request(path, {
headers: Object.assign(options.headers, {
Authorization: `Bearer ${token}`,
}),
...options,
})
.catch((err) => {
if (err instanceof JSONHTTPError && err.json) {
if (err.json.msg) {
err.message = err.json.msg;
} else if (err.json.error) {
err.message = `${err.json.error}: ${err.json.error_description}`;
}
}
return Promise.reject(err);
});
});
}
getUserData() {
return this._request('/user')
.then(this._saveUserData.bind(this))
.then(this._refreshSavedSession.bind(this));
}
_saveUserData(attributes, fromStorage) {
for (const key in attributes) {
if (key in User.prototype || key in forbiddenUpdateAttributes) {
continue;
}
this[key] = attributes[key];
}
if (fromStorage) {
this._fromStorage = true;
}
return this;
}
_processTokenResponse(tokenResponse) {
this.token = tokenResponse;
let claims;
try {
claims = JSON.parse(urlBase64Decode(tokenResponse.access_token.split('.')[1]));
this.token.expires_at = claims.exp * 1000;
} catch (e) {
console.error(
new Error(
`Gotrue-js: Failed to parse tokenResponse claims: ${JSON.stringify(tokenResponse)}`,
),
);
}
}
_refreshSavedSession() {
// only update saved session if we previously saved something
if (isBrowser() && localStorage.getItem(storageKey)) {
this._saveSession();
}
return this;
}
get _details() {
const userCopy = {};
for (const key in this) {
if (key in User.prototype || key in forbiddenSaveAttributes) {
continue;
}
userCopy[key] = this[key];
}
return userCopy;
}
_saveSession() {
isBrowser() && localStorage.setItem(storageKey, JSON.stringify(this._details));
return this;
}
tokenDetails() {
return this.token;
}
clearSession() {
User.removeSavedSession();
this.token = null;
currentUser = null;
}
}
function urlBase64Decode(str) {
// From https://jwt.io/js/jwt.js
var output = str.replace(/-/g, '+').replace(/_/g, '/');
switch (output.length % 4) {
case 0:
break;
case 2:
output += '==';
break;
case 3:
output += '=';
break;
default:
throw 'Illegal base64url string!';
}
var result = window.atob(output); //polifyll https://github.com/davidchambers/Base64.js
try {
return decodeURIComponent(escape(result));
} catch (err) {
return result;
}
}
@@ -1,171 +0,0 @@
import superagent from 'superagent'
const isBrowser = () => typeof window !== 'undefined'
const storageKey = 'supabase.auth.token'
class Auth {
constructor(authUrl, supabaseKey, options = { autoRefreshToken: true, persistSession: true }) {
this.authUrl = authUrl
this.accessToken = supabaseKey
this.refreshToken = null
this.supabaseKey = supabaseKey
this.currentUser = null
this.autoRefreshToken = options.autoRefreshToken === undefined ? true : options.autoRefreshToken
this.persistSession = options.persistSession === undefined ? true : options.persistSession
this.signup = async (email, password) => {
this.removeSavedSession() // clean out the old session before attempting
const response = await superagent
.post(`${authUrl}/signup`, { email, password })
.set('accept', 'json')
.set('apikey', this.supabaseKey)
if (response.status === 200 && response['body']['user']['confirmed_at']) {
this.accessToken = response.body['access_token']
this.refreshToken = response.body['refresh_token']
this.currentUser = response.body['user']
let tokenExpirySeconds = response.body['expires_in']
if (this.autoRefreshToken && tokenExpirySeconds)
setTimeout(this.callRefreshToken, (tokenExpirySeconds - 60) * 1000)
if (this.persistSession) {
const timeNow = Math.round(Date.now() / 1000)
this.saveSession(
this.accessToken,
this.refreshToken,
timeNow + tokenExpirySeconds,
this.currentUser
)
}
}
return response
}
this.login = async (email, password) => {
this.removeSavedSession() // clean out the old session before attempting
let response = await superagent
.post(`${authUrl}/token?grant_type=password`, { email, password })
.set('accept', 'json')
.set('apikey', this.supabaseKey)
if (response.status === 200) {
this.accessToken = response.body['access_token']
this.refreshToken = response.body['refresh_token']
this.currentUser = response.body['user']
let tokenExpirySeconds = response.body['expires_in']
if (this.autoRefreshToken && tokenExpirySeconds)
setTimeout(this.callRefreshToken, (tokenExpirySeconds - 60) * 1000)
if (this.persistSession) {
const timeNow = Math.round(Date.now() / 1000)
this.saveSession(
this.accessToken,
this.refreshToken,
timeNow + tokenExpirySeconds,
this.currentUser
)
}
}
return response
}
this.callRefreshToken = async () => {
let response = await superagent
.post(`${authUrl}/token?grant_type=refresh_token`, { refresh_token: this.refreshToken })
.set('accept', 'json')
.set('apikey', this.supabaseKey)
if (response.status === 200) {
this.accessToken = response.body['access_token']
this.refreshToken = response.body['refresh_token']
let tokenExpirySeconds = response.body['expires_in']
if (this.autoRefreshToken && tokenExpirySeconds)
setTimeout(this.callRefreshToken, (tokenExpirySeconds - 60) * 1000)
if (this.persistSession) {
const timeNow = Math.round(Date.now() / 1000)
this.saveSession(
this.accessToken,
this.refreshToken,
timeNow + tokenExpirySeconds,
this.currentUser
)
}
}
return response
}
this.logout = async () => {
await superagent
.post(`${authUrl}/logout`)
.set('Authorization', `Bearer ${this.accessToken}`)
.set('apikey', this.supabaseKey)
this.removeSavedSession()
}
this.user = async () => {
if (this.currentUser) return this.currentUser
let response = await superagent
.get(`${authUrl}/user`)
.set('Authorization', `Bearer ${this.accessToken}`)
.set('apikey', this.supabaseKey)
if (response.status === 200) {
this.currentUser = response.body
this.currentUser['access_token'] = this.accessToken
this.currentUser['refresh_token'] = this.refreshToken
}
return this.currentUser
}
this.saveSession = (accessToken, refreshToken, expiresAt, currentUser) => {
const data = { accessToken, refreshToken, expiresAt, currentUser }
isBrowser() && localStorage.setItem(storageKey, JSON.stringify(data))
}
this.removeSavedSession = () => {
this.currentUser = null
this.refreshToken = null
this.accessToken = this.supabaseKey
isBrowser() && localStorage.removeItem(storageKey)
}
this.authHeader = () => {
let json = isBrowser() && localStorage.getItem(storageKey)
let persisted = json ? JSON.parse(json) : null
if (persisted?.accessToken) return `Bearer ${persisted.accessToken}`
else if (this.accessToken) return `Bearer ${this.accessToken}`
else return null
}
this.recoverSession = () => {
const json = isBrowser() && localStorage.getItem(storageKey)
if (json) {
try {
const data = JSON.parse(json)
const { accessToken, refreshToken, currentUser, expiresAt } = data
const timeNow = Math.round(Date.now() / 1000)
if (expiresAt < timeNow) {
console.log('saved session has expired')
this.removeSavedSession()
} else {
this.accessToken = accessToken
this.refreshToken = refreshToken
this.currentUser = currentUser
// schedule a refresh 60 seconds before token due to expire
setTimeout(this.callRefreshToken, (expiresAt - timeNow - 60) * 1000)
}
} catch (err) {
console.error(err)
return null
}
}
return null
}
this.recoverSession()
}
}
export { Auth }
@@ -1,155 +0,0 @@
import { Socket } from '@supabase/realtime-js'
import * as ChangeMapper from './utils/ChangeMapper'
class Realtime {
constructor(
tableName,
realtimeUrl,
schema,
apikey,
uuid,
eventType,
callbackFunction,
queryFilters
) {
this.tableName = tableName
this.realtimeUrl = realtimeUrl
this.schema = schema
this.apikey = apikey
this.uuid = uuid
this.socket = null
this.channel = null
this.listeners = {}
this.queryFilters = queryFilters
this.on(eventType, callbackFunction)
}
/**
* REALTIME FUNCTIONALITY
*/
createListener() {
let socketUrl = `${this.realtimeUrl}`
var filterString = ''
this.queryFilters.forEach((queryFilter) => {
switch (queryFilter.filter) {
case 'filter':
// temporary solution
// this is the only thing we are supporting at the moment
if (queryFilter.operator === 'eq') {
// right now, the string is replaced instead of being stacked
// the server does not support multiple eq. statements
// as such, we will only process the very last eq. statement provided
filterString = `:${queryFilter.columnName}=${queryFilter.operator}.${queryFilter.criteria}`
}
break
default:
break
}
})
let channel =
this.tableName == '*'
? 'realtime:*'
: `realtime:${this.schema}:${this.tableName}${filterString}`
this.socket = new Socket(socketUrl, { params: { apikey: this.apikey } })
this.channel = this.socket.channel(channel)
this.socket.onOpen(() => {
console.debug(`${this.realtimeUrl}: REALTIME CONNECTED`)
})
this.socket.onClose(() => {
console.debug(`${this.realtimeUrl}: REALTIME DISCONNECTED`)
})
}
on(eventType, callbackFunction) {
if (this.socket == null) this.createListener()
this.channel.on(eventType, (payload) => {
let payloadEnriched = {
schema: payload.schema,
table: payload.table,
commit_timestamp: payload.commit_timestamp,
}
let newData = {}
let oldData = {}
let oldDataEnriched = {}
switch (payload.type) {
case 'INSERT':
newData = ChangeMapper.convertChangeData(payload.columns, payload.record)
payloadEnriched.eventType = 'INSERT'
payloadEnriched.new = newData
break
case 'UPDATE':
oldData = ChangeMapper.convertChangeData(payload.columns, payload.old_record)
newData = ChangeMapper.convertChangeData(payload.columns, payload.record)
Object.keys(oldData).forEach((key) => {
if (oldData[key] != null) oldDataEnriched[key] = oldData[key]
})
payloadEnriched.eventType = 'UPDATE'
payloadEnriched.old = oldDataEnriched
payloadEnriched.new = newData
break
case 'DELETE':
oldData = ChangeMapper.convertChangeData(payload.columns, payload.old_record)
Object.keys(oldData).forEach((key) => {
if (oldData[key] != null) oldDataEnriched[key] = oldData[key]
})
payloadEnriched.eventType = 'DELETE'
payloadEnriched.old = oldDataEnriched
break
default:
break
}
callbackFunction(payloadEnriched)
})
this.listeners[eventType] = callbackFunction
return this
}
subscribe() {
if (this.socket == null) this.createListener()
this.socket.connect()
if (this.channel.state !== 'joined') {
this.channel
.join()
.receive('ok', (resp) =>
console.debug(`${this.realtimeUrl}: Joined Realtime successfully `, resp)
)
.receive('error', (resp) => console.debug(`${this.realtimeUrl}: Unable to join `, resp))
.receive('timeout', () =>
console.debug(`${this.realtimeUrl}: Network timeout. Still waiting...`)
)
}
return this
}
unsubscribe() {
if (this.socket) this.socket.disconnect()
return this
}
}
export default Realtime
@@ -1,246 +0,0 @@
import { uuid } from './utils/Helpers'
import Realtime from './Realtime'
import { Auth } from './Auth'
import { PostgrestClient } from '@supabase/postgrest-js'
const DEPRICATED_KEY_LENGTH = 45
class SupabaseClient {
constructor(supabaseUrl, supabaseKey, options = { autoRefreshToken: true }) {
this.supabaseUrl = null
this.supabaseKey = null
this.restUrl = null
this.realtimeUrl = null
this.authUrl = null
this.schema = 'public'
this.subscriptions = {}
this.accessToken = null
this.tableName = null
this.queryFilters = []
if (options.schema) this.schema = options.schema
this.authenticate(supabaseUrl, supabaseKey)
this.auth = new Auth(this.authUrl, supabaseKey, { autoRefreshToken: options.autoRefreshToken })
}
/**
* General Functionalities
*/
authenticate(supabaseUrl, supabaseKey) {
this.supabaseUrl = supabaseUrl
this.supabaseKey = supabaseKey
this.restUrl = `${supabaseUrl}/rest/v1`
this.realtimeUrl = `${supabaseUrl}/realtime/v1`.replace('http', 'ws')
this.authUrl = `${supabaseUrl}/auth/v1`
}
clear() {
this.tableName = null
this.queryFilters = []
}
from(tableName) {
this.tableName = tableName
return this
}
/**
* Realtime Functionalities
*/
on(eventType, callbackFunction) {
let identifier = uuid()
this.subscriptions[identifier] = new Realtime(
this.tableName,
this.realtimeUrl,
this.schema,
this.supabaseKey,
identifier,
eventType,
callbackFunction,
this.queryFilters
)
this.clear()
return this.subscriptions[identifier]
}
getSubscriptions() {
return Object.values(this.subscriptions)
}
removeSubscription(mySubscription) {
mySubscription.unsubscribe()
delete this.subscriptions[mySubscription.uuid]
}
/**
* REST Functionalities
*/
rpc(functionName, functionParameters = null) {
let rest = new PostgrestClient(this.restUrl, {
headers: { apikey: this.supabaseKey },
schema: this.schema,
})
return rest.rpc(functionName, functionParameters)
}
initClient() {
let headers = { apikey: this.supabaseKey }
if (this.supabaseKey.length > DEPRICATED_KEY_LENGTH && this.accessToken)
headers['Authorization'] = `Bearer ${this.accessToken}`
// if (this.supabaseKey.length > DEPRICATED_KEY_LENGTH && this.auth.authHeader())
// headers['Authorization'] = this.auth.authHeader()
let rest = new PostgrestClient(this.restUrl, {
headers,
schema: this.schema,
})
let api = rest.from(this.tableName)
// go through queryFilters
this.queryFilters.forEach((queryFilter) => {
switch (queryFilter.filter) {
case 'filter':
api.filter(queryFilter.columnName, queryFilter.operator, queryFilter.criteria)
break
case 'match':
api.match(queryFilter.query)
break
case 'order':
api.order(queryFilter.property, queryFilter.ascending, queryFilter.nullsFirst)
break
case 'range':
api.range(queryFilter.from, queryFilter.to)
break
case 'single':
api.single()
break
default:
break
}
})
this.clear()
return api
}
select(columnQuery = '*', options = {}) {
let api = this.initClient()
return api.select(columnQuery, options)
}
insert(data, options = {}) {
let api = this.initClient()
return api.insert(data, options)
}
update(data, options = {}) {
let api = this.initClient()
return api.update(data, options)
}
delete(options = {}) {
let api = this.initClient()
return api.delete(options)
}
filter(columnName, operator, criteria) {
this.queryFilters.push({
filter: 'filter',
columnName,
operator,
criteria,
})
return this
}
match(query) {
this.queryFilters.push({
filter: 'match',
query,
})
return this
}
order(property, ascending = false, nullsFirst = false) {
this.queryFilters.push({
filter: 'order',
property,
ascending,
nullsFirst,
})
return this
}
range(from, to) {
this.queryFilters.push({
filter: 'range',
from,
to,
})
return this
}
single() {
this.queryFilters.push({ filter: 'single' })
return this
}
setAccessToken(value) {
this.accessToken = value
}
}
// pre-empts if any of the filters are used before select
const advancedFilters = [
'eq',
'neq',
'gt',
'lt',
'gte',
'lte',
'like',
'ilike',
'is',
'in',
'cs',
'cd',
'ova',
'ovr',
'sl',
'sr',
'nxr',
'nxl',
'adj',
]
advancedFilters.forEach(
(operator) =>
(SupabaseClient.prototype[operator] = function filterValue(columnName, criteria) {
return this.filter(columnName, operator, criteria)
})
)
const createClient = (supabaseUrl, supabaseKey, options = {}) => {
return new SupabaseClient(supabaseUrl, supabaseKey, options)
}
export { createClient }
@@ -1,189 +0,0 @@
// # Lifted from epgsql (src/epgsql_binary.erl), this module licensed under
// # 3-clause BSD found here: https://raw.githubusercontent.com/epgsql/epgsql/devel/LICENSE
/**
* Takes an array of columns and an object of string values then converts each string value
* to its mapped type
* @param {{name: String, type: String}[]} columns
* @param {Object} records
* @param {Object} options The map of various options that can be applied to the mapper
* @param {Array} options.skipTypes The array of types that should not be converted
*
* @example convertChangeData([{name: 'first_name', type: 'text'}, {name: 'age', type: 'int4'}], {first_name: 'Paul', age:'33'}, {})
* //=>{ first_name: 'Paul', age: 33 }
*/
export const convertChangeData = (columns, records, options = {}) => {
let result = {}
let skipTypes = typeof options.skipTypes !== 'undefined' ? options.skipTypes : []
Object.entries(records).map(([key, value]) => {
result[key] = convertColumn(key, columns, records, skipTypes)
})
return result
}
/**
* Converts the value of an individual column
* @param {String} columnName The column that you want to convert
* @param {{name: String, type: String}[]} columns All of the columns
* @param {Object} records The map of string values
* @param {Array} skipTypes An array of types that should not be converted
* @return {object} Useless information
*
* @example convertColumn('age', [{name: 'first_name', type: 'text'}, {name: 'age', type: 'int4'}], ['Paul', '33'], [])
* //=> 33
* @example convertColumn('age', [{name: 'first_name', type: 'text'}, {name: 'age', type: 'int4'}], ['Paul', '33'], ['int4'])
* //=> "33"
*/
export const convertColumn = (columnName, columns, records, skipTypes) => {
let column = columns.find(x => x.name == columnName)
if(skipTypes.includes(column.type)) return noop(records[columnName])
else return convertCell(column.type, records[columnName])
}
/**
* If the value of the cell is `null`, returns null.
* Otherwise converts the string value to the correct type.
* @param {String} type A postgres column type
* @param {String} stringValue The cell value
*
* @example convertCell('bool', 'true')
* //=> true
* @example convertCell('int8', '10')
* //=> 10
* @example convertCell('_int4', '{1,2,3,4}')
* //=> [1,2,3,4]
*/
export const convertCell = (type, stringValue) => {
try {
if (stringValue === null) return null
// if data type is an array
if (type.charAt(0) === '_') {
let arrayValue = type.slice(1, type.length)
return toArray(stringValue, arrayValue)
}
// If not null, convert to correct type.
switch (type) {
case 'abstime':
return noop(stringValue) // To allow users to cast it based on Timezone
case 'bool':
return toBoolean(stringValue)
case 'date':
return noop(stringValue) // To allow users to cast it based on Timezone
case 'daterange':
return toDateRange(stringValue)
case 'float4':
return toFloat(stringValue)
case 'float8':
return toFloat(stringValue)
case 'int2':
return toInt(stringValue)
case 'int4':
return toInt(stringValue)
case 'int4range':
return toIntRange(stringValue)
case 'int8':
return toInt(stringValue)
case 'int8range':
return toIntRange(stringValue)
case 'json':
return toJson(stringValue)
case 'jsonb':
return toJson(stringValue)
case 'money':
return toFloat(stringValue)
case 'numeric':
return toFloat(stringValue)
case 'oid':
return toInt(stringValue)
case 'reltime':
return noop(stringValue) // To allow users to cast it based on Timezone
case 'time':
return noop(stringValue) // To allow users to cast it based on Timezone
case 'timestamp':
return toTimestampString(stringValue) // Format to be consistent with PostgREST
case 'timestamptz':
return noop(stringValue) // To allow users to cast it based on Timezone
case 'timetz':
return noop(stringValue) // To allow users to cast it based on Timezone
case 'tsrange':
return toDateRange(stringValue)
case 'tstzrange':
return toDateRange(stringValue)
default:
// All the rest will be returned as strings
return noop(stringValue)
}
} catch (error) {
console.log(`Could not convert cell of type ${type} and value ${stringValue}`)
console.log(`This is the error: ${error}`)
return stringValue
}
}
export const noop = stringValue => {
return stringValue
}
export const toBoolean = stringValue => {
switch (stringValue) {
case 't':
return true
case 'f':
return false
default:
return null
}
}
export const toDate = stringValue => {
return new Date(stringValue)
}
export const toDateRange = stringValue => {
let arr = JSON.parse(stringValue)
return [new Date(arr[0]), new Date(arr[1])]
}
export const toFloat = stringValue => {
return parseFloat(stringValue)
}
export const toInt = stringValue => {
return parseInt(stringValue)
}
export const toIntRange = stringValue => {
let arr = JSON.parse(stringValue)
return [parseInt(arr[0]), parseInt(arr[1])]
}
export const toJson = stringValue => {
return JSON.parse(stringValue)
}
/**
* Converts a Postgres Array into a native JS array
* @example toArray('{1,2,3,4}', 'int4')
* //=> [1,2,3,4]
* @example toArray('{}', 'int4')
* //=> []
*/
export const toArray = (stringValue, type) => {
// this takes off the '{' & '}'
let stringEnriched = stringValue.slice(1, stringValue.length - 1)
// converts the string into an array
// if string is empty (meaning the array was empty), an empty array will be immediately returned
let stringArray = stringEnriched.length > 0 ? stringEnriched.split(',') : []
let array = stringArray.map(string => {
return convertCell(type, string)
})
return array
}
/**
* Fixes timestamp to be ISO-8601. Swaps the space between the date and time for a 'T'
* See https://github.com/supabase/supabase/issues/18
* @returns {string}
* @example toTimestampString('2019-09-10 00:00:00')
* //=> '2019-09-10T00:00:00'
*/
export const toTimestampString = stringValue => {
return stringValue.replace(' ', 'T')
}
@@ -1,8 +0,0 @@
export function uuid() {
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1)
}
return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4()
}
@@ -1,14 +0,0 @@
function RoundToFixDecimals(value, numDecimals = 5) {
function addZero(s, size) {
while (s.length <= (size || 2)) { s = s + "0"; }
return s;
}
const temp = addZero("1", numDecimals)
const tempNum = parseInt(temp, 10)
return Math.round((value + Number.EPSILON) * tempNum) / tempNum
}
export {
RoundToFixDecimals
}
File diff suppressed because it is too large. Load diff
@@ -1,24 +0,0 @@
{
"name": "supabase-realtime-map-v2",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
},
"dependencies": {
"@supabase/doctest-js": "^0.1.0",
"@supabase/postgrest-js": "^0.17.0",
"@supabase/realtime-js": "^0.9.0",
"dotenv": "^8.2.0",
"leaflet": "^1.6.0",
"micro-api-client": "^3.3.0",
"nanoid": "^3.1.12",
"next": "9.5.2",
"react": "16.13.1",
"react-dom": "16.13.1",
"react-leaflet": "^3.0.0-beta.4",
"superagent": "^6.0.0"
}
}
@@ -1,60 +0,0 @@
import '../styles/globals.css'
import React, { useState, useEffect } from 'react'
import UserContext from 'lib/UserContext'
import { auth, supabase } from 'lib/Store'
function MyApp({ Component, pageProps }) {
const [user, setUser] = useState(null)
useEffect(() => {
async function loadUser() {
try {
const authUser = auth.currentUser()
if (!authUser) return
const { data: user, error } = await supabase
.from('users')
.match({ id: authUser.id })
.select('*')
.single()
if (error) {
throw new Error(error)
}
setUser(user)
} catch (error) {
console.log(error)
}
}
loadUser()
}, [])
function onSignOut() {
try {
const user = auth.currentUser()
user
.signOut()
.then((response) => {
console.log('User logged out')
window.location.reload()
})
.catch((error) => {
console.log('Failed to logout user: %o', error)
})
} catch (error) {
console.log(error)
}
}
return (
<UserContext.Provider
value={{
user: user,
signOut: onSignOut,
}}
>
<Component {...pageProps} />
</UserContext.Provider>
)
}
export default MyApp
@@ -1,14 +0,0 @@
import { auth, supabase } from "lib/Store"
export default async function (req, res) {
try {
const { email, password } = req.body
const authBody = await auth.login(email, password)
return res.status(200).json({ refresh_token: authBody.token.refresh_token })
} catch (error) {
console.error('error', error)
res.status(error.status || 500).end(error.message)
}
}
@@ -1,17 +0,0 @@
import { supabase, auth } from "lib/Store"
export default async function (req, res) {
try {
const { email, password, role } = req.body
const authBody = await auth.signUp({email, password})
supabase.setAccessToken(authBody.data.access_token)
const { data: user } = await supabase.from('users').insert([{ id: authBody.user.id, username: email, role }]).single()
return res.status(200).json({ ...user, refresh_token: authBody.refresh_token })
} catch (error) {
console.error('error', error)
res.status(error.status || 500).end(error.message)
}
}
@@ -1,99 +0,0 @@
import { useContext } from 'react'
import Head from 'next/head'
import dynamic from 'next/dynamic'
import styles from 'styles/Map.module.css'
import { supabase } from 'lib/Store'
import SignIn from 'components/SignIn'
import UserContext from 'lib/UserContext'
const MapInput = dynamic(
() => import('components/MapInput'),
{ ssr: false }
)
function randomPosition() {
var array = [
{
lat: 1.3489728457596013,
lng: 103.77043978311998
},
{
lat: 1.37462,
lng: 103.77694
},
{
lat: 1.41006,
lng: 103.77144
},
{
lat: 1.34313,
lng: 103.84191
},
{
lat: 1.35454,
lng: 103.69746
}
];
return array[Math.floor(Math.random() * array.length)];
}
export default function Page() {
const { user, signOut } = useContext(UserContext)
const center = randomPosition()
const zoomLevel = 14
return (
<div className={styles.container}>
<Head>
<title>Driver View</title>
<link rel="icon" href="/favicon.ico" />
<link
rel="stylesheet"
href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css"
integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ=="
crossOrigin=""
/>
<script src="https://unpkg.com/leaflet@1.6.0/dist/leaflet.js"
integrity="sha512-gZwIG9x3wUXg2hdXF6+rVkLF/0Vi9U8D2Ntg4Ga5I5BZpVkVxlJWbSQtXPSiUTtC0TjtGOmxa1AJPuV0CPthew=="
crossOrigin=""></script>
</Head>
<main className={styles.main}>
<h1 className={styles.title}>
Driver View
</h1>
<p className={styles.description}>
Get started by drag-drop the marker around to simulate location update
</p>
<div className={styles.grid}>
{!user && <SignIn />}
{user && (
<div className={styles.card}>
{
user.role === "DRIVER"
? <MapInput supabase={supabase} clientRef={user?.id} center={center} zoom={zoomLevel} />
: <p className={styles.error}>Sorry, You need to sign in as driver</p>
}
<div className={styles.profile_container}>
Signed in as {user.username} [{user.role}]<br />
<button className={styles.sign_out} onClick={signOut}>Sign out</button>
</div>
</div>
)}
</div>
</main>
<footer className="footer">
<a
href="https://supabase.io"
target="_blank"
rel="noopener noreferrer"
>
Powered by{' '}
<img src="/supabase.svg" alt="Supabase Logo" className="logo" />
</a>
</footer>
</div >
)
}
@@ -1,54 +0,0 @@
import { useState, useEffect } from 'react'
import Link from 'next/link'
import Head from 'next/head'
import styles from 'styles/Home.module.css'
export default function Page() {
return (
<div className={styles.container}>
<Head>
<title>Live Tracker</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<main className={styles.main}>
<h1 className={styles.title}>
Live Tracker
</h1>
<p className={styles.description}>
Get started by choosing your role as Driver or Manager.
</p>
<p className={styles.sub_description}>
You can only login 1 role at a time. Please use incognito window to test multi users.
</p>
<div className={styles.grid}>
<Link href="/driver">
<a className={styles.card}>
<h3>Driver View &rarr;</h3>
<p>Find in-depth information about Next.js features and API.</p>
</a>
</Link>
<Link href="/manager">
<a className={styles.card}>
<h3>Manager View &rarr;</h3>
<p>Find in-depth information about Next.js features and API.</p>
</a>
</Link>
</div>
</main>
<footer className="footer">
<a
href="https://supabase.io"
target="_blank"
rel="noopener noreferrer"
>
Powered by{' '}
<img src="/supabase.svg" alt="Supabase Logo" className="logo" />
</a>
</footer>
</div>
)
}
@@ -1,77 +0,0 @@
import React, { useContext } from 'react'
import Head from 'next/head'
import styles from 'styles/Map.module.css'
import dynamic from 'next/dynamic'
import { supabase } from 'lib/Store'
import SignIn from 'components/SignIn'
import UserContext from 'lib/UserContext'
const MapView = dynamic(
() => import('components/MapView'),
{ ssr: false }
)
export default function Page() {
const { user, signOut } = useContext(UserContext)
const center = {
lat: 1.3489728457596013,
lng: 103.77043978311998
}
const zoomLevel = 12
return (
<div className={styles.container}>
<Head>
<title>Manager View</title>
<link rel="icon" href="/favicon.ico" />
<link
rel="stylesheet"
href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css"
integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ=="
crossOrigin=""
/>
<script src="https://unpkg.com/leaflet@1.6.0/dist/leaflet.js"
integrity="sha512-gZwIG9x3wUXg2hdXF6+rVkLF/0Vi9U8D2Ntg4Ga5I5BZpVkVxlJWbSQtXPSiUTtC0TjtGOmxa1AJPuV0CPthew=="
crossOrigin=""></script>
</Head>
<main className={styles.main}>
<h1 className={styles.title}>
Manager View
</h1>
<p className={styles.description}>
As manager, you can view all drivers' locations as long as the drivers are ONLINE
</p>
<div className={styles.grid}>
{!user && <SignIn role="MANAGER" />}
{user && (
<div className={styles.card}>
{
user.role === "MANAGER"
? <MapView supabase={supabase} center={center} zoom={zoomLevel} />
: <p className={styles.error}>Sorry, you need to sign in as manager</p>
}
<div className={styles.profile_container}>
Signed in as {user.username} [{user.role}]<br />
<button className={styles.sign_out} onClick={signOut}>Sign out</button>
</div>
</div>
)}
</div>
</main>
<footer className="footer">
<a
href="https://supabase.io"
target="_blank"
rel="noopener noreferrer"
>
Powered by{' '}
<img src="/supabase.svg" alt="Supabase Logo" className="logo" />
</a>
</footer>
</div>
)
}
@@ -1,62 +1,77 @@
import { useEffect, useState, useRef, useMemo, useCallback } from 'react'
import { MapContainer, TileLayer, Marker, Popup, Circle } from 'react-leaflet'
import { RoundToFixDecimals } from "lib/Utils"
import TextLog from "components/TextLog"
import { useEffect, useState, useRef, useMemo, useCallback } from "react";
import { MapContainer, TileLayer, Marker, Popup, Circle } from "react-leaflet";
import { RoundToFixDecimals } from "lib/utils";
import { supabase } from "lib/api";
import TextLog from "components/TextLog";
function DraggableMarker({ initialPos, addCircle }) {
const [position, setPosition] = useState(initialPos)
const markerRef = useRef(null)
const [position, setPosition] = useState(initialPos);
const markerRef = useRef(null);
const eventHandlers = useMemo(
() => ({
dragend() {
const marker = markerRef.current
const pos = marker.getLatLng()
if (addCircle) addCircle(pos)
if (marker) setPosition(pos)
const marker = markerRef.current;
const pos = marker.getLatLng();
if (addCircle) addCircle(pos);
if (marker) setPosition(pos);
},
}),
[addCircle],
)
[addCircle]
);
return (
<Marker
draggable={true}
eventHandlers={eventHandlers}
position={position}
ref={markerRef}>
ref={markerRef}
>
<Popup minWidth={90}>
{`lat: ${position.lat}`}<br />{`long: ${position.lng}`}
{`lat: ${position.lat}`}
<br />
{`long: ${position.lng}`}
</Popup>
</Marker>
)
);
}
function MapInput({ supabase, clientRef, center, zoom }) {
const [log, setLog] = useState(undefined)
const [circles, setCircles] = useState([])
function MapInput({ clientRef, center, zoom }) {
const [log, setLog] = useState(undefined);
const [circles, setCircles] = useState([]);
useEffect(() => {
let newLog = `Ref: ${clientRef}\nReady to send location...`
newLog += circles.map(item => { return `\nsent lat=${RoundToFixDecimals(item.lat)} long=${RoundToFixDecimals(item.lng)}` })
setLog(newLog)
}, [circles, clientRef])
let newLog = `Ref: ${clientRef}\nReady to send location...`;
newLog += circles.map((item) => {
return `\nsent lat=${RoundToFixDecimals(
item.lat
)} long=${RoundToFixDecimals(item.lng)}`;
});
setLog(newLog);
}, [circles, clientRef]);
const onAddCircle = useCallback(
async (pos) => {
setCircles([...circles, pos])
setCircles([...circles, pos]);
// insert new location
await supabase
.from('locations')
.from("locations")
.insert([
{ latitude: pos.lat, longitude: pos.lng, user_id: clientRef },
])
]);
},
[setCircles, circles, supabase, clientRef]
);
function renderCircles() {
return circles.map((item, index) => <Circle key={`${index}`} center={item} pathOptions={{ fillColor: 'blue' }} radius={20} />)
return circles.map((item, index) => (
<Circle
key={`${index}`}
center={item}
pathOptions={{ fillColor: "blue" }}
radius={20}
/>
));
}
return (
@@ -81,6 +96,6 @@ function MapInput({ supabase, clientRef, center, zoom }) {
}
`}</style>
</div>
)
);
}
export default MapInput
export default MapInput;
@@ -0,0 +1,91 @@
import { useEffect, useReducer } from "react";
import { MapContainer, TileLayer, Polyline } from "react-leaflet";
import { RoundToFixDecimals } from "lib/utils";
import { supabase } from "lib/api";
import TextLog from "components/TextLog";
export default function MapView({ center, zoom }) {
const [state, dispatch] = useReducer(mapReducer, initialState);
useEffect(() => {
// Listen to INSERT event on locations table
const subscription = supabase
.from("locations")
.on("INSERT", (payload) => {
console.log("New Position received!", payload);
dispatch({ payload: payload.new });
})
.subscribe();
return () => {
if (subscription) supabase.removeSubscription(subscription);
};
}, []);
function drawPolylines() {
let result = Object.keys(state.userPositions).map((key, idx) => {
const item = state.userPositions[key];
if (!item) return;
const { color, data } = item;
const polyline = data.map((item) => {
if (!item) return;
const { lat, lng } = item;
return [lat, lng];
});
return (
<Polyline
key={`line-${idx}`}
pathOptions={{ color: color }}
positions={polyline}
/>
);
});
return result;
}
return (
<div className="map-view">
<MapContainer center={center} zoom={zoom || 15} scrollWheelZoom={false}>
<TileLayer
attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
{drawPolylines()}
</MapContainer>
<TextLog log={state.log} />
</div>
);
}
function randomColor() {
var array = ["black", "magenta", "blue", "indigo", "green", "blueviolet"];
return array[Math.floor(Math.random() * array.length)];
}
const initialState = {
log: "Start listerning...",
userPositions: {},
};
const mapReducer = (state, action) => {
const { id, user_id, latitude: lat, longitude: lng } = action.payload;
const position = { id, user_id, lat, lng };
let positions = state.userPositions[user_id];
if (positions) positions.data = [...positions.data, position];
else positions = { color: randomColor(), data: [position] };
const userPositions = { ...state.userPositions, [user_id]: positions };
let log = state.log;
log += `\nuser_id=${user_id}`;
log += ` lat=${RoundToFixDecimals(lat)}`;
log += ` long=${RoundToFixDecimals(lng)}`;
return {
log,
userPositions,
};
};
@@ -0,0 +1,19 @@
import Head from "next/head";
export default function PageLayout({ title, children }) {
return (
<div className="container">
<Head>
<title>{title}</title>
</Head>
<main className="main">{children}</main>
<footer className="footer">
<a href="https://supabase.io" target="_blank" rel="noopener noreferrer">
Powered by{" "}
<img src="/supabase.svg" alt="Supabase Logo" className="logo" />
</a>
</footer>
</div>
);
}
@@ -0,0 +1,97 @@
import React, { useState } from "react";
import { supabase } from "lib/api";
/**
* Sign in with username and password
*
* @param {String} role DRIVER/MANAGER. default as DRIVER
*/
export default function SignIn({ role = "DRIVER" }) {
const [formData, updateFormData] = useState({ role });
const [action, setAction] = useState(null);
function onChange(e) {
updateFormData({
...formData,
// Trimming any whitespace
[e.target.name]: e.target.value.trim(),
});
}
async function updateUserRole(user) {
if (!user || user.user_metadata?.role) return;
// update user role
await supabase.auth.update({
data: { role },
});
}
async function onSubmit(event) {
event.preventDefault();
try {
if (action === "SIGNUP") {
const { email, password, role } = formData;
const { error, user } = await supabase.auth.signUp({ email, password });
if (error) throw error;
// update user role
await supabase.auth.update({
data: { role },
});
} else if (action === "LOGIN") {
const { error } = await supabase.auth.signIn(formData);
if (error) throw error;
}
} catch (error) {
alert(error.message);
}
}
return (
<form className="form-container" onSubmit={onSubmit}>
<label>
Email
<input name="email" type="email" onChange={onChange} />
</label>
<label>
Password
<input name="password" type="password" onChange={onChange} />
</label>
<button type="submit" onClick={() => setAction("SIGNUP")}>
Sign up
</button>
<button type="submit" onClick={() => setAction("LOGIN")}>
Login
</button>
<style jsx>{`
.form-container {
min-width: 20rem;
padding: 0 0.5rem;
display: flex;
flex-direction: column;
}
label {
display: flex;
flex-direction: column;
margin-bottom: 1rem;
}
input {
margin-top: 0.5rem;
padding: 0.5rem;
font-size: 1rem;
}
button {
margin-top: 0.5rem;
padding: 0.5rem;
font-size: 1rem;
}
`}</style>
</form>
);
}
@@ -0,0 +1,10 @@
import { createClient } from "@supabase/supabase-js";
import {
NEXT_PUBLIC_SUPABASE_URL,
NEXT_PUBLIC_SUPABASE_KEY,
} from "./constants";
export const supabase = createClient(
NEXT_PUBLIC_SUPABASE_URL,
NEXT_PUBLIC_SUPABASE_KEY
);
@@ -0,0 +1,8 @@
import { createContext } from "react";
export const NEXT_PUBLIC_SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL;
export const NEXT_PUBLIC_SUPABASE_KEY = process.env.NEXT_PUBLIC_SUPABASE_KEY;
export const DEFAULT_AVATARS_BUCKET = "avatars";
export const AppContext = createContext();
@@ -0,0 +1,12 @@
export function RoundToFixDecimals(value, numDecimals = 5) {
function addZero(s, size) {
while (s.length <= (size || 2)) {
s = s + "0";
}
return s;
}
const temp = addZero("1", numDecimals);
const tempNum = parseInt(temp, 10);
return Math.round((value + Number.EPSILON) * tempNum) / tempNum;
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,19 @@
{
"name": "supabase-realtime-map-v2",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
},
"dependencies": {
"@supabase/supabase-js": "^1.6.0",
"leaflet": "^1.7.1",
"nanoid": "^3.1.12",
"next": "^10.0.9",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-leaflet": "^3.1.0"
}
}
@@ -0,0 +1,25 @@
import "../styles/globals.css";
import React, { useState, useEffect } from "react";
import { supabase } from "lib/api";
import { AppContext } from "lib/constants";
function MyApp({ Component, pageProps }) {
const [session, setSession] = useState(null);
useEffect(() => {
setSession(supabase.auth.session());
supabase.auth.onAuthStateChange((_event, session) => {
setSession(session);
console.log("session state change", session);
});
}, []);
return (
<AppContext.Provider value={{ session: session }}>
<Component {...pageProps} />
</AppContext.Provider>
);
}
export default MyApp;
@@ -0,0 +1,30 @@
import Document, { Html, Head, Main, NextScript } from "next/document";
class MyDocument extends Document {
render() {
return (
<Html>
<Head>
<link rel="icon" href="/favicon.ico" />
<link
rel="stylesheet"
href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css"
integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ=="
crossOrigin=""
/>
<script
src="https://unpkg.com/leaflet@1.6.0/dist/leaflet.js"
integrity="sha512-gZwIG9x3wUXg2hdXF6+rVkLF/0Vi9U8D2Ntg4Ga5I5BZpVkVxlJWbSQtXPSiUTtC0TjtGOmxa1AJPuV0CPthew=="
crossOrigin=""
></script>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
export default MyDocument;
@@ -0,0 +1,89 @@
import { useContext, useEffect, useState } from "react";
import { AppContext } from "lib/constants";
import { supabase } from "lib/api";
import dynamic from "next/dynamic";
import styles from "styles/Map.module.css";
import SignIn from "components/SignIn";
import PageLayout from "components/PageLayout";
const MapInput = dynamic(() => import("components/MapInput"), { ssr: false });
function randomPosition() {
var array = [
{
lat: 1.3489728457596013,
lng: 103.77043978311998,
},
{
lat: 1.37462,
lng: 103.77694,
},
{
lat: 1.41006,
lng: 103.77144,
},
{
lat: 1.34313,
lng: 103.84191,
},
{
lat: 1.35454,
lng: 103.69746,
},
];
return array[Math.floor(Math.random() * array.length)];
}
const center = randomPosition();
const zoomLevel = 14;
export default function Page() {
const { session } = useContext(AppContext);
const [userRole, setRole] = useState(null);
const [username, setUsername] = useState(null);
useEffect(() => {
setRole(session?.user?.user_metadata?.role);
setUsername(session?.user?.user_metadata?.username);
}, [session]);
async function onSignOut() {
const { error } = await supabase.auth.signOut();
if (error) console.log("Error logging out:", error.message);
}
return (
<PageLayout title="Driver View">
<h1 className={styles.title}>Driver View</h1>
<p className={styles.description}>
Get started by drag-drop the marker around to simulate location update
</p>
<div className={styles.grid}>
{!session && <SignIn />}
{session && (
<div className={styles.card}>
{userRole?.toUpperCase() == "DRIVER" && (
<MapInput
clientRef={session.user?.id}
center={center}
zoom={zoomLevel}
/>
)}
{userRole?.toUpperCase() == "MANAGER" && (
<p className={styles.error}>
Sorry, You need to sign in as driver
</p>
)}
<div className={styles.profile_container}>
Signed in as {username} [{userRole}]<br />
<button className={styles.sign_out} onClick={onSignOut}>
Sign out
</button>
</div>
</div>
)}
</div>
</PageLayout>
);
}
@@ -0,0 +1,42 @@
import { useContext, useEffect } from "react";
import { AppContext } from "lib/constants";
import Link from "next/link";
import styles from "styles/Home.module.css";
import PageLayout from "components/PageLayout";
export default function Page() {
const { session } = useContext(AppContext);
useEffect(() => {
console.log("session: ", session);
}, []);
return (
<PageLayout title="Live Tracker">
<h1 className={styles.title}>Live Tracker</h1>
<p className={styles.description}>
Get started by choosing your role as Driver or Manager.
</p>
<p className={styles.sub_description}>
You can only login 1 role at a time. Please use incognito mode to test
multi users.
</p>
<div className={styles.grid}>
<Link href="/driver">
<a className={styles.card}>
<h3>Driver View &rarr;</h3>
<p>Find in-depth information about Next.js features and API.</p>
</a>
</Link>
<Link href="/manager">
<a className={styles.card}>
<h3>Manager View &rarr;</h3>
<p>Find in-depth information about Next.js features and API.</p>
</a>
</Link>
</div>
</PageLayout>
);
}
@@ -0,0 +1,63 @@
import { useContext, useEffect, useState } from "react";
import { AppContext } from "lib/constants";
import { supabase } from "lib/api";
import styles from "styles/Map.module.css";
import dynamic from "next/dynamic";
import SignIn from "components/SignIn";
import PageLayout from "components/PageLayout";
const MapView = dynamic(() => import("components/MapView"), { ssr: false });
const center = {
lat: 1.3489728457596013,
lng: 103.77043978311998,
};
const zoomLevel = 12;
export default function Page() {
const { session } = useContext(AppContext);
const [userRole, setRole] = useState(null);
const [username, setUsername] = useState(null);
useEffect(() => {
setRole(session?.user?.user_metadata?.role);
setUsername(session?.user?.user_metadata?.username);
}, [session]);
async function onSignOut() {
const { error } = await supabase.auth.signOut();
if (error) console.log("Error logging out:", error.message);
}
return (
<PageLayout title="Manager View">
<h1 className={styles.title}>Manager View</h1>
<p className={styles.description}>
As manager, you can view all drivers' locations as long as the drivers
are ONLINE
</p>
<div className={styles.grid}>
{!session && <SignIn role="MANAGER" />}
{session && (
<div className={styles.card}>
{userRole?.toUpperCase() == "MANAGER" && (
<MapView center={center} zoom={zoomLevel} />
)}
{userRole?.toUpperCase() == "DRIVER" && (
<p className={styles.error}>
Sorry, you need to sign in as manager
</p>
)}
<div className={styles.profile_container}>
Signed in as {username} [{userRole}]<br />
<button className={styles.sign_out} onClick={onSignOut}>
Sign out
</button>
</div>
</div>
)}
</div>
</PageLayout>
);
}
@@ -1,21 +1,3 @@
.container {
min-height: 100vh;
padding: 0 0.5rem;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.main {
padding: 3rem 0;
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.title {
margin: 0;
line-height: 1.15;
@@ -84,4 +66,4 @@
width: 100%;
flex-direction: column;
}
}
}
@@ -1,21 +1,3 @@
.container {
min-height: 100vh;
padding: 0 0.5rem;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.main {
padding: 3rem 0;
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.title {
margin: 0;
line-height: 1.15;
@@ -74,4 +56,4 @@
.card {
width: 100%;
}
}
}
@@ -2,15 +2,32 @@ html,
body {
padding: 0;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue,
sans-serif;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}
* {
box-sizing: border-box;
}
.container {
min-height: 100vh;
padding: 0 0.5rem;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.main {
padding: 3rem 0;
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.footer {
width: 100%;
height: 60px;
@@ -41,4 +58,4 @@ body {
.loading-spinner {
height: 2rem;
width: 2rem;
}
}
+1 -1
View File
@@ -33,7 +33,7 @@ const Auth = () => {
};
const forgotPassword = async (e) => {
// Read more on https://supabase.io/docs/client/reset-password-email#notes
// Read more on https://supabase.io/docs/reference/javascript/reset-password-email#notes
e.preventDefault();
const email = prompt("Please enter your email:");
+1 -1
View File
@@ -12,7 +12,7 @@ const Home = ({ user }) => {
useEffect(() => {
/* Recovery url is of the form
* <SITE_URL>#access_token=x&refresh_token=y&expires_in=z&token_type=bearer&type=recovery
* Read more on https://supabase.io/docs/client/reset-password-email#notes
* Read more on https://supabase.io/docs/reference/javascript/reset-password-email#notes
*/
let url = window.location.hash;
let query = url.substr(1);
+1 -1
View File
@@ -42,7 +42,7 @@ const supabaseConfig = {
};
```
You can find more about _Supabase Client_ config [on the official documentation page](https://supabase.io/docs/client/initializing).
You can find more about _Supabase Client_ config [on the official documentation page](https://supabase.io/docs/reference/javascript/initializing).
To start a project, run:
```sh
+2
View File
@@ -15,6 +15,8 @@
{ "source": "/", "destination": "http://localhost:3000/new" },
{ "source": "/beta", "destination": "http://localhost:3000/new/beta" },
{ "source": "/blog", "destination": "http://localhost:3000/new/blog" },
{ "source": "/database", "destination": "http://localhost:3000/new/database" },
{ "source": "/auth", "destination": "http://localhost:3000/new/auth" },
{
"source": "/blog/:match*",
"destination": "http://localhost:3000/new/blog/:match*"
+95 -31
View File
@@ -23,10 +23,19 @@
"destination": "https://www-internal.supabase.io/new/blog/:match*"
},
{ "source": "/rss", "destination": "https://www-internal.supabase.io/new/rss.xml" },
{ "source": "/rss.xml", "destination": "https://www-internal.supabase.io/new/rss.xml" },
{ "source": "/blog/rss.xml", "destination": "https://www-internal.supabase.io/new/rss.xml" },
{ "source": "/blog/rss", "destination": "https://www-internal.supabase.io/new/rss.xml" },
{ "source": "/beta", "destination": "https://www-internal.supabase.io/new/beta" },
{ "source": "/database", "destination": "https://www-internal.supabase.io/new/database" },
{ "source": "/auth", "destination": "https://www-internal.supabase.io/new/auth" },
{ "source": "/:match*", "destination": "https://docs-rose.vercel.app/:match*" }
],
"redirects": [
{
"source": "/blog/2021/03/08/toad-a-link-shorterner-with-simple-apis-for-low-coders",
"destination": "/blog/2021/03/08/toad-a-link-shortener-with-simple-apis-for-low-coders"
},
{
"source": "/blog/2020/07/10/surviving-hacker-news",
"destination": "/blog/2020/07/10/alpha-launch-postmortem"
@@ -37,37 +46,37 @@
},
{ "source": "/docs/common/_DummyData", "destination": "/docs" },
{ "source": "/docs/common/_FromFunction", "destination": "/docs" },
{ "source": "/docs/common/filters/_adj", "destination": "/docs/client/ajv" },
{ "source": "/docs/common/filters/_cd", "destination": "/docs/client/cd" },
{ "source": "/docs/common/filters/_cs", "destination": "/docs/client/cs" },
{ "source": "/docs/common/filters/_eq", "destination": "/docs/client/eq" },
{ "source": "/docs/common/filters/_filter", "destination": "/docs/client/filter" },
{ "source": "/docs/common/filters/_gt", "destination": "/docs/client/gt" },
{ "source": "/docs/common/filters/_gte", "destination": "/docs/client/gte" },
{ "source": "/docs/common/filters/_ilike", "destination": "/docs/client/ilike" },
{ "source": "/docs/common/filters/_in", "destination": "/docs/client/in" },
{ "source": "/docs/common/filters/_is", "destination": "/docs/client/is" },
{ "source": "/docs/common/filters/_like", "destination": "/docs/client/like" },
{ "source": "/docs/common/filters/_lt", "destination": "/docs/client/lt" },
{ "source": "/docs/common/filters/_lte", "destination": "/docs/client/lte" },
{ "source": "/docs/common/filters/_match", "destination": "/docs/client/match" },
{ "source": "/docs/common/filters/_neq", "destination": "/docs/client/neq" },
{ "source": "/docs/common/filters/_not", "destination": "/docs/client/not" },
{ "source": "/docs/common/filters/_nxl", "destination": "/docs/client/nxl" },
{ "source": "/docs/common/filters/_nxr", "destination": "/docs/client/nxr" },
{ "source": "/docs/common/filters/_or", "destination": "/docs/client/or" },
{ "source": "/docs/common/filters/_ova", "destination": "/docs/client/ov" },
{ "source": "/docs/common/filters/_ovr", "destination": "/docs/client/ov" },
{ "source": "/docs/common/filters/_sl", "destination": "/docs/client/sl" },
{ "source": "/docs/common/filters/_sr", "destination": "/docs/client/sr" },
{ "source": "/docs/common/filters/_adj", "destination": "/docs/reference/javascript/rangeAdjacent" },
{ "source": "/docs/common/filters/_cd", "destination": "/docs/reference/javascript/containedBy" },
{ "source": "/docs/common/filters/_cs", "destination": "/docs/reference/javascript/contains" },
{ "source": "/docs/common/filters/_eq", "destination": "/docs/reference/javascript/eq" },
{ "source": "/docs/common/filters/_filter", "destination": "/docs/reference/javascript/filter" },
{ "source": "/docs/common/filters/_gt", "destination": "/docs/reference/javascript/gt" },
{ "source": "/docs/common/filters/_gte", "destination": "/docs/reference/javascript/gte" },
{ "source": "/docs/common/filters/_ilike", "destination": "/docs/reference/javascript/ilike" },
{ "source": "/docs/common/filters/_in", "destination": "/docs/reference/javascript/in" },
{ "source": "/docs/common/filters/_is", "destination": "/docs/reference/javascript/is" },
{ "source": "/docs/common/filters/_like", "destination": "/docs/reference/javascript/like" },
{ "source": "/docs/common/filters/_lt", "destination": "/docs/reference/javascript/lt" },
{ "source": "/docs/common/filters/_lte", "destination": "/docs/reference/javascript/lte" },
{ "source": "/docs/common/filters/_match", "destination": "/docs/reference/javascript/match" },
{ "source": "/docs/common/filters/_neq", "destination": "/docs/reference/javascript/neq" },
{ "source": "/docs/common/filters/_not", "destination": "/docs/reference/javascript/not" },
{ "source": "/docs/common/filters/_nxl", "destination": "/docs/reference/javascript/rangeGte" },
{ "source": "/docs/common/filters/_nxr", "destination": "/docs/reference/javascript/rangeLte" },
{ "source": "/docs/common/filters/_or", "destination": "/docs/reference/javascript/or" },
{ "source": "/docs/common/filters/_ova", "destination": "/docs/reference/javascript/overlaps" },
{ "source": "/docs/common/filters/_ovr", "destination": "/docs/reference/javascript/overlaps" },
{ "source": "/docs/common/filters/_sl", "destination": "/docs/reference/javascript/rangeLt" },
{ "source": "/docs/common/filters/_sr", "destination": "/docs/reference/javascript/rangeGt" },
{ "source": "/docs/library/authentication", "destination": "/docs/guides/auth" },
{ "source": "/docs/library/delete", "destination": "/docs/client/delete" },
{ "source": "/docs/library/get", "destination": "/docs/client/select" },
{ "source": "/docs/library/getting-started", "destination": "/docs/client/supabase-client" },
{ "source": "/docs/library/patch", "destination": "/docs/client/update" },
{ "source": "/docs/library/post", "destination": "/docs/client/insert" },
{ "source": "/docs/library/stored-procedures", "destination": "/docs/client/rpc" },
{ "source": "/docs/library/subscribe", "destination": "/docs/client/subscribe" },
{ "source": "/docs/library/delete", "destination": "/docs/reference/javascript/delete" },
{ "source": "/docs/library/get", "destination": "/docs/reference/javascript/select" },
{ "source": "/docs/library/getting-started", "destination": "/docs/reference/javascript/supabase-client" },
{ "source": "/docs/library/patch", "destination": "/docs/reference/javascript/update" },
{ "source": "/docs/library/post", "destination": "/docs/reference/javascript/insert" },
{ "source": "/docs/library/stored-procedures", "destination": "/docs/reference/javascript/rpc" },
{ "source": "/docs/library/subscribe", "destination": "/docs/reference/javascript/subscribe" },
{ "source": "/docs/library/user-management", "destination": "/docs/guides/auth" },
{ "source": "/docs/postgres/postgres-intro", "destination": "/docs/postgres/server/about" },
{ "source": "/docs/realtime/about", "destination": "/docs/realtime/server/about" },
@@ -77,7 +86,62 @@
"destination": "/docs/postgres/server/digitalocean"
},
{ "source": "/docs/realtime/docker", "destination": "/docs/postgres/server/docker" },
{ "source": "/docs/realtime/source", "destination": "/docs/postgres/server/about" }
{ "source": "/docs/realtime/source", "destination": "/docs/postgres/server/about" },
{ "source": "/docs/client/supabase-client", "destination": "/docs/reference/javascript/supabase-client" },
{ "source": "/docs/client/installing", "destination": "/docs/reference/javascript/installing" },
{ "source": "/docs/client/initializing", "destination": "/docs/reference/javascript/initializing" },
{ "source": "/docs/client/generating-types", "destination": "/docs/reference/javascript/generating-types" },
{ "source": "/docs/client/auth-signup", "destination": "/docs/reference/javascript/auth-signup" },
{ "source": "/docs/client/auth-signin", "destination": "/docs/reference/javascript/auth-signin" },
{ "source": "/docs/client/auth-signout", "destination": "/docs/reference/javascript/auth-signout" },
{ "source": "/docs/client/auth-session", "destination": "/docs/reference/javascript/auth-session" },
{ "source": "/docs/client/auth-user", "destination": "/docs/reference/javascript/auth-user" },
{ "source": "/docs/client/auth-update", "destination": "/docs/reference/javascript/auth-update" },
{ "source": "/docs/client/auth-onauthstatechange", "destination": "/docs/reference/javascript/auth-onauthstatechange" },
{ "source": "/docs/client/reset-password-email", "destination": "/docs/reference/javascript/reset-password-email" },
{ "source": "/docs/client/select", "destination": "/docs/reference/javascript/select" },
{ "source": "/docs/client/insert", "destination": "/docs/reference/javascript/insert" },
{ "source": "/docs/client/update", "destination": "/docs/reference/javascript/update" },
{ "source": "/docs/client/delete", "destination": "/docs/reference/javascript/delete" },
{ "source": "/docs/client/rpc", "destination": "/docs/reference/javascript/rpc" },
{ "source": "/docs/client/subscribe", "destination": "/docs/reference/javascript/subscribe" },
{ "source": "/docs/client/removesubscription", "destination": "/docs/reference/javascript/removesubscription" },
{ "source": "/docs/client/getsubscriptions", "destination": "/docs/reference/javascript/getsubscriptions" },
{ "source": "/docs/client/using-modifiers", "destination": "/docs/reference/javascript/using-modifiers" },
{ "source": "/docs/client/limit", "destination": "/docs/reference/javascript/limit" },
{ "source": "/docs/client/order", "destination": "/docs/reference/javascript/order" },
{ "source": "/docs/client/range", "destination": "/docs/reference/javascript/range" },
{ "source": "/docs/client/single", "destination": "/docs/reference/javascript/single" },
{ "source": "/docs/client/using-filters", "destination": "/docs/reference/javascript/using-filters" },
{ "source": "/docs/client/filter", "destination": "/docs/reference/javascript/filter" },
{ "source": "/docs/client/or", "destination": "/docs/reference/javascript/or" },
{ "source": "/docs/client/not", "destination": "/docs/reference/javascript/not" },
{ "source": "/docs/client/match", "destination": "/docs/reference/javascript/match" },
{ "source": "/docs/client/eq", "destination": "/docs/reference/javascript/eq" },
{ "source": "/docs/client/neq", "destination": "/docs/reference/javascript/neq" },
{ "source": "/docs/client/gt", "destination": "/docs/reference/javascript/gt" },
{ "source": "/docs/client/gte", "destination": "/docs/reference/javascript/gte" },
{ "source": "/docs/client/lt", "destination": "/docs/reference/javascript/lt" },
{ "source": "/docs/client/lte", "destination": "/docs/reference/javascript/lte" },
{ "source": "/docs/client/like", "destination": "/docs/reference/javascript/like" },
{ "source": "/docs/client/ilike", "destination": "/docs/reference/javascript/ilike" },
{ "source": "/docs/client/is", "destination": "/docs/reference/javascript/is" },
{ "source": "/docs/client/in", "destination": "/docs/reference/javascript/in" },
{ "source": "/docs/client/gte", "destination": "/docs/reference/javascript/gte" },
{ "source": "/docs/client/cs", "destination": "/docs/reference/javascript/contains" },
{ "source": "/docs/client/cd", "destination": "/docs/reference/javascript/containedBy" },
{ "source": "/docs/client/sl", "destination": "/docs/reference/javascript/rangeLt" },
{ "source": "/docs/client/sr", "destination": "/docs/reference/javascript/rangeGt" },
{ "source": "/docs/client/nxl", "destination": "/docs/reference/javascript/rangeGte" },
{ "source": "/docs/client/nxr", "destination": "/docs/reference/javascript/rangeLte" },
{ "source": "/docs/client/adj", "destination": "/docs/reference/javascript/rangeAdjacent" },
{ "source": "/docs/client/ov", "destination": "/docs/reference/javascript/overlaps" },
{ "source": "/docs/client/ova", "destination": "/docs/reference/javascript/overlaps" },
{ "source": "/docs/client/fts", "destination": "/docs/reference/javascript/textSearch" },
{ "source": "/docs/client/plfts", "destination": "/docs/reference/javascript/textSearch" },
{ "source": "/docs/client/phfts", "destination": "/docs/reference/javascript/textSearch" },
{ "source": "/docs/client/wfts", "destination": "/docs/reference/javascript/textSearch" }
],
"trailingSlash": false
}
+3 -3
View File
@@ -17,9 +17,9 @@ Today we're releasing [supabase-js](https://github.com/supabase/supabase-js) ver
### New Docs
Before digging into the improvements, we're excited to point out our new [developer docs](/docs/client/supabase-client). While they're still a work in progress, here are some things we think you'll like:
Before digging into the improvements, we're excited to point out our new [developer docs](/docs/reference/javascript/supabase-client). While they're still a work in progress, here are some things we think you'll like:
- The [Reference Docs](/docs/client/supabase-client) are auto-generated from our TypeScript definitions and then enriched with examples. This forces us to document our code and makes it easier to keep everything in sync.
- The [Reference Docs](/docs/reference/javascript/supabase-client) are auto-generated from our TypeScript definitions and then enriched with examples. This forces us to document our code and makes it easier to keep everything in sync.
- We added placeholders for the other languages that the community is developing. They have already started with Python, C#, Dart, Rust, and Swift. Expect to see the docs filling up soon!
- We've added sections for all of the open source tools we use, including [Postgres](/docs/postgres/server/about), [PostgREST](/docs/postgrest/server/about), [GoTrue](/docs/gotrue/server/about), and [Realtime](/docs/realtime/server/about). We'll be filling these with lots of valuable information including self-hosting, benchmarks, and simple guides.
@@ -114,7 +114,7 @@ To summarise the steps:
1. Install the new version: `npm install @supabase/supabase-js@latest`
2. Update all your `body` constants to `data`
3. Update all your `supabase.auth` functions with the new [Auth interface](/docs/client/auth-signup)
3. Update all your `supabase.auth` functions with the new [Auth interface](/docs/reference/javascript/auth-signup)
### Get started
@@ -47,7 +47,7 @@ One of the most requested Auth features was the ability to send magic links that
### Kaizen
- We have new and improved [docs](https://supabase.io/docs/client/supabase-client).
- We have new and improved [docs](https://supabase.io/docs/reference/javascript/supabase-client).
- We converted [realtime-js](https://github.com/supabase/realtime-js/) to TypeScript.
- Dashboard Performance: we heavily optimised our dashboard routes.
- With the help of the community, we [closed a lot of issues](https://github.com/orgs/supabase/projects/5) during Hacktoberfest.
@@ -108,7 +108,7 @@ CREATE POLICY "Individuals can only write their own messages." ON messages FOR
### Know how to do business logic on SQL/[PLPGSQL](https://www.postgresql.org/docs/current/plpgsql-overview.html#PLPGSQL-ADVANTAGES)
PL/pgSQL is a procedural programming language that can be used to write functions inside of your database. It can be useful for making [remote procedure calls](https://supabase.io/docs/client/rpc) from an API. You can go as deep as you want here, since it's an entire programming language, but understanding the basics will really go a long way, and give you super powers when working with your data.
PL/pgSQL is a procedural programming language that can be used to write functions inside of your database. It can be useful for making [remote procedure calls](https://supabase.io/docs/reference/javascript/rpc) from an API. You can go as deep as you want here, since it's an entire programming language, but understanding the basics will really go a long way, and give you super powers when working with your data.
You can use FUNCTIONs in combination with TRIGGERs to do cool stuff like have auto-updating `updated_at` columns on your data:
@@ -39,7 +39,7 @@ Writing SQL just got 10x easier. We added autocomplete to the SQL editor, includ
### Auth Redirects
Redirect your users to specific route within your site on [`signIn()`](https://supabase.io/docs/client/auth-signin#sign-in-with-redirect) and [`signUp()`](https://supabase.io/docs/client/auth-signup#sign-up-with-redirect).
Redirect your users to specific route within your site on [`signIn()`](https://supabase.io/docs/reference/javascript/auth-signin#sign-in-with-redirect) and [`signUp()`](https://supabase.io/docs/reference/javascript/auth-signup#sign-up-with-redirect).
![Redirect your users after sign up](/img/blog/feb/auth-redirect.png)
@@ -1,5 +1,5 @@
---
title: Toad, a link shorterner with simple APIs for low-coders
title: Toad, a link shortener with simple APIs for low-coders
description: An easy-to-use link shortening tool with simple APIs
author: Rory Wilding
author_title: Supabase
+9 -9
View File
@@ -13,7 +13,7 @@ tags:
- Postgres
---
[Replit.com](http://replit.com) is an awesome new browser based IDE where you can code alone or collaboratively with friends using their awesome multiplayer features! It's particularly useful for education, and sharing code examples with others.
[Replit.com](http://replit.com) is an awesome new browser based IDE where you can code alone or collaboratively with friends using their amazing multiplayer features! It's particularly useful for education, and sharing code examples with others.
They support a ton of different languages and execution environments and even recently introduced a simple key value store you can use to persist data.
@@ -89,14 +89,14 @@ const main = async() => {
main()
```
Once this is working, if you want to learn more about the [query interface](https://supabase.io/docs/client/filter) you might want to try some of these challenges:
Once this is working, if you want to learn more about the [query interface](https://supabase.io/docs/reference/javascript/filter) you might want to try some of these challenges:
```jsx
// 1. List all the countries in Antarctica
// 2. Fetch the iso3 code of the country with ID 3
// 3. List the countries with 'Island' in the name
// 4. Count the number of countries that start with 'Z' or 'Q'
// 5. Fetch all the Countries where continents is null
```
1. List all the countries in Antarctica
2. Fetch the iso3 code of the country with ID 3
3. List the countries with 'Island' in the name
4. Count the number of countries that start with 'Z' or 'Q'
5. Fetch all the Countries where continents is null
```
There are full solutions provided in the video version of this blog, but some examples you may find useful are:
@@ -126,7 +126,7 @@ const { data, error } = await supabase
.select('name, country_id')
.neq('name', 'The shire')
// full docs here: https://supabase.io/docs/client/filter
// full docs here: https://supabase.io/docs/reference/javascript/filter
```
We look forward to showing off some more Supabase + Replit examples.
+177 -2
View File
@@ -14,7 +14,8 @@ Supabase makes it simple to manage your users.
When a user signs up, Supabase assigns them a unique ID. You can reference this ID anywhere in your database. For example, you might create a `profiles` table referencing the user using a `user_id` field.
Supabase provides the routes to [sign up](/docs/client/auth-signup), [log in](/docs/client/auth-signin), [log out](/docs/client/auth-signout), and manage users in your apps and websites.
Supabase provides the routes to [sign up](/docs/reference/javascript/auth-signup), [log in](/docs/reference/javascript/auth-signin),
[log out](/docs/reference/javascript/auth-signout), and manage users in your apps and websites.
### Third Party Logins
@@ -103,9 +104,183 @@ where auth.uid() = todos.user_id; -- Policy is implicitly added.
5. The user's UID can be used in Policies to restrict access to rows.
Supabase provides a special function in Postgres, `auth.uid()`, which extracts the user's UID from the JWT. This is especially useful when creating Policies.
## Policy Examples
Here are some examples to show you the power of PostgreSQL's Row Level Security. Each policy is attached to a table, and the policy is executed
every time a the table is accessed.
### Allow read access
```sql
-- 1. Create table
create table profiles (
id uuid references auth.users,
avatar_url text
);
-- 2. Enable RLS
alter table profiles
enable row level security;
-- 3. Create Policy
create policy "Public profiles are viewable by everyone."
on profiles for select using (
true
);
```
1. Creates a table called `profiles` in the public schema (default schema)
2. Enables Row Level Security
3. Creates a Policy which allows all `select` queries to run.
### Restrict updates
```sql
-- 1. Create table
create table profiles (
id uuid references auth.users,
avatar_url text
);
-- 2. Enable RLS
alter table profiles
enable row level security;
-- 3. Create Policy
create policy "Users can update their own profiles."
on profiles for update using (
auth.uid() = id
);
```
1. Creates a table called `profiles` in the public schema (default schema)
2. Enables Row Level Security
3. Creates a Policy which allows logged in users to update their own data.
### Policies with joins
Policies can even include table joins. This example shows how you can query "external" tables to build more advanced rules.
```sql
create table teams (
id serial primary key,
name text
);
-- 2. Create many to many join
create table members (
team_id bigint references teams,
user_id uuid references auth.users
);
-- 3. Enable RLS
alter table teams
enable row level security;
-- 4. Create Policy
create policy "Team members can update team details if they belong to the team."
on teams
for update using (
auth.uid() in (
select user_id from members
where team_id = id
)
);
```
### Verifying email domains
Postgres has a function `right(string, n)` that returns the rightmost n characters of a string.
You could use this to match staff member's email domains.
```sql
-- 1. Create table
create table leaderboard (
id uuid references auth.users,
high_score bigint
);
-- 2. Enable RLS
alter table leaderboard
enable row level security;
-- 3. Create Policy
create policy "Only Blizzard staff can update leaderboard"
on leaderboard
for update using (
right(auth.email(), 13) = '@blizzard.com'
);
```
### Advanced Policies
Use the full power of SQL to build extremely advanced rules.
In this example we will create a `posts` and `comments` database and then create a policy that depends on another policy.
(In this case the comments policy depends on the posts policy.)
```sql
create table posts (
id serial primary key,
creator_id uuid not null references auth.users(id),
title text not null,
body text not null,
publish_date date not null default now(),
audience uuid[] null -- many to many table ommited for brevity
);
create table comments (
id serial primary key,
post_id int not null references posts(id) on delete cascade,
user_id uuid not null references auth.users(id),
body text not null,
comment_date date not null default now()
);
create policy "Creator can see thier own posts"
on posts
for select
using (
auth.uid() = posts.creator_id
);
create policy "Logged in users can see the posts if they belong to the post 'audience'."
on posts
for select
using (
auth.uid() = any (posts.audience)
);
create policy "Users can see all comments for posts they have access to."
on comments
for select
using (
exists (
select 1 from posts
where posts.id = comments.post_id
)
);
```
## Tips
### You don't have to use Policies
You can also put your authorization rules in your middleware,
similar to how you would create security rules with any other `backend <-> middleware <-> frontend` architecture.
Policies are a tool. In the case of "serverless/Jastack" setups, they are especially effective because you don't have to deploy any middleware at all.
However if you want to use another Authorization method for your applications, that's also fine. Supabase is "just Postgres", so if your application
works with Postgres, then it also works with Supabase.
Tip: make sure to enable Row Level Security for all your tables, so that your tables are inaccessible. Then use the "Service" which we provide.
"Service" are designed to bypass RLS.
### Check authentication settings on Supabase
Navigate to Authentication > Settings on [app.supabase.io](https://app.supabase.io), and you'll be able to change settings for things like:
+11 -11
View File
@@ -9,12 +9,12 @@ import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
The [Supabase Client](/docs/client/supabase-client) makes it simple for developers to build secure and scalable products.
The [Supabase Client](/docs/reference/javascript/supabase-client) makes it simple for developers to build secure and scalable products.
## Auth
Create new users using [`signUp()`](/docs/client/auth-signup).
Create new users using [`signUp()`](/docs/reference/javascript/auth-signup).
<Tabs
groupId="libraries"
@@ -45,7 +45,7 @@ const { error, data } = await supabase.auth.signUp({
Existing users can log in using [`signIn()`](/docs/client/auth-signin).
Existing users can log in using [`signIn()`](/docs/reference/javascript/auth-signin).
<Tabs
groupId="libraries"
@@ -75,7 +75,7 @@ const { error, data } = await supabase.auth.signIn({
</Tabs>
If there is an email, but no password passed to [`signIn()`](/docs/client/auth-signin), the user will receive a magic link.
If there is an email, but no password passed to [`signIn()`](/docs/reference/javascript/auth-signin), the user will receive a magic link.
<Tabs
groupId="libraries"
@@ -104,7 +104,7 @@ const { error, data } = await supabase.auth.signIn({
</Tabs>
Third party logins are also handled through [`signIn()`](/docs/client/auth-signin).
Third party logins are also handled through [`signIn()`](/docs/reference/javascript/auth-signin).
<Tabs
@@ -136,7 +136,7 @@ const { user, error } = await supabase.auth.signIn({
## Managing data
Since Postgres is a Relational database, the client makes it simple to query tables and fetch related data in one round-trip, using [`select()`](/docs/client/select).
Since Postgres is a Relational database, the client makes it simple to query tables and fetch related data in one round-trip, using [`select()`](/docs/reference/javascript/select).
<Tabs
@@ -171,7 +171,7 @@ const { data, error } = await supabase
</Tabs>
You can do advanced [filtering](/docs/client/using-filters) to extract only the data that you need.
You can do advanced [filtering](/docs/reference/javascript/using-filters) to extract only the data that you need.
<Tabs
@@ -203,7 +203,7 @@ const { data, error } = await supabase
</Tabs>
You can create data easily using [`insert()`](/docs/client/insert).
You can create data easily using [`insert()`](/docs/reference/javascript/insert).
@@ -239,7 +239,7 @@ const { data, error } = await supabase
## Realtime Changes
The Supabase client makes it simple to listen to realtime database changes, using [`subscribe()`](/docs/client/subscribe).
The Supabase client makes it simple to listen to realtime database changes, using [`subscribe()`](/docs/reference/javascript/subscribe).
<Tabs
@@ -272,7 +272,7 @@ const mySubscription = supabase
</Tabs>
You can even [listen to Row Level changes](/docs/client/subscribe#listening-to-row-level-changes).
You can even [listen to Row Level changes](/docs/reference/javascript/subscribe#listening-to-row-level-changes).
<Tabs
@@ -306,5 +306,5 @@ const mySubscription = supabase
## Next steps
- View the [Client Docs](/docs/client/supabase-client)
- View the [Client Docs](/docs/reference/javascript/supabase-client)
- Sign in: [app.supabase.io](https://app.supabase.io)
+73 -10
View File
@@ -4,6 +4,8 @@ title: Database
description: Use Supabase to manage your data.
---
import ExtensionsComponent from '../../src/components/Extensions'
Supabase is built on top of [Postgres](/docs/postgres/server/about), an extremely scalable Relational Database.
## Features
@@ -33,14 +35,6 @@ You can duplicate your tables, just like you would inside a spreadsheet.
<source src="/videos/duplicate-tables.mp4" type="video/mp4" muted playsInline />
</video>
### Enable and disable extensions
Extensions are one of the best things about Postgres. You can enable Postgres extensions with the click of a button.
<video width="99%" muted playsInline controls={true}>
<source src="/videos/toggle-extensions.mp4" type="video/mp4" muted playsInline />
</video>
### The SQL Editor
@@ -58,21 +52,90 @@ Supabase comes with a SQL Editor. You can also save your favorite queries to run
- Managed backups - Supabase handles all your database backups.
- Data imports - import directly from a CSV or excel spreadsheet.
## Extensions
### Enable and disable extensions
To expand the functionality of your Postgres database, you can use extensions.
You can enable Postgres extensions with the click of a button within the Supabase dashboard.
<video width="99%" muted playsInline controls={true}>
<source src="/videos/toggle-extensions.mp4" type="video/mp4" muted playsInline />
</video>
### Full list of extensions
Supabase is pre-configured with over 50 extensions. You can also install your own SQL extensions directly into the database through our SQL editor.
<ExtensionsComponent />
## Tips
### Realtime
Supabase provides a realtime engine on top of Postgres, so that you can listen to changes as they happen.
Our realtime engine uses the built-in replication functionality of Postgres.
You can manage the realtime system, simply by
[updating](http://localhost:3006/docs/reference/postgres/publications) the `supabase_realtime` publication.
For example to enable realtime only for individual tables:
```sql
begin;
-- remove the realtime publication
drop publication if exists supabase_realtime;
-- re-create the publication but don't enable it for any tables
create publication supabase_realtime;
commit;
-- add a table to the publication
alter publication supabase_realtime add table products;
-- add other tables to the publication
alter publication supabase_realtime add table posts;
```
By default only "new" values are sent, but if you want to receive the old record (previous values) whenever you `update` or `delete` a record,
you can update the replica identity of your tables, setting it to `full`:
```sql
alter table_name your_table
replica identity full;
```
### Resetting your project password
When you create a new project in Supabase we ask for a password. You can use this password to connect direcly to your Postgres database.
If you forget your password, you can reset it from the Dashboard SQL editor:
For example:
```sql
alter user postgres
with password 'new_password';
```
Read more in [Database Configuration](/docs/reference/postgres/database-passwords).
### Changing the timezone of your server.
Your database is initialized with the UTC timezone. We recommend keeping it this way, as it is helpful for time calculation.
Your database is initialized with the UTC timezone. We recommend keeping it this way, as it is helpful for time calculations.
If, however, you want to update the timezone, you can do so using any of the [database timezones](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones).
For example:
```sql
alter database postgres set timezone to 'Asia/Singapore';
alter database postgres set timezone to 'America/New_York';
```
Read more in [Database Configuration](/docs/reference/postgres/changing-timezones).
## Next steps
- Read more about [Postgres](/docs/postgres/server/about)
@@ -12,6 +12,71 @@ How to add Google OAuth Logins to your Supabase Application.
<iframe className="w-full video-with-border" width="640" height="385" src="https://www.youtube-nocookie.com/embed/_XM9ziOzWk4" frameBorder="1" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowFullScreen></iframe>
### Logging in with external OAuth providers
Connecting social logins such as Google, Github, or Facebook couldn't be easier. In this guide we'll walk you through the process of connecting Google, but the process is basically the same for all of the providers which includes: azure, bitbucket, github, gitlab, facebook, and google.
First you'll need to create a google project inside their [cloud console](https://console.cloud.google.com/home/dashboard), in other providers they may refer to this as an "app" and is usually available on the company's developer portal.
![Create a new Google Project inside cloud console](/img/auth-5-1.png)
Once you have a project, type "OAuth" into the search bar and open up "OAuth Consent Screen"
![Open the OAuth consent screen](/img/auth-5-2.png)
Select 'External' and proceed to fill out the rest of the form fields
![Select External on the OAuth form](/img/auth-5-3.png)
Next open up Credentials page on the left
![Open up Credentials page](/img/auth-5-4.png)
And click to create a new set of credentials, select OAuth client ID as the option
![Create new oauth client id credentials](/img/auth-5-5.png)
Now choose Web Application (assuming you're creating a web app) and in the Authorized redirect URI section you need to add: `https://<your-ref>/supabase.co/auth/v1/callback`. You can find your Supabase URL in Settings > API inside the Supabase dashboard.
![Add your redirect URI](/img/auth-5-6.png)
Now you can grab the client ID and secret from the pop up, and insert them into the google section inside the Supabase dashboard in Auth > Settings:
![take client id and secret](/img/auth-5-7.png)
![insert client id and secret into supabase dashboard in auth > auth](/img/auth-5-8.png)
Hit save. Now you should be able to navigate in the browser to:
```
https://<your-ref>.supabase.co/auth/v1/authorize?provider=google
```
And log in to your service using any google or gmail account.
You can additionally add a query parameter `redirect_to=` to the end of the URL for example:
```
https://<your-ref>.supabase.co/auth/v1/authorize?provider=google&redirect_to=http://localhost:3000/welcome
```
But make sure any URL you enter here is on the same host as the site url that you have entered on the Auth > Settings page on the Supabase dashboard. (There is additional functionality coming soon, where you'll be able to add additional URLs to the allow list).
If you want to redirect the user to a specific page in your website or app after a successful authentication.
You also have the option of requesting additional scopes from the oauth provider. Let's say for example you want the ability to send emails on behalf of the user's gmail account. You can do this by adding the query parameter `scopes`, like:
```
https://<your-ref>.supabase.co/auth/v1/authorize?provider=google&https://www.googleapis.com/auth/gmail.send
```
Note however that your app will usually have to be verified by Google before you can request advanced scopes such as this.
The only thing left to implement is the UI, but if you prefer to use something pre-built, we have a handy [Auth Widget](https://github.com/supabase/ui/#using-supabase-ui-auth), where you can enable/disable whichever auth providers you want to support.
For any support please get in touch at beta at supabase.io or for feature requests open an issue in the [backend](https://github.com/supabase/gotrue) or [frontend](https://github.com/supabase/gotrue-js) repos.
### Resources
- JWT debugger: https://jwt.io/​
+45
View File
@@ -12,6 +12,51 @@ How to restrict table access to authenticated users, row level policies, and ema
<iframe className="w-full video-with-border" width="640" height="385" src="https://www.youtube.com/embed/neqfYym_84k" frameBorder="1" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowFullScreen></iframe>
### Gotrue Server
Gotrue is an auth API server written in Go by the Netlify team, find the Supabase fork here: https://github.com/supabase/gotrue. The list of available API endpoints is available [here](https://github.com/supabase/gotrue#endpoints).
When you deploy a new Supabase project, we deploy a new instance of this server alongside your database, and also inject your database with the required `auth` schema.
It makes it super easy to, for example, send magic link emails which your user's can use to login:
```bash
# replace <project-ref> with your own project reference
# and SUPABASE_KEY with your anon api key
curl -X POST 'https://<project-ref>.supabase.co/auth/v1/magiclink' \
-H "apikey: SUPABASE_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "someone@email.com"
}'
```
Gotrue is responsible for issuing access tokens for your users, sends confirmation, magic-link, and password recovery emails (by default we send these from a Supabase SMTP server, but you can easily plug in your own inside the dashboard at Auth > Settings) and also transacting with third party OAuth providers to get basic user data.
The community even recently built in the functionality to request custom OAuth scopes, if your users need to interact more closely with the provider. See the scopes parameter here: [https://github.com/supabase/gotrue#get-authorize](https://github.com/supabase/gotrue#get-authorize).
So let's say you want to send emails on behalf of a user via gmail, you might request the gmail.send scope by directing them to:
```
https://sjvwsaokcugktsdaxxze.supabase.co/auth/v1/authorize?provider=google&https://www.googleapis.com/auth/gmail.send
```
You'll have to make sure your google app is verified of course in order to request these advanced scopes.
[Gotrue-js](https://github.com/supabase/gotrue-js) (and also [gotrue-csharp](https://github.com/supabase/gotrue-csharp), [gotrue-py](https://github.com/j0/gotrue-py), [gotrue-kt](https://github.com/supabase/gotrue-kt), and [gotrue-dart](https://github.com/supabase/gotrue-dart)) are all wrappers around the gotrue API endpoints, and make for easier session management inside your client.
But all the functionality of gotrue-js is also available in supabase-js, which uses gotrue-js internall when you do things like:
```jsx
const { user, session, error } = await supabase.auth.signIn({
email: 'example@email.com',
password: 'example-password',
})
```
If you want to request a feature, or contribute to the project directly, just head to https://github.com/supabase/gotrue and open some issues/PRs, we're always open to help.
In the next guide we'll be looking at how to setup external OAuth providers: Watch [Part Five: Google Oauth](/docs/learn/auth-deep-dive/auth-google-oauth)
### Resources
+9 -2
View File
@@ -13,7 +13,7 @@ An introduction to JWTs and how they are used in Supabase Auth
<iframe className="w-full video-with-border" width="640" height="385" src="https://www.youtube-nocookie.com/embed/v3Exg5YpJvE" frameBorder="1" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowFullScreen></iframe>
### What are JSON Web Tokens (JWTs)
### What are JSON Web Tokens (JWTs)?
JWTs are JSON objects that are encoded and signed and sent around as a string. They are distributed to users of a service or website, who can later show the JWT to the website or service, as proof that they have the right to access certain content.
@@ -47,7 +47,14 @@ Well, the JSON object starts out looking something like this:
Just note that, the more data you store in your token, the longer the encoded string will be.
When we want to send the JWT to the user, we first encode the data using an algorithm such as `HS256`, there are many libraries (and several different algorithms) that can be used to do this encoding/decoding such as [jsonwebtoken](https://www.npmjs.com/package/jsonwebtoken). The resulting string will look like this:
When we want to send the JWT to the user, we first encode the data using an algorithm such as `HS256`, there are many libraries (and several different algorithms) that can be used to do this encoding/decoding such as [jsonwebtoken](https://www.npmjs.com/package/jsonwebtoken). I made a repl [here](https://replit.com/@awalias/jsonwebtokens#index.js) so you can try it for yourself. The signing is as simple as:
```js
// from https://replit.com/@awalias/jsonwebtokens#index.js
let token = jwt.sign({ name: 'Sam Vimes' }, 'some-secret');
```
And the resulting string will look like this:
```js
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIwMDAxIiwibmFtZSI6IlNhbSBWaW1lcyIsImlhdCI6MTUxNjIzOTAyMiwiZXhwIjoxNTE4MjM5MDIyfQ.zMcHjKlkGhuVsiPIkyAkB2rjXzyzJsMMgpvEGvGtjvA
+142
View File
@@ -12,10 +12,152 @@ How to restrict table access to authenticated users, row level policies, and ema
<iframe className="w-full video-with-border" width="640" height="385" src="https://www.youtube-nocookie.com/embed/0LvCOlELs5U" frameBorder="1" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowFullScreen></iframe>
### User based row level policies
Now that we know how to restrict access to tables based on JWT roles, we can combine this with user management to give us much more control over what data your users can read to and write from your database.
We'll start with how user sessions work in Supabase, and later move onto writing user-centric policies.
Let's say we're signing a user up to our service for the first time. The typical way to do this is by invoking the following method in supabase-js:
```jsx
// see full api reference here: https://supabase.io/docs/reference/javascript/auth-signup
supabase.auth.signUp({ email, password })
```
By default this will send a confirmation email to the user. When the user clicks the link in the email, they will be redirected to your site (you need to provide your site url in Auth > Settings on the dashboard. By default this is http://localhost:3000) and the full URL including query params will look something like this:
```
http://localhost:3000/#access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJhdXRoZW50aWNhdGVkIiwiZXhwIjoxNjE2NDI5MDY0LCJzdWIiOiI1YTQzNjVlNy03YzdkLTRlYWYtYThlZS05ZWM5NDMyOTE3Y2EiLCJlbWFpbCI6ImFudEBzdXBhYmFzZS5pbyIsImFwcF9tZXRhZGF0YSI6eyJwcm92aWRlciI6ImVtYWlsIn0sInVzZXJfbWV0YWRhdGEiOnt9LCJyb2xlIjoiYXV0aGVudGljYXRlZCJ9.4IFzn4eymqUNYYo2AHLxNRL8m08G93Qcg3_fblGqDjo&expires_in=3600&refresh_token=RuioJv2eLV05lgH5AlJwTw&token_type=bearer&type=signup
```
Let's break this up so that it's easier to read:
```jsx
// the base url - whatever you set in the Auth Settings in app.supabase.io dashboard
http://localhost:3000/
// note we use the '#' (fragment) instead of '?' query param
// the access token is a JWT issued to the user
#access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJhdXRoZW50aWNhdGVkIiwiZXhwIjoxNjE2NDI5MDY0LCJzdWIiOiI1YTQzNjVlNy03YzdkLTRlYWYtYThlZS05ZWM5NDMyOTE3Y2EiLCJlbWFpbCI6ImFudEBzdXBhYmFzZS5pbyIsImFwcF9tZXRhZGF0YSI6eyJwcm92aWRlciI6ImVtYWlsIn0sInVzZXJfbWV0YWRhdGEiOnt9LCJyb2xlIjoiYXV0aGVudGljYXRlZCJ9.4IFzn4eymqUNYYo2AHLxNRL8m08G93Qcg3_fblGqDjo
// valid for 60 minutes by default
&expires_in=3600
// use to get a new access_token before 60 minutes expires
&refresh_token=RuioJv2eLV05lgH5AlJwTw
// can use as the Authorization: Bearer header in requests to your API
&token_type=bearer
// why was this token issued? was it a signup, login, password reset, or magic link?
&type=signup
```
If we put the access_token into [https://jwt.io](https://jwt.io) we'll see it decodes to:
```jsx
{
"aud": "authenticated",
"exp": 1616429064,
"sub": "5a4365e7-7c7d-4eaf-a8ee-9ec9432917ca",
"email": "ant@supabase.io",
"app_metadata": {
"provider": "email"
},
"user_metadata": {},
"role": "authenticated"
}
```
The `authenticated` role is special in Supabase, it tells the API that this is an authenticated user and will know to compare the JWT against any polices you've added to the requested resource (table or row).
The `sub` claim is usually what we use to match the JWT to rows in your database, since by default it is the unique identifier of the user in the `auth.users` table (as a sidenote - it's generally not recommended to alter the `auth` schema in any way in your Supabase database since the Auth API relies on it to function correctly).
For the curious, try heading to the SQL editor and querying:
```sql
select * from auth.users;
```
If supabase-js is loaded on your site (in this case http://localhost:3000) then it will automatically pluck the access_token out of the URL and initiate a session. You can check the [session()](https://supabase.io/docs/reference/javascript/auth-session) method to see if there is a valid session:
```jsx
console.log(supabase.auth.session())
```
Now that we can use methods like `supabase.auth.signIn({ email, password})` to issue JWTs to users we want to start fetching resources specific to that user. So let's make some. Go to the SQL editor and run:
```sql
create table my_scores (
name text,
score int,
user_id uuid not null
);
ALTER TABLE my_scores ENABLE ROW LEVEL SECURITY;
insert into my_scores(name, score, user_id)
values
('Paul', 100, '5a4365e7-7c7d-4eaf-a8ee-9ec9432917ca'),
('Paul', 200, '5a4365e7-7c7d-4eaf-a8ee-9ec9432917ca'),
('Leto', 50, '9ec94326-2e2d-2ea2-22e3-3a535a4365e7');
-- use UUIDs from the auth.users table if you want to try it
-- for yourself
```
Now we'll write our policy, again in SQL, but note it's also possible to add via the dashboard in Auth > Policies:
```sql
CREATE POLICY user_update_own_scores ON my_scores
FOR ALL
USING (auth.uid() = user_id);
```
Now, assuming you have an active session in your javascript/supabase-js environment you can do:
```jsx
supabase
.from('my_scores')
.select('*')
.then(console.log)
```
and you should only receive scores belonging to the current logged in user. Alternatively you can use Bash like:
```bash
curl 'https://sjvwsaokcugktsdaxxze.supabase.co/rest/v1/my_scores?select=*' \
-H "apikey: <ANON_KEY>" \
-H "Authorization: Bearer <ACCESS_TOKEN>"
```
Note that the `anon key` (or `service role key`) is always needed to get past the API gateway. This can be passed in the `apikey` header or in a query param named `apikey`. It is passed automatically in supabase-js as long as you used it to instantiate the client.
There are some more notes here on how to structure your schema to best integrate with the `auth.users` table.
Once you get the hang of polices you can start to get a little bit fancy. Let's say I work at Blizzard and I only want Blizzard staff members to be able to update people's high scores, I can write something like:
```sql
create policy "Only Blizzard staff can update leaderboard"
on my_scores
for update using (
right(auth.email(), 13) = '@blizzard.com'
);
```
Supabase comes with three built-in helper functions: `auth.email()`, `auth.uid()` and `auth.role()`.
See the full PostgreSQL policy docs here: [https://www.postgresql.org/docs/12/sql-createpolicy.html](https://www.postgresql.org/docs/12/sql-createpolicy.html)
You can get as creative as you like with these policies.
### Resources
- JWT debugger: https://jwt.io​
- PostgeSQL Policies: https://www.postgresql.org/docs/12/sql-createpolicy.html
- PostgREST Row Level Security: https://postgrest.org/en/v7.0.0/auth.html
### Next steps
@@ -13,6 +13,110 @@ Learn how to restrict access to your database tables by enabling Row Level Secur
<iframe className="w-full video-with-border" width="640" height="385" src="https://www.youtube-nocookie.com/embed/qY_iQ10IUhs" frameBorder="1" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowFullScreen></iframe>
### Securing Your Tables
In Supabase, you can access your data directly from the client (often the web browser), you do this by passing your Supabase URL and Anon key to supabase-js like so:
```js
const supabase = createClient(
'https://qwertyuiop.supabase.co',
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'
)
```
This raises an interesting question however: "if my anon key is in the client, then can't someone read my javascript and steal my key?", the answer is yes. And this is where Postgres policies come in.
Using Postgres's "Row-Level-Security" policies, we can set rules on what data the anon key is allowed or not allowed to access by default.
We can say for example that the anon key should only be able to read from a particular table, but not write, update, nor delete.
And these rules can be as complex as we want. We could say that the anon key can only delete rows which were inserted on a thursday afternoon between 4 and 6pm, and where the id column is an even number. Pretty strange, but it shows the power of policies.
Let's say we create a leaderboard table. We want people on our website to be able to read the leaderboard, but not write, update, or delete from it. We start by defining our table in SQL and adding some dummy data:
```sql
create table leaderboard (
name text,
score int
);
insert into leaderboard(name, score)
values ('Paul', 100), ('Leto', 50), ('Chani', 200);
```
Now let's set up a client to read the data, I've created a repl here to show a living example: [https://replit.com/@awalias/supabase-leaderboard-demo#index.js](https://replit.com/@awalias/supabase-leaderboard-demo#index.js). If you copy the snippet you can plug in your own Supabase URL and anon key.
You can see that it's possible to freely read from and write to the table by using:
```js
// Writing
let { data, error } = await supabase
.from('leaderboard')
.insert({ name: 'Bob', score: 99999 })
// Reading
let { data, error } = await supabase
.from('leaderboard')
.select('name, score')
.order('score', {ascending: false})
```
Now let's restrict access. We'll start by fully restricting the table. We can do this in the SQL editor by making a query:
```sql
ALTER TABLE leaderboard ENABLE ROW LEVEL SECURITY;
```
or via the Supabase Dashboard, by navigating to Auth > Policies, and clicking the red padlock on the leaderboard table, so that it turns white.
![Enable row level security in Supabase](/img/auth-deep-dive-2.png)
You'll notice that both reading and writing now fail with an error like:
```jsx
{
hint: null,
details: null,
code: '42501',
message: 'new row violates row-level security policy for table "leaderboard"'
}
```
Now we need to add a policy to enable reading of the table, for everyone who sends the anon key (JWT) in the `Authorization: Bearer` header.
In SQL this can be done with:
```sql
CREATE POLICY anon_read_leaderboard ON leaderboard
FOR SELECT
USING (auth.role() = 'anon');
```
`anon_read_leaderboard` here is just a name that you choose for your policy. `leaderboard` is the table name. `FOR SELECT` says that we only want this policy to apply for reads (or rather "selects" in SQL). And finally the rule itself is `auth.role() = 'anon'`.
`auth.role()` is referring to a SQL function `role` that Supabase injects into the `auth` schema in your database. The function actually looks like this:
```sql
-- Gets the User Role from the request cookie
create or replace function auth.role() returns text as $$
select nullif(current_setting('request.jwt.claim.role', true), '')::text;
$$ language sql stable;
```
The purpose of the function is to extract the `role` claim from any JWTs that are passed to your API via the `Authorization: Bearer` header.
Other available functions for use here include: `auth.email()` and `auth.uid()` which will fetch the `email` and `sub` claims respectively.
If you'd prefer to use the dashboard to add your policy you can do so by clicking "Add Policy" in the Policies tab and making a policy like this:
![Add a read only policy in Supabase](/img/auth-deep-dive-2-2.png)
You should now be able to read from your leaderboard, but will still not be able to write, update, or delete from it, which is exactly what we wanted!
A quick reminder that you can always use your `service_role` API key to bypass these row level security policies. But be extra careful that you don't leak this key by including it in the client. This can be useful if you're building internal admin tools, or if you need to bulk insert or delete data via the API.
In the next guide we will look at using Policies in combination with User Accounts, so that you can restrict access to data on a User by User basis: Watch [Part Three: Policies](/docs/learn/auth-deep-dive/auth-policies)
### Resources
- JWT debugger: https://jwt.io/​
File renamed without changes.
+2 -2
View File
@@ -75,8 +75,8 @@ module.exports = {
},
{
label: 'Reference',
activeBasePath: '/docs/client/',
to: '/docs/client/supabase-client',
activeBasePath: '/docs/reference/',
to: '/docs/reference/javascript/supabase-client',
position: 'left',
},
// {
+1 -1
View File
@@ -11,7 +11,7 @@
"gen:gotrue": "ts-node ReferenceGenerator.ts -o docs/gotrue/client spec/gotrue.yml",
"gen:postgrest": "ts-node ReferenceGenerator.ts -o docs/postgrest/client spec/postgrest.yml",
"gen:realtime": "ts-node ReferenceGenerator.ts -o docs/realtime/client spec/realtime.yml",
"gen:supabase": "ts-node ReferenceGenerator.ts -o docs/client spec/supabase.yml",
"gen:supabase": "ts-node ReferenceGenerator.ts -o docs/reference/javascript spec/supabase.yml",
"gen:cli": "ts-node ReferenceGenerator.ts -o docs/reference/cli spec/cli.yml",
"gen:postgres": "ts-node ReferenceGenerator.ts -o docs/reference/postgres spec/postgres.yml"
},
+10 -4
View File
@@ -8,14 +8,20 @@ module.exports = {
},
{
type: 'category',
label: 'Database',
items: ['reference/postgres/database-passwords'],
label: 'Managing Tables',
items: ['reference/postgres/schemas', 'reference/postgres/tables'],
collapsed: true,
},
{
type: 'category',
label: 'Tables',
items: ['reference/postgres/creating-tables'],
label: 'Replication',
items: ['reference/postgres/publications'],
collapsed: true,
},
{
type: 'category',
label: 'Database Configuration',
items: ['reference/postgres/database-passwords', 'reference/postgres/changing-timezones'],
collapsed: true,
}
],
+6 -6
View File
@@ -3,37 +3,37 @@ module.exports = {
{
type: 'category',
label: 'About',
items: ['client/index', 'client/installing', 'client/initializing', 'client/generating-types'],
items: ['reference/javascript/index', 'reference/javascript/installing', 'reference/javascript/initializing', 'reference/javascript/generating-types'],
collapsed: true,
},
{
type: 'category',
label: 'Auth',
items: ['client/auth-signup', 'client/auth-signin', 'client/auth-signout', 'client/auth-session', 'client/auth-user', 'client/auth-update', 'client/auth-onauthstatechange', 'client/reset-password-email'],
items: ['reference/javascript/auth-signup', 'reference/javascript/auth-signin', 'reference/javascript/auth-signout', 'reference/javascript/auth-session', 'reference/javascript/auth-user', 'reference/javascript/auth-update', 'reference/javascript/auth-onauthstatechange', 'reference/javascript/reset-password-email'],
collapsed: true,
},
{
type: 'category',
label: 'Data',
items: ['client/select', 'client/insert', 'client/update', 'client/delete', 'client/rpc'],
items: ['reference/javascript/select', 'reference/javascript/insert', 'reference/javascript/update', 'reference/javascript/upsert', 'reference/javascript/delete', 'reference/javascript/rpc'],
collapsed: true,
},
{
type: 'category',
label: 'Realtime',
items: ['client/subscribe', 'client/removesubscription', 'client/getsubscriptions'],
items: ['reference/javascript/subscribe', 'reference/javascript/removesubscription', 'reference/javascript/getsubscriptions'],
collapsed: true,
},
{
type: 'category',
label: 'Modifiers',
items: ['client/using-modifiers', 'client/limit', 'client/order', 'client/range', 'client/single'],
items: ['reference/javascript/using-modifiers', 'reference/javascript/limit', 'reference/javascript/order', 'reference/javascript/range', 'reference/javascript/single'],
collapsed: true,
},
{
type: 'category',
label: 'Filters',
items: ['client/using-filters', 'client/filter', 'client/or', 'client/not', 'client/match', 'client/eq', 'client/neq', 'client/gt', 'client/gte', 'client/lt', 'client/lte', 'client/like', 'client/ilike', 'client/is', 'client/in', 'client/cs', 'client/cd', 'client/sl', 'client/sr', 'client/nxl', 'client/nxr', 'client/adj', 'client/ov', 'client/fts', 'client/plfts', 'client/phfts', 'client/wfts'],
items: ['reference/javascript/using-filters', 'reference/javascript/filter', 'reference/javascript/or', 'reference/javascript/not', 'reference/javascript/match', 'reference/javascript/eq', 'reference/javascript/neq', 'reference/javascript/gt', 'reference/javascript/gte', 'reference/javascript/lt', 'reference/javascript/lte', 'reference/javascript/like', 'reference/javascript/ilike', 'reference/javascript/is', 'reference/javascript/in', 'reference/javascript/contains', 'reference/javascript/containedby', 'reference/javascript/rangelt', 'reference/javascript/rangegt', 'reference/javascript/rangegte', 'reference/javascript/rangelte', 'reference/javascript/rangeadjacent', 'reference/javascript/overlaps', 'reference/javascript/textsearch'],
collapsed: true,
}
],
+2871 -1924
View File
File diff suppressed because it is too large. Load diff
+1084 -955
View File
File diff suppressed because it is too large. Load diff
+185 -37
View File
@@ -21,57 +21,67 @@ info:
- name: 'About'
items:
- index
- name: 'Database'
- name: 'Managing Tables'
items:
# - Database Users
- Database Passwords
# - name: 'Schemas'
# items:
# - Creating schemas
- name: 'Tables'
items:
- Creating tables
- Schemas
- Tables
# - name: 'Columns'
# items:
# - Creating columns
# - Column types
- name: 'Replication'
items:
- Publications
- name: 'Database Configuration'
items:
# - Database Users
- Database Passwords
- Changing Timezones
pages:
Database Users:
Schemas:
description: |
Users and Roles are almost interchangable.
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.
For example, you might want to use a `public` schema for user-facing data, and an `auth` schema for all logins and secured data.
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:
- name: Create New User
- name: Creating a schema
isSpotlight: true
sql: |
```sql
create user prisma
with password 'hello';
create schema schema_name;
```
Database Passwords:
- name: Removing a schema
sql: |
```sql
drop schema if exists schema_name;
```
- name: Using special characters
description: |
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.
sql: |
```sql
create schema "Schema Name";
```
Tables:
description: |
You can manage the passwords of your database users using any super user.
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.
examples:
- name: Password reset
sql: |
```sql
alter user postgres
with password 'new_password';
```
Creating schemas:
description: |
Creating schemas.
examples:
- name: Create schema
sql: |
```sql
create schema new_schema;
```
Creating tables:
description: |
A relational database consists of multiple related tables. A table consists of rows and columns.
notes: |
- 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:
- name: Create table
@@ -83,6 +93,13 @@ pages:
data jsonb,
name text
);
# with schema
create table schema_name.table_name (
id integer primary key,
data jsonb,
name text
);
```
- name: Primary keys using multiple columns
sql: |
@@ -94,7 +111,12 @@ pages:
primary key (column_1, column_2)
);
```
Creating columns:
- name: Delete a table
sql: |
```sql
delete table if exists table_name;
```
Columns:
description: |
Creating columns.
@@ -126,4 +148,130 @@ pages:
# alter table new_table
# add new_column text;
# ```
Publications:
description: |
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:
- name: Create a Publication
description: |
This publication will contain all changes to all tables.
sql: |
```sql
create publication publication_name
for all tables;
```
- name: Create a Publication which listens to individual tables
sql: |
```sql
create publication publication_name
for table table_one, table_two;
```
- name: Add tables to an existing publication
sql: |
```sql
alter publication publication_name
add table table_name;
```
- name: Listens to inserts only
sql: |
```sql
create publication publication_name
for all tables
with (publish = 'insert');
```
- name: Listens to updates only
sql: |
```sql
create publication publication_name
for all tables
with (publish = 'update');
```
- name: Listens to deletions only
sql: |
```sql
create publication publication_name
for all tables
with (publish = 'delete');
```
- name: Remove a Publication
sql: |
```sql
drop publication if exists publication_name;
```
- name: Recreate a Publication
description: |
If you are planning to re-create a publication, it's best to do it in a transaction to ensure the operation succeeds.
sql: |
```sql
begin;
-- remove the realtime publication
drop publication if exists publication_name;
-- re-create the publication but don't enable it for any tables
create publication publication_name;
commit;
```
Database Users:
description: |
Users and Roles are almost interchangable.
examples:
- name: Create New User
sql: |
```sql
create user new_user
with password 'hello';
```
Database Passwords:
description: |
Manage the passwords of your database users using any super user.
examples:
- name: Password reset
sql: |
```sql
alter user postgres
with password 'new_password';
```
Changing Timezones:
description: |
Data types.
notes: |
- View a full list of timezones on [Wikipedia](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones).
examples:
- name: Change timezone
isSpotlight: true
sql: |
```sql
alter database postgres
set timezone to 'America/New_York';
```
- name: Full list of timezones
description: |
Get a full list of timezones supported by your database. This will return the following columns:
- `name`: Time zone name
- `abbrev`: Time zone abbreviation
- `utc_offset`: Offset from UTC (positive means east of Greenwich)
- `is_dst`: True if currently observing daylight savings
sql: |
```sql
select name, abbrev, utc_offset, is_dst
from pg_timezone_names()
order by name;
```
- name: Search for a specific timezone
description: Use `ilike` (case insensitive search) to find specific timezones.
sql: |
```sql
select *
from pg_timezone_names()
where name ilike '%york%';
```
+1717 -899
View File
File diff suppressed because it is too large. Load diff
+70 -70
View File
@@ -90,7 +90,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 64,
"line": 63,
"character": 3
}
]
@@ -106,7 +106,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 36,
"line": 35,
"character": 10
}
],
@@ -131,7 +131,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 52,
"line": 51,
"character": 6
}
],
@@ -161,7 +161,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 50,
"line": 49,
"character": 8
}
],
@@ -181,7 +181,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 49,
"line": 48,
"character": 8
}
],
@@ -201,7 +201,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 37,
"line": 36,
"character": 10
}
],
@@ -223,7 +223,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 38,
"line": 37,
"character": 9
}
],
@@ -290,7 +290,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 42,
"line": 41,
"character": 21
}
],
@@ -311,7 +311,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 44,
"line": 43,
"character": 16
}
],
@@ -341,7 +341,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 48,
"line": 47,
"character": 8
}
],
@@ -362,7 +362,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 43,
"line": 42,
"character": 19
}
],
@@ -384,7 +384,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 39,
"line": 38,
"character": 8
}
],
@@ -451,7 +451,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 45,
"line": 44,
"character": 21
}
],
@@ -481,7 +481,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 51,
"line": 50,
"character": 18
}
],
@@ -501,7 +501,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 47,
"line": 46,
"character": 16
}
],
@@ -522,7 +522,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 46,
"line": 45,
"character": 5
}
],
@@ -543,7 +543,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 53,
"line": 52,
"character": 12
}
],
@@ -567,7 +567,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 40,
"line": 39,
"character": 9
}
],
@@ -588,7 +588,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 41,
"line": 40,
"character": 11
}
],
@@ -681,7 +681,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 347,
"line": 346,
"character": 44
}
]
@@ -698,7 +698,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 347,
"line": 346,
"character": 23
}
]
@@ -730,7 +730,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 355,
"line": 356,
"character": 26
}
]
@@ -777,7 +777,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 327,
"line": 326,
"character": 22
}
]
@@ -824,7 +824,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 335,
"line": 334,
"character": 22
}
]
@@ -856,7 +856,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 314,
"line": 313,
"character": 21
}
]
@@ -888,7 +888,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 362,
"line": 363,
"character": 24
}
]
@@ -920,7 +920,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 341,
"line": 340,
"character": 27
}
]
@@ -989,7 +989,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 246,
"line": 245,
"character": 9
}
]
@@ -1023,7 +1023,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 118,
"line": 117,
"character": 9
}
]
@@ -1074,7 +1074,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 215,
"line": 214,
"character": 17
}
]
@@ -1178,7 +1178,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 142,
"line": 141,
"character": 40
}
],
@@ -1198,7 +1198,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 142,
"line": 141,
"character": 20
}
],
@@ -1230,7 +1230,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 142,
"line": 141,
"character": 13
}
]
@@ -1244,7 +1244,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 139,
"line": 138,
"character": 12
}
]
@@ -1278,7 +1278,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 293,
"line": 292,
"character": 13
}
]
@@ -1312,7 +1312,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 231,
"line": 230,
"character": 13
}
]
@@ -1388,7 +1388,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 164,
"line": 163,
"character": 5
}
]
@@ -1422,7 +1422,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 303,
"line": 302,
"character": 9
}
]
@@ -1480,7 +1480,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 186,
"line": 185,
"character": 9
}
]
@@ -1526,7 +1526,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 267,
"line": 266,
"character": 15
}
]
@@ -1584,7 +1584,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 197,
"line": 196,
"character": 9
}
]
@@ -1642,7 +1642,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 208,
"line": 207,
"character": 11
}
]
@@ -1700,7 +1700,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 175,
"line": 174,
"character": 8
}
]
@@ -1747,7 +1747,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 252,
"line": 251,
"character": 6
}
]
@@ -1800,7 +1800,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 240,
"line": 239,
"character": 8
}
]
@@ -1825,7 +1825,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 61,
"line": 60,
"character": 9
}
],
@@ -1849,7 +1849,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 62,
"line": 61,
"character": 9
}
],
@@ -1873,7 +1873,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 63,
"line": 62,
"character": 11
}
],
@@ -1897,7 +1897,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 60,
"line": 59,
"character": 8
}
],
@@ -1926,7 +1926,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 54,
"line": 53,
"character": 22
}
],
@@ -2007,7 +2007,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 35,
"line": 34,
"character": 35
}
]
@@ -2021,7 +2021,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 26,
"line": 25,
"character": 12
}
],
@@ -2043,7 +2043,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 28,
"line": 27,
"character": 7
}
],
@@ -2061,7 +2061,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 29,
"line": 28,
"character": 9
}
],
@@ -2079,7 +2079,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 30,
"line": 29,
"character": 5
}
],
@@ -2097,7 +2097,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 27,
"line": 26,
"character": 7
}
],
@@ -2122,7 +2122,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 26,
"line": 25,
"character": 14
}
]
@@ -2138,7 +2138,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 14,
"line": 13,
"character": 12
}
],
@@ -2162,7 +2162,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 21,
"line": 20,
"character": 8
}
],
@@ -2182,7 +2182,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 20,
"line": 19,
"character": 8
}
],
@@ -2202,7 +2202,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 23,
"line": 22,
"character": 9
}
],
@@ -2263,7 +2263,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 17,
"line": 16,
"character": 21
}
],
@@ -2292,7 +2292,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 19,
"line": 18,
"character": 8
}
],
@@ -2312,7 +2312,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 18,
"line": 17,
"character": 19
}
],
@@ -2341,7 +2341,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 24,
"line": 23,
"character": 8
}
],
@@ -2402,7 +2402,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 22,
"line": 21,
"character": 18
}
],
@@ -2422,7 +2422,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 16,
"line": 15,
"character": 9
}
],
@@ -2451,7 +2451,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 15,
"line": 14,
"character": 11
}
],
@@ -2482,7 +2482,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 14,
"line": 13,
"character": 14
}
]
@@ -2513,7 +2513,7 @@
"sources": [
{
"fileName": "RealtimeClient.ts",
"line": 33,
"line": 32,
"character": 10
}
]
+147 -69
View File
@@ -21,7 +21,7 @@ info:
id: 'js'
version: '0.0.1'
docs:
path: client/
path: reference/javascript/
sidebar:
- name: 'About'
items:
@@ -44,6 +44,7 @@ info:
- select()
- insert()
- update()
- upsert()
- delete()
- rpc()
- name: 'Realtime'
@@ -75,18 +76,15 @@ info:
- .ilike()
- .is()
- .in()
- .cs()
- .cd()
- .sl()
- .sr()
- .nxl()
- .nxr()
- .adj()
- .ov()
- .fts()
- .plfts()
- .phfts()
- .wfts()
- .contains()
- .containedBy()
- .rangeLt()
- .rangeGt()
- .rangeGte()
- .rangeLte()
- .rangeAdjacent()
- .overlaps()
- .textSearch()
pages:
Installing:
@@ -234,7 +232,7 @@ pages:
```
- name: Sign up with third-party providers.
description: |
You can sign up with OAuth providers using the [`signIn()`](/docs/client/auth-signin#sign-in-using-third-party-providers) method.
You can sign up with OAuth providers using the [`signIn()`](/docs/reference/javascript/auth-signin#sign-in-using-third-party-providers) method.
js: |
```js
const { user, session, error } = await supabase.auth.signIn({
@@ -243,7 +241,7 @@ pages:
```
- name: Sign up with scopes.
description: |
You can provide additional scopes to OAuth providers using the [`signIn()`](/docs/client/auth-signin#sign-in-with-scopes) method.
You can provide additional scopes to OAuth providers using the [`signIn()`](/docs/reference/javascript/auth-signin#sign-in-with-scopes) method.
js: |
```js
const { user, session, error } = await supabase.auth.signIn({
@@ -401,8 +399,8 @@ pages:
$ref: '@supabase/postgrest-js."lib/PostgrestQueryBuilder".PostgrestQueryBuilder.select'
notes: |
- By default, Supabase projects will return a maximum of 1,000 rows. This setting can be changed Project API Settings. It's recommended that you keep it low to limit the payload size of accidental or malicious requests. You can use `range()` queries to paginate through your data.
- `select()` can be combined with [Modifiers](/docs/client/using-modifiers)
- `select()` can be combined with [Filters](/docs/client/using-filters)
- `select()` can be combined with [Modifiers](/docs/reference/javascript/using-modifiers)
- `select()` can be combined with [Filters](/docs/reference/javascript/using-filters)
examples:
- name: Getting your data
isSpotlight: true
@@ -531,7 +529,7 @@ pages:
title: 'Modify data: update()'
$ref: '@supabase/postgrest-js."lib/PostgrestQueryBuilder".PostgrestQueryBuilder.update'
notes: |
- `update()` should always be combined with [Filters](/docs/client/using-filters) to target the item(s) you wish to update.
- `update()` should always be combined with [Filters](/docs/reference/javascript/using-filters) to target the item(s) you wish to update.
examples:
- name: Updating your data
isSpotlight: true
@@ -561,11 +559,48 @@ pages:
.eq('address->postcode', 90210)
```
upsert():
title: 'Upsert data: upsert()'
$ref: '@supabase/postgrest-js."lib/PostgrestQueryBuilder".PostgrestQueryBuilder.upsert'
notes: |
- Primary keys should to be included in the data payload in order for an update to work correctly.
- Primary keys must be natural, not surrogate. There are however, [workarounds](https://github.com/PostgREST/postgrest/issues/1118) for surrogate primary keys.
examples:
- name: Upsert your data
isSpotlight: true
js: |
```js
const { data, error } = await supabase
.from('messages')
.upsert({ id: 3, message: 'foo', username: 'supabot' })
```
- name: Upserting into tables with constraints
isSpotlight: true
js: |
```js
const { data, error } = await supabase
.from('users')
.upsert({ username: 'supabot' }, { onConflict: 'username' })
```
- name: Return the exact number of rows
isSpotlight: true
js: |
```js
const { data, error, count } = await supabase
.from('users')
.upsert({
id: 3, message: 'foo',
username: 'supabot'
}, {
count: 'exact'
})
```
delete():
title: 'Delete data: delete()'
$ref: '@supabase/postgrest-js."lib/PostgrestQueryBuilder".PostgrestQueryBuilder.delete'
notes: |
- `delete()` should always be combined with [Filters](/docs/client/using-filters) to target the item(s) you wish to delete.
- `delete()` should always be combined with [Filters](/docs/reference/javascript/using-filters) to target the item(s) you wish to delete.
examples:
- name: Delete records
isSpotlight: true
@@ -615,8 +650,8 @@ pages:
- name: With filters.
description: |
Stored procedures that return tables can also be combined with
[Modifiers](/docs/client/using-modifiers) and
[Filters](/docs/client/using-filters).
[Modifiers](/docs/reference/javascript/using-modifiers) and
[Filters](/docs/reference/javascript/using-filters).
js: |
```js
const { data, error } = await supabase
@@ -1352,7 +1387,7 @@ pages:
.in('name', ['Rio de Janeiro', 'San Francisco'])
```
.cs():
.contains():
$ref: '@supabase/postgrest-js."lib/PostgrestFilterBuilder".PostgrestFilterBuilder.cs'
examples:
- name: With `select()`
@@ -1362,7 +1397,7 @@ pages:
const { data, error } = await supabase
.from('countries')
.select('name, id, main_exports')
.cs('main_exports', ['oil'])
.contains('main_exports', ['oil'])
```
- name: With `update()`
js: |
@@ -1370,7 +1405,7 @@ pages:
const { data, error } = await supabase
.from('countries')
.update({ name: 'Mordor' })
.cs('main_exports', ['oil'])
.contains('main_exports', ['oil'])
```
- name: With `delete()`
js: |
@@ -1378,7 +1413,7 @@ pages:
const { data, error } = await supabase
.from('countries')
.delete()
.cs('main_exports', ['oil'])
.contains('main_exports', ['oil'])
```
- name: With `rpc()`
js: |
@@ -1386,10 +1421,10 @@ pages:
// Only valid if the Stored Procedure returns a table type.
const { data, error } = await supabase
.rpc('echo_all_countries')
.cs('main_exports', ['oil'])
.contains('main_exports', ['oil'])
```
.cd():
.containedBy():
$ref: '@supabase/postgrest-js."lib/PostgrestFilterBuilder".PostgrestFilterBuilder.cd'
examples:
- name: With `select()`
@@ -1399,7 +1434,7 @@ pages:
const { data, error } = await supabase
.from('countries')
.select('name, id, main_exports')
.cd('main_exports', ['cars', 'food', 'machine'])
.containedBy('main_exports', ['cars', 'food', 'machine'])
```
- name: With `update()`
js: |
@@ -1407,7 +1442,7 @@ pages:
const { data, error } = await supabase
.from('countries')
.update({ name: 'Mordor' })
.cd('main_exports', ['orks', 'surveillance', 'evil'])
.containedBy('main_exports', ['orks', 'surveillance', 'evil'])
```
- name: With `delete()`
js: |
@@ -1415,7 +1450,7 @@ pages:
const { data, error } = await supabase
.from('countries')
.delete()
.cd('main_exports', ['cars', 'food', 'machine'])
.containedBy('main_exports', ['cars', 'food', 'machine'])
```
- name: With `rpc()`
js: |
@@ -1423,10 +1458,10 @@ pages:
// Only valid if the Stored Procedure returns a table type.
const { data, error } = await supabase
.rpc('echo_all_countries')
.cd('main_exports', ['cars', 'food', 'machine'])
.containedBy('main_exports', ['cars', 'food', 'machine'])
```
.sl():
.rangeLt():
$ref: '@supabase/postgrest-js."lib/PostgrestFilterBuilder".PostgrestFilterBuilder.sl'
examples:
- name: With `select()`
@@ -1436,7 +1471,7 @@ pages:
const { data, error } = await supabase
.from('countries')
.select('name, id, population_range_millions')
.sl('population_range_millions', [150, 250])
.rangeLt('population_range_millions', [150, 250])
```
- name: With `update()`
js: |
@@ -1444,7 +1479,7 @@ pages:
const { data, error } = await supabase
.from('countries')
.update({ name: 'Mordor' })
.sl('population_range_millions', [150, 250])
.rangeLt('population_range_millions', [150, 250])
```
- name: With `delete()`
js: |
@@ -1452,7 +1487,7 @@ pages:
const { data, error } = await supabase
.from('countries')
.delete()
.sl('population_range_millions', [150, 250])
.rangeLt('population_range_millions', [150, 250])
```
- name: With `rpc()`
js: |
@@ -1460,10 +1495,10 @@ pages:
// Only valid if the Stored Procedure returns a table type.
const { data, error } = await supabase
.rpc('echo_all_countries')
.sl('population_range_millions', [150, 250])
.rangeLt('population_range_millions', [150, 250])
```
.sr():
.rangeGt():
$ref: '@supabase/postgrest-js."lib/PostgrestFilterBuilder".PostgrestFilterBuilder.sr'
examples:
- name: With `select()`
@@ -1473,7 +1508,7 @@ pages:
const { data, error } = await supabase
.from('countries')
.select('name, id, population_range_millions')
.sr('population_range_millions', [150, 250])
.rangeGt('population_range_millions', [150, 250])
```
- name: With `update()`
js: |
@@ -1481,7 +1516,7 @@ pages:
const { data, error } = await supabase
.from('countries')
.update({ name: 'Mordor' })
.sr('population_range_millions', [150, 250])
.rangeGt('population_range_millions', [150, 250])
```
- name: With `delete()`
js: |
@@ -1489,7 +1524,7 @@ pages:
const { data, error } = await supabase
.from('countries')
.delete()
.sr('population_range_millions', [150, 250])
.rangeGt('population_range_millions', [150, 250])
```
- name: With `rpc()`
js: |
@@ -1497,10 +1532,10 @@ pages:
// Only valid if the Stored Procedure returns a table type.
const { data, error } = await supabase
.rpc('echo_all_countries')
.sr('population_range_millions', [150, 250])
.rangeGt('population_range_millions', [150, 250])
```
.nxl():
.rangeGte():
$ref: '@supabase/postgrest-js."lib/PostgrestFilterBuilder".PostgrestFilterBuilder.nxl'
examples:
- name: With `select()`
@@ -1510,7 +1545,7 @@ pages:
const { data, error } = await supabase
.from('countries')
.select('name, id, population_range_millions')
.nxl('population_range_millions', [150, 250])
.rangeGte('population_range_millions', [150, 250])
```
- name: With `update()`
js: |
@@ -1518,7 +1553,7 @@ pages:
const { data, error } = await supabase
.from('countries')
.update({ name: 'Mordor' })
.nxl('population_range_millions', [150, 250])
.rangeGte('population_range_millions', [150, 250])
```
- name: With `delete()`
js: |
@@ -1526,7 +1561,7 @@ pages:
const { data, error } = await supabase
.from('countries')
.delete()
.nxl('population_range_millions', [150, 250])
.rangeGte('population_range_millions', [150, 250])
```
- name: With `rpc()`
js: |
@@ -1534,10 +1569,10 @@ pages:
// Only valid if the Stored Procedure returns a table type.
const { data, error } = await supabase
.rpc('echo_all_countries')
.nxl('population_range_millions', [150, 250])
.rangeGte('population_range_millions', [150, 250])
```
.nxr():
.rangeLte():
$ref: '@supabase/postgrest-js."lib/PostgrestFilterBuilder".PostgrestFilterBuilder.nxr'
examples:
- name: With `select()`
@@ -1547,7 +1582,7 @@ pages:
const { data, error } = await supabase
.from('countries')
.select('name, id, population_range_millions')
.nxr('population_range_millions', [150, 250])
.rangeLte('population_range_millions', [150, 250])
```
- name: With `update()`
js: |
@@ -1555,7 +1590,7 @@ pages:
const { data, error } = await supabase
.from('countries')
.update({ name: 'Mordor' })
.nxr('population_range_millions', [150, 250])
.rangeLte('population_range_millions', [150, 250])
```
- name: With `delete()`
js: |
@@ -1563,7 +1598,7 @@ pages:
const { data, error } = await supabase
.from('countries')
.delete()
.nxr('population_range_millions', [150, 250])
.rangeLte('population_range_millions', [150, 250])
```
- name: With `rpc()`
js: |
@@ -1571,10 +1606,10 @@ pages:
// Only valid if the Stored Procedure returns a table type.
const { data, error } = await supabase
.rpc('echo_all_countries')
.nxr('population_range_millions', [150, 250])
.rangeLte('population_range_millions', [150, 250])
```
.adj():
.rangeAdjacent():
$ref: '@supabase/postgrest-js."lib/PostgrestFilterBuilder".PostgrestFilterBuilder.adj'
examples:
- name: With `select()`
@@ -1584,7 +1619,7 @@ pages:
const { data, error } = await supabase
.from('countries')
.select('name, id, population_range_millions')
.adj('population_range_millions', [70, 185])
.rangeAdjacent('population_range_millions', [70, 185])
```
- name: With `update()`
js: |
@@ -1592,7 +1627,7 @@ pages:
const { data, error } = await supabase
.from('countries')
.update({ name: 'Mordor' })
.adj('population_range_millions', [70, 185])
.rangeAdjacent('population_range_millions', [70, 185])
```
- name: With `delete()`
js: |
@@ -1600,7 +1635,7 @@ pages:
const { data, error } = await supabase
.from('countries')
.delete()
.adj('population_range_millions', [70, 185])
.rangeAdjacent('population_range_millions', [70, 185])
```
- name: With `rpc()`
js: |
@@ -1608,10 +1643,10 @@ pages:
// Only valid if the Stored Procedure returns a table type.
const { data, error } = await supabase
.rpc('echo_all_countries')
.adj('population_range_millions', [70, 185])
.rangeAdjacent('population_range_millions', [70, 185])
```
.ov():
.overlaps():
$ref: '@supabase/postgrest-js."lib/PostgrestFilterBuilder".PostgrestFilterBuilder.ov'
examples:
- name: With `select()`
@@ -1621,7 +1656,7 @@ pages:
const { data, error } = await supabase
.from('countries')
.select('name, id, main_exports')
.ova('main_exports', ['computers', 'minerals'])
.overlaps('main_exports', ['computers', 'minerals'])
```
- name: With `update()`
js: |
@@ -1629,7 +1664,7 @@ pages:
let countries = await supabase
.from('countries')
.update({ name: 'Mordor' })
.ova('main_exports', ['computers', 'minerals'])
.overlaps('main_exports', ['computers', 'minerals'])
```
- name: With `delete()`
js: |
@@ -1637,7 +1672,7 @@ pages:
const { data, error } = await supabase
.from('countries')
.delete()
.ova('main_exports', ['computers', 'minerals'])
.overlaps('main_exports', ['computers', 'minerals'])
```
- name: With `rpc()`
js: |
@@ -1645,17 +1680,60 @@ pages:
// Only valid if the Stored Procedure returns a table type.
const { data, error } = await supabase
.rpc('echo_all_countries')
.ova('main_exports', ['computers', 'minerals'])
.overlaps('main_exports', ['computers', 'minerals'])
```
.fts():
.textSearch():
$ref: '@supabase/postgrest-js."lib/PostgrestFilterBuilder".PostgrestFilterBuilder.fts'
examples:
- name: Text search
js: |
```js
const { data, error } = await supabase
.from('quotes')
.textSearch('catchphrase', `'fat' & 'cat'`, {
config: 'english'
})
```
- name: Basic normalization
description: Uses PostgreSQL's `plainto_tsquery` function.
js: |
```js
const { data, error } = await supabase
.from('quotes')
.textSearch('catchphrase', `'fat' & 'cat'`, {
type: 'plain',
config: 'english'
})
```
- name: Full normalization
description: Uses PostgreSQL's `phraseto_tsquery` function.
js: |
```js
const { data, error } = await supabase
.from('quotes')
.textSearch('catchphrase', `'fat' & 'cat'`, {
type: 'phrase',
config: 'english'
})
```
- name: Full normalization
description: |
Uses PostgreSQL's `websearch_to_tsquery` function.
This function will never raise syntax errors, which makes it possible to use raw user-supplied input for search, and can be used
with advanced operators.
.plfts():
$ref: '@supabase/postgrest-js."lib/PostgrestFilterBuilder".PostgrestFilterBuilder.plfts'
- `unquoted text`: text not inside quote marks will be converted to terms separated by & operators, as if processed by plainto_tsquery.
- `"quoted text"`: text inside quote marks will be converted to terms separated by <-> operators, as if processed by phraseto_tsquery.
- `OR`: the word “or” will be converted to the | operator.
- `-`: a dash will be converted to the ! operator.
.phfts():
$ref: '@supabase/postgrest-js."lib/PostgrestFilterBuilder".PostgrestFilterBuilder.phfts'
.wfts():
$ref: '@supabase/postgrest-js."lib/PostgrestFilterBuilder".PostgrestFilterBuilder.wfts'
js: |
```js
const { data, error } = await supabase
.from('quotes')
.textSearch('catchphrase', `'fat or cat'`, {
type: 'websearch',
config: 'english'
})
```
+67
View File
@@ -0,0 +1,67 @@
import React, { useState } from 'react'
import extensions from '../data/extensions.json'
export default function Extensions() {
const [filter, setFilter] = useState('')
return (
<>
<div style={styles.inputContainer}>
<input
type="text"
style={styles.input}
placeholder="Filter"
onChange={(e) => setFilter(e.target.value)}
/>
</div>
<div className="row" style={styles.row}>
{extensions
.filter((x) => x.name.indexOf(filter) >= 0)
.map((extension) => (
<div className={'col col--6'} key={extension.name} style={styles.column}>
<div style={styles.card}>
<h3>
<code style={styles.title}>{extension.name}</code>
</h3>
<p style={styles.description}>
{extension.comment.charAt(0).toUpperCase() + extension.comment.slice(1)}
</p>
</div>
</div>
))}
</div>
</>
)
}
const styles = {
inputContainer: {
marginBottom: '15px',
},
input: {
border: '1px solid var(--ifm-panel-border-color)',
borderRadius: 4,
backgroundColor: 'var(--custom-background-color-diff)',
color: 'var(--ifm-font-color-base)',
margin: 0,
padding: '6px 8px',
width: 300,
},
column: {
margin: '5px 0px',
padding: '0px 5px',
},
card: {
border: '1px solid var(--ifm-panel-border-color)',
borderRadius: 5,
padding: 8,
},
title: {
margin: 0,
fontSize: '0.9rem',
border: '1px solid var(--ifm-panel-border-color)',
},
description: {
fontSize: '0.8rem',
margin: 0,
},
}
+4
View File
@@ -1268,6 +1268,10 @@ html[data-theme='dark'] .navbar-item-twitter:before {
/* New CSS */
code {
border: 1px solid var(--ifm-hr-border-color);
}
.tabs {
margin: 0;
font-size: 0.7rem;
+506
View File
@@ -0,0 +1,506 @@
[
{
"name": "address_standardizer",
"schema": null,
"default_version": "3.1.1",
"installed_version": null,
"comment": "Used to parse an address into constituent elements. Generally used to support geocoding address normalization step."
},
{
"name": "address_standardizer-3",
"schema": null,
"default_version": "3.1.1",
"installed_version": null,
"comment": "Used to parse an address into constituent elements. Generally used to support geocoding address normalization step."
},
{
"name": "address_standardizer_data_us",
"schema": null,
"default_version": "3.1.1",
"installed_version": null,
"comment": "Address Standardizer US dataset example"
},
{
"name": "address_standardizer_data_us-3",
"schema": null,
"default_version": "3.1.1",
"installed_version": null,
"comment": "Address Standardizer US dataset example"
},
{
"name": "adminpack",
"schema": null,
"default_version": "2.0",
"installed_version": null,
"comment": "administrative functions for PostgreSQL"
},
{
"name": "amcheck",
"schema": null,
"default_version": "1.2",
"installed_version": null,
"comment": "functions for verifying relation integrity"
},
{
"name": "autoinc",
"schema": null,
"default_version": "1.0",
"installed_version": null,
"comment": "functions for autoincrementing fields"
},
{
"name": "bloom",
"schema": null,
"default_version": "1.0",
"installed_version": null,
"comment": "bloom access method - signature file based index"
},
{
"name": "btree_gin",
"schema": null,
"default_version": "1.3",
"installed_version": null,
"comment": "support for indexing common datatypes in GIN"
},
{
"name": "btree_gist",
"schema": null,
"default_version": "1.5",
"installed_version": null,
"comment": "support for indexing common datatypes in GiST"
},
{
"name": "citext",
"schema": null,
"default_version": "1.6",
"installed_version": null,
"comment": "data type for case-insensitive character strings"
},
{
"name": "cube",
"schema": null,
"default_version": "1.4",
"installed_version": null,
"comment": "data type for multidimensional cubes"
},
{
"name": "dblink",
"schema": null,
"default_version": "1.2",
"installed_version": null,
"comment": "connect to other PostgreSQL databases from within a database"
},
{
"name": "dict_int",
"schema": null,
"default_version": "1.0",
"installed_version": null,
"comment": "text search dictionary template for integers"
},
{
"name": "dict_xsyn",
"schema": null,
"default_version": "1.0",
"installed_version": null,
"comment": "text search dictionary template for extended synonym processing"
},
{
"name": "earthdistance",
"schema": null,
"default_version": "1.1",
"installed_version": null,
"comment": "calculate great-circle distances on the surface of the Earth"
},
{
"name": "file_fdw",
"schema": null,
"default_version": "1.0",
"installed_version": null,
"comment": "foreign-data wrapper for flat file access"
},
{
"name": "fuzzystrmatch",
"schema": null,
"default_version": "1.1",
"installed_version": null,
"comment": "determine similarities and distance between strings"
},
{
"name": "hstore",
"schema": null,
"default_version": "1.6",
"installed_version": null,
"comment": "data type for storing sets of (key, value) pairs"
},
{
"name": "hstore_plpython3u",
"schema": null,
"default_version": "1.0",
"installed_version": null,
"comment": "transform between hstore and plpython3u"
},
{
"name": "http",
"schema": "extensions",
"default_version": "1.3",
"installed_version": "1.3",
"comment": "HTTP client for PostgreSQL, allows web page retrieval inside the database."
},
{
"name": "insert_username",
"schema": null,
"default_version": "1.0",
"installed_version": null,
"comment": "functions for tracking who changed a table"
},
{
"name": "intagg",
"schema": null,
"default_version": "1.1",
"installed_version": null,
"comment": "integer aggregator and enumerator (obsolete)"
},
{
"name": "intarray",
"schema": null,
"default_version": "1.2",
"installed_version": null,
"comment": "functions, operators, and index support for 1-D arrays of integers"
},
{
"name": "isn",
"schema": null,
"default_version": "1.2",
"installed_version": null,
"comment": "data types for international product numbering standards"
},
{
"name": "jsonb_plpython3u",
"schema": null,
"default_version": "1.0",
"installed_version": null,
"comment": "transform between jsonb and plpython3u"
},
{
"name": "lo",
"schema": null,
"default_version": "1.1",
"installed_version": null,
"comment": "Large Object maintenance"
},
{
"name": "ltree",
"schema": null,
"default_version": "1.1",
"installed_version": null,
"comment": "data type for hierarchical tree-like structures"
},
{
"name": "ltree_plpython3u",
"schema": null,
"default_version": "1.0",
"installed_version": null,
"comment": "transform between ltree and plpython3u"
},
{
"name": "moddatetime",
"schema": null,
"default_version": "1.0",
"installed_version": null,
"comment": "functions for tracking last modification time"
},
{
"name": "pageinspect",
"schema": null,
"default_version": "1.7",
"installed_version": null,
"comment": "inspect the contents of database pages at a low level"
},
{
"name": "pg_buffercache",
"schema": null,
"default_version": "1.3",
"installed_version": null,
"comment": "examine the shared buffer cache"
},
{
"name": "pg_cron",
"schema": "extensions",
"default_version": "1.3",
"installed_version": "1.3",
"comment": "Job scheduler for PostgreSQL"
},
{
"name": "pg_freespacemap",
"schema": null,
"default_version": "1.2",
"installed_version": null,
"comment": "examine the free space map (FSM)"
},
{
"name": "pg_prewarm",
"schema": null,
"default_version": "1.2",
"installed_version": null,
"comment": "prewarm relation data"
},
{
"name": "pg_stat_statements",
"schema": null,
"default_version": "1.7",
"installed_version": null,
"comment": "track execution statistics of all SQL statements executed"
},
{
"name": "pg_trgm",
"schema": null,
"default_version": "1.4",
"installed_version": null,
"comment": "text similarity measurement and index searching based on trigrams"
},
{
"name": "pg_visibility",
"schema": null,
"default_version": "1.2",
"installed_version": null,
"comment": "examine the visibility map (VM) and page-level visibility info"
},
{
"name": "pgaudit",
"schema": null,
"default_version": "1.4",
"installed_version": null,
"comment": "provides auditing functionality"
},
{
"name": "pgcrypto",
"schema": "extensions",
"default_version": "1.3",
"installed_version": "1.3",
"comment": "cryptographic functions"
},
{
"name": "pgjwt",
"schema": "extensions",
"default_version": "0.1.0",
"installed_version": "0.1.0",
"comment": "JSON Web Token API for Postgresql"
},
{
"name": "pgrowlocks",
"schema": null,
"default_version": "1.2",
"installed_version": null,
"comment": "show row-level locking information"
},
{
"name": "pgstattuple",
"schema": null,
"default_version": "1.5",
"installed_version": null,
"comment": "show tuple-level statistics"
},
{
"name": "pgtap",
"schema": null,
"default_version": "1.1.0",
"installed_version": null,
"comment": "Unit testing for PostgreSQL"
},
{
"name": "plcoffee",
"schema": null,
"default_version": "3.0alpha",
"installed_version": null,
"comment": "PL/CoffeeScript (v8) trusted procedural language"
},
{
"name": "pljava",
"schema": null,
"default_version": "1.6.0",
"installed_version": null,
"comment": "PL/Java procedural language (https://tada.github.io/pljava/)"
},
{
"name": "plls",
"schema": null,
"default_version": "3.0alpha",
"installed_version": null,
"comment": "PL/LiveScript (v8) trusted procedural language"
},
{
"name": "plpgsql",
"schema": "pg_catalog",
"default_version": "1.0",
"installed_version": "1.0",
"comment": "PL/pgSQL procedural language"
},
{
"name": "plpgsql_check",
"schema": null,
"default_version": "1.11",
"installed_version": null,
"comment": "extended check for plpgsql functions"
},
{
"name": "plpython3u",
"schema": null,
"default_version": "1.0",
"installed_version": null,
"comment": "PL/Python3U untrusted procedural language"
},
{
"name": "plv8",
"schema": null,
"default_version": "3.0alpha",
"installed_version": null,
"comment": "PL/JavaScript (v8) trusted procedural language"
},
{
"name": "postgis",
"schema": null,
"default_version": "3.1.1",
"installed_version": null,
"comment": "PostGIS geometry and geography spatial types and functions"
},
{
"name": "postgis-3",
"schema": null,
"default_version": "3.1.1",
"installed_version": null,
"comment": "PostGIS geometry and geography spatial types and functions"
},
{
"name": "postgis_raster",
"schema": null,
"default_version": "3.1.1",
"installed_version": null,
"comment": "PostGIS raster types and functions"
},
{
"name": "postgis_raster-3",
"schema": null,
"default_version": "3.1.1",
"installed_version": null,
"comment": "PostGIS raster types and functions"
},
{
"name": "postgis_sfcgal",
"schema": null,
"default_version": "3.1.1",
"installed_version": null,
"comment": "PostGIS SFCGAL functions"
},
{
"name": "postgis_sfcgal-3",
"schema": null,
"default_version": "3.1.1",
"installed_version": null,
"comment": "PostGIS SFCGAL functions"
},
{
"name": "postgis_tiger_geocoder",
"schema": null,
"default_version": "3.1.1",
"installed_version": null,
"comment": "PostGIS tiger geocoder and reverse geocoder"
},
{
"name": "postgis_tiger_geocoder-3",
"schema": null,
"default_version": "3.1.1",
"installed_version": null,
"comment": "PostGIS tiger geocoder and reverse geocoder"
},
{
"name": "postgis_topology",
"schema": null,
"default_version": "3.1.1",
"installed_version": null,
"comment": "PostGIS topology spatial types and functions"
},
{
"name": "postgis_topology-3",
"schema": null,
"default_version": "3.1.1",
"installed_version": null,
"comment": "PostGIS topology spatial types and functions"
},
{
"name": "postgres_fdw",
"schema": null,
"default_version": "1.0",
"installed_version": null,
"comment": "foreign-data wrapper for remote PostgreSQL servers"
},
{
"name": "refint",
"schema": null,
"default_version": "1.0",
"installed_version": null,
"comment": "functions for implementing referential integrity (obsolete)"
},
{
"name": "seg",
"schema": null,
"default_version": "1.3",
"installed_version": null,
"comment": "data type for representing line segments or floating-point intervals"
},
{
"name": "sslinfo",
"schema": null,
"default_version": "1.2",
"installed_version": null,
"comment": "information about SSL certificates"
},
{
"name": "tablefunc",
"schema": null,
"default_version": "1.0",
"installed_version": null,
"comment": "functions that manipulate whole tables, including crosstab"
},
{
"name": "tcn",
"schema": null,
"default_version": "1.0",
"installed_version": null,
"comment": "Triggered change notifications"
},
{
"name": "tsm_system_rows",
"schema": null,
"default_version": "1.0",
"installed_version": null,
"comment": "TABLESAMPLE method which accepts number of rows as a limit"
},
{
"name": "tsm_system_time",
"schema": null,
"default_version": "1.0",
"installed_version": null,
"comment": "TABLESAMPLE method which accepts time in milliseconds as a limit"
},
{
"name": "unaccent",
"schema": null,
"default_version": "1.1",
"installed_version": null,
"comment": "text search dictionary that removes accents"
},
{
"name": "uuid-ossp",
"schema": "extensions",
"default_version": "1.1",
"installed_version": "1.1",
"comment": "generate universally unique identifiers (UUIDs)"
},
{
"name": "xml2",
"schema": null,
"default_version": "1.1",
"installed_version": null,
"comment": "XPath querying and XSLT"
}
]
-41
View File
@@ -1,41 +0,0 @@
/docs/common/_CommonResponses /docs
/docs/common/_DummyData /docs
/docs/common/_FromFunction /docs
/docs/common/filters/_adj /docs/client/ajv
/docs/common/filters/_cd /docs/client/cd
/docs/common/filters/_cs /docs/client/cs
/docs/common/filters/_eq /docs/client/eq
/docs/common/filters/_filter /docs/client/filter
/docs/common/filters/_gt /docs/client/gt
/docs/common/filters/_gte /docs/client/gte
/docs/common/filters/_ilike /docs/client/ilike
/docs/common/filters/_in /docs/client/in
/docs/common/filters/_is /docs/client/is
/docs/common/filters/_like /docs/client/like
/docs/common/filters/_lt /docs/client/lt
/docs/common/filters/_lte /docs/client/lte
/docs/common/filters/_match /docs/client/match
/docs/common/filters/_neq /docs/client/neq
/docs/common/filters/_not /docs/client/not
/docs/common/filters/_nxl /docs/client/nxl
/docs/common/filters/_nxr /docs/client/nxr
/docs/common/filters/_or /docs/client/or
/docs/common/filters/_ova /docs/client/ov
/docs/common/filters/_ovr /docs/client/ov
/docs/common/filters/_sl /docs/client/sl
/docs/common/filters/_sr /docs/client/sr
/docs/library/authentication /docs/guides/auth
/docs/library/delete /docs/client/delete
/docs/library/get /docs/client/select
/docs/library/getting-started /docs/client/supabase-client
/docs/library/patch /docs/client/update
/docs/library/post /docs/client/insert
/docs/library/stored-procedures /docs/client/rpc
/docs/library/subscribe /docs/client/subscribe
/docs/library/user-management /docs/guides/auth
/docs/postgres/postgres-intro /docs/postgres/server/about
/docs/realtime/about /docs/realtime/server/about
/docs/realtime/aws /docs/postgres/server/aws
/docs/realtime/digitalocean /docs/postgres/server/digitalocean
/docs/realtime/docker /docs/postgres/server/docker
/docs/realtime/source /docs/postgres/server/about
Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 293 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

+3 -3
View File
@@ -18,9 +18,9 @@ Today we're releasing [supabase-js](https://github.com/supabase/supabase-js) ver
### New Docs
Before digging into the improvements, we're excited to point out our new [developer docs](/docs/client/supabase-client). While they're still a work in progress, here are some things we think you'll like:
Before digging into the improvements, we're excited to point out our new [developer docs](/docs/reference/javascript/supabase-client). While they're still a work in progress, here are some things we think you'll like:
- The [Reference Docs](/docs/client/supabase-client) are auto-generated from our TypeScript definitions and then enriched with examples. This forces us to document our code and makes it easier to keep everything in sync.
- The [Reference Docs](/docs/reference/javascript/supabase-client) are auto-generated from our TypeScript definitions and then enriched with examples. This forces us to document our code and makes it easier to keep everything in sync.
- We added placeholders for the other languages that the community is developing. They have already started with Python, C#, Dart, Rust, and Swift. Expect to see the docs filling up soon!
- We've added sections for all of the open source tools we use, including [Postgres](/docs/postgres/server/about), [PostgREST](/docs/postgrest/server/about), [GoTrue](/docs/gotrue/server/about), and [Realtime](/docs/realtime/server/about). We'll be filling these with lots of valuable information including self-hosting, benchmarks, and simple guides.
@@ -109,7 +109,7 @@ To summarise the steps:
1. Install the new version: `npm install @supabase/supabase-js@latest`
2. Update all your `body` constants to `data`
3. Update all your `supabase.auth` functions with the new [Auth interface](/docs/client/auth-signup)
3. Update all your `supabase.auth` functions with the new [Auth interface](/docs/reference/javascript/auth-signup)
### Get started
@@ -56,7 +56,7 @@ One of the most requested Auth features was the ability to send magic links that
### Kaizen
- We have new and improved [docs](https://supabase.io/docs/client/supabase-client).
- We have new and improved [docs](https://supabase.io/docs/reference/javascript/supabase-client).
- We converted [realtime-js](https://github.com/supabase/realtime-js/) to TypeScript.
- Dashboard Performance: we heavily optimised our dashboard routes.
- With the help of the community, we [closed a lot of issues](https://github.com/orgs/supabase/projects/5) during Hacktoberfest.
@@ -4,7 +4,7 @@ description: Improving the performance of the Supabase dashboard
author: Inian Parameshwaran
author_title: Supabase
author_url: https://github.com/inian
author_image_url: https://avatars3.githubusercontent.com/u/2155545
author_image_url: https://github.com/inian.png
authorURL: https://github.com/inian
tags:
- supabase
@@ -10,7 +10,7 @@ image: supabase-december-2020.png
thumb: supabase-december-2020.png
tags:
- supabase
date: '01-02-2020'
date: '01-02-2021'
---
After 10 hectic months of building, Supabase is now in Beta.
@@ -109,7 +109,7 @@ CREATE POLICY "Individuals can only write their own messages." ON messages FOR
### Know how to do business logic on SQL/[PLPGSQL](https://www.postgresql.org/docs/current/plpgsql-overview.html#PLPGSQL-ADVANTAGES)
PL/pgSQL is a procedural programming language that can be used to write functions inside of your database. It can be useful for making [remote procedure calls](https://supabase.io/docs/client/rpc) from an API. You can go as deep as you want here, since it's an entire programming language, but understanding the basics will really go a long way, and give you super powers when working with your data.
PL/pgSQL is a procedural programming language that can be used to write functions inside of your database. It can be useful for making [remote procedure calls](https://supabase.io/docs/reference/javascript/rpc) from an API. You can go as deep as you want here, since it's an entire programming language, but understanding the basics will really go a long way, and give you super powers when working with your data.
You can use FUNCTIONs in combination with TRIGGERs to do cool stuff like have auto-updating `updated_at` columns on your data:
@@ -46,7 +46,7 @@ Writing SQL just got 10x easier. We added autocomplete to the SQL editor, includ
### Auth Redirects
Redirect your users to specific route within your site on [`signIn()`](https://supabase.io/docs/client/auth-signin#sign-in-with-redirect) and [`signUp()`](https://supabase.io/docs/client/auth-signup#sign-up-with-redirect).
Redirect your users to specific route within your site on [`signIn()`](https://supabase.io/docs/reference/javascript/auth-signin#sign-in-with-redirect) and [`signUp()`](https://supabase.io/docs/reference/javascript/auth-signup#sign-up-with-redirect).
![Redirect your users after sign up](/new/images/blog/feb/auth-redirect.png)
Loaded 100 of 212 files, more files were not shown because too many files have changed in this diff. Show more