Merge pull request #1 from supabase/feature/marketing-content-update
Feature/marketing content update
No files matched your search
@@ -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.
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
- [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
|
||||
|
||||
[](https://github.com/sponsors/supabase)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
@@ -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='© <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
|
||||
}
|
||||
@@ -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 →</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 →</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='© <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;
|
||||
}
|
||||
@@ -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 →</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 →</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;
|
||||
}
|
||||
}
|
||||
@@ -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:");
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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*"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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).
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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.
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
@@ -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.
|
||||
|
||||

|
||||
|
||||
Once you have a project, type "OAuth" into the search bar and open up "OAuth Consent Screen"
|
||||
|
||||

|
||||
|
||||
Select 'External' and proceed to fill out the rest of the form fields
|
||||
|
||||

|
||||
|
||||
Next open up Credentials page on the left
|
||||
|
||||

|
||||
|
||||
And click to create a new set of credentials, select OAuth client ID as the option
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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:
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
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/
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||

|
||||
|
||||
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:
|
||||
|
||||

|
||||
|
||||
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/
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
// {
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
],
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
],
|
||||
|
||||
@@ -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%';
|
||||
```
|
||||
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
|
||||
@@ -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'
|
||||
})
|
||||
```
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
@@ -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
|
||||
|
After Width: | Height: | Size: 184 KiB |
|
After Width: | Height: | Size: 274 KiB |
|
After Width: | Height: | Size: 410 KiB |
|
After Width: | Height: | Size: 129 KiB |
|
After Width: | Height: | Size: 291 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 293 KiB |
|
After Width: | Height: | Size: 221 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 33 KiB |
@@ -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).
|
||||
|
||||

|
||||
|
||||
|
||||