mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
Merge branch 'master' into chore/use-vercel-url-for-redirect
This commit is contained in:
commit
6bbd4d7ea5
1509 files changed
+59168
-19240
No files matched your search
@@ -1,27 +0,0 @@
|
||||
name: Deploy studio app to production on vercel
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- studio
|
||||
paths:
|
||||
- 'studio/**'
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-18.04
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
# remove ./studio/pages/api folder before deploying to vercel
|
||||
- uses: JesseTG/rm@v1.0.2
|
||||
with:
|
||||
path: ./studio/pages/api
|
||||
- uses: amondnet/vercel-action@v19
|
||||
with:
|
||||
vercel-token: ${{ secrets.VERCEL_TOKEN }} # Required
|
||||
github-comment: false
|
||||
vercel-args: '--prod -A .vercel/production.json'
|
||||
vercel-org-id: team_E6KJ1W561hMTjon1QSwOh0WO #Required
|
||||
vercel-project-id: Qmd7Mw8yFW8SbtvTWFV9uyRzw9P1rJLmgtHjSezywhtYec #Required
|
||||
working-directory: ./studio #Your Working Directory, Optional
|
||||
vercel-project-name: app
|
||||
@@ -1,27 +0,0 @@
|
||||
name: Deploy studio app to staging on vercel
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- 'studio/**'
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-18.04
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
# remove ./studio/pages/api folder before deploying to vercel
|
||||
- uses: JesseTG/rm@v1.0.2
|
||||
with:
|
||||
path: ./studio/pages/api
|
||||
- uses: amondnet/vercel-action@v19
|
||||
with:
|
||||
vercel-token: ${{ secrets.VERCEL_TOKEN }} # Required
|
||||
github-comment: false
|
||||
vercel-args: '--prod -A .vercel/staging.json'
|
||||
vercel-org-id: team_E6KJ1W561hMTjon1QSwOh0WO #Required
|
||||
vercel-project-id: QmcmhbiAtCMFTAHCuGgQscNbke4TzgWULECctNcKmxWCoT #Required
|
||||
working-directory: ./studio #Your Working Directory, Optional
|
||||
vercel-project-name: staging-app
|
||||
@@ -1,15 +1,9 @@
|
||||
# This is a basic workflow to help you get started with Actions
|
||||
name: Tests
|
||||
|
||||
# Controls when the workflow will run
|
||||
on:
|
||||
# Triggers the workflow on push or pull request events but only for the main branch
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
schedule:
|
||||
- cron: '0 4/6 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
@@ -41,17 +35,23 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run infrastructure
|
||||
run: |
|
||||
cp ../docker/.env.example ../docker/.env
|
||||
npm run docker:up
|
||||
- uses: supabase/setup-cli@v1
|
||||
- run: supabase start
|
||||
|
||||
- name: Run Test
|
||||
run: npm run test
|
||||
run: npm run test:local
|
||||
env:
|
||||
SUPABASE_DB_PORT: 54322
|
||||
SUPABASE_DB_PASS: postgres
|
||||
SUPABASE_DB_HOST: localhost
|
||||
SUPABASE_GOTRUE: http://localhost:54321
|
||||
SUPABASE_URL: http://localhost:54321
|
||||
SUPABASE_KEY_ANON: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24ifQ.625_WdcF3KHqz5amU0x2X5WWHP-OEs_4qj0ssLNHzTs
|
||||
SUPABASE_KEY_ADMIN: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSJ9.vI9obAHOGyVVKa3pD--kJlyxp-Z2zV9UUMAhKpNLAcU
|
||||
|
||||
- name: Stop infrastructure
|
||||
if: always()
|
||||
run: npm run docker:down
|
||||
run: supabase stop
|
||||
|
||||
- name: Get Allure history
|
||||
uses: actions/checkout@v2
|
||||
@@ -73,7 +73,7 @@ jobs:
|
||||
keep_reports: 50
|
||||
|
||||
- name: Deploy report to Github Pages
|
||||
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||
if: always()
|
||||
uses: peaceiris/actions-gh-pages@v2
|
||||
env:
|
||||
EXTERNAL_REPOSITORY: supabase/test-reports
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
PUBLISH_DIR: allure-history
|
||||
|
||||
- name: Post the link to the report
|
||||
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||
if: always()
|
||||
uses: Sibz/github-status-action@v1
|
||||
with:
|
||||
authToken: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -9,7 +9,6 @@ on:
|
||||
- 'studio/**'
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
@@ -18,15 +17,15 @@ jobs:
|
||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'npm'
|
||||
- name: Install deps
|
||||
run: npm i
|
||||
working-directory: ./studio
|
||||
- name: Run build
|
||||
run: npm run build
|
||||
working-directory: ./studio
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'npm'
|
||||
- name: Install deps
|
||||
run: npm ci
|
||||
working-directory: ./
|
||||
- name: Run build
|
||||
run: npx turbo run build --filter=studio
|
||||
working-directory: ./
|
||||
@@ -5,16 +5,15 @@ name: Studio Unit Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
branches: [master]
|
||||
paths:
|
||||
- 'studio/**'
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
branches: [master]
|
||||
paths:
|
||||
- 'studio/**'
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
@@ -23,15 +22,15 @@ jobs:
|
||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'npm'
|
||||
- name: Install deps
|
||||
run: npm install
|
||||
working-directory: ./
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
working-directory: ./studio
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'npm'
|
||||
- name: Install deps
|
||||
run: npm ci
|
||||
working-directory: ./
|
||||
- name: Run tests
|
||||
run: npm run test:studio
|
||||
working-directory: ./
|
||||
@@ -195,6 +195,15 @@ The monorepo has a set of shared components under `/packages`:
|
||||
- `/packages/config`: All shared config
|
||||
- `/packages/spec`: Generates documentation using spec files.
|
||||
- `/packages/tsconfig`: Shared Typescript settings
|
||||
- `/packages/ui`: Shared UI components (formerly @supabase/ui)
|
||||
|
||||
To use these 'packages', or any of their components from a Next.JS app, you must use `next-transpile-modules` in the `next.config.js` file. This looks like:
|
||||
|
||||
```tsx
|
||||
// next.config.js
|
||||
const withTM = require('next-transpile-modules')(['ui', 'common'])
|
||||
module.exports = withTM({})
|
||||
```
|
||||
|
||||
### Installing packages
|
||||
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
---
|
||||
slug: /
|
||||
sidebar_label: Auth Helpers
|
||||
---
|
||||
|
||||
# Auth Helpers
|
||||
|
||||
A collection of framework specific Auth utilities for working with Supabase.
|
||||
|
||||
## Status
|
||||
|
||||
The Auth Helpers are in `beta`. They are usable in their current state, but it's likely that there will be breaking changes.
|
||||
|
||||
## Additional Links
|
||||
|
||||
- [Source code](https://github.com/supabase/auth-helpers)
|
||||
- [Known bugs and issues](https://github.com/supabase/auth-helpers/issues)
|
||||
@@ -1,315 +0,0 @@
|
||||
---
|
||||
id: next-js
|
||||
slug: next-js
|
||||
sidebar_label: With Next.js
|
||||
---
|
||||
|
||||
# Supabase Auth with Next.js
|
||||
|
||||
This submodule provides convenience helpers for implementing user authentication in Next.js applications.
|
||||
|
||||
## Installation
|
||||
|
||||
Using [npm](https://npmjs.org):
|
||||
|
||||
```sh
|
||||
npm install @supabase/auth-helpers-nextjs
|
||||
|
||||
# Main components and hooks for React based frameworks (optional)
|
||||
npm install @supabase/auth-helpers-react
|
||||
```
|
||||
|
||||
Using [yarn](https://yarnpkg.com/):
|
||||
|
||||
```sh
|
||||
yarn add @supabase/auth-helpers-nextjs
|
||||
|
||||
# Main components and hooks for React based frameworks (optional)
|
||||
yarn add @supabase/auth-helpers-react
|
||||
```
|
||||
|
||||
This library supports the following tooling versions:
|
||||
|
||||
- Node.js: `^10.13.0 || >=12.0.0`
|
||||
|
||||
- Next.js: `>=10`
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Configuration
|
||||
|
||||
Set up the following env vars. For local development you can set them in a `.env.local` file. See an [example](https://github.com/supabase/auth-helpers/blob/main/examples/nextjs/.env.local.example).
|
||||
|
||||
```bash
|
||||
# Find these in your Supabase project settings > API
|
||||
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
|
||||
```
|
||||
|
||||
### Basic Setup
|
||||
|
||||
- Create an `auth` directory under the `/pages/api/` directory.
|
||||
|
||||
- Create a `[...supabase].js` file under the newly created `auth` directory.
|
||||
|
||||
The path to your dynamic API route file would be `/pages/api/auth/[...supabase].js`. Populate that file as follows:
|
||||
|
||||
```js
|
||||
import { handleAuth } from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
export default handleAuth({ logout: { returnTo: '/' } })
|
||||
```
|
||||
|
||||
Executing `handleAuth()` creates the following route handlers under the hood that perform different parts of the authentication flow:
|
||||
|
||||
- `/api/auth/callback`: The `UserProvider` forwards the session details here every time `onAuthStateChange` fires on the client side. This is needed to set up the cookies for your application so that SSR works seamlessly.
|
||||
|
||||
- `/api/auth/user`: You can fetch user profile information in JSON format.
|
||||
|
||||
- `/api/auth/logout`: Your Next.js application logs out the user. You can optionally pass a `returnTo` parameter to return to a custom relative URL after logout, eg `/api/auth/logout?returnTo=/login`. This will overwrite the logout `returnTo` option specified `handleAuth()`
|
||||
|
||||
Wrap your `pages/_app.js` component with the `UserProvider` component:
|
||||
|
||||
```jsx
|
||||
// pages/_app.js
|
||||
import React from 'react'
|
||||
import { UserProvider } from '@supabase/auth-helpers-react'
|
||||
import { supabaseClient } from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
export default function App({ Component, pageProps }) {
|
||||
return (
|
||||
<UserProvider supabaseClient={supabaseClient}>
|
||||
<Component {...pageProps} />
|
||||
</UserProvider>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
You can now determine if a user is authenticated by checking that the `user` object returned by the `useUser()` hook is defined.
|
||||
|
||||
## Client-side data fetching with RLS
|
||||
|
||||
For [row level security](https://supabase.com/docs/learn/auth-deep-dive/auth-row-level-security) to work properly when fetching data client-side, you need to make sure to import the `{ supabaseClient }` from `# @supabase/auth-helpers-nextjs` and only run your query once the user is defined client-side in the `useUser()` hook:
|
||||
|
||||
```js
|
||||
import { Auth } from '@supabase/ui'
|
||||
import { useUser } from '@supabase/auth-helpers-react'
|
||||
import { supabaseClient } from '@supabase/auth-helpers-nextjs'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
const LoginPage = () => {
|
||||
const { user, error } = useUser()
|
||||
const [data, setData] = useState()
|
||||
|
||||
useEffect(() => {
|
||||
async function loadData() {
|
||||
const { data } = await supabaseClient.from('test').select('*')
|
||||
setData(data)
|
||||
}
|
||||
// Only run query once user is logged in.
|
||||
if (user) loadData()
|
||||
}, [user])
|
||||
|
||||
if (!user)
|
||||
return (
|
||||
<>
|
||||
{error && <p>{error.message}</p>}
|
||||
<Auth
|
||||
supabaseClient={supabaseClient}
|
||||
providers={['google', 'github']}
|
||||
socialLayout="horizontal"
|
||||
socialButtonSize="xlarge"
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<button onClick={() => supabaseClient.auth.signOut()}>Sign out</button>
|
||||
<p>user:</p>
|
||||
<pre>{JSON.stringify(user, null, 2)}</pre>
|
||||
<p>client-side data fetching with RLS</p>
|
||||
<pre>{JSON.stringify(data, null, 2)}</pre>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default LoginPage
|
||||
```
|
||||
|
||||
### Server-side rendering (SSR) - withPageAuth
|
||||
|
||||
If you wrap your `getServerSideProps` with `withPageAuth` your props object will be augmented with the user object.
|
||||
|
||||
```js
|
||||
// pages/profile.js
|
||||
import { withPageAuth } from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
export default function Profile({ user }) {
|
||||
return <div>Hello {user.name}</div>
|
||||
}
|
||||
|
||||
export const getServerSideProps = withPageAuth({ redirectTo: '/login' })
|
||||
```
|
||||
|
||||
If there is no authenticated user, they will be redirect to your home page, unless you specify the `redirectTo` option.
|
||||
|
||||
You can pass in your own `getServerSideProps` method, the props returned from this will be merged with the
|
||||
user props. You can also access the user session data by calling `getUser` inside of this method, eg:
|
||||
|
||||
```js
|
||||
// pages/protected-page.js
|
||||
import { withPageAuth, getUser } from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
export default function ProtectedPage({ user, customProp }) {
|
||||
return <div>Protected content</div>
|
||||
}
|
||||
|
||||
export const getServerSideProps = withPageAuth({
|
||||
redirectTo: '/foo',
|
||||
async getServerSideProps(ctx) {
|
||||
// Access the user object
|
||||
const { user, accessToken } = await getUser(ctx)
|
||||
return { props: { email: user?.email } }
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Server-side data fetching with RLS
|
||||
|
||||
For [row level security](https://supabase.com/docs/learn/auth-deep-dive/auth-row-level-security) to work in a server environment, you need to inject the request context into the supabase client:
|
||||
|
||||
```js
|
||||
import {
|
||||
User,
|
||||
withPageAuth,
|
||||
supabaseServerClient,
|
||||
} from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
export default function ProtectedPage({
|
||||
user,
|
||||
data,
|
||||
}: {
|
||||
user: User,
|
||||
data: any,
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<div>Protected content for {user.email}</div>
|
||||
<pre>{JSON.stringify(data, null, 2)}</pre>
|
||||
<pre>{JSON.stringify(user, null, 2)}</pre>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const getServerSideProps = withPageAuth({
|
||||
redirectTo: '/',
|
||||
async getServerSideProps(ctx) {
|
||||
// Run queries with RLS on the server
|
||||
const { data } = await supabaseServerClient(ctx).from('test').select('*')
|
||||
return { props: { data } }
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Server-side data fetching to OAuth APIs using `provider_token`
|
||||
|
||||
When using third-party auth providers, sessions are initiated with an additional `provider_token` field which is persisted as an HTTPOnly cookie upon logging in to enabled usage on the server side. The `provider_token` can be used to make API requests to the OAuth provider's API endpoints on behalf of the logged-in user. In the following example, we fetch the user's full profile from the third-party API during SSR using their id and auth token:
|
||||
|
||||
```js
|
||||
import { User, withPageAuth, getUser } from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
interface Profile {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
export default function ProtectedPage({
|
||||
user,
|
||||
data,
|
||||
}: {
|
||||
user: User,
|
||||
profile: Profile,
|
||||
}) {
|
||||
return <div>Protected content</div>
|
||||
}
|
||||
|
||||
export const getServerSideProps = withPageAuth({
|
||||
redirectTo: '/',
|
||||
async getServerSideProps(ctx) {
|
||||
// Retrieve provider_token from cookies
|
||||
const provider_token = ctx.req.cookies['sb-provider-token']
|
||||
// Get logged in user's third-party id from metadata
|
||||
const { user } = await getUser(ctx)
|
||||
const userId = user?.user_metadata.provider_id
|
||||
const profile: Profile = await (
|
||||
await fetch(`https://api.example.com/users/${userId}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: `Bearer ${provider_token}`,
|
||||
},
|
||||
})
|
||||
).json()
|
||||
return { props: { profile } }
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Protecting API routes
|
||||
|
||||
Wrap an API Route to check that the user has a valid session. If they're not logged in the handler will return a
|
||||
401 Unauthorized.
|
||||
|
||||
```js
|
||||
// pages/api/protected-route.js
|
||||
import {
|
||||
withApiAuth,
|
||||
supabaseServerClient,
|
||||
} from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
export default withApiAuth(async function ProtectedRoute(req, res) {
|
||||
// Run queries with RLS on the server
|
||||
const { data } = await supabaseServerClient({ req, res })
|
||||
.from('test')
|
||||
.select('*')
|
||||
res.json(data)
|
||||
})
|
||||
```
|
||||
|
||||
If you visit `/api/protected-route` without a valid session cookie, you will get a 401 response.
|
||||
|
||||
## Protecting routes with [Nextjs Middleware](https://nextjs.org/docs/middleware)
|
||||
|
||||
As an alternative to protecting individual pages using `getServerSideProps` with `withPageAuth`, `withMiddlewareAuth` can be used from inside a `_middleware` file to protect an entire directory. In the following example, all requests to `/protected/*` will check whether a user is signed in, if successful the request will be forwarded to the destination route, otherwise the user will be redirected to `/login` (defaults to: `/`) with a 307 Temporary Redirect response status:
|
||||
|
||||
```ts
|
||||
// pages/protected/_middleware.ts
|
||||
import { withMiddlewareAuth } from '@supabase/auth-helpers-nextjs/middleware'
|
||||
|
||||
export const middleware = withMiddlewareAuth({ redirectTo: '/login' })
|
||||
```
|
||||
|
||||
It is also possible to add finer granularity based on the user logged in. I.e. you can specify a promise to determine if a specific user has permission or not.
|
||||
|
||||
```ts
|
||||
// pages/protected/_middleware.ts
|
||||
import { withMiddlewareAuth } from '@supabase/auth-helpers-nextjs/dist/middleware'
|
||||
|
||||
export const middleware = withMiddlewareAuth({
|
||||
redirectTo: '/login',
|
||||
authGuard: {
|
||||
isPermitted: async (user) => user.email?.endsWith('@example.com') ?? false,
|
||||
redirectTo: '/insufficient-permissions',
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Migrating from @supabase/supabase-auth-helpers to @supabase/auth-helpers
|
||||
|
||||
This is a step by step guide on migrating away from the `@supabase/supabase-auth-helpers` to the newly released `@supabase/auth-helpers`.
|
||||
|
||||
1. Install `@supabase/supabase-js`, `@supabase/auth-helpers-nextjs` and `@supabase/auth-helpers-react` libraries from npm.
|
||||
2. Replace all imports of `@supabase/supabase-auth-helpers/nextjs` in your project with `@supabase/auth-helpers-nextjs`.
|
||||
3. Replace all imports of `@supabase/supabase-auth-helpers/react` in your project with `@supabase/auth-helpers-react`.
|
||||
4. Replace all instances of `withAuthRequired` in any of your NextJS pages with `withPageAuth`.
|
||||
5. Replace all instances of `withAuthRequired` in any of your NextJS API endpoints with `withApiAuth`.
|
||||
6. Uninstall `@supabase/supabase-auth-helpers`.
|
||||
@@ -1,303 +0,0 @@
|
||||
---
|
||||
id: sveltekit
|
||||
slug: sveltekit
|
||||
sidebar_label: With SvelteKit
|
||||
---
|
||||
|
||||
# Supabase Auth with SvelteKit
|
||||
|
||||
This submodule provides convenience helpers for implementing user authentication in [SvelteKit](https://kit.svelte.dev/) applications.
|
||||
|
||||
## Installation
|
||||
|
||||
Using [npm](https://npmjs.org):
|
||||
|
||||
```sh
|
||||
npm install @supabase/auth-helpers-sveltekit
|
||||
|
||||
# Main component for Svelte based frameworks (optional but recommended)
|
||||
npm install @supabase/auth-helpers-svelte
|
||||
```
|
||||
|
||||
Using [yarn](https://yarnpkg.com/):
|
||||
|
||||
```sh
|
||||
yarn add @supabase/auth-helpers-sveltekit
|
||||
|
||||
# Main component for Svelte based frameworks (optional but recommended)
|
||||
yarn add @supabase/auth-helpers-svelte
|
||||
```
|
||||
|
||||
This library supports the following tooling versions:
|
||||
|
||||
- Node.js: `^16.15.0`
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Configuration
|
||||
|
||||
Set up the fillowing env vars. For local development you can set them in a `.env` file. See an [example](https://github.com/supabase/auth-helpers/blob/main/examples/sveltekit/.env.example).
|
||||
|
||||
```bash
|
||||
# Find these in your Supabase project settings > API
|
||||
VITE_SUPABASE_URL=https://your-project.supabase.co
|
||||
VITE_SUPABASE_ANON_KEY=your-anon-key
|
||||
```
|
||||
|
||||
### SupabaseClient and SupaAuthHelper component setup
|
||||
|
||||
We will start off by creating a `db.ts` file inside of our `src/lib` directory. Now lets instantiate our `supabaseClient` by using our `createSupabaseClient` function from the `@supabase/auth-helpers-sveltekit` library.
|
||||
|
||||
```ts
|
||||
// src/lib/db.ts
|
||||
import { createSupabaseClient } from '@supabase/auth-helpers-sveltekit'
|
||||
|
||||
const { supabaseClient } = createSupabaseClient(
|
||||
import.meta.env.VITE_SUPABASE_URL as string,
|
||||
import.meta.env.VITE_SUPABASE_ANON_KEY as string
|
||||
)
|
||||
|
||||
export { supabaseClient }
|
||||
```
|
||||
|
||||
Edit your `__layout.svelte` file and add import the `SupaAuthHelper` component, the `supabaseClient` we just instantiated and the `session` store.
|
||||
|
||||
```html
|
||||
// src/routes/__layout.svelte
|
||||
<script>
|
||||
import { session } from '$app/stores'
|
||||
import { supabaseClient } from '$lib/db'
|
||||
import { SupaAuthHelper } from '@supabase/auth-helpers-svelte'
|
||||
</script>
|
||||
|
||||
<SupaAuthHelper {supabaseClient} {session}>
|
||||
<slot />
|
||||
</SupaAuthHelper>
|
||||
```
|
||||
|
||||
### Hooks setup
|
||||
|
||||
Our `hooks.ts` file is where the heavy lifting of this library happens, we need to import our function to handle the sign in, signing out and cookie creation phase. we can import all the hooks using `handleAuth` function and destructure its returned data.
|
||||
|
||||
```ts
|
||||
// src/hooks.ts
|
||||
import { handleAuth } from '@supabase/auth-helpers-sveltekit'
|
||||
import type { GetSession, Handle } from '@sveltejs/kit'
|
||||
import { sequence } from '@sveltejs/kit/hooks'
|
||||
|
||||
export const handle: Handle = sequence(...handleAuth())
|
||||
|
||||
export const getSession: GetSession = async (event) => {
|
||||
const { user, accessToken, error } = event.locals
|
||||
return {
|
||||
user,
|
||||
accessToken,
|
||||
error,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
These will create the handlers under the hood that perform different parts of the authentication flow:
|
||||
|
||||
- `/api/auth/callback`: The `UserHelper` forwards the session details here every time `onAuthStateChange` fires on the client side. This is needed to set up the cookies for your application so that SSR works seamlessly.
|
||||
- `/api/auth/user`: You can fetch user profile information in JSON format.
|
||||
- `/api/auth/logout`: You can logout the user.
|
||||
|
||||
### Typings
|
||||
|
||||
In order to get the most out of TypeScript and its intellisense, you should import our types into the `app.d.ts` type definition file that comes with your SvelteKit project.
|
||||
|
||||
```ts
|
||||
// src/app.d.ts
|
||||
/// <reference types="@sveltejs/kit" />
|
||||
// See https://kit.svelte.dev/docs/types#app
|
||||
// for information about these interfaces
|
||||
declare namespace App {
|
||||
interface UserSession {
|
||||
user: import('@supabase/supabase-js').User
|
||||
accessToken?: string
|
||||
}
|
||||
interface Locals extends UserSession {
|
||||
error: import('@supabase/supabase-js').ApiError
|
||||
}
|
||||
|
||||
interface Session extends UserSession {} // interface Platform {} // interface Stuff {}
|
||||
}
|
||||
```
|
||||
|
||||
### Signing out
|
||||
|
||||
This library has provided a dedicated endpoint for you to use to sign a user out. This endpoint will sign the user out of the Gotrue server, clear the cookies that were set when the user logged in and redirect the user to a configurable path.
|
||||
|
||||
The logout handler endpoint is `/api/auth/logout`, this will take a `GET` request which means it can be used as the href for a normal `a` tag in your html.
|
||||
|
||||
```html
|
||||
<a href="/api/auth/logout">Sign out</a>
|
||||
```
|
||||
|
||||
### Logout handler configuration
|
||||
|
||||
In your `src/hooks.ts` file the logout handler is already setup and you can configure the redirect path from here.
|
||||
|
||||
> By default the redirect path after logging out will be `/`.
|
||||
|
||||
```ts
|
||||
export const handle = sequence(
|
||||
...handleAuth({
|
||||
logout: { returnTo: '/auth/signin' },
|
||||
})
|
||||
)
|
||||
```
|
||||
|
||||
### Basic Setup
|
||||
|
||||
You can now determine if a user is authenticated on the client-side by checking that the `user` object returned by the `$session` store is defined.
|
||||
|
||||
```html
|
||||
// example
|
||||
<script>
|
||||
import { session } from '$app/stores'
|
||||
</script>
|
||||
|
||||
{#if !$session.user}
|
||||
<h1>I am not logged in</h1>
|
||||
{:else}
|
||||
<h1>Welcome {$session.user.email}</h1>
|
||||
<p>I am logged in!</p>
|
||||
{/if}
|
||||
```
|
||||
|
||||
## Client-side data fetching with RLS
|
||||
|
||||
For [row level security](https://supabase.com/docs/learn/auth-deep-dive/auth-row-level-security) to work properly when fetching data client-side, you need to make sure to import the `{ supabaseClient }` from `@supabase/auth-helpers-sveltekit` and only run your query once the user is defined client-side in the `$session`:
|
||||
|
||||
```html
|
||||
<script>
|
||||
import Auth from 'supabase-ui-svelte';
|
||||
import { error, isLoading } from '@supabase/auth-helpers-svelte';
|
||||
import { supabaseClient } from '$lib/db';
|
||||
import { session } from '$app/stores';
|
||||
|
||||
let loadedData = [];
|
||||
async function loadData() {
|
||||
const { data } = await supabaseClient.from('test').select('*').single();
|
||||
loadedData = data
|
||||
}
|
||||
|
||||
$: {
|
||||
if ($session.user && $session.user.id) {
|
||||
loadData();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if !$session.user}
|
||||
{#if $error}
|
||||
<p>{$error.message}</p>
|
||||
{/if}
|
||||
<h1>{$isLoading ? `Loading...` : `Loaded!`}</h1>
|
||||
<Auth
|
||||
supabaseClient={supabaseClient}
|
||||
providers={['google', 'github']}
|
||||
/>
|
||||
{:else}
|
||||
<a href=="/api/auth/logout">Sign out</a>
|
||||
<p>user:</p>
|
||||
<pre>{JSON.stringify($session.user, null, 2)}</pre>
|
||||
<p>client-side data fetching with RLS</p>
|
||||
<pre>{JSON.stringify(loadedData, null, 2)}</pre>
|
||||
{/if}
|
||||
```
|
||||
|
||||
### Server-side data fetching with RLS
|
||||
|
||||
For [row level security](https://supabase.com/docs/learn/auth-deep-dive/auth-row-level-security) to work in a server environment, you need to inject the request context into the supabase client:
|
||||
|
||||
```html
|
||||
<!-- src/routes/profile.svelte -->
|
||||
<script>
|
||||
export let user
|
||||
export let data
|
||||
</script>
|
||||
|
||||
<div>Protected content for {user.email}</div>
|
||||
<pre>{JSON.stringify(data, null, 2)}</pre>
|
||||
<pre>{JSON.stringify(user, null, 2)}</pre>
|
||||
```
|
||||
|
||||
```ts
|
||||
// src/routes/profile.ts
|
||||
import {
|
||||
supabaseServerClient,
|
||||
withApiAuth,
|
||||
} from '@supabase/auth-helpers-sveltekit'
|
||||
import type { RequestHandler } from './__types/profile'
|
||||
|
||||
interface TestTable {
|
||||
id: string
|
||||
created_at: string
|
||||
}
|
||||
|
||||
interface GetOutput {
|
||||
user: User
|
||||
data: TestTable[]
|
||||
}
|
||||
|
||||
export const GET: RequestHandler<GetOutput> = async ({ locals }) =>
|
||||
withApiAuth(
|
||||
{
|
||||
redirectTo: '/',
|
||||
user: locals.user,
|
||||
},
|
||||
async () => {
|
||||
const { data } = await supabaseServerClient(session.accessToken)
|
||||
.from<TestTable>('test')
|
||||
.select('*')
|
||||
|
||||
return {
|
||||
body: {
|
||||
user: locals.user,
|
||||
data,
|
||||
},
|
||||
}
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
## Protecting API routes
|
||||
|
||||
Wrap an API Route to check that the user has a valid session. If they're not logged in the handler will return a
|
||||
303 and redirect header.
|
||||
|
||||
```ts
|
||||
// src/routes/api/protected-route.ts
|
||||
import {
|
||||
supabaseServerClient,
|
||||
withApiAuth,
|
||||
} from '@supabase/auth-helpers-sveltekit'
|
||||
import type { RequestHandler } from './__types/protected-route'
|
||||
|
||||
interface TestTable {
|
||||
id: string
|
||||
created_at: string
|
||||
}
|
||||
|
||||
interface GetOutput {
|
||||
data: TestTable[]
|
||||
}
|
||||
|
||||
export const GET: RequestHandler<GetOutput> = async ({ locals, request }) =>
|
||||
withApiAuth({ user: locals.user }, async () => {
|
||||
// Run queries with RLS on the server
|
||||
const { data } = await supabaseServerClient(request)
|
||||
.from('test')
|
||||
.select('*')
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
body: { data },
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
If you visit `/api/protected-route` without a valid session cookie, you will get a 303 response.
|
||||
@@ -16,6 +16,9 @@ The CLI is still under development, but it contains all the functionality for wo
|
||||
- CI/CD for releasing to production: [`supabase db push`](https://supabase.com/docs/reference/cli/usage#supabase-db-push)
|
||||
- Manage your Supabase projects: [`supabase projects`](https://supabase.com/docs/reference/cli/usage#supabase-projects)
|
||||
- Generate types directly from your database schema: [`supabase gen types`](https://supabase.com/docs/reference/cli/usage#supabase-gen)
|
||||
- A [community-supported GitHub Action](https://github.com/lyqht/generate-supabase-db-types-github-action) to generate TypeScript types
|
||||
- Shell autocomplete: [`supabase completion`](https://supabase.com/docs/reference/cli/usage#supabase-completion)
|
||||
- A [community-supported Fig autocomplete spec](https://fig.io/manual/supabase) for macOS terminal
|
||||
|
||||
## Additional Links
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
id: usage
|
||||
slug: /usage
|
||||
title: Usage
|
||||
toc_max_heading_level: 3
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
|
||||
<!-- AUTOGENERATED: DO NOT EDIT DIRECTLY IF THIS IS VERSION "next" -->
|
||||
|
||||
Documentation of the gotrue API.
|
||||
|
||||
<!-- AUTOGENERATED: DO NOT EDIT DIRECTLY IF THIS IS VERSION "next" -->
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ Find the source code on [GitHub](https://github.com/supabase/supabase-js).
|
||||
Or via CDN
|
||||
|
||||
```js
|
||||
<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2"></script>
|
||||
//or
|
||||
<script src="https://unpkg.com/@supabase/supabase-js"></script>
|
||||
<script src="https://unpkg.com/@supabase/supabase-js@2"></script>
|
||||
```
|
||||
@@ -7,14 +7,6 @@ hide_table_of_contents: true
|
||||
|
||||
# Supabase JavaScript Library
|
||||
|
||||
:::note
|
||||
|
||||
You're viewing the Supabase docs for the next version of our library which is not yet released.
|
||||
|
||||
Refer to the `v1` docs for a stable release.
|
||||
|
||||
:::
|
||||
|
||||
This reference documents every object and method available in Supabase's isomorphic JavaScript library, `supabase-js`.
|
||||
|
||||
You can use the `supabase-js` library to:
|
||||
@@ -28,6 +20,6 @@ You can use the `supabase-js` library to:
|
||||
## Additional Links
|
||||
|
||||
- Source Code: [github.com/supabase/supabase-js](https://github.com/supabase/supabase-js)
|
||||
- TypeDoc: [supabase.github.io/supabase-js](https://supabase.github.io/supabase-js)
|
||||
- TypeDoc: [supabase.github.io/supabase-js](https://supabase.github.io/supabase-js/v2/)
|
||||
- NPM: [npmjs.com/package/@supabase/supabase-js](https://www.npmjs.com/package/@supabase/supabase-js)
|
||||
- [Known bugs and issues](https://github.com/supabase/supabase-js/issues)
|
||||
@@ -6,9 +6,9 @@ id: release-notes
|
||||
|
||||
Supabase.js v2 release notes.
|
||||
|
||||
## 2.0.0 Release Candidate
|
||||
## 2.0.0
|
||||
|
||||
Install the latest with `npm install @supabase/supabase-js@rc`.
|
||||
Install the latest with `npm install @supabase/supabase-js`.
|
||||
|
||||
### Explicit constructor options
|
||||
|
||||
|
||||
@@ -76,9 +76,9 @@ async function getMovies() {
|
||||
return await supabase.from('movies').select('id, title, actors(*)')
|
||||
}
|
||||
|
||||
type actors = Database['public']['Tables']['actors']['Row']
|
||||
type Actors = Database['public']['Tables']['actors']['Row']
|
||||
type MoviesResponse = Awaited<ReturnType<typeof getMovies>>
|
||||
type MoviesResponseSuccess = MoviesResponse['data'] & {
|
||||
actors: actors[]
|
||||
actors: Actors[]
|
||||
}
|
||||
```
|
||||
@@ -1,12 +1,12 @@
|
||||
---
|
||||
id: generating-types
|
||||
title: "Generating Types"
|
||||
title: 'Generating Types'
|
||||
slug: generating-types
|
||||
custom_edit_url: https://github.com/supabase/supabase/edit/master/spec/supabase_js_v1_legacy.yml
|
||||
custom_edit_url: https://github.com/supabase/supabase/edit/master/spec/supabase_js_v1.yml
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
|
||||
Supabase will soon release native type generators that dump your database types for various languages. For now, we support TypeScript [through third-party tools](/docs/guides/api/generating-types).
|
||||
|
||||
@@ -19,12 +19,12 @@ If you don't provide a type for the row you need to explicitly pass `from<any>('
|
||||
|
||||
```ts
|
||||
type Message = {
|
||||
id: number;
|
||||
inserted_at: string;
|
||||
message: string;
|
||||
user_id: string;
|
||||
channel_id: number;
|
||||
author: { username: string };
|
||||
id: number
|
||||
inserted_at: string
|
||||
message: string
|
||||
user_id: string
|
||||
channel_id: number
|
||||
author: { username: string }
|
||||
}
|
||||
|
||||
const response = await supabase
|
||||
@@ -39,4 +39,4 @@ const response = await supabase
|
||||
.select('*, author:user_id(username)')
|
||||
.match({ channel_id: 2 })
|
||||
response.data // Response data will be of type Array<any>.
|
||||
```
|
||||
```
|
||||
@@ -1,12 +1,11 @@
|
||||
---
|
||||
id: index
|
||||
title: "Supabase Client"
|
||||
title: 'Supabase Client'
|
||||
slug: supabase-client
|
||||
custom_edit_url: ../../spec/supabase_js_v1_legacy.yml
|
||||
custom_edit_url: ../../spec/supabase_js_v1.yml
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
|
||||
|
||||
Supabase JavaScript.
|
||||
Supabase JavaScript.
|
||||
@@ -1,24 +1,20 @@
|
||||
---
|
||||
id: initializing
|
||||
title: "Initializing"
|
||||
title: 'Initializing'
|
||||
slug: initializing
|
||||
custom_edit_url: https://github.com/supabase/supabase/edit/master/spec/supabase_js_v1_legacy.yml
|
||||
custom_edit_url: https://github.com/supabase/supabase/edit/master/spec/supabase_js_v1.yml
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
|
||||
You can initialize a new Supabase client using the `createClient()` method.
|
||||
|
||||
The Supabase client is your entrypoint to the rest of the Supabase functionality
|
||||
and is the easiest way to interact with everything we offer within the Supabase ecosystem.
|
||||
|
||||
|
||||
|
||||
|
||||
## Parameters
|
||||
|
||||
|
||||
<ul className="method-list-group">
|
||||
|
||||
<li className="method-list-item">
|
||||
@@ -41,7 +37,6 @@ The unique Supabase URL which is supplied when you create a new project in your
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li className="method-list-item">
|
||||
<h4 className="method-list-item-label">
|
||||
<span className="method-list-item-label-name">
|
||||
@@ -62,7 +57,6 @@ The unique Supabase Key which is supplied when you create a new project in your
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li className="method-list-item">
|
||||
<h4 className="method-list-item-label">
|
||||
<span className="method-list-item-label-name">
|
||||
@@ -77,7 +71,7 @@ The unique Supabase Key which is supplied when you create a new project in your
|
||||
</h4>
|
||||
<div class="method-list-item-description">
|
||||
|
||||
No description provided.
|
||||
No description provided.
|
||||
|
||||
</div>
|
||||
|
||||
@@ -85,36 +79,22 @@ No description provided.
|
||||
|
||||
</ul>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
### createClient()
|
||||
|
||||
|
||||
|
||||
```js
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
|
||||
// Create a single supabase client for interacting with your database
|
||||
const supabase = createClient('https://xyzcompany.supabase.co', 'public-anon-key')
|
||||
const supabase = createClient(
|
||||
'https://xyzcompany.supabase.co',
|
||||
'public-anon-key'
|
||||
)
|
||||
```
|
||||
|
||||
### With additional parameters
|
||||
|
||||
|
||||
|
||||
```js
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
|
||||
@@ -123,22 +103,28 @@ const options = {
|
||||
headers: { 'x-my-custom-header': 'my-app-name' },
|
||||
autoRefreshToken: true,
|
||||
persistSession: true,
|
||||
detectSessionInUrl: true
|
||||
detectSessionInUrl: true,
|
||||
}
|
||||
const supabase = createClient("https://xyzcompany.supabase.co", "public-anon-key", options)
|
||||
const supabase = createClient(
|
||||
'https://xyzcompany.supabase.co',
|
||||
'public-anon-key',
|
||||
options
|
||||
)
|
||||
```
|
||||
|
||||
### API schemas
|
||||
|
||||
|
||||
|
||||
```js
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
|
||||
// Provide a custom schema. Defaults to "public".
|
||||
const supabase = createClient('https://xyzcompany.supabase.co', 'public-anon-key', {
|
||||
schema: 'other_schema'
|
||||
})
|
||||
const supabase = createClient(
|
||||
'https://xyzcompany.supabase.co',
|
||||
'public-anon-key',
|
||||
{
|
||||
schema: 'other_schema',
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
By default the API server points to the `public` schema. You can enable other database schemas within the Dashboard.
|
||||
@@ -148,16 +134,18 @@ Note: each client connection can only access a single schema, so the code above
|
||||
|
||||
### Custom `fetch` implementation
|
||||
|
||||
|
||||
|
||||
```js
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
|
||||
const supabase = createClient('https://xyzcompany.supabase.co', 'public-anon-key', {
|
||||
fetch: fetch.bind(globalThis)
|
||||
})
|
||||
const supabase = createClient(
|
||||
'https://xyzcompany.supabase.co',
|
||||
'public-anon-key',
|
||||
{
|
||||
fetch: fetch.bind(globalThis),
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
`supabase-js` uses the [`cross-fetch`](https://www.npmjs.com/package/cross-fetch) library to make HTTP requests,
|
||||
but an alternative `fetch` implementation can be provided as an option.
|
||||
This is most useful in environments where `cross-fetch` is not compatible (for instance Cloudflare Workers).
|
||||
This is most useful in environments where `cross-fetch` is not compatible (for instance Cloudflare Workers).
|
||||
@@ -1,12 +1,12 @@
|
||||
---
|
||||
id: installing
|
||||
title: "Installing"
|
||||
title: 'Installing'
|
||||
slug: installing
|
||||
custom_edit_url: https://github.com/supabase/supabase/edit/master/spec/supabase_js_v1_legacy.yml
|
||||
custom_edit_url: https://github.com/supabase/supabase/edit/master/spec/supabase_js_v1.yml
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
|
||||
All JavaScript libraries are built directly by the Supabase team.
|
||||
|
||||
@@ -15,11 +15,13 @@ Other languages are built by the community and supported by Supabase.
|
||||
## JavaScript
|
||||
|
||||
Via NPM
|
||||
|
||||
```bash
|
||||
npm install @supabase/supabase-js
|
||||
```
|
||||
|
||||
Via Yarn
|
||||
|
||||
```bash
|
||||
yarn add @supabase/supabase-js
|
||||
```
|
||||
@@ -27,8 +29,9 @@ yarn add @supabase/supabase-js
|
||||
Find the source code on [GitHub](https://github.com/supabase/supabase-js).
|
||||
|
||||
Or via CDN
|
||||
|
||||
```js
|
||||
<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@1"></script>
|
||||
//or
|
||||
<script src="https://unpkg.com/@supabase/supabase-js"></script>
|
||||
```
|
||||
<script src="https://unpkg.com/@supabase/supabase-js@1"></script>
|
||||
```
|
||||
@@ -5,6 +5,10 @@ slug: /
|
||||
sidebar_label: Supabase JavaScript Library
|
||||
---
|
||||
|
||||
:::caution
|
||||
You're viewing the docs for an older version of the `supabase-js` library.
|
||||
:::
|
||||
|
||||
This reference documents every object and method available in Supabase's isomorphic JavaScript library, `supabase-js`.
|
||||
|
||||
You can use the `supabase-js` library to:
|
||||
|
||||
@@ -79,7 +79,7 @@ You can use it completely, or just the features you need.
|
||||
description={'User management with Row Level Security.'}
|
||||
style={{ height: '100%' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* Auto-generated APIs */}
|
||||
<div class="col col--4">
|
||||
<ButtonCard
|
||||
@@ -116,7 +116,7 @@ You can use it completely, or just the features you need.
|
||||
class="card"
|
||||
to={useBaseUrl('/guides/realtime')}
|
||||
title={'Realtime'}
|
||||
description={'Build with Broadcast, Presence, and Postgres Changes.'}
|
||||
description={'Build with Broadcast, Presence, and Postgres CDC.'}
|
||||
style={{ height: '100%' }}
|
||||
/>
|
||||
</div>
|
||||
@@ -150,7 +150,7 @@ Supabase is just Postgres, which makes it compatible with a large number of tool
|
||||
<div>
|
||||
<div class="row is-multiline">
|
||||
{frameworks.map((x) => (
|
||||
<div key={x.name} class="col col--3">
|
||||
<div key={x.name} class="col col--3">
|
||||
<ButtonCard
|
||||
layout="horizontal"
|
||||
icon={
|
||||
@@ -162,7 +162,12 @@ Supabase is just Postgres, which makes it compatible with a large number of tool
|
||||
/>
|
||||
) : (
|
||||
<ThemedImage
|
||||
style={{ display: 'block', maxHeight: 20, minWidth: 20, margin: 0 }}
|
||||
style={{
|
||||
display: 'block',
|
||||
maxHeight: 20,
|
||||
minWidth: 20,
|
||||
margin: 0,
|
||||
}}
|
||||
alt={x.name}
|
||||
width="20"
|
||||
sources={{
|
||||
|
||||
@@ -23,9 +23,9 @@ Most notably, we use Postgres rather than a NoSQL store. This choice was deliber
|
||||
| Database | Postgres | GA | [Link](/docs/guides/database) |
|
||||
| Database | Webhooks | Alpha | |
|
||||
| Database | Point in time Recovery | Alpha | |
|
||||
| Realtime | Postgres Changes | Beta | [Link](/docs/guides/realtime/postgres-changes) |
|
||||
| Realtime | Broadcast | Alpha | [Link](/docs/guides/realtime/broadcast) |
|
||||
| Realtime | Presence | Alpha | [Link](/docs/guides/realtime/presence) |
|
||||
| Realtime | Postgres CDC | Beta | [Link](/docs/guides/realtime/postgres-cdc) |
|
||||
| Realtime | Broadcast | Alpha | [Link](/docs/guides/realtime#broadcast) |
|
||||
| Realtime | Presence | Alpha | [Link](/docs/guides/realtime#presence) |
|
||||
| Storage | | Beta | [Link](/docs/guides/storage) |
|
||||
| Storage | CDN | Beta | [Link](/docs/guides/storage-cdn) |
|
||||
| Edge Functions | | Beta | [Link](/docs/guides/functions) |
|
||||
@@ -35,5 +35,5 @@ Most notably, we use Postgres rather than a NoSQL store. This choice was deliber
|
||||
| Auth | SvelteKit Auth Helpers | Alpha | [Link](/docs/guides/auth/auth-helpers/sveltekit) |
|
||||
| Public API | | Beta | [Link](/docs/reference/api) |
|
||||
| CLI | | Beta | [Link](/docs/guides/cli) |
|
||||
| Client Library: JavaScript | | GA | [Link](/docs/reference/javascript/next/) |
|
||||
| Client Library: JavaScript | | GA | [Link](/docs/reference/javascript) |
|
||||
| Client Library: Dart | | Beta | [Link](/docs/reference/dart) |
|
||||
@@ -248,7 +248,7 @@ import { createClient, useQuery } from 'urql'
|
||||
// Prepare API key and Authorization header
|
||||
const headers = {
|
||||
apikey: <SUPABASE_ANON_KEY>,
|
||||
authorization: `Bearer: ${<SUPABASE_ANON_KEY}`>
|
||||
authorization: `Bearer ${<SUPABASE_ANON_KEY}`>
|
||||
}
|
||||
|
||||
// Create GraphQL client
|
||||
|
||||
@@ -177,7 +177,14 @@ Supabase provides a special function in Postgres, `auth.uid()`, which extracts t
|
||||
|
||||
## User Management
|
||||
|
||||
Supabase makes it simple to manage your users.
|
||||
Supabase provides multiple endpoints to authenticate and manage your users:
|
||||
- [Sign up](/docs/reference/javascript/auth-signup)
|
||||
- [Sign in with password](/docs/reference/javascript/auth-signinwithpassword)
|
||||
- [Sign in with passwordless / one-time password (OTP)](/docs/reference/javascript/auth-signinwithotp)
|
||||
- [Sign in with OAuth](/docs/reference/javascript/auth-signinwithoauth)
|
||||
- [Sign out](/docs/reference/javascript/auth-signout)
|
||||
|
||||
When users sign 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 `id` in the `auth.users` table using a `user_id` field.
|
||||
|
||||
<video width="99%" muted playsInline controls="true">
|
||||
<source
|
||||
@@ -187,12 +194,3 @@ Supabase makes it simple to manage your users.
|
||||
playsInline
|
||||
/>
|
||||
</video>
|
||||
|
||||
When users sign 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 `id` in the `auth.users` table using a `user_id` field.
|
||||
|
||||
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.
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Sign in: [app.supabase.com](https://app.supabase.com)
|
||||
@@ -20,16 +20,14 @@ Apple OAuth consists of six broad steps:
|
||||
- Add your `client id` and `client secret` keys to your [Supabase Project](https://app.supabase.com).
|
||||
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js).
|
||||
|
||||
## Steps
|
||||
|
||||
### Access your Apple Developer account
|
||||
## Access your Apple Developer account
|
||||
|
||||
- Go to [developer.apple.com](https://developer.apple.com).
|
||||
- Click on `Account` at the top right to log in.
|
||||
|
||||

|
||||
|
||||
### Obtain an App ID
|
||||
## Obtain an App ID
|
||||
|
||||
- Go to `Certificates, Identifiers & Profiles`.
|
||||
- Click on `Identifiers` at the left.
|
||||
@@ -43,7 +41,7 @@ Apple OAuth consists of six broad steps:
|
||||
- Click `Continue` at the top right.
|
||||
- Click `Register` at the top right.
|
||||
|
||||
### Obtain a Services ID
|
||||
## Obtain a Services ID
|
||||
|
||||
This will serve as the `client_id` when you make API calls to authenticate the user.
|
||||
|
||||
@@ -58,7 +56,7 @@ This will serve as the `client_id` when you make API calls to authenticate the u
|
||||
- Click `Continue` at the top right.
|
||||
- Click `Register` at the top right.
|
||||
|
||||
### Find your callback URL
|
||||
## Find your callback URL
|
||||
|
||||
The next step requires a callback URL, which looks like this:
|
||||
|
||||
@@ -79,7 +77,7 @@ The next step requires a callback URL, which looks like this:
|
||||
/>
|
||||
</video>
|
||||
|
||||
### Configure your Services ID
|
||||
## Configure your Services ID
|
||||
|
||||
- Under `Identifiers`, click on your newly-created Services ID.
|
||||
- Check the box next to `Sign In With Apple` to enable it.
|
||||
@@ -91,7 +89,7 @@ The next step requires a callback URL, which looks like this:
|
||||
- Click `Continue` at the top right.
|
||||
- Click `Save` at the top right.
|
||||
|
||||
### Download your secret key
|
||||
## Download your secret key
|
||||
|
||||
Now you'll need to download a `secret key` file from Apple that will be used to generate your `client_secret`.
|
||||
|
||||
@@ -109,7 +107,7 @@ Now you'll need to download a `secret key` file from Apple that will be used to
|
||||
- Save the downloaded file -- this contains your "secret key" that will be used to generate your `client_secret`.
|
||||
- Click `Done` at the top right.
|
||||
|
||||
### Generate a `client_secret`
|
||||
## Generate a `client_secret`
|
||||
|
||||
The `secret key` you downloaded is used to create the `client_secret` string you'll need to authenticate your users.
|
||||
|
||||
@@ -173,7 +171,7 @@ puts token
|
||||
2. From the command line, run: `ruby secret_gen.rb > client_secret.txt`.
|
||||
3. Your `client_secret` is now stored in this `client_secret.txt` file.
|
||||
|
||||
### Add your OAuth credentials to Supabase
|
||||
## Add your OAuth credentials to Supabase
|
||||
|
||||
- Go to your [Supabase Dashboard](https://app.supabase.com).
|
||||
- In the left sidebar, click the `Authentication` icon (near the top).
|
||||
@@ -183,27 +181,19 @@ puts token
|
||||
- Enter your `client_id` and `client_secret` saved in the previous steps.
|
||||
- Click `Save`.
|
||||
|
||||
### Add login code to your client app
|
||||
## Add login code to your client app
|
||||
|
||||
The JavaScript client code is documented in the [Supabase OAuth Reference](/docs/reference/javascript/auth-signin#sign-in-using-third-party-providers).
|
||||
|
||||
```js
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
provider: 'apple',
|
||||
})
|
||||
```
|
||||
|
||||
Add a function which you can call from a button, link, or UI element.
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `apple` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithApple() {
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||
provider: 'apple',
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
To log out:
|
||||
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
|
||||
|
||||
```js
|
||||
async function signout() {
|
||||
|
||||
@@ -18,14 +18,12 @@ Azure OAuth consists of four broad steps:
|
||||
- Create a `Secret ID` with “Sign In with Azure” capabilities. The value of the secret will be used as the `client secret`.
|
||||
- Add the callback url of your application to the allowlist.
|
||||
|
||||
## Steps
|
||||
|
||||
### Access your Azure Developer account
|
||||
## Access your Azure Developer account
|
||||
|
||||
- Go to [portal.azure.com](https://portal.azure.com/#home).
|
||||
- Login and select "Azure Active Directory" under the list of Azure Services.
|
||||
|
||||
### Register an application
|
||||
## Register an application
|
||||
|
||||
- Under Azure Active Directory, select "App registrations" in the side panel.
|
||||
- Select "New registration".
|
||||
@@ -36,7 +34,7 @@ Azure OAuth consists of four broad steps:
|
||||
|
||||

|
||||
|
||||
### Obtain a Client ID
|
||||
## Obtain a Client ID
|
||||
|
||||
This will serve as the `client_id` when you make API calls to authenticate the user.
|
||||
|
||||
@@ -44,7 +42,7 @@ This will serve as the `client_id` when you make API calls to authenticate the u
|
||||
|
||||

|
||||
|
||||
### Obtain a Secret ID
|
||||
## Obtain a Secret ID
|
||||
|
||||
This will serve as the `client_secret` when you make API calls to authenticate the user.
|
||||
|
||||
@@ -65,21 +63,13 @@ This will allow your users to use your custom Azure login page when logging in.
|
||||
|
||||

|
||||
|
||||
### Add login code to your client app
|
||||
## Add login code to your client app
|
||||
|
||||
The JavaScript client code is documented in the [Supabase OAuth Reference](/docs/reference/javascript/auth-signin#sign-in-using-third-party-providers).
|
||||
|
||||
```js
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
provider: 'azure',
|
||||
})
|
||||
```
|
||||
|
||||
Add a function which you can call from a button, link, or UI element.
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `azure` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithAzure() {
|
||||
const { user, session, error } = await supabase.auth.signIn(
|
||||
const { data, error } = await supabase.auth.signInWithOAuth(
|
||||
{
|
||||
provider: 'azure',
|
||||
},
|
||||
@@ -90,7 +80,7 @@ async function signInWithAzure() {
|
||||
}
|
||||
```
|
||||
|
||||
To log out:
|
||||
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
|
||||
|
||||
```js
|
||||
async function signout() {
|
||||
@@ -100,5 +90,5 @@ async function signout() {
|
||||
|
||||
## Resources
|
||||
|
||||
- [Azure Developer Account](https://portal.azure.com).
|
||||
- [GitHub Discussion](https://github.com/supabase/gotrue/pull/54#issuecomment-757043573).
|
||||
- [Azure Developer Account](https://portal.azure.com)
|
||||
- [GitHub Discussion](https://github.com/supabase/gotrue/pull/54#issuecomment-757043573)
|
||||
@@ -17,16 +17,14 @@ Setting up Bitbucket logins for your application consists of 3 parts:
|
||||
- Add your Bitbucket OAuth Consumer keys to your [Supabase Project](https://app.supabase.com)
|
||||
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js)
|
||||
|
||||
## Steps
|
||||
|
||||
### Access your Bitbucket account
|
||||
## Access your Bitbucket account
|
||||
|
||||
- Go to [bitbucket.org](https://bitbucket.org/).
|
||||
- Click on `Login` at the top right to log in.
|
||||
|
||||

|
||||
|
||||
### Find your callback URL
|
||||
## Find your callback URL
|
||||
|
||||
The next step requires a callback URL, which looks like this:
|
||||
|
||||
@@ -47,7 +45,7 @@ The next step requires a callback URL, which looks like this:
|
||||
/>
|
||||
</video>
|
||||
|
||||
### Create a Bitbucket OAuth app
|
||||
## Create a Bitbucket OAuth app
|
||||
|
||||
- Click on your profile icon at the bottom left
|
||||
- Click on `All Workspaces`
|
||||
@@ -62,7 +60,7 @@ The next step requires a callback URL, which looks like this:
|
||||
- Click on your app name (the name of your new OAuth Consumer)
|
||||
- Copy your `Key` (`client_key`) and `Secret` (`client_secret`) codes
|
||||
|
||||
### Add your Bitbucket credentials into your Supabase Project
|
||||
## Add your Bitbucket credentials into your Supabase Project
|
||||
|
||||
- Go to your [Supabase Project Dashboard](https://app.supabase.com)
|
||||
- In the left sidebar, click the `Authentication` icon (near the top)
|
||||
@@ -72,27 +70,19 @@ The next step requires a callback URL, which looks like this:
|
||||
- Enter your `client_id` and `client_secret` saved in the previous step
|
||||
- Click `Save`
|
||||
|
||||
### Add login code to your client app
|
||||
## Add login code to your client app
|
||||
|
||||
The JavaScript client code is documented here: [Supabase OAuth Client Code](/docs/reference/javascript/auth-signin#sign-in-using-third-party-providers)
|
||||
|
||||
```js
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
provider: 'bitbucket',
|
||||
})
|
||||
```
|
||||
|
||||
Add this function which you can call from a button, link, or UI element.
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `bitbucket` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithBitbucket() {
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||
provider: 'bitbucket',
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
To log out:
|
||||
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
|
||||
|
||||
```js
|
||||
async function signout() {
|
||||
|
||||
@@ -17,9 +17,7 @@ Setting up Discord logins for your application consists of 3 parts:
|
||||
- Add your Discord OAuth Consumer keys to your [Supabase Project](https://app.supabase.com)
|
||||
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js)
|
||||
|
||||
## Steps
|
||||
|
||||
### Access your Discord account
|
||||
## Access your Discord account
|
||||
|
||||
- Go to [discord.com](https://discord.com/).
|
||||
- Click on `Login` at the top right to log in.
|
||||
@@ -30,7 +28,7 @@ Setting up Discord logins for your application consists of 3 parts:
|
||||
|
||||

|
||||
|
||||
### Find your callback URL
|
||||
## Find your callback URL
|
||||
|
||||
In the next step you require a callback URL, which looks like this:
|
||||
|
||||
@@ -51,7 +49,7 @@ In the next step you require a callback URL, which looks like this:
|
||||
/>
|
||||
</video>
|
||||
|
||||
### Create a Discord Application
|
||||
## Create a Discord Application
|
||||
|
||||
- Click on `New Application` at the top right.
|
||||
- Enter the name of your application and click `Create`.
|
||||
@@ -61,7 +59,7 @@ In the next step you require a callback URL, which looks like this:
|
||||
- Click `Save Changes` at the bottom.
|
||||
- Copy your `Client ID` and `Client Secret` under `Client information`.
|
||||
|
||||
### Add your Discord credentials into your Supabase Project
|
||||
## Add your Discord credentials into your Supabase Project
|
||||
|
||||
- Go to your [Supabase Project Dashboard](https://app.supabase.com)
|
||||
- In the left sidebar, click the `Authentication` icon (near the top)
|
||||
@@ -71,33 +69,21 @@ In the next step you require a callback URL, which looks like this:
|
||||
- Enter your `client_id` and `client_secret` saved in the previous step
|
||||
- Click `Save`
|
||||
|
||||
### Add login code to your client app
|
||||
## Add login code to your client app
|
||||
|
||||
The JavaScript client code is documented here: [Supabase OAuth Client Code](/docs/reference/javascript/auth-signin#sign-in-using-third-party-providers)
|
||||
|
||||
```js
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
provider: 'discord',
|
||||
})
|
||||
```
|
||||
|
||||
:::note
|
||||
|
||||
If you call `signIn()` when already logged in, Discord will prompt the user again for authorization.
|
||||
|
||||
:::
|
||||
|
||||
Add this function which you can call from a button, link, or UI element.
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `discord` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithDiscord() {
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||
provider: 'discord',
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
To log out:
|
||||
If your user is already signed in, Discord prompts the user again for authorization.
|
||||
|
||||
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
|
||||
|
||||
```js
|
||||
async function signout() {
|
||||
|
||||
@@ -30,12 +30,7 @@ See the [self-hosting docs](/docs/guides/hosting/overview#configuration) for det
|
||||
|
||||
:::
|
||||
|
||||
### Add login code to your client app
|
||||
|
||||
Add logins using our client libraries:
|
||||
|
||||
- [JavaScript](/docs/reference/javascript/auth-signin#sign-in-with-email)
|
||||
- [Dart](/docs/reference/dart/auth-signin#sign-in-with-email)
|
||||
## Add login code to your client app
|
||||
|
||||
<Tabs
|
||||
defaultValue="js"
|
||||
@@ -45,44 +40,11 @@ Add logins using our client libraries:
|
||||
|
||||
<TabItem value="js">
|
||||
|
||||
```js
|
||||
const { user, error } = await supabase.auth.signIn({
|
||||
email: 'example@email.com',
|
||||
password: 'example-password',
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="dart">
|
||||
|
||||
```dart
|
||||
final res = await supabase.auth.signIn(
|
||||
email: 'example@email.com',
|
||||
password: 'example-password'
|
||||
);
|
||||
|
||||
final user = res.data?.user;
|
||||
final error = res.error;
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
Add this function which you can call from a button, link, or UI element.
|
||||
|
||||
<Tabs
|
||||
defaultValue="js"
|
||||
groupId="guides/auth"
|
||||
values={[{ label: 'JavaScript', value: 'js' }, { label: 'Dart', value: 'dart' }]}
|
||||
>
|
||||
|
||||
<TabItem value="js">
|
||||
When your user signs in, call [signInWithPassword()](/docs/reference/javascript/auth-signinwithpassword) with their email address and password:
|
||||
|
||||
```js
|
||||
async function signInWithEmail() {
|
||||
const { user, error } = await supabase.auth.signIn({
|
||||
const { data, error } = await supabase.auth.signInWithPassword({
|
||||
email: 'example@email.com',
|
||||
password: 'example-password',
|
||||
})
|
||||
@@ -93,6 +55,8 @@ async function signInWithEmail() {
|
||||
|
||||
<TabItem value="dart">
|
||||
|
||||
When your user signs in, call [signIn()](/docs/reference/dart/auth-signin) with their email address and password:
|
||||
|
||||
```dart
|
||||
Future<void> signInWithEmail() async {
|
||||
await supabase.auth.signIn(
|
||||
@@ -106,8 +70,6 @@ Future<void> signInWithEmail() async {
|
||||
|
||||
</Tabs>
|
||||
|
||||
To log out:
|
||||
|
||||
<Tabs
|
||||
defaultValue="js"
|
||||
groupId="guides/auth"
|
||||
@@ -116,6 +78,8 @@ To log out:
|
||||
|
||||
<TabItem value="js">
|
||||
|
||||
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
|
||||
|
||||
```js
|
||||
async function signOut() {
|
||||
const { error } = await supabase.auth.signOut()
|
||||
@@ -126,6 +90,8 @@ async function signOut() {
|
||||
|
||||
<TabItem value="dart">
|
||||
|
||||
When your user signs out, call [signOut()](/docs/reference/dart/auth-signout) to remove them from the browser session and any objects from localStorage:
|
||||
|
||||
```dart
|
||||
Future<void> signOut() async {
|
||||
await supabase.auth.signOut();
|
||||
|
||||
@@ -17,16 +17,14 @@ Setting up Facebook logins for your application consists of 3 parts:
|
||||
- Add your Facebook keys to your [Supabase Project](https://app.supabase.com)
|
||||
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js)
|
||||
|
||||
## Steps
|
||||
|
||||
### Access your Facebook Developer account
|
||||
## Access your Facebook Developer account
|
||||
|
||||
- Go to [developers.facebook.com](https://developers.facebook.com).
|
||||
- Click on `Log In` at the top right to log in.
|
||||
|
||||

|
||||
|
||||
### Create a Facebook App
|
||||
## Create a Facebook App
|
||||
|
||||
- Click on `My Apps` at the top right.
|
||||
- Click `Create App` near the top right.
|
||||
@@ -34,7 +32,7 @@ Setting up Facebook logins for your application consists of 3 parts:
|
||||
- Fill in your app information, then click `Create App`.
|
||||
- This should bring you to the screen: `Add Products to Your App`. (Alternatively you can click on `Add Product` in the left sidebar to get to this screen.)
|
||||
|
||||
### Find your callback URI
|
||||
## Find your callback URI
|
||||
|
||||
The next step requires a callback URI, which looks like this:
|
||||
|
||||
@@ -55,7 +53,7 @@ The next step requires a callback URI, which looks like this:
|
||||
/>
|
||||
</video>
|
||||
|
||||
### Set up FaceBook Login for your Facebook App
|
||||
## Set up FaceBook Login for your Facebook App
|
||||
|
||||
From the `Add Products to your App` screen:
|
||||
|
||||
@@ -72,14 +70,14 @@ From the `App Review -> Permissions and Features` screen:
|
||||
|
||||
You can read more about access levels [here](https://developers.facebook.com/docs/graph-api/overview/access-levels/)
|
||||
|
||||
### Copy your Facebook App ID and Secret
|
||||
## Copy your Facebook App ID and Secret
|
||||
|
||||
- Click `Settings / Basic` in the left sidebar
|
||||
- Copy your App ID from the top of the `Basic Settings` page
|
||||
- Under `App Secret` click `Show` then copy your secret
|
||||
- Make sure all required fields are completed on this screen.
|
||||
|
||||
### Enter your Facebook App ID and Secret into your Supabase Project
|
||||
## Enter your Facebook App ID and Secret into your Supabase Project
|
||||
|
||||
- Go to your [Supabase Project Dashboard](https://app.supabase.com)
|
||||
- In the left sidebar, click the `Authentication` icon (near the top)
|
||||
@@ -89,27 +87,19 @@ You can read more about access levels [here](https://developers.facebook.com/doc
|
||||
- Enter your `Facebook client ID` and `Facebook secret` saved in the previous step
|
||||
- Click `Save`
|
||||
|
||||
### Add login code to your client app
|
||||
## Add login code to your client app
|
||||
|
||||
The JavaScript client code is documented here: [Supabase OAuth Client Code](/docs/reference/javascript/auth-signin#sign-in-using-third-party-providers)
|
||||
|
||||
```js
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
provider: 'facebook',
|
||||
})
|
||||
```
|
||||
|
||||
Add this function which you can call from a button, link, or UI element.
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `facebook` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithFacebook() {
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||
provider: 'facebook',
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
To log out:
|
||||
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
|
||||
|
||||
```js
|
||||
async function signout() {
|
||||
|
||||
@@ -17,16 +17,14 @@ Setting up GitHub logins for your application consists of 3 parts:
|
||||
- Add your GitHub OAuth keys to your [Supabase Project](https://app.supabase.com)
|
||||
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js)
|
||||
|
||||
## Steps
|
||||
|
||||
### Access your GitHub account
|
||||
## Access your GitHub account
|
||||
|
||||
- Go to [github.com](https://github.com).
|
||||
- Click on `Sign In` at the top right to log in.
|
||||
|
||||

|
||||
|
||||
### Create a GitHub Oauth App
|
||||
## Create a GitHub Oauth App
|
||||
|
||||
Go to the [GitHub Developer Settings](https://github.com/settings/developers) page:
|
||||
|
||||
@@ -35,7 +33,7 @@ Go to the [GitHub Developer Settings](https://github.com/settings/developers) pa
|
||||
- In the left sidebar, click `Developer settings` (near the bottom)
|
||||
- In the left sidebar, click `OAuth Apps`
|
||||
|
||||
### Find your callback URL
|
||||
## Find your callback URL
|
||||
|
||||
The next step requires a callback URL, which looks like this:
|
||||
|
||||
@@ -56,7 +54,7 @@ The next step requires a callback URL, which looks like this:
|
||||
/>
|
||||
</video>
|
||||
|
||||
### Register a new OAuth application
|
||||
## Register a new OAuth application
|
||||
|
||||
- Click `Register a new application`. If you've created an app before, click `New OAuth App` here.
|
||||
- In `Application name`, type the name of your app.
|
||||
@@ -72,7 +70,7 @@ Copy your new OAuth credentials
|
||||
- Click `Generate a new client secret`.
|
||||
- Copy and save your `Client secret`.
|
||||
|
||||
### Enter your GitHub credentials into your Supabase Project
|
||||
## Enter your GitHub credentials into your Supabase Project
|
||||
|
||||
- Go to your [Supabase Project Dashboard](https://app.supabase.com)
|
||||
- In the left sidebar, click the `Authentication` icon (near the top)
|
||||
@@ -82,27 +80,19 @@ Copy your new OAuth credentials
|
||||
- Enter your `GitHub Client ID` and `GitHub Client Secret` saved in the previous step
|
||||
- Click `Save`
|
||||
|
||||
### Add login code to your client app
|
||||
## Add login code to your client app
|
||||
|
||||
The JavaScript client code is documented here: [Supabase OAuth Client Code](/docs/reference/javascript/auth-signin#sign-in-using-third-party-providers)
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `github` as the `provider`:
|
||||
|
||||
```js
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
provider: 'github',
|
||||
})
|
||||
```
|
||||
|
||||
Add this function which you can call from a button, link, or UI element.
|
||||
|
||||
```js
|
||||
async function signInWithGithub() {
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
async function signInWithGitHub() {
|
||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||
provider: 'github',
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
To log out:
|
||||
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
|
||||
|
||||
```js
|
||||
async function signout() {
|
||||
|
||||
@@ -17,16 +17,14 @@ Setting up GitLab logins for your application consists of 3 parts:
|
||||
- Add your GitLab Application keys to your [Supabase Project](https://app.supabase.com)
|
||||
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js)
|
||||
|
||||
## Steps
|
||||
|
||||
### Access your GitLab account
|
||||
## Access your GitLab account
|
||||
|
||||
- Go to [gitlab.com](https://gitlab.com).
|
||||
- Click on `Login` at the top right to log in.
|
||||
|
||||

|
||||
|
||||
### Find your callback URL
|
||||
## Find your callback URL
|
||||
|
||||
The next step requires a callback URL, which looks like this:
|
||||
|
||||
@@ -47,7 +45,7 @@ The next step requires a callback URL, which looks like this:
|
||||
/>
|
||||
</video>
|
||||
|
||||
### Create your GitLab Application
|
||||
## Create your GitLab Application
|
||||
|
||||
- Click on your `profile logo` (avatar) in the top-right corner.
|
||||
- Select `Edit profile`.
|
||||
@@ -59,7 +57,7 @@ The next step requires a callback URL, which looks like this:
|
||||
- Click `Save Application` at the bottom.
|
||||
- Copy and save your `Application ID` (`client_id`) and `Secret` (`client_secret`) which you'll need later.
|
||||
|
||||
### Add your GitLab credentials into your Supabase Project
|
||||
## Add your GitLab credentials into your Supabase Project
|
||||
|
||||
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
|
||||
- In the left sidebar, click the `Authentication` icon (near the top).
|
||||
@@ -69,27 +67,19 @@ The next step requires a callback URL, which looks like this:
|
||||
- Enter your `client_id` and `client_secret` saved in the previous step.
|
||||
- Click `Save`.
|
||||
|
||||
### Add login code to your client app
|
||||
## Add login code to your client app
|
||||
|
||||
The JavaScript client code is documented here: [Supabase OAuth Client Code](/docs/reference/javascript/auth-signin#sign-in-using-third-party-providers)
|
||||
|
||||
```js
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
provider: 'gitlab',
|
||||
})
|
||||
```
|
||||
|
||||
Add this function which you can call from a button, link, or UI element.
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `gitlab` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithGitLab() {
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||
provider: 'gitlab',
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
To log out:
|
||||
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
|
||||
|
||||
```js
|
||||
async function signout() {
|
||||
|
||||
@@ -17,16 +17,14 @@ Setting up Google logins for your application consists of 3 parts:
|
||||
- Add your Google OAuth keys to your [Supabase Project](https://app.supabase.com)
|
||||
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js)
|
||||
|
||||
## Steps
|
||||
|
||||
### Access your Google Cloud Platform account
|
||||
## Access your Google Cloud Platform account
|
||||
|
||||
- Go to [cloud.google.com](https://cloud.google.com).
|
||||
- Click on `Sign in` at the top right to log in.
|
||||
|
||||

|
||||
|
||||
### Create a Google Cloud Platform Project
|
||||
## Create a Google Cloud Platform Project
|
||||
|
||||
- Click on `Select a Project` at the top left.
|
||||
- (Or, if a project is currently selected, click on the current project name at the top left.)
|
||||
@@ -35,7 +33,7 @@ Setting up Google logins for your application consists of 3 parts:
|
||||
- (This can take a few minutes.)
|
||||
- This should bring you to the dashboard for your new project.
|
||||
|
||||
### Create the OAuth Keys for your project
|
||||
## Create the OAuth Keys for your project
|
||||
|
||||
From your project's dashboard screen:
|
||||
|
||||
@@ -44,12 +42,12 @@ From your project's dashboard screen:
|
||||
- On the `OAuth consent screen` page select `External`.
|
||||
- Click `Create`.
|
||||
|
||||
### Edit your app information
|
||||
## Edit your app information
|
||||
|
||||
- On the `Edit app registration` page fill out your app information.
|
||||
- Click `Save and continue` at the bottom.
|
||||
|
||||
### Find your callback URL
|
||||
## Find your callback URL
|
||||
|
||||
The next step requires a callback URL, which looks like this:
|
||||
|
||||
@@ -70,7 +68,7 @@ The next step requires a callback URL, which looks like this:
|
||||
/>
|
||||
</video>
|
||||
|
||||
### Create your Google credentials
|
||||
## Create your Google credentials
|
||||
|
||||
- Click `Credentials` at the left to go to the `Credentials` page on the Google Cloud Platform console.
|
||||
- Click `Create Credentials` near the top then select `OAuth client ID`
|
||||
@@ -87,7 +85,7 @@ Copy your new OAuth credentials
|
||||
- A box will appear called `OAuth client created`.
|
||||
- Copy and save the values under `Your Client ID` and `Your Client Secret`.
|
||||
|
||||
### Enter your Google credentials into your Supabase Project
|
||||
## Enter your Google credentials into your Supabase Project
|
||||
|
||||
- Go to your [Supabase Project Dashboard](https://app.supabase.com)
|
||||
- In the left sidebar, click the `Authentication` icon (near the top)
|
||||
@@ -97,27 +95,19 @@ Copy your new OAuth credentials
|
||||
- Enter your `Google Client ID` and `Google Client Secret` saved in the previous step
|
||||
- Click `Save`
|
||||
|
||||
### Add login code to your client app
|
||||
## Add login code to your client app
|
||||
|
||||
The JavaScript client code is documented here: [Supabase OAuth Client Code](/docs/reference/javascript/auth-signin#sign-in-using-third-party-providers)
|
||||
|
||||
```js
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
provider: 'google',
|
||||
})
|
||||
```
|
||||
|
||||
Add this function which you can call from a button, link, or UI element.
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `google` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithGoogle() {
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||
provider: 'google',
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
To log out:
|
||||
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
|
||||
|
||||
```js
|
||||
async function signout() {
|
||||
|
||||
@@ -18,10 +18,10 @@ It supports custom themes and extensible styles to match your brand and aestheti
|
||||
|
||||
## Set up Auth UI
|
||||
|
||||
Install the latest version of [supabase-js](/docs/reference/javascript/next/) and the Auth UI package:
|
||||
Install the latest version of [supabase-js](/docs/reference/javascript) and the Auth UI package:
|
||||
|
||||
```bash
|
||||
npm install @supabase/supabase-js@rc @supabase/auth-ui-react
|
||||
npm install @supabase/supabase-js @supabase/auth-ui-react
|
||||
```
|
||||
|
||||
### Import the Auth component
|
||||
|
||||
@@ -21,14 +21,16 @@ A collection of framework-specific Auth utilities for working with Supabase.
|
||||
description={'A pre-built React component for authenticating users.'}
|
||||
style={{ height: '100%' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* Next.js */}
|
||||
<div class="col col--4">
|
||||
<ButtonCard
|
||||
class="card"
|
||||
to={useBaseUrl('/guides/auth/auth-helpers/nextjs')}
|
||||
title={'Next.js'}
|
||||
description={'Helpers for authenticating users in Next.js applications.'}
|
||||
description={
|
||||
'Helpers for authenticating users in Next.js applications.'
|
||||
}
|
||||
style={{ height: '100%' }}
|
||||
/>
|
||||
</div>
|
||||
@@ -38,7 +40,9 @@ A collection of framework-specific Auth utilities for working with Supabase.
|
||||
class="card"
|
||||
to={useBaseUrl('/guides/auth/auth-helpers/sveltekit')}
|
||||
title={'SvelteKit'}
|
||||
description={'Helpers for authenticating users in SvelteKit applications.'}
|
||||
description={
|
||||
'Helpers for authenticating users in SvelteKit applications.'
|
||||
}
|
||||
style={{ height: '100%' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -2,104 +2,196 @@
|
||||
id: nextjs
|
||||
title: Supabase Auth with Next.js
|
||||
description: Authentication helpers for Next.js API routes, middleware, and SSR.
|
||||
sidebar_label: "Next.js"
|
||||
sidebar_label: 'Next.js'
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
|
||||
This submodule provides convenience helpers for implementing user authentication in Next.js applications.
|
||||
|
||||
## Installation
|
||||
## Install the Next.js helper library
|
||||
|
||||
Using [npm](https://npmjs.org):
|
||||
<Tabs
|
||||
groupId="install"
|
||||
defaultValue="npm"
|
||||
values={[
|
||||
{label: 'npm', value: 'npm'},
|
||||
{label: 'Yarn', value: 'yarn'},
|
||||
]}>
|
||||
|
||||
<TabItem value="npm">
|
||||
|
||||
```sh
|
||||
npm install @supabase/auth-helpers-nextjs
|
||||
|
||||
# Main components and hooks for React based frameworks (optional)
|
||||
npm install @supabase/auth-helpers-react
|
||||
```
|
||||
|
||||
Using [yarn](https://yarnpkg.com/):
|
||||
|
||||
```sh
|
||||
yarn add @supabase/auth-helpers-nextjs
|
||||
|
||||
# Main components and hooks for React based frameworks (optional)
|
||||
yarn add @supabase/auth-helpers-react
|
||||
```
|
||||
|
||||
This library supports the following tooling versions:
|
||||
|
||||
- Node.js: `^10.13.0 || >=12.0.0`
|
||||
|
||||
- Next.js: `>=10`
|
||||
|
||||
## Getting Started
|
||||
Additionally, install the **React Auth Helpers** for components and hooks that can be used across all React-based frameworks.
|
||||
|
||||
### Configuration
|
||||
|
||||
Set up the following env vars. For local development you can set them in a `.env.local` file. See an [example](https://github.com/supabase/auth-helpers/blob/main/examples/nextjs/.env.local.example).
|
||||
|
||||
```bash
|
||||
# Find these in your Supabase project settings > API
|
||||
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
|
||||
```sh
|
||||
npm install @supabase/auth-helpers-react
|
||||
```
|
||||
|
||||
### Basic Setup
|
||||
</TabItem>
|
||||
<TabItem value="yarn">
|
||||
|
||||
- Create an `auth` directory under the `/pages/api/` directory.
|
||||
|
||||
- Create a `[...supabase].js` file under the newly created `auth` directory.
|
||||
|
||||
The path to your dynamic API route file would be `/pages/api/auth/[...supabase].js`. Populate that file as follows:
|
||||
|
||||
```js
|
||||
import { handleAuth } from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
export default handleAuth({ logout: { returnTo: '/' } })
|
||||
```sh
|
||||
yarn add @supabase/auth-helpers-nextjs
|
||||
```
|
||||
|
||||
Executing `handleAuth()` creates the following route handlers under the hood that perform different parts of the authentication flow:
|
||||
This library supports the following tooling versions:
|
||||
|
||||
- `/api/auth/callback`: The `UserProvider` forwards the session details here every time `onAuthStateChange` fires on the client side. This is needed to set up the cookies for your application so that SSR works seamlessly.
|
||||
- Node.js: `^10.13.0 || >=12.0.0`
|
||||
- Next.js: `>=10`
|
||||
|
||||
- `/api/auth/user`: You can fetch user profile information in JSON format.
|
||||
Additionally, install the **React Auth Helpers** for components and hooks that can be used across all React-based frameworks.
|
||||
|
||||
- `/api/auth/logout`: Your Next.js application logs out the user. You can optionally pass a `returnTo` parameter to return to a custom relative URL after logout, eg `/api/auth/logout?returnTo=/login`. This will overwrite the logout `returnTo` option specified `handleAuth()`
|
||||
```sh
|
||||
yarn add @supabase/auth-helpers-react
|
||||
```
|
||||
|
||||
Wrap your `pages/_app.js` component with the `UserProvider` component:
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
```jsx
|
||||
// pages/_app.js
|
||||
import React from 'react'
|
||||
import { UserProvider } from '@supabase/auth-helpers-react'
|
||||
import { supabaseClient } from '@supabase/auth-helpers-nextjs'
|
||||
## Set up environment variables
|
||||
|
||||
Retrieve your project URL and anon key in your project's [API settings](https://app.supabase.com/project/_/settings/api) in the Dashboard to set up the following environment variables. For local development you can set them in a `.env.local` file. See an [example](https://github.com/supabase/auth-helpers/blob/main/examples/nextjs/.env.local.example).
|
||||
|
||||
```bash title=".env.local"
|
||||
NEXT_PUBLIC_SUPABASE_URL=YOUR_SUPABASE_URL
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
|
||||
```
|
||||
|
||||
## Basic Setup
|
||||
|
||||
<Tabs
|
||||
defaultValue="js"
|
||||
values={[
|
||||
{label: 'JavaScript', value: 'js'},
|
||||
{label: 'TypeScript', value: 'ts'},
|
||||
]}>
|
||||
<TabItem value="js">
|
||||
|
||||
Wrap your `pages/_app.js` component with the `SessionContextProvider` component:
|
||||
|
||||
```jsx title="pages/_app.js"
|
||||
import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'
|
||||
import { SessionContextProvider } from '@supabase/auth-helpers-react'
|
||||
|
||||
function MyApp({ Component, pageProps }) {
|
||||
const router = useRouter()
|
||||
// Create a new supabase browser client on every first render.
|
||||
const [supabaseClient] = useState(() => createBrowserSupabaseClient())
|
||||
|
||||
export default function App({ Component, pageProps }) {
|
||||
return (
|
||||
<UserProvider supabaseClient={supabaseClient}>
|
||||
<SessionContextProvider
|
||||
supabaseClient={supabaseClient}
|
||||
initialSession={pageProps.initialSession}
|
||||
>
|
||||
<Component {...pageProps} />
|
||||
</UserProvider>
|
||||
</SessionContextProvider>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="ts">
|
||||
|
||||
Wrap your `pages/_app.tsx` component with the `SessionContextProvider` component:
|
||||
|
||||
```jsx title="pages/_app.tsx"
|
||||
import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'
|
||||
// highlight-next-line
|
||||
import { SessionContextProvider, Session } from '@supabase/auth-helpers-react'
|
||||
|
||||
function MyApp({
|
||||
Component,
|
||||
pageProps,
|
||||
}: AppProps<{
|
||||
// highlight-next-line
|
||||
initialSession: Session,
|
||||
}>) {
|
||||
// Create a new supabase browser client on every first render.
|
||||
const [supabaseClient] = useState(() => createBrowserSupabaseClient())
|
||||
|
||||
return (
|
||||
<SessionContextProvider
|
||||
supabaseClient={supabaseClient}
|
||||
initialSession={pageProps.initialSession}
|
||||
>
|
||||
<Component {...pageProps} />
|
||||
</SessionContextProvider>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
You can now determine if a user is authenticated by checking that the `user` object returned by the `useUser()` hook is defined.
|
||||
|
||||
## Usage with TypeScript
|
||||
|
||||
You can pass types that were [generated with the Supabase CLI](/docs/reference/javascript/typescript-support#generating-types) to the Supabase Client to get enhanced type safety and auto completion:
|
||||
|
||||
### Browser client
|
||||
|
||||
```ts
|
||||
// Creating a new supabase client object:
|
||||
import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'
|
||||
import { Database } from '../database.types'
|
||||
|
||||
const supabaseClient = createBrowserSupabaseClient<Database>()
|
||||
```
|
||||
|
||||
```ts
|
||||
// Retrieving a supabase client object from the SessionContext:
|
||||
import { useSupabaseClient } from '@supabase/auth-helpers-react'
|
||||
import { Database } from '../database.types'
|
||||
|
||||
const supabaseClient = useSupabaseClient<Database>()
|
||||
```
|
||||
|
||||
### Server client
|
||||
|
||||
```ts
|
||||
// Creating a new supabase server client object (e.g. in API route):
|
||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||
import type { Database } from 'types_db'
|
||||
|
||||
export default async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const supabaseServerClient = createServerSupabaseClient<Database>({
|
||||
req,
|
||||
res,
|
||||
})
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabaseServerClient.auth.getUser()
|
||||
|
||||
res.status(200).json({ name: user?.name ?? '' })
|
||||
}
|
||||
```
|
||||
|
||||
## Client-side data fetching with RLS
|
||||
|
||||
For [row level security](https://supabase.com/docs/learn/auth-deep-dive/auth-row-level-security) to work properly when fetching data client-side, you need to make sure to import the `{ supabaseClient }` from `# @supabase/auth-helpers-nextjs` and only run your query once the user is defined client-side in the `useUser()` hook:
|
||||
For [row level security](https://supabase.com/docs/learn/auth-deep-dive/auth-row-level-security) to work properly when fetching data client-side, you need to make sure to use the `supabaseClient` from the `useSupabaseClient` hook and only run your query once the user is defined client-side in the `useUser()` hook:
|
||||
|
||||
```js
|
||||
import { Auth } from '@supabase/ui'
|
||||
import { useUser } from '@supabase/auth-helpers-react'
|
||||
import { supabaseClient } from '@supabase/auth-helpers-nextjs'
|
||||
import { Auth, ThemeSupa } from '@supabase/auth-ui-react'
|
||||
import { useUser, useSupabaseClient } from '@supabase/auth-helpers-react'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
const LoginPage = () => {
|
||||
const { user, error } = useUser()
|
||||
const supabaseClient = useSupabaseClient()
|
||||
const user = useUser()
|
||||
const [data, setData] = useState()
|
||||
|
||||
// highlight-start
|
||||
useEffect(() => {
|
||||
async function loadData() {
|
||||
const { data } = await supabaseClient.from('test').select('*')
|
||||
@@ -108,18 +200,17 @@ const LoginPage = () => {
|
||||
// Only run query once user is logged in.
|
||||
if (user) loadData()
|
||||
}, [user])
|
||||
// highlight-end
|
||||
|
||||
if (!user)
|
||||
return (
|
||||
<>
|
||||
{error && <p>{error.message}</p>}
|
||||
<Auth
|
||||
supabaseClient={supabaseClient}
|
||||
providers={['google', 'github']}
|
||||
socialLayout="horizontal"
|
||||
socialButtonSize="xlarge"
|
||||
/>
|
||||
</>
|
||||
<Auth
|
||||
redirectTo="http://localhost:3000/"
|
||||
appearance={{ theme: ThemeSupa }}
|
||||
supabaseClient={supabaseClient}
|
||||
providers={['google', 'github']}
|
||||
socialLayout="horizontal"
|
||||
/>
|
||||
)
|
||||
|
||||
return (
|
||||
@@ -136,12 +227,11 @@ const LoginPage = () => {
|
||||
export default LoginPage
|
||||
```
|
||||
|
||||
### Server-side rendering (SSR) - withPageAuth
|
||||
## Server-side rendering (SSR) - withPageAuth
|
||||
|
||||
If you wrap your `getServerSideProps` with `withPageAuth` your props object will be augmented with the user object.
|
||||
|
||||
```js
|
||||
// pages/profile.js
|
||||
```js title="pages/profile.js"
|
||||
import { withPageAuth } from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
export default function Profile({ user }) {
|
||||
@@ -154,11 +244,10 @@ export const getServerSideProps = withPageAuth({ redirectTo: '/login' })
|
||||
If there is no authenticated user, they will be redirect to your home page, unless you specify the `redirectTo` option.
|
||||
|
||||
You can pass in your own `getServerSideProps` method, the props returned from this will be merged with the
|
||||
user props. You can also access the user session data by calling `getUser` inside of this method, eg:
|
||||
user props. You can also access the user session data by calling `supabase.auth.getUser()` inside of this method, eg:
|
||||
|
||||
```js
|
||||
// pages/protected-page.js
|
||||
import { withPageAuth, getUser } from '@supabase/auth-helpers-nextjs'
|
||||
```js title="pages/protected-page.js"
|
||||
import { withPageAuth } from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
export default function ProtectedPage({ user, customProp }) {
|
||||
return <div>Protected content</div>
|
||||
@@ -166,24 +255,22 @@ export default function ProtectedPage({ user, customProp }) {
|
||||
|
||||
export const getServerSideProps = withPageAuth({
|
||||
redirectTo: '/foo',
|
||||
async getServerSideProps(ctx) {
|
||||
async getServerSideProps(ctx, supabase) {
|
||||
// Access the user object
|
||||
const { user, accessToken } = await getUser(ctx)
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabase.auth.getUser()
|
||||
return { props: { email: user?.email } }
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Server-side data fetching with RLS
|
||||
## Server-side data fetching with RLS
|
||||
|
||||
For [row level security](https://supabase.com/docs/learn/auth-deep-dive/auth-row-level-security) to work in a server environment, you need to inject the request context into the supabase client:
|
||||
Both `withApiAuth` and `withPageAuth` return a supabase client that you can use to run [row level security](https://supabase.com/docs/learn/auth-deep-dive/auth-row-level-security) authenticated queries server-side:
|
||||
|
||||
```js
|
||||
import {
|
||||
User,
|
||||
withPageAuth,
|
||||
supabaseServerClient,
|
||||
} from '@supabase/auth-helpers-nextjs'
|
||||
import { User, withPageAuth } from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
export default function ProtectedPage({
|
||||
user,
|
||||
@@ -203,7 +290,7 @@ export default function ProtectedPage({
|
||||
|
||||
export const getServerSideProps = withPageAuth({
|
||||
redirectTo: '/',
|
||||
async getServerSideProps(ctx) {
|
||||
async getServerSideProps(ctx, supabaseServerClient) {
|
||||
// Run queries with RLS on the server
|
||||
const { data } = await supabaseServerClient(ctx).from('test').select('*')
|
||||
return { props: { data } }
|
||||
@@ -211,44 +298,62 @@ export const getServerSideProps = withPageAuth({
|
||||
})
|
||||
```
|
||||
|
||||
### Server-side data fetching to OAuth APIs using `provider_token`
|
||||
## Server-side data fetching to OAuth APIs using `provider_token`
|
||||
|
||||
When using third-party auth providers, sessions are initiated with an additional `provider_token` field which is persisted as an HTTPOnly cookie upon logging in to enabled usage on the server side. The `provider_token` can be used to make API requests to the OAuth provider's API endpoints on behalf of the logged-in user. In the following example, we fetch the user's full profile from the third-party API during SSR using their id and auth token:
|
||||
|
||||
```js
|
||||
import { User, withPageAuth, getUser } from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
interface Profile {
|
||||
/* ... */
|
||||
}
|
||||
import { User, withPageAuth } from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
export default function ProtectedPage({
|
||||
user,
|
||||
data,
|
||||
allRepos,
|
||||
}: {
|
||||
user: User,
|
||||
profile: Profile,
|
||||
allRepos: any,
|
||||
}) {
|
||||
return <div>Protected content</div>
|
||||
return (
|
||||
<>
|
||||
<div>Protected content for {user.email}</div>
|
||||
<p>Data fetched with provider token:</p>
|
||||
<pre>{JSON.stringify(allRepos, null, 2)}</pre>
|
||||
<p>user:</p>
|
||||
<pre>{JSON.stringify(user, null, 2)}</pre>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const getServerSideProps = withPageAuth({
|
||||
redirectTo: '/',
|
||||
async getServerSideProps(ctx) {
|
||||
// Retrieve provider_token from cookies
|
||||
const provider_token = ctx.req.cookies['sb-provider-token']
|
||||
// Get logged in user's third-party id from metadata
|
||||
const { user } = await getUser(ctx)
|
||||
const userId = user?.user_metadata.provider_id
|
||||
const profile: Profile = await (
|
||||
await fetch(`https://api.example.com/users/${userId}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: `Bearer ${provider_token}`,
|
||||
},
|
||||
})
|
||||
async getServerSideProps(ctx, supabase) {
|
||||
const {
|
||||
data: { session },
|
||||
error,
|
||||
} = await supabase.auth.getSession()
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
if (!session) {
|
||||
return { props: {} }
|
||||
}
|
||||
|
||||
// Retrieve provider_token & logged in user's third-party id from metadata
|
||||
const { provider_token, user } = session
|
||||
const userId = user.user_metadata.user_name
|
||||
|
||||
const allRepos = await (
|
||||
await fetch(
|
||||
`https://api.github.com/search/repositories?q=user:${userId}`,
|
||||
{
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: `token ${provider_token}`,
|
||||
},
|
||||
}
|
||||
)
|
||||
).json()
|
||||
return { props: { profile } }
|
||||
|
||||
return { props: { allRepos, user } }
|
||||
},
|
||||
})
|
||||
```
|
||||
@@ -258,18 +363,16 @@ export const getServerSideProps = withPageAuth({
|
||||
Wrap an API Route to check that the user has a valid session. If they're not logged in the handler will return a
|
||||
401 Unauthorized.
|
||||
|
||||
```js
|
||||
// pages/api/protected-route.js
|
||||
import {
|
||||
withApiAuth,
|
||||
supabaseServerClient,
|
||||
} from '@supabase/auth-helpers-nextjs'
|
||||
```js title="pages/api/protected-route.js"
|
||||
import { withApiAuth } from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
export default withApiAuth(async function ProtectedRoute(req, res) {
|
||||
export default withApiAuth(async function ProtectedRoute(
|
||||
req,
|
||||
res,
|
||||
supabaseServerClient
|
||||
) {
|
||||
// Run queries with RLS on the server
|
||||
const { data } = await supabaseServerClient({ req, res })
|
||||
.from('test')
|
||||
.select('*')
|
||||
const { data } = await supabaseServerClient.from('test').select('*')
|
||||
res.json(data)
|
||||
})
|
||||
```
|
||||
@@ -278,20 +381,22 @@ If you visit `/api/protected-route` without a valid session cookie, you will get
|
||||
|
||||
## Protecting routes with [Nextjs Middleware](https://nextjs.org/docs/middleware)
|
||||
|
||||
As an alternative to protecting individual pages using `getServerSideProps` with `withPageAuth`, `withMiddlewareAuth` can be used from inside a `_middleware` file to protect an entire directory. In the following example, all requests to `/protected/*` will check whether a user is signed in, if successful the request will be forwarded to the destination route, otherwise the user will be redirected to `/login` (defaults to: `/`) with a 307 Temporary Redirect response status:
|
||||
As an alternative to protecting individual pages using `getServerSideProps` with `withPageAuth`, `withMiddlewareAuth` can be used from inside a `middleware` file to protect the entire directory or those that match the config object. In the following example, all requests to `/middleware-protected/*` will check whether a user is signed in, if successful the request will be forwarded to the destination route, otherwise the user will be redirected to `/login` (defaults to: `/`) with a 307 Temporary Redirect response status:
|
||||
|
||||
```ts
|
||||
// pages/protected/_middleware.ts
|
||||
import { withMiddlewareAuth } from '@supabase/auth-helpers-nextjs/middleware'
|
||||
```ts title="middleware.ts"
|
||||
import { withMiddlewareAuth } from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
export const middleware = withMiddlewareAuth({ redirectTo: '/login' })
|
||||
|
||||
export const config = {
|
||||
matcher: ['/middleware-protected/:path*'],
|
||||
}
|
||||
```
|
||||
|
||||
It is also possible to add finer granularity based on the user logged in. I.e. you can specify a promise to determine if a specific user has permission or not.
|
||||
|
||||
```ts
|
||||
// pages/protected/_middleware.ts
|
||||
import { withMiddlewareAuth } from '@supabase/auth-helpers-nextjs/dist/middleware'
|
||||
```ts title="middleware.ts"
|
||||
import { withMiddlewareAuth } from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
export const middleware = withMiddlewareAuth({
|
||||
redirectTo: '/login',
|
||||
@@ -300,20 +405,64 @@ export const middleware = withMiddlewareAuth({
|
||||
redirectTo: '/insufficient-permissions',
|
||||
},
|
||||
})
|
||||
|
||||
export const config = {
|
||||
matcher: ['/middleware-protected/:path*'],
|
||||
}
|
||||
```
|
||||
|
||||
## Migrating from @supabase/supabase-auth-helpers to @supabase/auth-helpers
|
||||
## Migrating to version `0.4.X` {#migration}
|
||||
|
||||
This is a step by step guide on migrating away from the `@supabase/supabase-auth-helpers` to the newly released `@supabase/auth-helpers`.
|
||||
- With `supabase-js` v2, the `auth` API routes are no longer required and you can delete the `auth` directory under the `/pages/api/` directory.
|
||||
- The `/api/auth/logout` API route has been removed—use the `signout` method instead.
|
||||
```js
|
||||
<button
|
||||
onClick={async () => {
|
||||
await supabaseClient.auth.signOut()
|
||||
router.push('/')
|
||||
}}
|
||||
>
|
||||
Logout
|
||||
</button>
|
||||
```
|
||||
- The `supabaseClient` and `supabaseServerClient` have been removed in favor of the `createBrowserSupabaseClient` and `createServerSupabaseClient` methods. This allows you to provide the CLI-generated types to the client:
|
||||
|
||||
1. Install `@supabase/supabase-js`, `@supabase/auth-helpers-nextjs` and `@supabase/auth-helpers-react` libraries from npm.
|
||||
2. Replace all imports of `@supabase/supabase-auth-helpers/nextjs` in your project with `@supabase/auth-helpers-nextjs`.
|
||||
3. Replace all imports of `@supabase/supabase-auth-helpers/react` in your project with `@supabase/auth-helpers-react`.
|
||||
4. Replace all instances of `withAuthRequired` in any of your NextJS pages with `withPageAuth`.
|
||||
5. Replace all instances of `withAuthRequired` in any of your NextJS API endpoints with `withApiAuth`.
|
||||
6. Uninstall `@supabase/supabase-auth-helpers`.
|
||||
```js
|
||||
// client-side
|
||||
import type { Database } from 'types_db';
|
||||
const [supabaseClient] = useState(() =>
|
||||
createBrowserSupabaseClient<Database>()
|
||||
);
|
||||
|
||||
## Additional Links
|
||||
// server-side API route
|
||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||
import type { Database } from 'types_db';
|
||||
|
||||
- [Auth Helpers Source code](https://github.com/supabase/auth-helpers)
|
||||
- [Next.js example](https://github.com/supabase/auth-helpers/tree/main/examples/nextjs)
|
||||
export default async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const supabaseServerClient = createServerSupabaseClient<Database>({ req, res })
|
||||
const { data:{ user } } = await supabaseServerClient.auth.getUser()
|
||||
|
||||
res.status(200).json({ name: user?.name ?? '' })
|
||||
}
|
||||
```
|
||||
|
||||
- The `UserProvider` has been replaced by the `SessionContextProvider`. Make sure to wrap your `pages/_app.js` componenent with the `SessionContextProvider`. Then, throughout your application you can use the `useSessionContext` hook to get the `session` and the `useSupabaseClient` hook to get an authenticated `supabaseClient`.
|
||||
- The `useUser` hook now returns the `user` object or `null`.
|
||||
- Usage with TypeScript: You can pass types that were [generated with the Supabase CLI](/docs/reference/javascript/typescript-support#generating-types) to the Supabase Client to get enhanced type safety and auto completion:
|
||||
|
||||
```js
|
||||
// Creating a new supabase client object:
|
||||
import { Database } from '../database.types';
|
||||
|
||||
const [supabaseClient] = useState(() =>
|
||||
createBrowserSupabaseClient<Database>()
|
||||
);
|
||||
```
|
||||
|
||||
```js
|
||||
// Retrieving a supabase client object from the SessionContext:
|
||||
import { useSupabaseClient } from '@supabase/auth-helpers-react';
|
||||
import { Database } from '../database.types';
|
||||
|
||||
const supabaseClient = useSupabaseClient<Database>();
|
||||
```
|
||||
@@ -56,24 +56,28 @@ PUBLIC_SUPABASE_ANON_KEY=your-anon-key
|
||||
Start off by creating a `db.ts` file inside of the `src/lib` directory and instantiate the `supabaseClient`.
|
||||
|
||||
```ts title="src/lib/db.ts"
|
||||
import { createClient } from '@supabase/supabase-js';
|
||||
import { setupSupabaseHelpers } from '@supabase/auth-helpers-sveltekit';
|
||||
import { dev } from '$app/environment';
|
||||
import { env } from '$env/dynamic/public';
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
import { setupSupabaseHelpers } from '@supabase/auth-helpers-sveltekit'
|
||||
import { dev } from '$app/environment'
|
||||
import { env } from '$env/dynamic/public'
|
||||
// or use the static env
|
||||
// import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public';
|
||||
|
||||
export const supabaseClient = createClient(env.PUBLIC_SUPABASE_URL, env.PUBLIC_SUPABASE_ANON_KEY, {
|
||||
persistSession: false,
|
||||
autoRefreshToken: false
|
||||
});
|
||||
export const supabaseClient = createClient(
|
||||
env.PUBLIC_SUPABASE_URL,
|
||||
env.PUBLIC_SUPABASE_ANON_KEY,
|
||||
{
|
||||
persistSession: false,
|
||||
autoRefreshToken: false,
|
||||
}
|
||||
)
|
||||
|
||||
setupSupabaseHelpers({
|
||||
supabaseClient,
|
||||
cookieOptions: {
|
||||
secure: !dev
|
||||
}
|
||||
});
|
||||
supabaseClient,
|
||||
cookieOptions: {
|
||||
secure: !dev,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Initialize the client
|
||||
@@ -83,16 +87,16 @@ Edit your `+layout.svelte` file and set up the client-side.
|
||||
```html title="src/routes/+layout.svelte"
|
||||
<script lang="ts">
|
||||
// make sure the supabase instance is initialized on the client
|
||||
import '$lib/db';
|
||||
import { startSupabaseSessionSync } from '@supabase/auth-helpers-sveltekit';
|
||||
import { page } from '$app/stores';
|
||||
import { invalidateAll } from '$app/navigation';
|
||||
import '$lib/db'
|
||||
import { startSupabaseSessionSync } from '@supabase/auth-helpers-sveltekit'
|
||||
import { page } from '$app/stores'
|
||||
import { invalidateAll } from '$app/navigation'
|
||||
|
||||
// this sets up automatic token refreshing
|
||||
startSupabaseSessionSync({
|
||||
page,
|
||||
handleRefresh: () => invalidateAll()
|
||||
});
|
||||
page,
|
||||
handleRefresh: () => invalidateAll(),
|
||||
})
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
@@ -100,20 +104,20 @@ Edit your `+layout.svelte` file and set up the client-side.
|
||||
|
||||
### Set up hooks
|
||||
|
||||
The `hooks.ts` file is where the heavy lifting of this library happens:
|
||||
The `hooks.server.ts` file is where the heavy lifting of this library happens:
|
||||
|
||||
```ts title="src/hooks.server.ts"
|
||||
// make sure the supabase instance is initialized on the server
|
||||
import '$lib/db';
|
||||
import { dev } from '$app/environment';
|
||||
import { auth } from '@supabase/auth-helpers-sveltekit/server';
|
||||
import '$lib/db'
|
||||
import { dev } from '$app/environment'
|
||||
import { auth } from '@supabase/auth-helpers-sveltekit/server'
|
||||
|
||||
export const handle = auth();
|
||||
export const handle = auth()
|
||||
|
||||
// use the sequence helper if you have additional Handle methods
|
||||
import { sequence } from '@sveltejs/kit/hooks';
|
||||
import { sequence } from '@sveltejs/kit/hooks'
|
||||
|
||||
export const handle = sequence(auth(), yourHandler);
|
||||
export const handle = sequence(auth(), yourHandler)
|
||||
```
|
||||
|
||||
There are three handle methods available:
|
||||
@@ -130,13 +134,13 @@ There are three handle methods available:
|
||||
In order to make the session available to the UI (e.g., pages and layouts), pass the session in the root layout load function:
|
||||
|
||||
```ts title="src/routes/+layout.server.ts"
|
||||
import type { LayoutServerLoad } from './$types';
|
||||
import type { LayoutServerLoad } from './$types'
|
||||
|
||||
export const load: LayoutServerLoad = async ({ locals }) => {
|
||||
return {
|
||||
session: locals.session
|
||||
};
|
||||
};
|
||||
session: locals.session,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Typings
|
||||
@@ -151,10 +155,10 @@ In order to get the most out of TypeScript and IntelliSense, import the types in
|
||||
// and what to do when importing types
|
||||
declare namespace App {
|
||||
interface Locals {
|
||||
session: import('@supabase/auth-helpers-sveltekit').SupabaseSession;
|
||||
session: import('@supabase/auth-helpers-sveltekit').SupabaseSession
|
||||
}
|
||||
interface PageData {
|
||||
session: import('@supabase/auth-helpers-sveltekit').SupabaseSession;
|
||||
session: import('@supabase/auth-helpers-sveltekit').SupabaseSession
|
||||
}
|
||||
// interface Error {}
|
||||
// interface Platform {}
|
||||
@@ -168,22 +172,23 @@ This library has provided a method to delete the session cookie.
|
||||
Create a server only page with an action to delete the cookie
|
||||
|
||||
```ts title="src/routes/logout/+page.server.ts"
|
||||
import { deleteSession } from '@supabase/auth-helpers-sveltekit/server';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import type { Actions } from './$types';
|
||||
import { deleteSession } from '@supabase/auth-helpers-sveltekit/server'
|
||||
import { redirect } from '@sveltejs/kit'
|
||||
import type { Actions } from './$types'
|
||||
|
||||
export const actions: Actions = {
|
||||
async default({ cookies }) {
|
||||
deleteSession(cookies);
|
||||
throw redirect(303, '/');
|
||||
}
|
||||
};
|
||||
async default({ cookies }) {
|
||||
deleteSession(cookies)
|
||||
throw redirect(303, '/')
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Then make a **POST** request with a form to the server only page above.
|
||||
|
||||
```html
|
||||
<script lang="ts">
|
||||
import { enhanceAndInvalidate } from '@supabase/auth-helpers-sveltekit';
|
||||
import { enhanceAndInvalidate } from '@supabase/auth-helpers-sveltekit'
|
||||
</script>
|
||||
|
||||
<form action="/logout" method="post" use:enhanceAndInvalidate>
|
||||
@@ -197,14 +202,14 @@ You can now determine if a user is authenticated on the client-side by checking
|
||||
|
||||
```html title="src/routes/+page.svelte"
|
||||
<script>
|
||||
import { page } from '$app/stores';
|
||||
import { page } from '$app/stores'
|
||||
</script>
|
||||
|
||||
{#if !$page.data.session.user}
|
||||
<h1>I am not logged in</h1>
|
||||
<h1>I am not logged in</h1>
|
||||
{:else}
|
||||
<h1>Welcome {$page.data.session.user.email}</h1>
|
||||
<p>I am logged in!</p>
|
||||
<h1>Welcome {$page.data.session.user.email}</h1>
|
||||
<p>I am logged in!</p>
|
||||
{/if}
|
||||
```
|
||||
|
||||
@@ -214,23 +219,23 @@ For [row level security](https://supabase.com/docs/learn/auth-deep-dive/auth-row
|
||||
|
||||
```html
|
||||
<script>
|
||||
import { supabaseClient } from '$lib/db';
|
||||
import { page } from '$app/stores';
|
||||
import { supabaseClient } from '$lib/db'
|
||||
import { page } from '$app/stores'
|
||||
|
||||
let loadedData = [];
|
||||
let loadedData = []
|
||||
async function loadData() {
|
||||
const { data } = await supabaseClient.from('test').select('*').limit(20);
|
||||
loadedData = data;
|
||||
const { data } = await supabaseClient.from('test').select('*').limit(20)
|
||||
loadedData = data
|
||||
}
|
||||
|
||||
$: if ($page.data.session.user) {
|
||||
loadData();
|
||||
loadData()
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if $page.data.session.user}
|
||||
<p>client-side data fetching with RLS</p>
|
||||
<pre>{JSON.stringify(loadedData, null, 2)}</pre>
|
||||
<p>client-side data fetching with RLS</p>
|
||||
<pre>{JSON.stringify(loadedData, null, 2)}</pre>
|
||||
{/if}
|
||||
```
|
||||
|
||||
@@ -239,8 +244,8 @@ For [row level security](https://supabase.com/docs/learn/auth-deep-dive/auth-row
|
||||
```html title="src/routes/profile/+page.svelte"
|
||||
<script>
|
||||
/** @type {import('./$types').PageData} */
|
||||
export let data;
|
||||
$: ({ user, tableData } = data);
|
||||
export let data
|
||||
$: ({ user, tableData } = data)
|
||||
</script>
|
||||
|
||||
<div>Protected content for {user.email}</div>
|
||||
@@ -281,14 +286,14 @@ Always use the instance returned by `getSupabaseClient()` directly!
|
||||
|
||||
```ts
|
||||
// Bad
|
||||
const supabaseClient = getSupabaseClient();
|
||||
const supabaseClient = getSupabaseClient()
|
||||
|
||||
await supabaseClient.from('table1').select();
|
||||
await supabaseClient.from('table2').select();
|
||||
await supabaseClient.from('table1').select()
|
||||
await supabaseClient.from('table2').select()
|
||||
|
||||
// Good
|
||||
await getSupabaseClient().from('table1').select();
|
||||
await getSupabaseClient().from('table2').select();
|
||||
await getSupabaseClient().from('table1').select()
|
||||
await getSupabaseClient().from('table2').select()
|
||||
```
|
||||
|
||||
## Protecting API routes
|
||||
@@ -324,34 +329,34 @@ If you visit `/api/protected-route` without a valid session cookie, you will get
|
||||
Wrap an Action to check that the user has a valid session. If they're not logged in, the session is `null`.
|
||||
|
||||
```ts title="src/routes/posts/+page.server.ts"
|
||||
import type { Actions } from './$types';
|
||||
import { withAuth } from '@supabase/auth-helpers-sveltekit';
|
||||
import { error, invalid } from '@sveltejs/kit';
|
||||
import type { Actions } from './$types'
|
||||
import { withAuth } from '@supabase/auth-helpers-sveltekit'
|
||||
import { error, invalid } from '@sveltejs/kit'
|
||||
|
||||
export const actions: Actions = {
|
||||
createPost: withAuth(async ({ session, getSupabaseClient, request }) => {
|
||||
if (!session.user) {
|
||||
// the user is not signed in
|
||||
throw error(403, { message: 'Unauthorized' });
|
||||
throw error(403, { message: 'Unauthorized' })
|
||||
}
|
||||
// we are save, let the user create the post
|
||||
const formData = await request.formData();
|
||||
const content = formData.get('content');
|
||||
const formData = await request.formData()
|
||||
const content = formData.get('content')
|
||||
|
||||
const { error: createPostError, data: newPost } = await getSupabaseClient()
|
||||
.from('posts')
|
||||
.insert({ content });
|
||||
.insert({ content })
|
||||
|
||||
if (createPostError) {
|
||||
return invalid(500, {
|
||||
supabaseErrorMessage: createPostError.message
|
||||
});
|
||||
supabaseErrorMessage: createPostError.message,
|
||||
})
|
||||
}
|
||||
return {
|
||||
newPost
|
||||
};
|
||||
})
|
||||
};
|
||||
newPost,
|
||||
}
|
||||
}),
|
||||
}
|
||||
```
|
||||
|
||||
If you try to submit a form with the action `?/createPost` without a valid session cookie, you will get a 403 error response.
|
||||
@@ -361,62 +366,62 @@ If you try to submit a form with the action `?/createPost` without a valid sessi
|
||||
Use `saveSession` to save the session cookies:
|
||||
|
||||
```ts
|
||||
import type { Actions } from './$types';
|
||||
import { supabaseClient } from '$lib/db';
|
||||
import { invalid, redirect } from '@sveltejs/kit';
|
||||
import { saveSession } from '@supabase/auth-helpers-sveltekit/server';
|
||||
import type { Actions } from './$types'
|
||||
import { supabaseClient } from '$lib/db'
|
||||
import { invalid, redirect } from '@sveltejs/kit'
|
||||
import { saveSession } from '@supabase/auth-helpers-sveltekit/server'
|
||||
|
||||
export const actions: Actions = {
|
||||
async signin({ request, cookies, url }) {
|
||||
const formData = await request.formData();
|
||||
const formData = await request.formData()
|
||||
|
||||
const email = formData.get('email') as string;
|
||||
const password = formData.get('password') as string;
|
||||
const email = formData.get('email') as string
|
||||
const password = formData.get('password') as string
|
||||
|
||||
const { data, error } = await supabaseClient.auth.api.signInWithEmail(
|
||||
email,
|
||||
password,
|
||||
{
|
||||
redirectTo: `${url.origin}/logging-in`
|
||||
redirectTo: `${url.origin}/logging-in`,
|
||||
}
|
||||
);
|
||||
)
|
||||
|
||||
if (error || !data) {
|
||||
if (error?.status === 400) {
|
||||
return invalid(400, {
|
||||
error: 'Invalid credentials',
|
||||
values: {
|
||||
email
|
||||
}
|
||||
});
|
||||
email,
|
||||
},
|
||||
})
|
||||
}
|
||||
return invalid(500, {
|
||||
error: 'Server error. Try again later.',
|
||||
values: {
|
||||
email
|
||||
}
|
||||
});
|
||||
email,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
saveSession(cookies, data);
|
||||
throw redirect(303, '/dashboard');
|
||||
}
|
||||
};
|
||||
saveSession(cookies, data)
|
||||
throw redirect(303, '/dashboard')
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Use `deleteSession` to delete the session cookies:
|
||||
|
||||
```ts
|
||||
import type { Actions } from './$types';
|
||||
import { deleteSession } from '@supabase/auth-helpers-sveltekit/server';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import type { Actions } from './$types'
|
||||
import { deleteSession } from '@supabase/auth-helpers-sveltekit/server'
|
||||
import { redirect } from '@sveltejs/kit'
|
||||
|
||||
export const actions: Actions = {
|
||||
async logout({ cookies }) {
|
||||
deleteSession(cookies);
|
||||
throw redirect(303, '/');
|
||||
}
|
||||
};
|
||||
deleteSession(cookies)
|
||||
throw redirect(303, '/')
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Custom session namespace
|
||||
@@ -426,10 +431,10 @@ If you want to use something other than `locals.session` and `$page.data.session
|
||||
```ts title="src/app.d.ts"
|
||||
declare namespace App {
|
||||
interface Locals {
|
||||
mySupabaseSession: import('@supabase/auth-helpers-sveltekit').SupabaseSession;
|
||||
mySupabaseSession: import('@supabase/auth-helpers-sveltekit').SupabaseSession
|
||||
}
|
||||
interface PageData {
|
||||
mySupabaseSession: import('@supabase/auth-helpers-sveltekit').SupabaseSession;
|
||||
mySupabaseSession: import('@supabase/auth-helpers-sveltekit').SupabaseSession
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -438,20 +443,20 @@ declare namespace App {
|
||||
setupSupabaseServer({
|
||||
supabaseClient,
|
||||
cookieOptions: {
|
||||
secure: !dev
|
||||
secure: !dev,
|
||||
},
|
||||
// --- change location within locals ---
|
||||
getSessionFromLocals: (locals) => locals.mySupabaseSession,
|
||||
setSessionToLocals: (locals, session) => (locals.mySupabaseSession = session)
|
||||
});
|
||||
setSessionToLocals: (locals, session) => (locals.mySupabaseSession = session),
|
||||
})
|
||||
```
|
||||
|
||||
```ts title="src/lib/db.ts"
|
||||
setupSupabaseClient({
|
||||
supabaseClient,
|
||||
// --- change location within pageData ---
|
||||
getSessionFromPageData: (data) => data.mySupabaseSession
|
||||
});
|
||||
getSessionFromPageData: (data) => data.mySupabaseSession,
|
||||
})
|
||||
```
|
||||
|
||||
## Migrate from 0.6.11 and below to 0.7.0 {#migration}
|
||||
@@ -489,25 +494,29 @@ export { supabaseClient };
|
||||
<TabItem value="latest">
|
||||
|
||||
```js title="src/lib/db.ts"
|
||||
import { createClient } from '@supabase/supabase-js';
|
||||
import { setupSupabaseHelpers } from '@supabase/auth-helpers-sveltekit';
|
||||
import { dev } from '$app/environment';
|
||||
import { env } from '$env/dynamic/public';
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
import { setupSupabaseHelpers } from '@supabase/auth-helpers-sveltekit'
|
||||
import { dev } from '$app/environment'
|
||||
import { env } from '$env/dynamic/public'
|
||||
// or use the static env
|
||||
|
||||
// import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public';
|
||||
|
||||
export const supabaseClient = createClient(env.PUBLIC_SUPABASE_URL, env.PUBLIC_SUPABASE_ANON_KEY, {
|
||||
persistSession: false,
|
||||
autoRefreshToken: false
|
||||
});
|
||||
export const supabaseClient = createClient(
|
||||
env.PUBLIC_SUPABASE_URL,
|
||||
env.PUBLIC_SUPABASE_ANON_KEY,
|
||||
{
|
||||
persistSession: false,
|
||||
autoRefreshToken: false,
|
||||
}
|
||||
)
|
||||
|
||||
setupSupabaseHelpers({
|
||||
supabaseClient,
|
||||
cookieOptions: {
|
||||
secure: !dev
|
||||
}
|
||||
});
|
||||
secure: !dev,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
@@ -526,9 +535,9 @@ setupSupabaseHelpers({
|
||||
|
||||
```html title="src/routes/__layout.svelte"
|
||||
<script>
|
||||
import { session } from '$app/stores';
|
||||
import { supabaseClient } from '$lib/db';
|
||||
import { SupaAuthHelper } from '@supabase/auth-helpers-svelte';
|
||||
import { session } from '$app/stores'
|
||||
import { supabaseClient } from '$lib/db'
|
||||
import { SupaAuthHelper } from '@supabase/auth-helpers-svelte'
|
||||
</script>
|
||||
|
||||
<SupaAuthHelper {supabaseClient} {session}>
|
||||
@@ -545,16 +554,16 @@ The `@supabase/auth-helpers-svelte` library is no longer required as the `@supab
|
||||
```html title="src/routes/+layout.svelte"
|
||||
<script lang="ts">
|
||||
// make sure the supabase instance is initialized on the client
|
||||
import '$lib/db';
|
||||
import { startSupabaseSessionSync } from '@supabase/auth-helpers-sveltekit';
|
||||
import { page } from '$app/stores';
|
||||
import { invalidateAll } from '$app/navigation';
|
||||
import '$lib/db'
|
||||
import { startSupabaseSessionSync } from '@supabase/auth-helpers-sveltekit'
|
||||
import { page } from '$app/stores'
|
||||
import { invalidateAll } from '$app/navigation'
|
||||
|
||||
// this sets up automatic token refreshing
|
||||
startSupabaseSessionSync({
|
||||
page,
|
||||
handleRefresh: () => invalidateAll()
|
||||
});
|
||||
page,
|
||||
handleRefresh: () => invalidateAll(),
|
||||
})
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
@@ -575,18 +584,18 @@ The `@supabase/auth-helpers-svelte` library is no longer required as the `@supab
|
||||
<TabItem value="older">
|
||||
|
||||
```ts title="src/hooks.ts"
|
||||
import { handleAuth } from '@supabase/auth-helpers-sveltekit';
|
||||
import type { GetSession, Handle } from '@sveltejs/kit';
|
||||
import { sequence } from '@sveltejs/kit/hooks';
|
||||
import { handleAuth } from '@supabase/auth-helpers-sveltekit'
|
||||
import type { GetSession, Handle } from '@sveltejs/kit'
|
||||
import { sequence } from '@sveltejs/kit/hooks'
|
||||
|
||||
export const handle: Handle = sequence(...handleAuth());
|
||||
export const handle: Handle = sequence(...handleAuth())
|
||||
|
||||
export const getSession: GetSession = async (event) => {
|
||||
const { user, accessToken, error } = event.locals;
|
||||
return {
|
||||
user,
|
||||
accessToken,
|
||||
error
|
||||
const { user, accessToken, error } = event.locals
|
||||
return {
|
||||
user,
|
||||
accessToken,
|
||||
error,
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -597,23 +606,23 @@ export const getSession: GetSession = async (event) => {
|
||||
|
||||
```ts title="src/hooks.server.ts"
|
||||
// make sure the supabase instance is initialized on the server
|
||||
import '$lib/db';
|
||||
import { dev } from '$app/environment';
|
||||
import { auth } from '@supabase/auth-helpers-sveltekit/server';
|
||||
import '$lib/db'
|
||||
import { dev } from '$app/environment'
|
||||
import { auth } from '@supabase/auth-helpers-sveltekit/server'
|
||||
|
||||
export const handle = auth();
|
||||
export const handle = auth()
|
||||
```
|
||||
|
||||
**Optional** *if using additional handle methods*
|
||||
**Optional** _if using additional handle methods_
|
||||
|
||||
```ts title="src/hooks.server.ts"
|
||||
// make sure the supabase instance is initialized on the server
|
||||
import '$lib/db';
|
||||
import { dev } from '$app/environment';
|
||||
import { auth } from '@supabase/auth-helpers-sveltekit/server';
|
||||
import { sequence } from '@sveltejs/kit/hooks';
|
||||
import '$lib/db'
|
||||
import { dev } from '$app/environment'
|
||||
import { auth } from '@supabase/auth-helpers-sveltekit/server'
|
||||
import { sequence } from '@sveltejs/kit/hooks'
|
||||
|
||||
export const handle = sequence(auth(), yourHandler);
|
||||
export const handle = sequence(auth(), yourHandler)
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
@@ -636,16 +645,16 @@ export const handle = sequence(auth(), yourHandler);
|
||||
// for information about these interfaces
|
||||
declare namespace App {
|
||||
interface UserSession {
|
||||
user: import('@supabase/supabase-js').User;
|
||||
accessToken?: string;
|
||||
user: import('@supabase/supabase-js').User
|
||||
accessToken?: string
|
||||
}
|
||||
|
||||
interface Locals extends UserSession {
|
||||
error: import('@supabase/supabase-js').ApiError;
|
||||
error: import('@supabase/supabase-js').ApiError
|
||||
}
|
||||
|
||||
interface Session extends UserSession {}
|
||||
|
||||
|
||||
// interface Platform {}
|
||||
// interface Stuff {}
|
||||
}
|
||||
@@ -663,11 +672,11 @@ declare namespace App {
|
||||
// and what to do when importing types
|
||||
declare namespace App {
|
||||
interface Locals {
|
||||
session: import('@supabase/auth-helpers-sveltekit').SupabaseSession;
|
||||
session: import('@supabase/auth-helpers-sveltekit').SupabaseSession
|
||||
}
|
||||
|
||||
interface PageData {
|
||||
session: import('@supabase/auth-helpers-sveltekit').SupabaseSession;
|
||||
session: import('@supabase/auth-helpers-sveltekit').SupabaseSession
|
||||
}
|
||||
|
||||
// interface Error {}
|
||||
@@ -691,14 +700,14 @@ declare namespace App {
|
||||
|
||||
```html title="src/routes/index.svelte"
|
||||
<script>
|
||||
import { session } from '$app/stores';
|
||||
import { session } from '$app/stores'
|
||||
</script>
|
||||
|
||||
{#if !$session.user}
|
||||
<h1>I am not logged in</h1>
|
||||
<h1>I am not logged in</h1>
|
||||
{:else}
|
||||
<h1>Welcome {$session.user.email}</h1>
|
||||
<p>I am logged in!</p>
|
||||
<h1>Welcome {$session.user.email}</h1>
|
||||
<p>I am logged in!</p>
|
||||
{/if}
|
||||
```
|
||||
|
||||
@@ -708,14 +717,14 @@ declare namespace App {
|
||||
|
||||
```html title="src/routes/+page.svelte"
|
||||
<script>
|
||||
import { page } from '$app/stores';
|
||||
import { page } from '$app/stores'
|
||||
</script>
|
||||
|
||||
{#if !$page.data.session.user}
|
||||
<h1>I am not logged in</h1>
|
||||
<h1>I am not logged in</h1>
|
||||
{:else}
|
||||
<h1>Welcome {$page.data.session.user.email}</h1>
|
||||
<p>I am logged in!</p>
|
||||
<h1>Welcome {$page.data.session.user.email}</h1>
|
||||
<p>I am logged in!</p>
|
||||
{/if}
|
||||
```
|
||||
|
||||
@@ -735,25 +744,30 @@ declare namespace App {
|
||||
|
||||
```html title="src/routes/protected-route.svelte"
|
||||
<script lang="ts" context="module">
|
||||
import { supabaseServerClient, withPageAuth } from '@supabase/auth-helpers-sveltekit';
|
||||
import type { Load } from './__types/protected-page';
|
||||
import {
|
||||
supabaseServerClient,
|
||||
withPageAuth,
|
||||
} from '@supabase/auth-helpers-sveltekit'
|
||||
import type { Load } from './__types/protected-page'
|
||||
|
||||
export const load: Load = async ({ session }) =>
|
||||
withPageAuth(
|
||||
{
|
||||
redirectTo: '/',
|
||||
user: session.user
|
||||
},
|
||||
async () => {
|
||||
const { data } = await supabaseServerClient(session.accessToken).from('test').select('*');
|
||||
return { props: { data, user: session.user } };
|
||||
}
|
||||
);
|
||||
export const load: Load = async ({ session }) =>
|
||||
withPageAuth(
|
||||
{
|
||||
redirectTo: '/',
|
||||
user: session.user,
|
||||
},
|
||||
async () => {
|
||||
const { data } = await supabaseServerClient(session.accessToken)
|
||||
.from('test')
|
||||
.select('*')
|
||||
return { props: { data, user: session.user } }
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<script>
|
||||
export let data;
|
||||
export let user;
|
||||
export let data
|
||||
export let user
|
||||
</script>
|
||||
|
||||
<div>Protected content for {user.email}</div>
|
||||
@@ -769,13 +783,12 @@ export let user;
|
||||
|
||||
```html title="src/routes/protected-route/+page.svelte"
|
||||
<script lang="ts">
|
||||
import type { PageData } from './$types';
|
||||
import type { PageData } from './$types'
|
||||
|
||||
export let data: PageData;
|
||||
$: ({ tableData, user } = data);
|
||||
export let data: PageData
|
||||
$: ({ tableData, user } = data)
|
||||
</script>
|
||||
|
||||
|
||||
<div>Protected content for {user.email}</div>
|
||||
<p>server-side fetched data with RLS:</p>
|
||||
<pre>{JSON.stringify(tableData, null, 2)}</pre>
|
||||
@@ -784,18 +797,22 @@ export let user;
|
||||
```
|
||||
|
||||
```ts title="src/routes/protected-route/+page.ts"
|
||||
import { withAuth } from '@supabase/auth-helpers-sveltekit';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import type { PageLoad } from './$types';
|
||||
import { withAuth } from '@supabase/auth-helpers-sveltekit'
|
||||
import { redirect } from '@sveltejs/kit'
|
||||
import type { PageLoad } from './$types'
|
||||
|
||||
export const load: PageLoad = withAuth(async ({ session, getSupabaseClient }) => {
|
||||
if (!session.user) {
|
||||
throw redirect(303, '/');
|
||||
export const load: PageLoad = withAuth(
|
||||
async ({ session, getSupabaseClient }) => {
|
||||
if (!session.user) {
|
||||
throw redirect(303, '/')
|
||||
}
|
||||
|
||||
const { data: tableData } = await getSupabaseClient()
|
||||
.from('test')
|
||||
.select('*')
|
||||
return { tableData, user: session.user }
|
||||
}
|
||||
|
||||
const { data: tableData } = await getSupabaseClient().from('test').select('*');
|
||||
return { tableData, user: session.user };
|
||||
});
|
||||
)
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
@@ -813,28 +830,33 @@ export const load: PageLoad = withAuth(async ({ session, getSupabaseClient }) =>
|
||||
<TabItem value="older">
|
||||
|
||||
```ts title="src/routes/api/protected-route.ts"
|
||||
import { supabaseServerClient, withApiAuth } from "@supabase/auth-helpers-sveltekit";
|
||||
import type { RequestHandler } from "./__types/protected-route";
|
||||
import {
|
||||
supabaseServerClient,
|
||||
withApiAuth,
|
||||
} from '@supabase/auth-helpers-sveltekit'
|
||||
import type { RequestHandler } from './__types/protected-route'
|
||||
|
||||
interface TestTable {
|
||||
id: string;
|
||||
created_at: string;
|
||||
id: string
|
||||
created_at: string
|
||||
}
|
||||
|
||||
interface GetOutput {
|
||||
data: TestTable[];
|
||||
data: TestTable[]
|
||||
}
|
||||
|
||||
export const GET: RequestHandler<GetOutput> = async ({ locals, request }) =>
|
||||
withApiAuth({ user: locals.user }, async () => {
|
||||
// Run queries with RLS on the server
|
||||
const { data } = await supabaseServerClient(request).from("test").select("*");
|
||||
const { data } = await supabaseServerClient(request)
|
||||
.from('test')
|
||||
.select('*')
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
body: { data }
|
||||
};
|
||||
});
|
||||
body: { data },
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
@@ -24,13 +24,11 @@ Keycloak OAuth consists of five broad steps:
|
||||
- Obtain the `Secret` from the credentials tab which will be used as the `client secret`.
|
||||
- Add the callback url of your application to your allowlist.
|
||||
|
||||
## Steps
|
||||
|
||||
### Access your Keycloak Admin console
|
||||
## Access your Keycloak Admin console
|
||||
|
||||
- Login by visiting [`http://localhost:8080`](http://localhost:8080) and clicking on "Administration Console".
|
||||
|
||||
### Create a Keycloak Realm
|
||||
## Create a Keycloak Realm
|
||||
|
||||
- Once you've logged in to the Keycloak console, you can add a realm from the side panel. The default realm should be named "Master".
|
||||
- After you've added a new realm, you can retrieve the `issuer` from the "OpenID Endpoint Configuration" endpoint. The `issuer` will be used as the `Keycloak URL`.
|
||||
@@ -38,13 +36,13 @@ Keycloak OAuth consists of five broad steps:
|
||||
|
||||

|
||||
|
||||
### Create a Keycloak Client
|
||||
## Create a Keycloak Client
|
||||
|
||||
The "Client ID" of the created client will serve as the `client_id` when you make API calls to authenticate the user.
|
||||
|
||||

|
||||
|
||||
### Client Settings
|
||||
## Client Settings
|
||||
|
||||
After you've created the client successfully, ensure that you set the following settings:
|
||||
|
||||
@@ -55,34 +53,26 @@ After you've created the client successfully, ensure that you set the following
|
||||

|
||||

|
||||
|
||||
### Obtain the Client Secret
|
||||
## Obtain the Client Secret
|
||||
|
||||
This will serve as the `client_secret` when you make API calls to authenticate the user.
|
||||
Under the "Credentials" tab, the `Secret` value will be used as the `client secret`.
|
||||
|
||||

|
||||
|
||||
### Add login code to your client app
|
||||
## Add login code to your client app
|
||||
|
||||
The JavaScript client code is documented in the [Supabase OAuth Reference](/docs/reference/javascript/auth-signin#sign-in-using-third-party-providers).
|
||||
|
||||
```js
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
provider: 'keycloak',
|
||||
})
|
||||
```
|
||||
|
||||
Add a function which you can call from a button, link, or UI element.
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `keycloak` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithKeycloak() {
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||
provider: 'keycloak',
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
To log out:
|
||||
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
|
||||
|
||||
```js
|
||||
async function signout() {
|
||||
|
||||
@@ -17,16 +17,14 @@ Setting up LinkedIn logins for your application consists of 3 parts:
|
||||
- Add your LinkedIn `client_id` and `client_secret` to your [Supabase Project](https://app.supabase.com).
|
||||
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js).
|
||||
|
||||
## Steps
|
||||
|
||||
### Access your LinkedIn Developer account
|
||||
## Access your LinkedIn Developer account
|
||||
|
||||
- Go to [LinkedIn Developer Dashboard](https://www.linkedin.com/developers/apps).
|
||||
- Log in (if necessary.)
|
||||
|
||||

|
||||
|
||||
### Find your callback URL
|
||||
## Find your callback URL
|
||||
|
||||
The next step requires a callback URL, which looks like this:
|
||||
|
||||
@@ -47,7 +45,7 @@ The next step requires a callback URL, which looks like this:
|
||||
/>
|
||||
</video>
|
||||
|
||||
### Create a LinkedIn OAuth app
|
||||
## Create a LinkedIn OAuth app
|
||||
|
||||
- Go to [LinkedIn Developer Dashboard](https://www.linkedin.com/developers/apps).
|
||||
- Click on `Create App` at the top right
|
||||
@@ -58,7 +56,7 @@ The next step requires a callback URL, which looks like this:
|
||||
- Copy and save your newly-generated `Client ID`
|
||||
- Copy and save your newly-generated `Client Secret`
|
||||
|
||||
### Enter your LinkedIn credentials into your Supabase Project
|
||||
## Enter your LinkedIn credentials into your Supabase Project
|
||||
|
||||
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
|
||||
- In the left sidebar, click the `Authentication` icon (near the top).
|
||||
@@ -68,27 +66,19 @@ The next step requires a callback URL, which looks like this:
|
||||
- Enter your `client_id` and `client_secret` saved in the previous step.
|
||||
- Click `Save`.
|
||||
|
||||
### Add login code to your client app
|
||||
## Add login code to your client app
|
||||
|
||||
The JavaScript client code is documented here: [Supabase OAuth Client Code](/docs/reference/javascript/auth-signin#sign-in-using-third-party-providers).
|
||||
|
||||
```js
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
provider: 'LinkedIn',
|
||||
})
|
||||
```
|
||||
|
||||
Add this function which you can call from a button, link, or UI element.
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `linkedin` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithLinkedIn() {
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
provider: 'LinkedIn',
|
||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||
provider: 'linkedin',
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
To log out:
|
||||
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
|
||||
|
||||
```js
|
||||
async function signout() {
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
---
|
||||
id: auth-magic-link
|
||||
title: 'Login With Magic Link'
|
||||
description: Use Supabase to Authenticate and Authorize your users using Magic Link.
|
||||
description: Use Supabase to authenticate and authorize your users using magic links.
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
|
||||
By default, if no password is provided, the user will be sent a "magic link" to their email address, which they can click to open your application with a valid session. By default, a given user can only request a Magic Link once every 60 seconds.
|
||||
Magic links are a form of passwordless logins where users click on a link sent to their email address to log in to their accounts.
|
||||
Magic links only work with email addresses. By default, a user can only request a magic link once every 60 seconds.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -25,12 +26,7 @@ Setting up Magic Link logins for your Supabase application.
|
||||
- Under `Email Auth` turn `Enable Email Signup` to ON
|
||||
- Click `Save`
|
||||
|
||||
### Add login code to your client app
|
||||
|
||||
Add logins using our client libraries:
|
||||
|
||||
- [JavaScript](/docs/reference/javascript/auth-signin#sign-in-with-magic-link)
|
||||
- [Dart](/docs/reference/dart/auth-signin#sign-in-with-magic-link)
|
||||
## Add login code to your client app
|
||||
|
||||
<Tabs
|
||||
defaultValue="js"
|
||||
@@ -40,39 +36,11 @@ Add logins using our client libraries:
|
||||
|
||||
<TabItem value="js">
|
||||
|
||||
```js
|
||||
const { user, error } = await supabase.auth.signIn({
|
||||
email: 'example@email.com',
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="dart">
|
||||
|
||||
```dart
|
||||
final res = await supabase.auth.signIn(email: 'example@email.com');
|
||||
|
||||
final error = res.error;
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
Add this function which you can call from a button, link, or UI element.
|
||||
|
||||
<Tabs
|
||||
defaultValue="js"
|
||||
groupId="guides/auth"
|
||||
values={[{ label: 'JavaScript', value: 'js' }, { label: 'Dart', value: 'dart' }]}
|
||||
>
|
||||
|
||||
<TabItem value="js">
|
||||
When your user signs in, call [signInWithOtp()](/docs/reference/javascript/auth-signinwithotp) with their email address:
|
||||
|
||||
```js
|
||||
async function signInWithEmail() {
|
||||
const { user, error } = await supabase.auth.signIn({
|
||||
const { data, error } = await supabase.auth.signInWithOtp({
|
||||
email: 'example@email.com',
|
||||
})
|
||||
}
|
||||
@@ -82,6 +50,8 @@ async function signInWithEmail() {
|
||||
|
||||
<TabItem value="dart">
|
||||
|
||||
When your user signs in, call [signIn()](/docs/reference/dart/auth-signin) with their email address:
|
||||
|
||||
```dart
|
||||
Future<void> signInWithEmail() async {
|
||||
await supabase.auth.signIn(email: 'example@email.com');
|
||||
@@ -92,8 +62,6 @@ Future<void> signInWithEmail() async {
|
||||
|
||||
</Tabs>
|
||||
|
||||
To log out:
|
||||
|
||||
<Tabs
|
||||
defaultValue="js"
|
||||
groupId="guides/auth"
|
||||
@@ -102,6 +70,8 @@ To log out:
|
||||
|
||||
<TabItem value="js">
|
||||
|
||||
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
|
||||
|
||||
```js
|
||||
async function signOut() {
|
||||
const { error } = await supabase.auth.signOut()
|
||||
@@ -112,6 +82,8 @@ async function signOut() {
|
||||
|
||||
<TabItem value="dart">
|
||||
|
||||
When your user signs out, call [signOut()](/docs/reference/dart/auth-signout) to remove them from the browser session and any objects from localStorage:
|
||||
|
||||
```dart
|
||||
Future<void> signOut() async {
|
||||
await supabase.auth.signOut();
|
||||
|
||||
@@ -172,7 +172,7 @@ values={[
|
||||
<TabItem value="JS">
|
||||
|
||||
```js
|
||||
let { user, error } = await supabase.auth.signIn({
|
||||
let { user, error } = await supabase.auth.signInWithPassword({
|
||||
phone: '+13334445555',
|
||||
password: 'some-password',
|
||||
})
|
||||
@@ -209,7 +209,7 @@ values={[
|
||||
<TabItem value="JS">
|
||||
|
||||
```js
|
||||
let { user, error } = await supabase.auth.signIn({
|
||||
let { user, error } = await supabase.auth.signInWithOtp({
|
||||
phone: '+13334445555',
|
||||
})
|
||||
```
|
||||
|
||||
@@ -17,9 +17,7 @@ Setting up Notion logins for your application consists of 3 parts:
|
||||
- Retrieve your OAuth client ID and OAuth client secret and add them to your [Supabase Project](https://app.supabase.com)
|
||||
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js)
|
||||
|
||||
## Steps
|
||||
|
||||
### Create your notion integration
|
||||
## Create your notion integration
|
||||
|
||||
- Go to [developers.notion.com](https://developers.notion.com/).
|
||||
- Click "View my integrations" and login.
|
||||
@@ -33,7 +31,7 @@ Setting up Notion logins for your application consists of 3 parts:
|
||||
|
||||

|
||||
|
||||
### Add the redirect uri
|
||||
## Add the redirect URI
|
||||
|
||||
- After selecting "Public integration", you should see an option to add "Redirect URIs".
|
||||
|
||||
@@ -58,7 +56,7 @@ Your redirect uri should look like the following: `https://<project-ref>.supabas
|
||||
/>
|
||||
</video>
|
||||
|
||||
### Add your Notion credentials into your Supabase Project
|
||||
## Add your Notion credentials into your Supabase Project
|
||||
|
||||
- Once you've created your notion integration, you should be able to retrieve the "OAuth client ID" and "OAuth client secret" from the "OAuth Domain and URIs" tab.
|
||||
|
||||
@@ -71,27 +69,19 @@ Your redirect uri should look like the following: `https://<project-ref>.supabas
|
||||
- Enter the "OAuth client ID" and "OAuth client secret" obtained in the `client id` and `client secret` fields.
|
||||
- Click `Save`
|
||||
|
||||
### Add login code to your client app
|
||||
## Add login code to your client app
|
||||
|
||||
The JavaScript client code is documented here: [Supabase OAuth Client Code](/docs/reference/javascript/auth-signin#sign-in-using-third-party-providers)
|
||||
|
||||
```js
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
provider: 'notion',
|
||||
})
|
||||
```
|
||||
|
||||
Add this function which you can call from a button, link, or UI element.
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `notion` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithNotion() {
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||
provider: 'notion',
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
To log out:
|
||||
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
|
||||
|
||||
```js
|
||||
async function signout() {
|
||||
|
||||
@@ -17,16 +17,14 @@ Setting up Slack logins for your application consists of 3 parts:
|
||||
- Add your Slack `API Key` and `API Secret Key` to your [Supabase Project](https://app.supabase.com).
|
||||
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js).
|
||||
|
||||
## Steps
|
||||
|
||||
### Access your Slack Developer account
|
||||
## Access your Slack Developer account
|
||||
|
||||
- Go to [api.slack.com](https://api.slack.com/apps).
|
||||
- Click on `Your Apps` at the top right to log in.
|
||||
|
||||

|
||||
|
||||
### Find your callback URL
|
||||
## Find your callback URL
|
||||
|
||||
The next step requires a callback URL, which looks like this:
|
||||
|
||||
@@ -47,7 +45,7 @@ The next step requires a callback URL, which looks like this:
|
||||
/>
|
||||
</video>
|
||||
|
||||
### Create a Slack OAuth app
|
||||
## Create a Slack OAuth app
|
||||
|
||||
- Go to [api.slack.com](https://api.slack.com/apps).
|
||||
- Click on `Create an App`
|
||||
@@ -71,7 +69,7 @@ Under `Redirect URLs`:
|
||||
- Paste your `Callback URL` then click `Add`
|
||||
- Click `Save URLs`
|
||||
|
||||
### Enter your Slack credentials into your Supabase Project
|
||||
## Enter your Slack credentials into your Supabase Project
|
||||
|
||||
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
|
||||
- In the left sidebar, click the `Authentication` icon (near the top).
|
||||
@@ -81,27 +79,19 @@ Under `Redirect URLs`:
|
||||
- Enter your `Client ID` (`client_id`) and `Client Secret` (`client_secret`) saved in the previous step.
|
||||
- Click `Save`.
|
||||
|
||||
### Add login code to your client app
|
||||
## Add login code to your client app
|
||||
|
||||
The JavaScript client code is documented here: [Supabase OAuth Client Code](/docs/reference/javascript/auth-signin#sign-in-using-third-party-providers).
|
||||
|
||||
```js
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
provider: 'slack',
|
||||
})
|
||||
```
|
||||
|
||||
Add this function which you can call from a button, link, or UI element.
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `slack` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithSlack() {
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||
provider: 'slack',
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
To log out:
|
||||
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
|
||||
|
||||
```js
|
||||
async function signout() {
|
||||
|
||||
@@ -17,16 +17,14 @@ Setting up Spotify logins for your application consists of 3 parts:
|
||||
- Add your Spotify `API Key` and `API Secret Key` to your [Supabase Project](https://app.supabase.com).
|
||||
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js).
|
||||
|
||||
## Steps
|
||||
|
||||
### Access your Spotify Developer account
|
||||
## Access your Spotify Developer account
|
||||
|
||||
- Log into [Spotify](https://spotify.com)
|
||||
- Access the [Spotify Developer Dashboard](https://developer.spotify.com/dashboard)
|
||||
|
||||

|
||||
|
||||
### Find your callback URL
|
||||
## Find your callback URL
|
||||
|
||||
The next step requires a callback URL, which looks like this:
|
||||
|
||||
@@ -47,7 +45,7 @@ The next step requires a callback URL, which looks like this:
|
||||
/>
|
||||
</video>
|
||||
|
||||
### Create a Spotify OAuth app
|
||||
## Create a Spotify OAuth app
|
||||
|
||||
- Log into [Spotify](https://spotify.com).
|
||||
- Go to the [Spotify Developer Dashboard](https://developer.spotify.com/dashboard)
|
||||
@@ -66,7 +64,7 @@ Under `Redirect URIs`:
|
||||
- Click `Add`
|
||||
- Click `Save` at the bottom
|
||||
|
||||
### Enter your Spotify credentials into your Supabase Project
|
||||
## Enter your Spotify credentials into your Supabase Project
|
||||
|
||||
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
|
||||
- In the left sidebar, click the `Authentication` icon (near the top).
|
||||
@@ -76,27 +74,19 @@ Under `Redirect URIs`:
|
||||
- Enter your `Client ID` (`client_id`) and `Client Secret` (`client_secret`) saved in the previous step.
|
||||
- Click `Save`.
|
||||
|
||||
### Add login code to your client app
|
||||
## Add login code to your client app
|
||||
|
||||
The JavaScript client code is documented here: [Supabase OAuth Client Code](/docs/reference/javascript/auth-signin#sign-in-using-third-party-providers).
|
||||
|
||||
```js
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
provider: 'spotify',
|
||||
})
|
||||
```
|
||||
|
||||
Add this function which you can call from a button, link, or UI element.
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `spotify` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithSpotify() {
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||
provider: 'spotify',
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
To log out:
|
||||
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
|
||||
|
||||
```js
|
||||
async function signout() {
|
||||
|
||||
@@ -189,7 +189,7 @@ values={[
|
||||
<TabItem value="JS">
|
||||
|
||||
```js
|
||||
let { user, error } = await supabase.auth.signIn({
|
||||
let { user, error } = await supabase.auth.signInWithPassword({
|
||||
phone: '+13334445555',
|
||||
password: 'some-password',
|
||||
})
|
||||
@@ -226,7 +226,7 @@ values={[
|
||||
<TabItem value="JS">
|
||||
|
||||
```js
|
||||
let { user, error } = await supabase.auth.signIn({
|
||||
let { user, error } = await supabase.auth.signInWithOtp({
|
||||
phone: '+13334445555',
|
||||
})
|
||||
```
|
||||
|
||||
@@ -17,9 +17,7 @@ Setting up Twitch logins for your application consists of 3 parts:
|
||||
- Add your Twitch OAuth Consumer keys to your [Supabase Project](https://app.supabase.com)
|
||||
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js)
|
||||
|
||||
## Steps
|
||||
|
||||
### Access your Twitch Developer account
|
||||
## Access your Twitch Developer account
|
||||
|
||||
- Go to [dev.twitch.tv](https://dev.twitch.tv).
|
||||
- Click on `Log in with Twitch` at the top right to log in.
|
||||
@@ -31,7 +29,7 @@ Setting up Twitch logins for your application consists of 3 parts:
|
||||
|
||||

|
||||
|
||||
### Find your callback URL
|
||||
## Find your callback URL
|
||||
|
||||
In the next step you require a callback URL, which looks like this:
|
||||
|
||||
@@ -52,7 +50,7 @@ In the next step you require a callback URL, which looks like this:
|
||||
/>
|
||||
</video>
|
||||
|
||||
### Create a Twitch Application
|
||||
## Create a Twitch Application
|
||||
|
||||

|
||||
|
||||
@@ -65,7 +63,7 @@ In the next step you require a callback URL, which looks like this:
|
||||
- Select a category for your app.
|
||||
- Check the Captcha box and click `Create`.
|
||||
|
||||
### Retrieve your Twitch OAuth Client ID and Client Secret
|
||||
## Retrieve your Twitch OAuth Client ID and Client Secret
|
||||
|
||||
- Click `Manage` at the right of your application entry in the list.
|
||||
|
||||
@@ -77,7 +75,7 @@ In the next step you require a callback URL, which looks like this:
|
||||
|
||||

|
||||
|
||||
### Add your Twitch credentials into your Supabase Project
|
||||
## Add your Twitch credentials into your Supabase Project
|
||||
|
||||
- Go to your [Supabase Project Dashboard](https://app.supabase.com)
|
||||
- In the left sidebar, click the `Authentication` icon (near the top)
|
||||
@@ -87,27 +85,19 @@ In the next step you require a callback URL, which looks like this:
|
||||
- Enter your `client_id` and `client_secret` saved in the previous step
|
||||
- Click `Save`
|
||||
|
||||
### Add login code to your client app
|
||||
## Add login code to your client app
|
||||
|
||||
The JavaScript client code is documented here: [Supabase OAuth Client Code](/docs/reference/javascript/auth-signin#sign-in-using-third-party-providers)
|
||||
|
||||
```js
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
provider: 'twitch',
|
||||
})
|
||||
```
|
||||
|
||||
Add this function which you can call from a button, link, or UI element.
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `twitch` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithTwitch() {
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||
provider: 'twitch',
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
To log out:
|
||||
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
|
||||
|
||||
```js
|
||||
async function signout() {
|
||||
|
||||
@@ -17,16 +17,14 @@ Setting up Twitter logins for your application consists of 3 parts:
|
||||
- Add your Twitter `API Key` and `API Secret Key` to your [Supabase Project](https://app.supabase.com).
|
||||
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js).
|
||||
|
||||
## Steps
|
||||
|
||||
### Access your Twitter Developer account
|
||||
## Access your Twitter Developer account
|
||||
|
||||
- Go to [developer.twitter.com](https://developer.twitter.com).
|
||||
- Click on `Sign in` at the top right to log in.
|
||||
|
||||

|
||||
|
||||
### Find your callback URL
|
||||
## Find your callback URL
|
||||
|
||||
The next step requires a callback URL, which looks like this:
|
||||
|
||||
@@ -47,7 +45,7 @@ The next step requires a callback URL, which looks like this:
|
||||
/>
|
||||
</video>
|
||||
|
||||
### Create a Twitter OAuth app
|
||||
## Create a Twitter OAuth app
|
||||
|
||||
- Click `+ Create Project`.
|
||||
- Enter your project name, click `Next`.
|
||||
@@ -67,7 +65,7 @@ The next step requires a callback URL, which looks like this:
|
||||
- Enter your `Privacy policy URL`.
|
||||
- Click `Save`.
|
||||
|
||||
### Enter your Twitter credentials into your Supabase Project
|
||||
## Enter your Twitter credentials into your Supabase Project
|
||||
|
||||
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
|
||||
- In the left sidebar, click the `Authentication` icon (near the top).
|
||||
@@ -77,27 +75,19 @@ The next step requires a callback URL, which looks like this:
|
||||
- Enter your `API Key` (`client_id`) and `API Secret Key` (`client_secret`) saved in the previous step.
|
||||
- Click `Save`.
|
||||
|
||||
### Add login code to your client app
|
||||
## Add login code to your client app
|
||||
|
||||
The JavaScript client code is documented here: [Supabase OAuth Client Code](/docs/reference/javascript/auth-signin#sign-in-using-third-party-providers).
|
||||
|
||||
```js
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
provider: 'twitter',
|
||||
})
|
||||
```
|
||||
|
||||
Add this function which you can call from a button, link, or UI element.
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `twitter` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithTwitter() {
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||
provider: 'twitter',
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
To log out:
|
||||
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
|
||||
|
||||
```js
|
||||
async function signout() {
|
||||
|
||||
@@ -163,7 +163,7 @@ values={[
|
||||
<TabItem value="JS">
|
||||
|
||||
```js
|
||||
let { user, error } = await supabase.auth.signIn({
|
||||
let { user, error } = await supabase.auth.signInWithPassword({
|
||||
phone: '491512223334444',
|
||||
password: 'some-password',
|
||||
})
|
||||
@@ -200,7 +200,7 @@ values={[
|
||||
<TabItem value="JS">
|
||||
|
||||
```js
|
||||
let { user, error } = await supabase.auth.signIn({
|
||||
let { user, error } = await supabase.auth.signInWithOtp({
|
||||
phone: '491512223334444',
|
||||
})
|
||||
```
|
||||
|
||||
@@ -21,27 +21,25 @@ The procedure consists of five broad steps:
|
||||
- Connect a WorkOS Supported Identity Provider
|
||||
- Add your WorkOS credentials into your Supabase project
|
||||
|
||||
## Steps
|
||||
|
||||
### Create a WorkOS Organization
|
||||
## Create a WorkOS Organization
|
||||
|
||||
Log in to the dashboard and hop over to the Organizations tab to create and organization
|
||||

|
||||
|
||||
### Obtain the Client ID and configure Redirect URI
|
||||
## Obtain the Client ID and configure Redirect URI
|
||||
|
||||
Head over to the Configuration tab and configure the redirect URI.The redirect URI should look like `https://<project-ref>.supabase.co/auth/v1/callback`
|
||||
Note that this is distinct from the redirect URI referred to in the Supabase dashboard
|
||||
|
||||

|
||||
|
||||
### Obtain the WorkOS Secret
|
||||
## Obtain the WorkOS Secret
|
||||
|
||||
Head over to the API Keys page and obtain the secret key.
|
||||
|
||||

|
||||
|
||||
### Connect a WorkOS Supported Identity Provider
|
||||
## Connect a WorkOS Supported Identity Provider
|
||||
|
||||
Set up the identity provider by visiting the setup link.
|
||||
|
||||
@@ -49,7 +47,7 @@ Set up the identity provider by visiting the setup link.
|
||||
|
||||
You can pick between any one of the many identity providers that WorkOS supports.
|
||||
|
||||
### Add your WorkOS credentials into your Supabase Project
|
||||
## Add your WorkOS credentials into your Supabase Project
|
||||
|
||||
- Go to your [Supabase Project Dashboard](https://app.supabase.com)
|
||||
- In the left sidebar, click the `Authentication` icon (near the top)
|
||||
@@ -58,42 +56,30 @@ You can pick between any one of the many identity providers that WorkOS supports
|
||||
- Enter the `Client ID`, `Secret`, and `WorkOS URL` saved in the previous steps. The ``WorkOS URL` setting should be set to https://api.workos.com/
|
||||
- Click `Save`
|
||||
|
||||
### Add login code to your client app
|
||||
## Add login code to your client app
|
||||
|
||||
The JavaScript client code is documented in the [Supabase OAuth Reference](/docs/reference/javascript/auth-signin#sign-in-using-third-party-providers). Note that you only need to include one of the three parameters: `connection`, `organization`, and `provider`.
|
||||
You can refer to the [WorkOS Documentation](https://workos.com/docs/reference/sso/authorize/) to learn more about the different methods.
|
||||
|
||||
```js
|
||||
const { user, session, error } = await supabase.auth.signIn(
|
||||
{
|
||||
provider: 'workos',
|
||||
},
|
||||
{
|
||||
connection: '<your_connection>',
|
||||
organization: '<your_organization',
|
||||
provider: '<your_provider>',
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
Add a function which you can call from a button, link, or UI element.
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `workos` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithWorkOS() {
|
||||
const { user, session, error } = await supabase.auth.signIn(
|
||||
const { data, error } = await supabase.auth.signInWithOAuth(
|
||||
{
|
||||
provider: 'workos',
|
||||
options: {
|
||||
queryParams: {
|
||||
connection: '<your_connection>',
|
||||
organization: '<your_organization',
|
||||
provider: '<your_provider>',
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
connection: '<your_connection>',
|
||||
organization: '<your_organization',
|
||||
provider: '<your_provider>',
|
||||
}
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
To log out:
|
||||
Refer to the [WorkOS Documentation](https://workos.com/docs/reference/sso/authorize/) to learn more about the different methods.
|
||||
|
||||
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
|
||||
|
||||
```js
|
||||
async function signout() {
|
||||
|
||||
@@ -17,16 +17,14 @@ Setting up Zoom logins for your application consists of 3 parts:
|
||||
- Add your Zoom OAuth keys to your [Supabase Project](https://app.supabase.com)
|
||||
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js)
|
||||
|
||||
## Steps
|
||||
|
||||
### Access your Zoom Developer account
|
||||
## Access your Zoom Developer account
|
||||
|
||||
- Go to [marketplace.zoom.us](https://marketplace.zoom.us/).
|
||||
- Click on `Sign In` at the top right to log in.
|
||||
|
||||

|
||||
|
||||
### Find your callback URL
|
||||
## Find your callback URL
|
||||
|
||||
The next step requires a callback URL, which looks like this:
|
||||
|
||||
@@ -47,7 +45,7 @@ The next step requires a callback URL, which looks like this:
|
||||
/>
|
||||
</video>
|
||||
|
||||
### Create a Zoom Oauth App
|
||||
## Create a Zoom Oauth App
|
||||
|
||||
- Go to [marketplace.zoom.us](https://marketplace.zoom.us/).
|
||||
- Click on `Sign In` at the top right to log in.
|
||||
@@ -67,7 +65,7 @@ Under `Redirect URL for OAuth`
|
||||
- Paste your `Callback URL`
|
||||
- Click `Continue`
|
||||
|
||||
### Enter your Zoom credentials into your Supabase Project
|
||||
## Enter your Zoom credentials into your Supabase Project
|
||||
|
||||
- Go to your [Supabase Project Dashboard](https://app.supabase.com)
|
||||
- In the left sidebar, click the `Authentication` icon (near the top)
|
||||
@@ -77,27 +75,19 @@ Under `Redirect URL for OAuth`
|
||||
- Enter your `Zoom Client ID` and `Zoom Client Secret` saved in the previous step
|
||||
- Click `Save`
|
||||
|
||||
### Add login code to your client app
|
||||
## Add login code to your client app
|
||||
|
||||
The JavaScript client code is documented here: [Supabase OAuth Client Code](/docs/reference/javascript/auth-signin#sign-in-using-third-party-providers)
|
||||
|
||||
```js
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
provider: 'zoom',
|
||||
})
|
||||
```
|
||||
|
||||
Add this function which you can call from a button, link, or UI element.
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `zoom` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithZoom() {
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||
provider: 'zoom',
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
To log out:
|
||||
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
|
||||
|
||||
```js
|
||||
async function signout() {
|
||||
|
||||
@@ -348,6 +348,24 @@ Once pushed, check that the migration version is up to date for both local and r
|
||||
supabase migration list
|
||||
```
|
||||
|
||||
### Permission denied on db remote commit
|
||||
|
||||
If you have been using Supabase hosted projects for a long time, you might encounter the following permission error when executing `db remote commit`.
|
||||
|
||||
```bash
|
||||
Error: Error running pg_dump on remote database: pg_dump: error: query failed: ERROR: permission denied for table _type
|
||||
|
||||
pg_dump: error: query was: LOCK TABLE "graphql"."_type" IN ACCESS SHARE MODE
|
||||
```
|
||||
|
||||
To resolve this error, you need to grant `postgres` role permissions to `graphql` schema. You can do that by running the following query from Supabase dashboard's SQL Editor.
|
||||
|
||||
```sql
|
||||
grant all on all tables in schema graphql to postgres, anon, authenticated, service_role;
|
||||
grant all on all functions in schema graphql to postgres, anon, authenticated, service_role;
|
||||
grant all on all sequences in schema graphql to postgres, anon, authenticated, service_role;
|
||||
```
|
||||
|
||||
### Permission denied on db push
|
||||
|
||||
If you created a table through Supabase dashboard, and your new migration script contains `ALTER TABLE` statements, you might run into permission error when applying them on staging or production databases.
|
||||
@@ -358,12 +376,14 @@ ERROR: must be owner of table employees (SQLSTATE 42501); while executing migrat
|
||||
|
||||
This is because tables created through Supabase dashboard are owned by `supabase_admin` role while the migration scripts executed through CLI are under `postgres` role.
|
||||
|
||||
One way to solve this is to grant `postgres` role additional privileges through the SQL Editor available on Supabase dashboard. For example, the following command grants postgres permissions to alter any table in the public schema.
|
||||
One way to solve this is to reassign the owner of those tables to `postgres` role. For example, if your table is named `users` in the public schema, you can run the following command to reassign owner.
|
||||
|
||||
```sql
|
||||
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO postgres;
|
||||
ALTER TABLE users OWNER TO postgres;
|
||||
```
|
||||
|
||||
Apart from tables, you also need to reassign owner of other entities using their respective commands, including [types](https://www.postgresql.org/docs/current/sql-alterschema.html), [functions](https://www.postgresql.org/docs/current/sql-alterroutine.html), and [schemas](https://www.postgresql.org/docs/current/sql-altertype.html).
|
||||
|
||||
### Rebasing new migrations
|
||||
|
||||
Sometimes your teammate may merge a new migration file to git main branch, and now you need to rebase your local schema changes on top.
|
||||
|
||||
@@ -110,7 +110,7 @@ http://localhost:54321/auth/v1/ # Auth (GoTrue)
|
||||
:::note
|
||||
To access the database from an edge function in your local Supabase setup, replace `localhost` with `host.docker.internal`.
|
||||
:::
|
||||
|
||||
|
||||
## Database migrations
|
||||
|
||||
Database changes are managed through "migrations." Database migrations are a common way of tracking changes to your database over time.
|
||||
|
||||
@@ -112,7 +112,12 @@ This is the most granular option. Connections are returned to the pool after eve
|
||||
3. Find your Connection Info and Connection String. Connection pooling is on port `6543`.
|
||||
|
||||
<video width="99%" muted playsInline controls="true">
|
||||
<source src="/docs/videos/connection-pool-config.mp4" type="video/mp4" muted playsInline />
|
||||
<source
|
||||
src="/docs/videos/connection-pool-config.mp4"
|
||||
type="video/mp4"
|
||||
muted
|
||||
playsInline
|
||||
/>
|
||||
</video>
|
||||
|
||||
## Connecting with SSL
|
||||
|
||||
@@ -10,27 +10,49 @@ Migrating projects can be achieved using standard PostgreSQL tooling. This is pa
|
||||
|
||||
- Install [Postgres](https://www.postgresql.org/download/) so you can run `psql` and `pg_dump`.
|
||||
- Create a new [Supabase project](https://app.supabase.com).
|
||||
- Enable [Database Webhooks](https://app.supabase.com/project/_/database/hooks) in your new project if you enabled them in your old project.
|
||||
- Store the old project's database URL as `$OLD_DB_URL` and the new project's as `$NEW_DB_URL`.
|
||||
|
||||
## Migrate the database
|
||||
|
||||
In your old project:
|
||||
1. Run `ALTER ROLE postgres SUPERUSER` in the [SQL editor](https://app.supabase.com/project/_/sql).
|
||||
1. Run `pg_dump --clean --if-exists --quote-all-identifiers -h $OLD_DB_URL -U postgres > dump.sql` from your terminal.
|
||||
1. Run `ALTER ROLE postgres NOSUPERUSER` in the [SQL editor](https://app.supabase.com/project/_/sql).
|
||||
1. Enable [Database Webhooks](https://app.supabase.com/project/_/database/hooks) in your new project if you enabled them in your old project.
|
||||
2. In your new project, enable all extensions that were enabled in your old project.
|
||||
3. Run the following command from your terminal:
|
||||
|
||||
In your new project:
|
||||
1. Run `ALTER ROLE postgres SUPERUSER` in the [SQL editor](https://app.supabase.com/project/_/sql).
|
||||
1. Run `psql -h $NEW_DB_URL -U postgres -f dump.sql` from your terminal.
|
||||
1. Run `TRUNCATE storage.objects` in the [SQL editor](https://app.supabase.com/project/_/sql).
|
||||
1. Run `ALTER ROLE postgres NOSUPERUSER` in the [SQL editor](https://app.supabase.com/project/_/sql).
|
||||
```sh
|
||||
set -euo pipefail
|
||||
|
||||
## Migrate storage objects
|
||||
pg_dump \
|
||||
--clean \
|
||||
--if-exists \
|
||||
--quote-all-identifiers \
|
||||
--exclude-table-data 'storage.objects' \
|
||||
--exclude-schema 'extensions|graphql|graphql_public|net|pgbouncer|pgsodium|pgsodium_masks|realtime|supabase_functions|pg_toast|pg_catalog|information_schema' \
|
||||
--schema '*' \
|
||||
--dbname "$OLD_DB_URL" \
|
||||
| sed 's/^DROP SCHEMA IF EXISTS "auth";$/-- DROP SCHEMA IF EXISTS "auth";/' \
|
||||
| sed 's/^DROP SCHEMA IF EXISTS "storage";$/-- DROP SCHEMA IF EXISTS "storage";/' \
|
||||
| sed 's/^CREATE SCHEMA "auth";$/-- CREATE SCHEMA "auth";/' \
|
||||
| sed 's/^CREATE SCHEMA "storage";$/-- CREATE SCHEMA "storage";/' \
|
||||
| sed 's/^ALTER DEFAULT PRIVILEGES FOR ROLE "supabase_admin"/-- ALTER DEFAULT PRIVILEGES FOR ROLE "supabase_admin"/' \
|
||||
> dump.sql
|
||||
|
||||
The new project has the old project's Storage buckets, but the Storage objects need to be migrated manually. Use this script to move storage objects from one project to another. If you have more than 10k objects, we can move the objects for you. Just contact us at [support@supabase.io](mailto:support@supabase.io).
|
||||
psql \
|
||||
--single-transaction \
|
||||
--variable ON_ERROR_STOP=1 \
|
||||
--file dump.sql \
|
||||
--dbname "$NEW_DB_URL"
|
||||
```
|
||||
|
||||
## Enable publication on tables
|
||||
|
||||
Replication for Realtime is disabled for all tables in your new project. On the [Replication](https://app.supabase.com/project/_/database/replication) page in the Dashboard, select your new project and enable replication for tables that were enabled in your old project.
|
||||
|
||||
## Migrate Storage objects
|
||||
|
||||
The new project has the old project's Storage buckets, but the Storage objects need to be migrated manually. Use this script to move storage objects from one project to another. If you have more than 10k objects, we can move the objects for you. Just contact us at [support@supabase.com](mailto:support@supabase.com).
|
||||
|
||||
```js
|
||||
// npm install @supabase/supabase-js@1
|
||||
const { createClient } = require('@supabase/supabase-js')
|
||||
|
||||
const OLD_PROJECT_URL = 'https://xxx.supabase.co'
|
||||
@@ -94,4 +116,3 @@ const NEW_PROJECT_SERVICE_KEY = 'new-project-service-key-yyy'
|
||||
}
|
||||
})()
|
||||
```
|
||||
|
||||
@@ -2,33 +2,11 @@
|
||||
id: functions
|
||||
title: Edge Functions
|
||||
description: 'Globally distributed TypeScript functions.'
|
||||
sidebar_label: Overview
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
import ButtonCard from '@site/src/components/ButtonCard'
|
||||
const examples = [
|
||||
{
|
||||
name: 'With supabase-js',
|
||||
description: 'Use the Supabase client inside your Edge Function.',
|
||||
href: 'https://github.com/supabase/examples/tree/main/supabase-js-v1/edge-functions',
|
||||
},
|
||||
{
|
||||
name: 'With CORS headers',
|
||||
description: 'Send CORS headers for invoking from the browser.',
|
||||
href: 'https://github.com/supabase/examples/tree/main/supabase-js-v1/edge-functions/supabase/functions/browser-with-cors',
|
||||
},
|
||||
{
|
||||
name: 'React Native with Stripe',
|
||||
description: 'Full example for using Supabase and Stripe, with Expo.',
|
||||
href: 'https://github.com/supabase-community/expo-stripe-payments-with-supabase-functions',
|
||||
},
|
||||
{
|
||||
name: 'Flutter with Stripe',
|
||||
description: 'Full example for using Supabase and Stripe, with Flutter.',
|
||||
href: 'https://github.com/supabase-community/flutter-stripe-payments-with-supabase-functions',
|
||||
},
|
||||
]
|
||||
|
||||
Edge Functions are server-side TypeScript functions, distributed globally at the edge—close to your users. They can be used for listening to webhooks or integrating your Supabase project with third-parties [like Stripe](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/stripe-webhooks).
|
||||
|
||||
@@ -89,7 +67,7 @@ The command outputs a URL to the Supabase Dashboard which you can open to find v
|
||||
|
||||
By default, Edge Functions require a valid JWT in the authorization header. This header is automatically set when invoking your function via a Supabase client library.
|
||||
|
||||
If you want to use Edge Functions to handle webhooks (e.g. [Stripe payment webhooks](https://github.com/supabase/examples/tree/main/supabase-js-v1/edge-functions/supabase/functions/stripe-webhooks), or [chat bot webhooks](https://github.com/supabase/examples/tree/main/supabase-js-v1/edge-functions/supabase/functions/telegram-bot) etc.), you need to pass the `--no-verify-jwt` flag when deploying your function.
|
||||
If you want to use Edge Functions to handle webhooks (e.g. [Stripe payment webhooks](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/stripe-webhooks) etc.), you need to pass the `--no-verify-jwt` flag when deploying your function.
|
||||
|
||||
:::
|
||||
|
||||
@@ -234,26 +212,6 @@ To see all the secrets which you have set remotely, use [`supabase secrets list`
|
||||
supabase secrets list
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
You can find a list of useful [Edge Function Examples](https://github.com/supabase/examples/tree/main/supabase-js-v1/edge-functions) in our GitHub repository.
|
||||
|
||||
<div class="container" style={{ padding: 0 }}>
|
||||
<div class="row is-multiline">
|
||||
{examples.map((x) => (
|
||||
<div class="col col--6">
|
||||
<ButtonCard
|
||||
class="card"
|
||||
to={x.href}
|
||||
title={x.name}
|
||||
description={x.description}
|
||||
style={{ height: '100%' }}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
## Suggestions
|
||||
|
||||
### Database Functions vs Edge Functions
|
||||
@@ -289,6 +247,8 @@ We recommend using hyphens to name functions because hyphens are the most URL-fr
|
||||
|
||||
We recommend adding a check to handle [CORS Preflight](https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request) requests in your edge function to be able to invoke the function from browsers.
|
||||
|
||||
See the [example on GitHub](https://github.com/supabase/supabase/blob/master/examples/edge-functions/supabase/functions/browser-with-cors/index.ts).
|
||||
|
||||
```ts
|
||||
export const corsHeaders = {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
@@ -310,5 +270,5 @@ serve(async (req) => {
|
||||
- Cannot write to File System
|
||||
- Edge Functions
|
||||
- Local development - only one function at a time
|
||||
- Supabase Functions only supports `POST` requests.
|
||||
- Supabase Functions do not support HTML responses.
|
||||
- Supabase Functions only supports `POST`, `OPTIONS`, and `GET` requests.
|
||||
- Serving of HTML content is not supported (`GET` requests that return `text/html` will be rewritten to `text/plain`).
|
||||
@@ -0,0 +1,63 @@
|
||||
---
|
||||
id: auth
|
||||
title: Auth
|
||||
description: Supabase Edge Functions and Auth.
|
||||
---
|
||||
|
||||
Edge Functions work seamlessly with [Supabase Auth](/docs/guides/auth), allowing you to identify which user called your function.
|
||||
|
||||
When [invoking](/docs/reference/javascript/invoke) a function with one of the [client libraries](/docs/reference), the logged in user's JWT is automatically attached to the function call and becomes accessible within your function.
|
||||
|
||||
This is important, for example, to identify which customer's credit card should be charged. You can see this concept end-to-end in our [Stripe example app](https://github.com/supabase-community/expo-stripe-payments-with-supabase-functions).
|
||||
|
||||
## Auth Context & RLS
|
||||
|
||||
By creating a supabase client with the auth context from the function, you can do two things:
|
||||
|
||||
1. Get the user object.
|
||||
2. Run queries in the context of the user with [Row Level Security (RLS)](/docs/guides/auth/row-level-security) policies enforced.
|
||||
|
||||
```js title="supabase/functions/select-from-table-with-auth-rls/index.ts"
|
||||
import { serve } from 'https://deno.land/std@0.131.0/http/server.ts'
|
||||
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.0.0-rc.12'
|
||||
|
||||
serve(async (req: Request) => {
|
||||
try {
|
||||
// Create a Supabase client with the Auth context of the logged in user.
|
||||
const supabaseClient = createClient(
|
||||
// Supabase API URL - env var exported by default.
|
||||
Deno.env.get('SUPABASE_URL') ?? '',
|
||||
// Supabase API ANON KEY - env var exported by default.
|
||||
Deno.env.get('SUPABASE_ANON_KEY') ?? '',
|
||||
// Create client with Auth context of the user that called the function.
|
||||
// This way your row-level-security (RLS) policies are applied.
|
||||
// highlight-next-line
|
||||
{ global: { headers: { Authorization: req.headers.get('Authorization')! } } }
|
||||
)
|
||||
// Now we can get the session or user object
|
||||
// highlight-start
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabaseClient.auth.getUser()
|
||||
// highlight-end
|
||||
|
||||
// And we can run queries in the context of our authenticated user
|
||||
// highlight-start
|
||||
const { data, error } = await supabaseClient.from('users').select('*')
|
||||
if (error) throw error
|
||||
// highlight-end
|
||||
|
||||
return new Response(JSON.stringify({ user, data }), {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
status: 200,
|
||||
})
|
||||
} catch (error) {
|
||||
return new Response(JSON.stringify({ error: error.message }), {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
status: 400,
|
||||
})
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
See the [example on GitHub](https://github.com/supabase/supabase/blob/master/examples/edge-functions/supabase/functions/select-from-table-with-auth-rls/index.ts).
|
||||
@@ -0,0 +1,36 @@
|
||||
---
|
||||
id: cicd-workflow
|
||||
title: CI / CD Workflow
|
||||
description: How to deploy Supabase Edge Functions with a CI / CD pipeline.
|
||||
---
|
||||
|
||||
As described in the Supabase CLI [CI / CD Workflow documentation](/docs/guides/cli/cicd-workflow), you can use the [`setup-cli` GitHub Action](https://github.com/marketplace/actions/supabase-cli-action) to run Supabase CLI commands in your GitHub Actions, for example to deploy a Supabase Edge Function:
|
||||
|
||||
```yaml
|
||||
name: Deploy Function
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
|
||||
PROJECT_ID: zdtdtxajzydjqzuktnqx
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: supabase/setup-cli@v1
|
||||
with:
|
||||
version: 1.0.0
|
||||
|
||||
- run: supabase functions deploy your-function-name --project-ref $PROJECT_ID
|
||||
```
|
||||
|
||||
See the [example on GitHub](https://github.com/supabase/supabase/blob/master/examples/edge-functions/.github/workflows/deploy.yaml).
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
id: examples
|
||||
title: Examples
|
||||
description: Useful Supabase Edge Functions Examples.
|
||||
---
|
||||
|
||||
import ButtonCard from '@site/src/components/ButtonCard'
|
||||
const examples = [
|
||||
{
|
||||
name: 'With supabase-js',
|
||||
description: 'Use the Supabase client inside your Edge Function.',
|
||||
href: 'https://github.com/supabase/supabase/blob/master/examples/edge-functions/supabase/functions/select-from-table-with-auth-rls/index.ts',
|
||||
},
|
||||
{
|
||||
name: 'With CORS headers',
|
||||
description: 'Send CORS headers for invoking from the browser.',
|
||||
href: 'https://github.com/supabase/supabase/blob/master/examples/edge-functions/supabase/functions/browser-with-cors/index.ts',
|
||||
},
|
||||
{
|
||||
name: 'React Native with Stripe',
|
||||
description: 'Full example for using Supabase and Stripe, with Expo.',
|
||||
href: 'https://github.com/supabase-community/expo-stripe-payments-with-supabase-functions',
|
||||
},
|
||||
{
|
||||
name: 'Flutter with Stripe',
|
||||
description: 'Full example for using Supabase and Stripe, with Flutter.',
|
||||
href: 'https://github.com/supabase-community/flutter-stripe-payments-with-supabase-functions',
|
||||
},
|
||||
]
|
||||
|
||||
You can find a list of useful [Edge Function Examples](https://github.com/supabase/supabase/tree/master/examples/edge-functions) in our GitHub repository.
|
||||
|
||||
<div class="container" style={{ padding: 0 }}>
|
||||
<div class="row is-multiline">
|
||||
{examples.map((x) => (
|
||||
<div class="col col--6">
|
||||
<ButtonCard
|
||||
class="card"
|
||||
to={x.href}
|
||||
title={x.name}
|
||||
description={x.description}
|
||||
style={{ height: '100%' }}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
@@ -29,17 +29,22 @@ Organizations are a way to group your projects. Each organization can be configu
|
||||
You can invite your team members into your organizations to collaborate on projects.
|
||||
|
||||
<video width="99%" muted playsInline controls="true">
|
||||
<source src="/docs/videos/invite-team.mp4" type="video/mp4" muted playsInline />
|
||||
<source
|
||||
src="/docs/videos/invite-team.mp4"
|
||||
type="video/mp4"
|
||||
muted
|
||||
playsInline
|
||||
/>
|
||||
</video>
|
||||
|
||||
You can also assign roles to your team members with different access levels. The table below shows the corresponding permissions for each available role in the Dashboard.
|
||||
|
||||
| Permissions | Owner | Administrator | Developer |
|
||||
| ------------------------ | ----- | ------------- | --------- |
|
||||
| **Organization** |
|
||||
| **Organization** |
|
||||
| Change organization name | ✅ | | |
|
||||
| Delete organization | ✅ | | |
|
||||
| **Members** |
|
||||
| **Members** |
|
||||
| Add an Owner | ✅ | | |
|
||||
| Remove an Owner | ✅ | | |
|
||||
| Add an Adminstrator | ✅ | ✅ | |
|
||||
@@ -48,7 +53,7 @@ You can also assign roles to your team members with different access levels. The
|
||||
| Remove a Developer | ✅ | ✅ | |
|
||||
| Revoke an invite | ✅ | ✅ | |
|
||||
| Resend an invite | ✅ | ✅ | |
|
||||
| **Billing** |
|
||||
| **Billing** |
|
||||
| Read invoices | ✅ | ✅ | ✅ |
|
||||
| Read billing email | ✅ | ✅ | ✅ |
|
||||
| Change billing email | ✅ | | |
|
||||
@@ -60,7 +65,7 @@ You can also assign roles to your team members with different access levels. The
|
||||
| Update tax codes | ✅ | ✅ | |
|
||||
| Read payment methods | ✅ | ✅ | ✅ |
|
||||
| Update payment methods | ✅ | ✅ | |
|
||||
| **Projects** |
|
||||
| **Projects** |
|
||||
| Create a project | ✅ | ✅ | |
|
||||
| Delete a project | ✅ | ✅ | |
|
||||
| Update a project | ✅ | ✅ | |
|
||||
|
||||
@@ -346,7 +346,7 @@ export default function HomePage({ user, isEnrolled }: Props) {
|
||||
<main>
|
||||
<section>
|
||||
<div> Signed in as: {user?.email}</div>
|
||||
<button
|
||||
<button
|
||||
onClick={async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -540,6 +540,7 @@ export default withApiAuth(async function mfa(
|
||||
res.send({ mfaUrl });
|
||||
});
|
||||
```
|
||||
|
||||
Because the user should be authenticated with Supabase to set up or manage MFA, we can use Supabase's `withApiAuth` wrapper to protect this route.
|
||||
|
||||
The `redirectToSettings` param specifies whether the user should be redirected to the MFA page settings panel after a challenge, rather than redirecting them immediately back to the application.
|
||||
|
||||
@@ -1,294 +1,99 @@
|
||||
---
|
||||
id: dashibase
|
||||
title: 'Dashibase'
|
||||
description: 'Create a CRUD app with Supabase and Dashibase in 15 mins.'
|
||||
description: 'Create an internal CRUD dashboard with Supabase and Dashibase in 15 mins.'
|
||||
---
|
||||
|
||||
import ThemedImage from '@theme/ThemedImage'
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl'
|
||||
Are you tired of building admin panels? [Dashibase](https://dashibase.com/) is a super fast way to build internal CRUD dashboards with a Notion-like UI.
|
||||
|
||||
Let’s build an app together!
|
||||
This guide walks you through how to build a customer admin panel using Supabase on the backend and Dashibase on the front so that you and your team can easily find and update customer information.
|
||||
|
||||
This guide walks you through how to build a CRM for superheroes using Supabase on the backend and Dashibase on the front.
|
||||
## Quick explanation
|
||||
|
||||
[Dashibase](https://dashibase.com/) is a lightning-fast way to build your app dashboard so that your end users can manage their data. You get a full dashboard UI right out of the box. Not boilerplate code. Not drag-and-drop. Not individual components.
|
||||
Just so we are on the same page, here are some of the terms we use:
|
||||
|
||||
## Step 1. Write down your idea
|
||||
- **Data source:** This is the data that powers your dashboard (i.e. your Supabase database).
|
||||
- **Dashboard:** This is the friendly interface that you build on top of your Supabase database using Dashibase.
|
||||
- **Page:** Each dashboard can have multiple pages. Each page can have multiple blocks of text or tables.
|
||||
|
||||
Let’s say we want to build a CRM for Marvel Cinematic Universe. With the multiverse, the superheroes would need a CRM to stay in touch.
|
||||
Now that is out of the way, let's build our customer admin panel.
|
||||
|
||||
Here is the information we want our users to store in our app:
|
||||
(Start your stopwatch!)
|
||||
|
||||
1. Name
|
||||
2. Universe
|
||||
3. Email
|
||||
4. Last contacted date
|
||||
5. Relationship: Never met, Acquaintance, Close
|
||||
6. Can he/she fly?
|
||||
7. History
|
||||
## Step 1. Connect Supabase to Dashibase
|
||||
|
||||
## Step 2. Set up your Supabase project
|
||||
First, in Dashibase, go to “Data Sources” via the left sidebar and click on “Connect data source” in the upper-right corner.
|
||||
|
||||
First, sign up for a Supabase account and create a new project. We are naming our project MultiverseCRM.
|
||||
Note: We do not download or store any of your database data.
|
||||
|
||||
<ThemedImage
|
||||
alt="Create Supabase project"
|
||||
sources={{
|
||||
light: useBaseUrl(
|
||||
'/img/guides/integrations/dashibase/create-supabase-project-light.png'
|
||||
),
|
||||
dark: useBaseUrl(
|
||||
'/img/guides/integrations/dashibase/create-supabase-project-dark.png'
|
||||
),
|
||||
}}
|
||||
style={{ marginBottom: 20 }}
|
||||
/>
|
||||

|
||||
|
||||
Next, set up the table for our data. Our table is called ‘contacts'.
|
||||
Enter your database credentials, which you can find in Supabase under Settings > Database > Connection info. For additional security, you can also download your SSL certificate and upload it to Dashibase.
|
||||
|
||||
To store the various information we want, we need to set up the following columns:
|
||||

|
||||
|
||||
1. Name (text)
|
||||
2. Universe (int4)
|
||||
3. Email (text)
|
||||
4. Last contacted date (date)
|
||||
5. Relationship: Never met, Acquaintance, Close (text)
|
||||
6. Can he/she fly? (boolean)
|
||||
7. History (text)
|
||||
## Step 2. Set up your data access policy
|
||||
|
||||
We also need a `user` column, which is a [foreign key](https://supabase.com/docs/guides/database/tables#joining-tables-with-foreign-keys) to the auth.users table so that we can keep track of which user these contacts belong to. To do that, add another column, name it ‘user’, and click on the chainlink button to add the foreign key relation. Then, select ‘users’ and ‘id’.
|
||||
Once you have connected your database, you can also control what your team can see and edit in your database via Dashibase. There are usually certain data that you don’t want others to edit or even see (e.g. address, email, identification number).
|
||||
|
||||
<ThemedImage
|
||||
alt="Create user column in Supabase"
|
||||
sources={{
|
||||
light: useBaseUrl(
|
||||
'/img/guides/integrations/dashibase/supabase-user-column-light.png'
|
||||
),
|
||||
dark: useBaseUrl(
|
||||
'/img/guides/integrations/dashibase/supabase-user-column-dark.png'
|
||||
),
|
||||
}}
|
||||
style={{ marginBottom: 20 }}
|
||||
/>
|
||||
Note: If you prefer to restrict access to your database _before_ you connect to Dashibase for security reasons, you can [create a restricted user](https://aws.amazon.com/blogs/database/managing-postgresql-users-and-roles/) via [Supabase's SQL Editor](https://supabase.com/docs/guides/database#the-sql-editor). The slight advantage of doing this in Dashibase is that there is a GUI, which makes it easier to set up the access control (but we understand some developers might find it more secure to restrict the access themselves).
|
||||
|
||||
This is what the columns should look like:
|
||||

|
||||
|
||||
<ThemedImage
|
||||
alt="Columns in our table in Supabase"
|
||||
sources={{
|
||||
light: useBaseUrl(
|
||||
'/img/guides/integrations/dashibase/supabase-columns-light.png'
|
||||
),
|
||||
dark: useBaseUrl(
|
||||
'/img/guides/integrations/dashibase/supabase-columns-dark.png'
|
||||
),
|
||||
}}
|
||||
style={{ marginBottom: 20 }}
|
||||
/>
|
||||
You can even control which columns within a table that others can see and edit. In the example below, for the `tags` table, the `name` column can be read and updated, the `created_at` column can be read, and the `id` column cannot even be read.
|
||||
|
||||
Congrats! You have set up the backend of your app.
|
||||

|
||||
|
||||
## Step 3. Set up your frontend with Dashibase
|
||||
Once you are happy with the data access control, you can decide who in your organization can edit this data source (usually other developers) or use this data source to create dashboards.
|
||||
|
||||
Let’s hop over to [Dashibase](https://dashibase.com/).
|
||||

|
||||
|
||||
When you log in to your new account, you will see an empty dashboard.
|
||||
## Step 3. Build your dashboard
|
||||
|
||||
<ThemedImage
|
||||
alt="New Dashibase account"
|
||||
sources={{
|
||||
light: useBaseUrl(
|
||||
'/img/guides/integrations/dashibase/dashibase-empty-light.png'
|
||||
),
|
||||
dark: useBaseUrl(
|
||||
'/img/guides/integrations/dashibase/dashibase-empty-dark.png'
|
||||
),
|
||||
}}
|
||||
style={{ marginBottom: 20 }}
|
||||
/>
|
||||
To create a dashboard, go to “Dashboards” via the left sidebar in Dashibase and click on “Create dashboard” in the upper-right corner.
|
||||
|
||||
The first things to do are to name our app, pick a URL, and connect our Supabase project. You can find your Supabase URL and Anon Key in your Supabase project > Settings > API.
|
||||
Here’s where the fun begins. There are many ways to build your dashboards in Dashibase using features such as markdown formatting and slash command.
|
||||
|
||||
<ThemedImage
|
||||
alt="Set your dashibase settings"
|
||||
sources={{
|
||||
light: useBaseUrl(
|
||||
'/img/guides/integrations/dashibase/dashibase-dashboard-settings-light.png'
|
||||
),
|
||||
dark: useBaseUrl(
|
||||
'/img/guides/integrations/dashibase/dashibase-dashboard-settings-dark.png'
|
||||
),
|
||||
}}
|
||||
style={{ marginBottom: 20 }}
|
||||
/>
|
||||
You will see some automatically-generated content in your dashboard so that you can set up your dashboard more quickly. For this example, let's clear them out and start from scratch.
|
||||
|
||||
Connecting your Supabase project to Dashibase is as simple as that.
|
||||

|
||||
|
||||
Next, let’s create a page where our users can view, add, and edit their contacts. Simply click on “Add page”.
|
||||
Using our Notion-like UI, you can quickly build out your dashboard through typing. For example, you can type '/table' to add a table from your database to your dashboard. No more dragging components across your screen and dropping them on a blank canvas repeatedly.
|
||||
|
||||
Here are the settings we use for our Contacts page:
|
||||

|
||||
|
||||
- Name: Contacts
|
||||
- Page URL: /contacts
|
||||
- Supabase table: contacts (This is automatically pulled from your Supabase project. If you have multiple tables, you can select the relevant one.)
|
||||
- Display: list (We will display our contacts in a list for now.)
|
||||
- Read-only: false (Our users should be able to edit their contacts.)
|
||||
- ID column: id (This is the ID of each contact in our database. If you are not using ‘id’ as your ID column, feel free to change this.)
|
||||
- User column: user (This is the `user` column we created earlier.)
|
||||
If you have set up [foreign keys in your Supabase database](https://supabase.com/docs/guides/database/tables#joining-tables-with-foreign-keys), you can easily show data from multiple tables on the table in your Dashibase dashboard. Simply click on the "Columns" button beside the "Filter" button and select the columns you want to show or hide. For this example, we want to show the customers' country and plan price, which are in separate `countries` and `plans` tables.
|
||||
|
||||
<ThemedImage
|
||||
alt="Set your page settings"
|
||||
sources={{
|
||||
light: useBaseUrl(
|
||||
'/img/guides/integrations/dashibase/dashibase-page-settings-light.png'
|
||||
),
|
||||
dark: useBaseUrl(
|
||||
'/img/guides/integrations/dashibase/dashibase-page-settings-dark.png'
|
||||
),
|
||||
}}
|
||||
style={{ marginBottom: 20 }}
|
||||
/>
|
||||

|
||||
|
||||
Then, we will add the attributes for each contact so that our users can view or edit them:
|
||||
For all the tables in your dashboard, you can rename the column headers, add filter and sort, adjust the column width, and more.
|
||||
|
||||
1. Name (text)
|
||||
2. Universe (text)
|
||||
3. Email (text)
|
||||
4. Last contacted date (date)
|
||||
5. Relationship: Never met, Acquaintance, Close (single select)
|
||||
6. Can he/she fly? (boolean)
|
||||
7. History (long text)
|
||||
You can also add text to provide context or instructions for the rest of your team as they are using your dashboard. This saves you the trouble of having your dashboard and documentation in separate places.
|
||||
|
||||
<ThemedImage
|
||||
alt="Add your attributes"
|
||||
sources={{
|
||||
light: useBaseUrl(
|
||||
'/img/guides/integrations/dashibase/dashibase-attributes-light.png'
|
||||
),
|
||||
dark: useBaseUrl(
|
||||
'/img/guides/integrations/dashibase/dashibase-attributes-dark.png'
|
||||
),
|
||||
}}
|
||||
style={{ marginBottom: 20 }}
|
||||
/>
|
||||

|
||||
|
||||
When you use the 'Single Select' type, you will get to add the options you want.
|
||||
Most importantly, we automatically generate a single-item view for each of the items on your table so that you do not have to manually create it yourself. You can easily edit or rearrange the blocks just like in Notion.
|
||||
|
||||
<ThemedImage
|
||||
alt="Add your Single Select options"
|
||||
sources={{
|
||||
light: useBaseUrl(
|
||||
'/img/guides/integrations/dashibase/dashibase-attributes-options-light.png'
|
||||
),
|
||||
dark: useBaseUrl(
|
||||
'/img/guides/integrations/dashibase/dashibase-attributes-options-dark.png'
|
||||
),
|
||||
}}
|
||||
style={{ marginBottom: 20 }}
|
||||
/>
|
||||

|
||||
|
||||
Let’s customize our contacts a little. We will set Name, Universe, and Email as required fields.
|
||||
## Step 4. Share your dashboard with your team
|
||||
|
||||
<ThemedImage
|
||||
alt="Set attributes as required"
|
||||
sources={{
|
||||
light: useBaseUrl(
|
||||
'/img/guides/integrations/dashibase/dashibase-attributes-required-light.png'
|
||||
),
|
||||
dark: useBaseUrl(
|
||||
'/img/guides/integrations/dashibase/dashibase-attributes-required-dark.png'
|
||||
),
|
||||
}}
|
||||
style={{ marginBottom: 20 }}
|
||||
/>
|
||||
Finally, save and share the dashboard with your team. There are two levels of access:
|
||||
|
||||
Then, we will hide History from the list because it can get too long. Our users will still be able to view a contact’s history by clicking on the contact in the dashboard.
|
||||
- Edit dashboard: Edit the text blocks, edit the tables, etc.
|
||||
- Use dashboard: View and update the data via the dashboard
|
||||
|
||||
<ThemedImage
|
||||
alt="Hide certain attributes"
|
||||
sources={{
|
||||
light: useBaseUrl(
|
||||
'/img/guides/integrations/dashibase/dashibase-attributes-hide-light.png'
|
||||
),
|
||||
dark: useBaseUrl(
|
||||
'/img/guides/integrations/dashibase/dashibase-attributes-hide-dark.png'
|
||||
),
|
||||
}}
|
||||
style={{ marginBottom: 20 }}
|
||||
/>
|
||||
Click on the “Share” button in the upper-right corner of your dashboard to share access:
|
||||
|
||||
Finally, we will try the card display (under the page settings).
|
||||

|
||||
|
||||
<ThemedImage
|
||||
alt="Set display as card"
|
||||
sources={{
|
||||
light: useBaseUrl(
|
||||
'/img/guides/integrations/dashibase/dashibase-display-card-light.png'
|
||||
),
|
||||
dark: useBaseUrl(
|
||||
'/img/guides/integrations/dashibase/dashibase-display-card-dark.png'
|
||||
),
|
||||
}}
|
||||
style={{ marginBottom: 20 }}
|
||||
/>
|
||||
Now your team can use this customer admin panel to find and update customer information easily!
|
||||
|
||||
The cards look pretty good in the preview. We will use that for now.
|
||||
|
||||
**Once done, just hit “Save” and “Deploy”. Your app is now live!**
|
||||
|
||||
One quick thing: You want to add your app URL to your Supabase project > Settings > Authentication > Additional redirect URLs. This redirects your users to your app when they sign up.
|
||||
|
||||
<ThemedImage
|
||||
alt="Add app URL to Supabase redirect URLs"
|
||||
sources={{
|
||||
light: useBaseUrl(
|
||||
'/img/guides/integrations/dashibase/supabase-redirect-light.png'
|
||||
),
|
||||
dark: useBaseUrl(
|
||||
'/img/guides/integrations/dashibase/supabase-redirect-dark.png'
|
||||
),
|
||||
}}
|
||||
style={{ marginBottom: 20 }}
|
||||
/>
|
||||
|
||||
## Step 4: Share your app
|
||||
|
||||
Your app comes with signup and login pages right out of the box. You don’t have to spend time building that at all.
|
||||
|
||||
<ThemedImage
|
||||
alt="Your dashboard login page"
|
||||
sources={{
|
||||
light: useBaseUrl(
|
||||
'/img/guides/integrations/dashibase/dashibase-dashboard-login-light.png'
|
||||
),
|
||||
dark: useBaseUrl(
|
||||
'/img/guides/integrations/dashibase/dashibase-dashboard-login-dark.png'
|
||||
),
|
||||
}}
|
||||
style={{ marginBottom: 20 }}
|
||||
/>
|
||||
|
||||
When you are ready, share your app with your family, friends, and followers!
|
||||
|
||||
P.S. You can check out our app at [https://multiversecrm.app.dashibase.com](https://multiversecrm.app.dashibase.com/). You can add new contacts and edit or delete existing contacts. You can also filter or sort to look for certain contacts.
|
||||
|
||||
<ThemedImage
|
||||
alt="Our MultiverseCRM"
|
||||
sources={{
|
||||
light: useBaseUrl(
|
||||
'/img/guides/integrations/dashibase/dashibase-dashboard-light.png'
|
||||
),
|
||||
dark: useBaseUrl(
|
||||
'/img/guides/integrations/dashibase/dashibase-dashboard-dark.png'
|
||||
),
|
||||
}}
|
||||
style={{ marginBottom: 20 }}
|
||||
/>
|
||||
|
||||
Fun fact: Both Dashibase and apps created with Dashibase have light and dark modes out of the box. Toggle the light/dark mode for this page to see them!
|
||||
P.S. How long did we take? :)
|
||||
|
||||
## Resources
|
||||
|
||||
- [Dashibase website](https://dashibase.com/)
|
||||
- [Dashibase open-source repo](https://github.com/dashibase/dashibase)
|
||||
- [Dashibase open-source repo](https://github.com/dashibase)
|
||||
- [Dashibase Twitter](https://twitter.com/dashibase)
|
||||
- [Dashibase Discord](https://discord.gg/crhDFYvbbq)
|
||||
@@ -0,0 +1,175 @@
|
||||
---
|
||||
id: dhiwise
|
||||
title: 'DhiWise'
|
||||
description: 'Get started with Supabase and DhiWise. Convert your Figma designs into Flutter apps, store data, and authenticate your users'
|
||||
---
|
||||
import select from "/img/guides/integrations/dhiwise/select.png"
|
||||
import create from "/img/guides/integrations/dhiwise/create.png"
|
||||
|
||||
This guide explains how to connect Supabase backend to DhiWise Flutter application quickly.
|
||||
|
||||
[DhiWise](https://www.dhiwise.com/) is a Developer tool to convert Figma designs into React and Flutter applications. It lets you quickly integrate Databases and APIs into your React and Flutter Apps.
|
||||
|
||||
If you don't have a DhiWise account, create one [here](https://app.dhiwise.com).
|
||||
|
||||
DhiWise supports easy Supabase Integration in just five steps.
|
||||
|
||||
Let's get started!
|
||||
|
||||
## Step 1: SignIn to Supabase
|
||||
|
||||
Go to [Supabase](https://supabase.com/), Click `Sign In`, and create a new account by authenticating with **GitHub**. If you already have an account, you will be logged in.
|
||||
|
||||
## Step 2: Create a new project in Supabase
|
||||
|
||||
Click on `New project` from the Dashboard and select an organization. If you don't have an organization, create one using `+ New organization.`
|
||||
|
||||
- Give your Supabase project a `name.`
|
||||
- Enter a secure `Database Password.`
|
||||
- Choose the `region` where your app's backend is hosted.
|
||||
- Click `Create new project.`
|
||||
|
||||

|
||||
|
||||
## Step 3: Find the API key and URL
|
||||
|
||||
Once your project is created, you can access the API Key and URL string, Or if you already have an account go to your `organization-> app-> settings-> API`.
|
||||
|
||||

|
||||
|
||||
## Step 4: Integrations
|
||||
|
||||
There are two ways you can integrate Supabase into your DhiWise Flutter applications.
|
||||
|
||||
### Authentication
|
||||
|
||||
You can integrate `Supabase Email/Password SignUp` or `Supabase Email/Password SignIn` on your components.
|
||||
|
||||
- Open the screen of your flutter application
|
||||
- Go to the component on which you want to add authentication
|
||||
- on the `onClick` method - select `authentication`
|
||||
- From the list, If you want SignUp - select `SignUp with Email/Password`; otherwise, select `SignIn with Email/Password` from Supabase Auth section
|
||||
|
||||

|
||||
|
||||
And that's it. Supabase authentication will be added to the selected component.
|
||||
|
||||
After downloading the application source code,
|
||||
1. Add Supabase URL and Supabase public key inside ***lib/core/utils/initial_bindings*** file.
|
||||
2. For additional details, refer ***https://supabase.com/docs/guides/with-flutter***
|
||||
|
||||
### Working with Data
|
||||
|
||||
When you first integrate Supabase in your DhiWise Flutter application, You will be asked to add [Supabase auth key and URL](##step-3-find-the-api-key-and-url). When you add them, all the tables available in your Supabase project will be synced in DhiWise. You can integrate Select and Create queries on your Flutter screen for a particular table in DhiWise.
|
||||
|
||||
<h3> Select records </h3>
|
||||
|
||||
#### **Step 1:**
|
||||
Select the screen from the screen list where you want to integrate Supabase.
|
||||
|
||||
#### **Step 2:**
|
||||
Next, go to the view where you want to add Integration, and from the suggestion box for the `onClick` property, choose `Supabase integration,` which will take you to the Integration screen. Where you will be asked to `Enter function name.` Enter the name of your function and click `Submit.`
|
||||
|
||||
#### **Step 3:**
|
||||
After submitting the function name, you will be asked to select a type of Supabase integration. To retrieve data from Supabase, choose `select.`
|
||||
|
||||
#### **Step 4:**
|
||||
Next, select the table from which you want to fetch records from the listed Tables.
|
||||
|
||||
#### **Step 5:**
|
||||
Select the type of integration
|
||||
|
||||
| Type | Description |
|
||||
| --- | --- |
|
||||
| **Single** | Used to fetch a single record from the database. |
|
||||
| **Multiple** | Used to fetch multiple records from the database. |
|
||||
|
||||
:::info
|
||||
For Multiple types, you need to set `data limit,` `order by, and `order.`
|
||||
:::
|
||||
|
||||
#### **Step 6:**
|
||||
You will be redirected to the API Integration screen, where you can set request and response.
|
||||
|
||||
For request binding, the below types are supported. Also, Select the operator for comparison before moving forward.
|
||||
|
||||
| Type | Description |
|
||||
| --- | --- |
|
||||
| **View** | Select any component from your screen. |
|
||||
| **Constant** | Select a constant you've created in your app. |
|
||||
| **Get from preference** | Select the key you want to fetch from preference. |
|
||||
| **Navigation argument** | Select data that's been passed from one screen to another. |
|
||||
|
||||
For response binding, the below types are supported.
|
||||
|
||||
| Type | Description |
|
||||
| --- | --- |
|
||||
| **View** | Select any component from the screen. |
|
||||
| **Save to preference** | Storing the data to preference. |
|
||||
|
||||
#### **Select 7:**
|
||||
`Handle action` - Select the action you wish to take once the Supabase call has either been accepted successfully or refused due to an error.
|
||||
|
||||
Available action for On success and On error are,
|
||||
|
||||
1. [Show Alert](https://docs.dhiwise.com/docs/flutter/show-alert)
|
||||
2. [Navigation](https://docs.dhiwise.com/docs/flutter/navigation)
|
||||
|
||||
#### **Step 8:**
|
||||
Finally, you have added Supabase to your application to fetch records on your screen!
|
||||
|
||||
:::tip Example
|
||||
Suppose you want to fetch records from Supabase and populate the item list on your screen. You can integrate Supabase as discussed above and bind the response with your list view.
|
||||
:::
|
||||
|
||||
<h3> Create records </h3>
|
||||
|
||||
#### **Step 1:**
|
||||
Choose the screen you wish to integrate Supabase for from the list of screens.
|
||||
|
||||
#### **Step 2:**
|
||||
Next, switch to the component you want to add Integration, and on the `onClick` property, choose `Supabase integration,` which will take you to its integration screen, where you will be asked to **Enter function name**, which will be used in generated code. Enter the name for it and click `Submit`
|
||||
|
||||
#### **Step 3:**
|
||||
After submitting the function name, you will be asked to select a type of Supabase integration. For example, to create a record in Supabase, choose `Create.`
|
||||
|
||||
#### **Step 4:**
|
||||
Next, select the table where you want to create a record from the listed Tables.
|
||||
|
||||
#### **Step 5:**
|
||||
If you want to create a Single record, Select **Select**. Otherwise, **Multiple**.
|
||||
|
||||
#### **Step 6:**
|
||||
Now, you will be redirected to the API Integration screen, where you can set request and response.
|
||||
|
||||
For request binding, the below types are supported.
|
||||
|
||||
| Type | Description |
|
||||
| --- | --- |
|
||||
| **View** | Select any component from the screen |
|
||||
| **Constant** | Select a constant you've created in your app.|
|
||||
| **Get from preference** | Select the key you want to fetch from preference. |
|
||||
| **Navigation argument** | Select data that's been passed from one screen to another. |
|
||||
|
||||
For response binding, the below types are supported.
|
||||
|
||||
| Type | Description |
|
||||
| --- | --- |
|
||||
| **View** | Select any component from the screen |
|
||||
| **Save to preference** | Storing the data to preference. |
|
||||
|
||||
#### **Select 7:**
|
||||
`Handle action` - Select the action you wish to take once the Supabase call has either been accepted successfully or refused due to an error.
|
||||
|
||||
Available action for On success and On error are,
|
||||
|
||||
1. [Show Alert](https://docs.dhiwise.com/docs/flutter/show-alert)
|
||||
2. [Navigation](https://docs.dhiwise.com/docs/flutter/navigation)
|
||||
|
||||
#### **Step 9:**
|
||||
Finally, you have added Supabase to your application to create records from your screen data!
|
||||
|
||||
## Resources
|
||||
|
||||
- [DhiWise Official Website](https://dhiwise.com)
|
||||
- [DhiWise Documentation](https://docs.dhiwise.com)
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
id: integrations
|
||||
title: 'Supabase Marketplace'
|
||||
description: 'Integrations and Partners'
|
||||
---
|
||||
|
||||
The Supabase Marketplace brings together all the tools you need to extend your Supabase project. This includes:
|
||||
|
||||
- [Experts](https://supabase.com/partners/experts) - partners to help you build and support your Supabase project.
|
||||
- [Integrations](https://supabase.com/partners/integrations) - extend your projects with external Auth, Caching, Hosting, and Low-code tools.
|
||||
|
||||
## Build an integration
|
||||
|
||||
Supabase provides several integration points:
|
||||
|
||||
- The [Postgres connection](/docs/guides/database/connecting-to-postgres). Anything that works with Postgres also works with Supabase projects.
|
||||
- The [Project REST API](/docs/guides/api#rest-api) & client libraries.
|
||||
- The [Project GraphQL API](/docs/guides/api#graphql-api).
|
||||
- The [Platform API](/docs/reference/api).
|
||||
|
||||
## List your integration
|
||||
|
||||
[Apply to the Partners program](https://supabase.com/partners/integrations#become-a-partner) to list your integration in the Partners marketplace and in the Supabase docs.
|
||||
|
||||
Integrations are assessed on the following criteria:
|
||||
|
||||
- **Business viability**
|
||||
While we welcome everyone to built an integration, we only list companies that are deemed to be long-term viable. This includes an official business registration and bank account, meaningful revenue, or Venture Capital backing. We require this criteria to ensure the health of the marketplace.
|
||||
- **Compliance**
|
||||
Integrations should not infringe on the Supabase brand/trademark. In short, you cannot use "Supabase" in the name. As the listing appears on the Supabase domain, we don't want to mislead developers into thinking that an integration is an official product.
|
||||
- **Service Level Agreements**
|
||||
All listings are required to have their own Terms and Conditions, Privacy Policy, and Acceptable Use Policy, and the company must have resources to meet their SLAs.
|
||||
- **Maintainability**
|
||||
All integrations are required to be maintained and functional with Supabase, and the company may be assessed on your ability to remain functional over a long time horizon.
|
||||
@@ -60,4 +60,4 @@ That's it.
|
||||
|
||||
## All done!
|
||||
|
||||
You can read more about PolyScale [here](https://www.polyscale.ai/) or check out our [documentation](https://docs.polyscale.ai/).
|
||||
You can read more about PolyScale [here](https://www.polyscale.ai/) or check out our [documentation](https://docs.polyscale.ai/).
|
||||
@@ -62,7 +62,7 @@ This project comes with TypeScript configured and has the following structure.
|
||||
### Configuring the project to use PostgreSQL
|
||||
|
||||
Go ahead and delete the `prisma/dev.db` file because we will be switching to PostgreSQL.
|
||||
Next, inside the `prisma/.env` file, update the value of the `DATABASE_URL` variable to the connection string you got in **step 3**. The URL might look as follows:
|
||||
Next, inside the `prisma/.env` file, update the value of the `DATABASE_URL` variable to the connection string you got in **step 1**. The URL might look as follows:
|
||||
|
||||
```env
|
||||
# prisma/.env
|
||||
@@ -122,7 +122,7 @@ This will create a `prisma/migrations` folder inside your `prisma` directory and
|
||||
If you’re working in a serverless environment (for example Node.js functions hosted on AWS Lambda, Vercel or Netlify Functions), you need to set up [connection pooling](https://www.prisma.io/docs/guides/performance-and-optimization/connection-management#serverless-environments-faas) using a tool like [PgBouncer](https://www.pgbouncer.org/). That’s because every function invocation may result in a [new connection to the database](https://www.prisma.io/docs/guides/performance-and-optimization/connection-management#the-serverless-challenge). Supabase [supports connection management using PgBouncer](https://supabase.io/blog/2021/04/02/supabase-pgbouncer#what-is-connection-pooling) and are enabled by default.
|
||||
Go to the **Database** page from the sidebar in the Supabase dashboard and navigate to **connection pool** settings
|
||||

|
||||
When running migrations you need to use the non pooled connection URL (like the one we used in **step 4**). However, when deploying your app, you’ll use the pooled connection URL and add the `?pgbouncer=true` flag to the PostgreSQL connection URL. To minimize the number of concurrent connections, setting the `connection_limit` to `1` is also recommended. So the URL might look as follows:
|
||||
When running migrations you need to use the non pooled connection URL (like the one we used in **step 1**). However, when deploying your app, you’ll use the pooled connection URL and add the `?pgbouncer=true` flag to the PostgreSQL connection URL. To minimize the number of concurrent connections, setting the `connection_limit` to `1` is also recommended. So the URL might look as follows:
|
||||
|
||||
```env
|
||||
# prisma/.env
|
||||
|
||||
@@ -16,19 +16,23 @@ Supabase provides several [tools](https://github.com/supabase-community/firebase
|
||||
## Set up the migration tool {#set-up-migration-tool}
|
||||
|
||||
1. Clone the [firebase-to-supabase](https://github.com/supabase-community/firebase-to-supabase) repository:
|
||||
```
|
||||
git clone https://github.com/supabase-community/firebase-to-supabase.git
|
||||
```
|
||||
|
||||
```
|
||||
git clone https://github.com/supabase-community/firebase-to-supabase.git
|
||||
```
|
||||
|
||||
1. In the `/auth` directory, create a file named `supabase-service.json` with the following contents:
|
||||
```json
|
||||
{
|
||||
"host": "database.server.com",
|
||||
"password": "secretpassword",
|
||||
"user": "postgres",
|
||||
"database": "postgres",
|
||||
"port": 5432
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"host": "database.server.com",
|
||||
"password": "secretpassword",
|
||||
"user": "postgres",
|
||||
"database": "postgres",
|
||||
"port": 5432
|
||||
}
|
||||
```
|
||||
|
||||
1. Go to the [Database settings](https://app.supabase.com/project/_/settings/database) for your project in the Supabase Dashboard.
|
||||
1. Under **Connection Info**, copy the Host string and replace the entry in your `supabase-service.json` file.
|
||||
1. Enter the password you used when you created your Supabase project in the `password` entry in the `supabase-service.json` file.
|
||||
|
||||
@@ -6,15 +6,18 @@ sidebar_label: Firebase Storage
|
||||
---
|
||||
|
||||
Supabase provides several [tools](https://github.com/supabase-community/firebase-to-supabase/tree/main/storage) to convert storage files from Firebase Storage to Supabase Storage. Conversion is a two-step process:
|
||||
|
||||
1. Files are downloaded from a Firebase storage bucket to a local filesystem.
|
||||
2. Files are uploaded from the local filesystem to a Supabase storage bucket.
|
||||
|
||||
## Set up the migration tool {#set-up-migration-tool}
|
||||
|
||||
1. Clone the [firebase-to-supabase](https://github.com/supabase-community/firebase-to-supabase) repository:
|
||||
```
|
||||
git clone https://github.com/supabase-community/firebase-to-supabase.git
|
||||
```
|
||||
|
||||
```
|
||||
git clone https://github.com/supabase-community/firebase-to-supabase.git
|
||||
```
|
||||
|
||||
1. In the `/storage` directory, rename [supabase-keys-sample.js](https://github.com/supabase-community/firebase-to-supabase/blob/main/storage/supabase-keys-sample.js) to `supabase-keys.js`.
|
||||
1. Go to your Supabase project's [API settings](https://app.supabase.com/project/_/settings/api) in the Dashboard.
|
||||
1. Copy the **Project URL** and update the `SUPABASE_URL` value in `supabase-keys.js`.
|
||||
@@ -45,6 +48,7 @@ To process in batches using multiple command-line executions, you must use the s
|
||||
### Upload files to Supabase Storage bucket {#upload-to-supabase-storage-bucket}
|
||||
|
||||
`node upload.js <prefix> <folder> <bucket>`
|
||||
|
||||
- `<prefix>`: The prefix of the files to download. To process all files, use an empty prefix: "".
|
||||
- `<folder>`: Name of subfolder of files to upload. The selected folder is read as a subfolder of the current folder (e.g., `./downloads/`). The default is `downloads`.
|
||||
- `<bucket>`: Name of the bucket to upload to.
|
||||
|
||||
@@ -7,24 +7,28 @@ sidebar_label: Firestore Data
|
||||
|
||||
Supabase provides several [tools](https://github.com/supabase-community/firebase-to-supabase/tree/main/firestore) to convert data from a Firebase Firestore database to a Supabase PostgreSQL database. The process copies the entire contents of a single Firestore `collection` to a single PostgreSQL `table`.
|
||||
|
||||
The Firestore `collection` is "flattened" and converted to a table with basic columns of one of the following types: `text`, `numeric`, `boolean`, or `jsonb`. If your structure is more complex, you can write a program to split the newly-created `json` file into multiple, related tables before you import your `json` file(s) to Supabase.
|
||||
The Firestore `collection` is "flattened" and converted to a table with basic columns of one of the following types: `text`, `numeric`, `boolean`, or `jsonb`. If your structure is more complex, you can write a program to split the newly-created `json` file into multiple, related tables before you import your `json` file(s) to Supabase.
|
||||
|
||||
## Set up the migration tool {#set-up-migration-tool}
|
||||
|
||||
1. Clone the [firebase-to-supabase](https://github.com/supabase-community/firebase-to-supabase) repository:
|
||||
```
|
||||
git clone https://github.com/supabase-community/firebase-to-supabase.git
|
||||
```
|
||||
|
||||
```
|
||||
git clone https://github.com/supabase-community/firebase-to-supabase.git
|
||||
```
|
||||
|
||||
1. In the `/firestore` directory, create a file named `supabase-service.json` with the following contents:
|
||||
```json
|
||||
{
|
||||
"host": "database.server.com",
|
||||
"password": "secretpassword",
|
||||
"user": "postgres",
|
||||
"database": "postgres",
|
||||
"port": 5432
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"host": "database.server.com",
|
||||
"password": "secretpassword",
|
||||
"user": "postgres",
|
||||
"database": "postgres",
|
||||
"port": 5432
|
||||
}
|
||||
```
|
||||
|
||||
1. Go to the [Database settings](https://app.supabase.com/project/_/settings/database) for your project in the Supabase Dashboard.
|
||||
1. Under **Connection Info**, copy the Host string and replace the entry in your `supabase-service.json` file.
|
||||
1. Enter the password you used when you created your Supabase project in the `password` entry in the `supabase-service.json` file.
|
||||
@@ -40,56 +44,52 @@ The Firestore `collection` is "flattened" and converted to a table with basic co
|
||||
## Command line options
|
||||
|
||||
### List all Firestore collections
|
||||
|
||||
`node collections.js`
|
||||
|
||||
### Dump Firestore collection to JSON file
|
||||
|
||||
`node firestore2json.js <collectionName> [<batchSize>] [<limit>]`
|
||||
|
||||
* `batchSize` (optional) defaults to 1000
|
||||
* output filename is `<collectionName>.json`
|
||||
* `limit` (optional) defaults to 0 (no limit)
|
||||
- `batchSize` (optional) defaults to 1000
|
||||
- output filename is `<collectionName>.json`
|
||||
- `limit` (optional) defaults to 0 (no limit)
|
||||
|
||||
#### Customize the JSON file with hooks
|
||||
|
||||
You can customize the way your JSON file is written using a [custom hook](#custom-hooks). A common use for this is to "flatten" the JSON file, or to split nested data into separate, related database tables. For example, you could take a Firestore document that looks like this:
|
||||
|
||||
```json title="Firestore document"
|
||||
[{ "user": "mark",
|
||||
"score": 100,
|
||||
"items": ["hammer","nail","glue"]
|
||||
}]
|
||||
[{ "user": "mark", "score": 100, "items": ["hammer", "nail", "glue"] }]
|
||||
```
|
||||
|
||||
And split it into two files (one table for users and one table for items):
|
||||
|
||||
```json title="Users table"
|
||||
[{ "user": "mark",
|
||||
"score": 100
|
||||
}]
|
||||
[{ "user": "mark", "score": 100 }]
|
||||
```
|
||||
|
||||
```json title="Items table"
|
||||
[{ "user": "mark",
|
||||
"item": "hammer"},
|
||||
{"user": "mark",
|
||||
"item": "nail"},
|
||||
{"user": "mark",
|
||||
"item": "glue"}]
|
||||
[
|
||||
{ "user": "mark", "item": "hammer" },
|
||||
{ "user": "mark", "item": "nail" },
|
||||
{ "user": "mark", "item": "glue" }
|
||||
]
|
||||
```
|
||||
|
||||
### Import JSON file to Supabase (PostgreSQL) {#import-to-supabase}
|
||||
|
||||
`node json2supabase.js <path_to_json_file> [<primary_key_strategy>] [<primary_key_name>]`
|
||||
|
||||
* `<path_to_json_file>` The full path of the file you created in the previous step (`Dump Firestore collection to JSON file
|
||||
`), such as `./my_collection.json`
|
||||
* `[<primary_key_strategy>]` (optional) Is one of:
|
||||
* `none` (default) No primary key is added to the table.
|
||||
* `smallserial` Creates a key using `(id SMALLSERIAL PRIMARY KEY)` (autoincrementing 2-byte integer).
|
||||
* `serial` Creates a key using `(id SERIAL PRIMARY KEY)` (autoincrementing 4-byte integer).
|
||||
* `bigserial` Creates a key using `(id BIGSERIAL PRIMARY KEY)` (autoincrementing 8-byte integer).
|
||||
* `uuid` Creates a key using `(id UUID PRIMARY KEY DEFAULT uuid_generate_v4())` (randomly generated UUID).
|
||||
* `firestore_id` Creates a key using `(id TEXT PRIMARY KEY)` (uses existing `firestore_id` random text as key).
|
||||
* `[<primary_key_name>]` (optional) Name of primary key. Defaults to "id".
|
||||
- `<path_to_json_file>` The full path of the file you created in the previous step (`Dump Firestore collection to JSON file `), such as `./my_collection.json`
|
||||
- `[<primary_key_strategy>]` (optional) Is one of:
|
||||
- `none` (default) No primary key is added to the table.
|
||||
- `smallserial` Creates a key using `(id SMALLSERIAL PRIMARY KEY)` (autoincrementing 2-byte integer).
|
||||
- `serial` Creates a key using `(id SERIAL PRIMARY KEY)` (autoincrementing 4-byte integer).
|
||||
- `bigserial` Creates a key using `(id BIGSERIAL PRIMARY KEY)` (autoincrementing 8-byte integer).
|
||||
- `uuid` Creates a key using `(id UUID PRIMARY KEY DEFAULT uuid_generate_v4())` (randomly generated UUID).
|
||||
- `firestore_id` Creates a key using `(id TEXT PRIMARY KEY)` (uses existing `firestore_id` random text as key).
|
||||
- `[<primary_key_name>]` (optional) Name of primary key. Defaults to "id".
|
||||
|
||||
## Custom hooks
|
||||
|
||||
@@ -108,10 +108,11 @@ If your Firestore collection is called `users`, create a file called `users.js`
|
||||
#### Construct your .js file
|
||||
|
||||
The basic format of a hook file looks like this:
|
||||
|
||||
```js
|
||||
module.exports = (collectionName, doc, recordCounters, writeRecord) => {
|
||||
// modify the doc here
|
||||
return doc;
|
||||
return doc
|
||||
}
|
||||
```
|
||||
|
||||
@@ -121,9 +122,9 @@ module.exports = (collectionName, doc, recordCounters, writeRecord) => {
|
||||
- `doc`: The current document (JSON object) being processed.
|
||||
- `recordCounters`: An internal object that keeps track of how many records have been processed in each collection.
|
||||
- `writeRecord`: This function automatically handles the process of writing data to other JSON files (useful for "flatting" your document into separate JSON files to be written to separate database tables). `writeRecord` takes the following parameters:
|
||||
- `name`: Name of the JSON file to write to.
|
||||
- `doc`: The document to write to the file.
|
||||
- `recordCounters`: The same `recordCounters` object that was passed to this hook (just passes it on).
|
||||
- `name`: Name of the JSON file to write to.
|
||||
- `doc`: The document to write to the file.
|
||||
- `recordCounters`: The same `recordCounters` object that was passed to this hook (just passes it on).
|
||||
|
||||
### Examples
|
||||
|
||||
@@ -131,8 +132,8 @@ module.exports = (collectionName, doc, recordCounters, writeRecord) => {
|
||||
|
||||
```js
|
||||
module.exports = (collectionName, doc, recordCounters, writeRecord) => {
|
||||
doc.unique_key = (recordCounter[collectionName] + 1);
|
||||
return doc;
|
||||
doc.unique_key = recordCounter[collectionName] + 1
|
||||
return doc
|
||||
}
|
||||
```
|
||||
|
||||
@@ -140,8 +141,8 @@ module.exports = (collectionName, doc, recordCounters, writeRecord) => {
|
||||
|
||||
```js
|
||||
module.exports = (collectionName, doc, recordCounters, writeRecord) => {
|
||||
doc.dump_time = new Date().toISOString();
|
||||
return doc;
|
||||
doc.dump_time = new Date().toISOString()
|
||||
return doc
|
||||
}
|
||||
```
|
||||
|
||||
@@ -150,56 +151,56 @@ module.exports = (collectionName, doc, recordCounters, writeRecord) => {
|
||||
Flatten the `users` collection into separate files:
|
||||
|
||||
```json
|
||||
[{"uid": "abc123",
|
||||
"name": "mark",
|
||||
"score": 100,
|
||||
"weapons": ["toothpick","needle","rock"]},
|
||||
{"uid": "xyz789",
|
||||
"name": "chuck",
|
||||
"score": 9999999,
|
||||
"weapons": ["hand","foot","head"]}]
|
||||
[
|
||||
{
|
||||
"uid": "abc123",
|
||||
"name": "mark",
|
||||
"score": 100,
|
||||
"weapons": ["toothpick", "needle", "rock"]
|
||||
},
|
||||
{
|
||||
"uid": "xyz789",
|
||||
"name": "chuck",
|
||||
"score": 9999999,
|
||||
"weapons": ["hand", "foot", "head"]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
The `users.js` hook file:
|
||||
|
||||
```js
|
||||
module.exports = (collectionName, doc, recordCounters, writeRecord) => {
|
||||
for (let i=0; i < doc.weapons.length; i++) {
|
||||
for (let i = 0; i < doc.weapons.length; i++) {
|
||||
const weapon = {
|
||||
"uid": doc.uid,
|
||||
"weapon": doc.weapons[i]
|
||||
uid: doc.uid,
|
||||
weapon: doc.weapons[i],
|
||||
}
|
||||
writeRecord("weapons", weapon, recordCounters);
|
||||
writeRecord('weapons', weapon, recordCounters)
|
||||
}
|
||||
delete doc.weapons; // moved to separate file
|
||||
return doc;
|
||||
delete doc.weapons // moved to separate file
|
||||
return doc
|
||||
}
|
||||
```
|
||||
|
||||
The result is two separate JSON files:
|
||||
|
||||
```json title="users.json"
|
||||
[{"uid": "abc123",
|
||||
"name": "mark",
|
||||
"score": 100},
|
||||
{"uid": "xyz789",
|
||||
"name": "chuck",
|
||||
"score": 9999999}]
|
||||
[
|
||||
{ "uid": "abc123", "name": "mark", "score": 100 },
|
||||
{ "uid": "xyz789", "name": "chuck", "score": 9999999 }
|
||||
]
|
||||
```
|
||||
|
||||
```json title="weapons.json"
|
||||
[{"uid": "abc123",
|
||||
"weapon": "toothpick"},
|
||||
{"uid": "abc123",
|
||||
"weapon": "needle"},
|
||||
{"uid": "abc123",
|
||||
"weapon": "rock"},
|
||||
{"uid": "xyz789",
|
||||
"weapon": "hand"},
|
||||
{"uid": "xyz789",
|
||||
"weapon": "foot"},
|
||||
{"uid": "xyz789",
|
||||
"weapon": "head"}]
|
||||
[
|
||||
{ "uid": "abc123", "weapon": "toothpick" },
|
||||
{ "uid": "abc123", "weapon": "needle" },
|
||||
{ "uid": "abc123", "weapon": "rock" },
|
||||
{ "uid": "xyz789", "weapon": "hand" },
|
||||
{ "uid": "xyz789", "weapon": "foot" },
|
||||
{ "uid": "xyz789", "weapon": "head" }
|
||||
]
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
@@ -5,7 +5,7 @@ description: 'Migrate your Heroku Postgres database to Supabase.'
|
||||
sidebar_label: Heroku
|
||||
---
|
||||
|
||||
Supabase is one of the best [free alternatives to Heroku Postgres](https://supabase.com/alternatives/supabase-vs-heroku-postgres). This guide shows how to migrate your Heroku Postgres database to Supabase. This migration requires the [pg_dump](https://www.postgresql.org/docs/current/app-pgdump.html) and [psql](https://www.postgresql.org/docs/current/app-psql.html) CLI tools, which are installed automatically as part of the complete PostgreSQL installation package.
|
||||
Supabase is one of the best [free alternatives to Heroku Postgres](https://supabase.com/alternatives/supabase-vs-heroku-postgres). This guide shows how to migrate your Heroku Postgres database to Supabase. This migration requires the [pg_dump](https://www.postgresql.org/docs/current/app-pgdump.html) and [psql](https://www.postgresql.org/docs/current/app-psql.html) CLI tools, which are installed automatically as part of the complete PostgreSQL installation package.
|
||||
|
||||
Alternatively, use the [Heroku to Supabase migration tool](https://migrate.supabase.com/) to migrate in just a few clicks.
|
||||
|
||||
@@ -25,7 +25,7 @@ Alternatively, use the [Heroku to Supabase migration tool](https://migrate.supab
|
||||
1. Log in to your [Heroku account](https://heroku.com) and select the project you want to migrate.
|
||||
1. Click **Resources** in the menu and select your **Heroku Postgres** database.
|
||||
1. Click **Settings** in the menu.
|
||||
1. Click **View Credentials** and save the following information:
|
||||
1. Click **View Credentials** and save the following information:
|
||||
- Host (`$HEROKU_HOST`)
|
||||
- Database (`$HEROKU_DATABASE`)
|
||||
- User (`$HEROKU_USER`)
|
||||
@@ -52,7 +52,7 @@ pg_dump --clean --if-exists --quote-all-identifiers \
|
||||
Use `psql` to import the Heroku database file to your Supabase project.
|
||||
|
||||
```
|
||||
psql -h $SUPABASE_HOST -U postgres -f heroku_dump.sql
|
||||
psql -h $SUPABASE_HOST -U postgres -f heroku_dump.sql
|
||||
```
|
||||
|
||||
## (Optional) Post processing {#post-processing}
|
||||
@@ -67,7 +67,7 @@ select graphql.rebuild_schema();
|
||||
|
||||
- To only migrate a single database schema, add the `--schema=PATTERN` parameter to your `pg_dump` command.
|
||||
- To exclude a schema: `--exclude-schema=PATTERN`.
|
||||
- To only migrate a single table: `--table=PATTERN`.
|
||||
- To only migrate a single table: `--table=PATTERN`.
|
||||
- To exclude a table: `--exclude-table=PATTERN`.
|
||||
|
||||
Run `pg_dump --help` for a full list of options.
|
||||
@@ -4,7 +4,6 @@ title: Disk space usage
|
||||
description: Learn how database disk space usage is reported.
|
||||
---
|
||||
|
||||
|
||||
Database disk space usage refers to the _monthly average disk usage_, as reported by Postgres. This metric is reported in your project's [billing page](https://app.supabase.com/project/_/settings/billing) and is updated daily.
|
||||
|
||||
For an instantaneous live view of the DB disk space being used by your project, you can execute in Postgres:
|
||||
|
||||
@@ -6,8 +6,8 @@ description: Getting started with Supabase Platform Log Browser
|
||||
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
import ThemedImage from '@theme/ThemedImage';
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
import ThemedImage from '@theme/ThemedImage'
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl'
|
||||
|
||||
The Supabase Platform includes a Logs Explorer that allows log tracing and debugging. Log retention is based on your [project's pricing plan](https://supabase.com/pricing).
|
||||
|
||||
@@ -34,7 +34,32 @@ To update the screenshots, ensure that at least one log line is selected to disp
|
||||
</TabItem>
|
||||
<TabItem value="postgres" label="Postgres">
|
||||
|
||||
[Postgres logs](https://app.supabase.com/project/_/database/postgres-logs) show all queries and activity for your [database](../../guides/database).
|
||||
[Postgres logs](https://app.supabase.com/project/_/database/postgres-logs) show queries and activity for your [database](../../guides/database).
|
||||
|
||||
By default, query logs are disabled for all new Supabase projects, as they can reveal metadata about the contents of your database (e.g. table and column names). If you'd like to enable query logs, you can enable and configure the [pgAudit extension](https://www.pgaudit.org/):
|
||||
|
||||
- [Enable the pgAudit extension for your project](https://app.supabase.com/project/_/database/extensions)
|
||||
- [Reboot the DB](https://app.supabase.com/project/_/settings/general); a "Fast database reboot" is sufficient, and faster than a full project restart
|
||||
- Configure `pgaudit.log` to the [appropriate value](https://github.com/pgaudit/pgaudit/blob/master/README.md#pgauditlog)
|
||||
- You should be able to view query logs in your [log explorer](https://app.supabase.com/project/_/database/postgres-logs) now
|
||||
|
||||
As an example, if you wanted to enable logs for writes and DDL statements for a single session, you could execute within the session:
|
||||
|
||||
```sql
|
||||
set pgaudit.log = 'write, ddl';
|
||||
```
|
||||
|
||||
To _permanently_ enable logs for all statements that relate to roles and privileges, you could execute (followed by a db reboot):
|
||||
|
||||
```sql
|
||||
alter system set pgaudit.log to 'role';
|
||||
```
|
||||
|
||||
To remove a system-wide setting, you can use (followed by a db reboot):
|
||||
|
||||
```sql
|
||||
alter system reset pgaudit.log
|
||||
```
|
||||
|
||||

|
||||
|
||||
@@ -124,6 +149,7 @@ For example, to query the edge logs without any joins:
|
||||
```sql
|
||||
select timestamp, metadata from edge_logs t
|
||||
```
|
||||
|
||||
The resulting `metadata` key is rendered as an array of objects in the Logs Explorer. In the following diagram, each box represents a nested array of objects:
|
||||
|
||||
<!-- Scene is here https://app.excalidraw.com/s/8gj16loJfGZ/3HzccK9MyLx -->
|
||||
@@ -131,13 +157,12 @@ The resulting `metadata` key is rendered as an array of objects in the Logs Expl
|
||||
<ThemedImage
|
||||
alt="Without Unnesting"
|
||||
sources={{
|
||||
light: useBaseUrl("/img/guides/platform/logs/unnesting-none.png"),
|
||||
dark: useBaseUrl("/img/guides/platform/logs/unnesting-none-dark.png"),
|
||||
light: useBaseUrl('/img/guides/platform/logs/unnesting-none.png'),
|
||||
dark: useBaseUrl('/img/guides/platform/logs/unnesting-none-dark.png'),
|
||||
}}
|
||||
style={{maxHeight: 200}}
|
||||
style={{ maxHeight: 200 }}
|
||||
/>
|
||||
|
||||
|
||||
Perform a `cross join unnest()` to work with the keys nested in the `metadata` key.
|
||||
|
||||
To query for a nested value, add a join for each array level:
|
||||
@@ -155,10 +180,10 @@ This surfaces the following columns available for selection:
|
||||
<ThemedImage
|
||||
alt="With Two Level Unnesting"
|
||||
sources={{
|
||||
light: useBaseUrl("/img/guides/platform/logs/unnesting-2.png"),
|
||||
dark: useBaseUrl("/img/guides/platform/logs/unnesting-2-dark.png"),
|
||||
light: useBaseUrl('/img/guides/platform/logs/unnesting-2.png'),
|
||||
dark: useBaseUrl('/img/guides/platform/logs/unnesting-2-dark.png'),
|
||||
}}
|
||||
style={{maxHeight: 200}}
|
||||
style={{ maxHeight: 200 }}
|
||||
/>
|
||||
|
||||
This allows you to select the `method` and `cf_ipcountry` columns. In JS dot notation, the full paths for each selected column are:
|
||||
@@ -174,30 +199,30 @@ The Logs Explorer has a maximum of 1000 rows per run. Use `LIMIT` to optimize yo
|
||||
|
||||
1. Include a filter over **timestamp**
|
||||
|
||||
Querying your entire log history might seem appealing. For **Enterprise** customers that have a large retention range, you run the risk of timeouts due additional time required to scan the larger dataset.
|
||||
Querying your entire log history might seem appealing. For **Enterprise** customers that have a large retention range, you run the risk of timeouts due additional time required to scan the larger dataset.
|
||||
|
||||
2. Avoid selecting large nested objects. Select individual values instead.
|
||||
|
||||
When querying large objects, the columnar storage engine selects each column associated with each nested key, resulting in a large number of columns being selected. This inadvertently impacts the query speed and may result in timeouts or memory errors, especially for projects with a lot of logs.
|
||||
When querying large objects, the columnar storage engine selects each column associated with each nested key, resulting in a large number of columns being selected. This inadvertently impacts the query speed and may result in timeouts or memory errors, especially for projects with a lot of logs.
|
||||
|
||||
Instead, select only the values required.
|
||||
Instead, select only the values required.
|
||||
|
||||
```sql
|
||||
-- ❌ Avoid doing this
|
||||
select
|
||||
datetime(timestamp),
|
||||
m as metadata -- <- metadata contains many nested keys
|
||||
from edge_logs t
|
||||
cross join unnest(t.metadata) as m;
|
||||
|
||||
-- ✅ Do this
|
||||
select
|
||||
```sql
|
||||
-- ❌ Avoid doing this
|
||||
select
|
||||
datetime(timestamp),
|
||||
r.method -- <- select only the required values
|
||||
from edge_logs t
|
||||
cross join unnest(t.metadata) as m
|
||||
cross join unnest(m.request) as r
|
||||
```
|
||||
m as metadata -- <- metadata contains many nested keys
|
||||
from edge_logs t
|
||||
cross join unnest(t.metadata) as m;
|
||||
|
||||
-- ✅ Do this
|
||||
select
|
||||
datetime(timestamp),
|
||||
r.method -- <- select only the required values
|
||||
from edge_logs t
|
||||
cross join unnest(t.metadata) as m
|
||||
cross join unnest(m.request) as r
|
||||
```
|
||||
|
||||
### Examples and Templates
|
||||
|
||||
|
||||
@@ -1,44 +1,51 @@
|
||||
---
|
||||
id: realtime
|
||||
title: Realtime
|
||||
description: Using Supabase Realtime for multiplayer functionality.
|
||||
description: Supabase Realtime with Broadcast, Presence, and Postgres CDC.
|
||||
sidebar_label: Overview
|
||||
---
|
||||
|
||||
Supabase provides a Realtime API using [Realtime](https://github.com/supabase/realtime).
|
||||
Supabase provides a globally distributed cluster of [Realtime](https://github.com/supabase/realtime) servers that enable the following functionality:
|
||||
|
||||
You can use this to:
|
||||
- [Broadcast](#broadcast): Send ephemeral messages from client to clients with low latency.
|
||||
- [Presence](#presence): Track and synchrononize shared state between clients.
|
||||
- [Postgres CDC](#postgres-cdc): Listen to Postgres database changes and send them to authorized clients.
|
||||
|
||||
- broadcast messages between users
|
||||
- manage shared state between users
|
||||
- listen to database changes over websockets
|
||||
A [channel](https://hexdocs.pm/phoenix/channels.html) is the basic building block of Realtime and narrows the scope of data flow to subscribed clients. You can think of a channel as a chatroom where participants are able to see who's online and send and receive messages; similar to a Discord or Slack channel.
|
||||
|
||||
## Realtime Channels
|
||||
All clients can connect to a channel and take advantage of the built-in features, Broadcast and Presence, while extenstions, like Postgres CDC, must be enabled prior to use.
|
||||
|
||||
In Realtime, everything is a [_channel_](https://hexdocs.pm/phoenix/channels.html).
|
||||
Channels are like chatrooms where participants can join and leave—similar to Slack or Discord channels.
|
||||
## Broadcast
|
||||
|
||||
### Broadcast
|
||||
Broadcast follows the [publish-subscribe pattern](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern) where a client publishes messages to a channel with a unique identifier. For example, a user could send a message to a channel with id `room-1`.
|
||||
|
||||
Broadcast follows the classic Publisher/Subscriber pattern. A client (publisher) “broadcasts” messages using a unique identifier. For example, a user could send a message to a `#random` channel.
|
||||
Other clients can elect to receive the message in real-time by subscribing to the channel with id `room-1`. If these clients are online and subscribed then they will receive the message.
|
||||
|
||||
Other clients (subscribers) can “listen” to these messages in real-time, using the unique identifier. If they are listening to the `#random` channel, then they will receive the message.
|
||||
Broadcast works by connecting your client to the nearest Realtime server, which will communicate with other servers to relay messages to other clients.
|
||||
|
||||
A common use-case for Broadcast is sharing a user's cursor position with other clients in an online game.
|
||||
A common use-case is sharing a user's cursor position with other clients in an online game.
|
||||
|
||||
### Presence
|
||||
## Presence
|
||||
|
||||
Presence synchronizes shared state between users.
|
||||
Presence is very similar to Broadcast, except that the state is “persisted” in the Channel
|
||||
so that new joiners immediately receive the state without waiting for another user to broadcast it.
|
||||
In our Slack example, when you turn on Slack you can see who's online without each individual user
|
||||
sending an “I'm online” message to the new-joiner. Presence utilizes Broadcast as the transport layer.
|
||||
Presence utilizes an in-memory conflict-free replicated data type (CRDT) to track and synchronize shared state in an eventually consistent manner. It computes the difference between existing state and new state changes and sends the necessary updates to clients via Broadcast.
|
||||
|
||||
The neat thing about Presence is that if a user is suddenly disconnected (for example, they go offline), their state will be automatically removed from the shared state.
|
||||
If you've ever tried to build an “I'm online” feature which handles unexpected disconnects, you'll appreciate how useful this is.
|
||||
When a new client subscribes to a channel, it will immediately receive the channel's latest state in a single message instead of waiting for all other clients to send their individual states.
|
||||
|
||||
### Database changes
|
||||
Clients are free to come-and-go as they please, and as long as they are all subscribed to the same channel then they will all have the same Presence state as each other.
|
||||
|
||||
You can subscribe to database changes using Realtime.
|
||||
The Realtime API works through PostgreSQL's replication functionality. Postgres sends database changes to a [publication](/docs/guides/database/replication#publications)
|
||||
called `supabase_realtime`, and by managing this publication you can control which data is broadcast.
|
||||
The neat thing about Presence is that if a client is suddenly disconnected (for example, they go offline), their state will be automatically removed from the shared state. If you've ever tried to build an “I'm online” feature which handles unexpected disconnects, you'll appreciate how useful this is.
|
||||
|
||||
## Postgres CDC
|
||||
|
||||
Postgres Change Data Capture (CDC) enables you to listen to database changes and have them broadcast to authorized clients based on [Row Level Security (RLS)](/docs/guides/auth/row-level-security) policies.
|
||||
|
||||
This works by Realtime polling your database's logical replication slot for changes, passing those changes to the [apply_rls](https://github.com/supabase/walrus#reading-wal) SQL function to determine which clients have permission, and then using Broadcast to send those changes to clients.
|
||||
|
||||
Realtime requires a publication called `supabase_realtime` to determine which tables to poll. You must add tables to this publication prior to clients subscribing to channels that want to listen for database changes.
|
||||
|
||||
We strongly encourage you to enable RLS on your database tables and have RLS policies in place to prevent unauthorized parties from accessing your data.
|
||||
|
||||
## See Also
|
||||
|
||||
- [Realtime quickstart](/docs/guides/realtime/quickstart)
|
||||
- [Realtime: Multiplayer Edition](https://supabase.com/blog/supabase-realtime-multiplayer-general-availability) blog post
|
||||
@@ -1,70 +0,0 @@
|
||||
---
|
||||
id: broadcast
|
||||
title: Broadcast
|
||||
description: Getting started with Realtime's Broadcast feature
|
||||
---
|
||||
|
||||
Supabase Broadcast allows a client to send messages, and multiple receivers to receive the messages.
|
||||
|
||||
The broadcasted messages are ephemeral. They are not persisted to the database and are directly relayed through the Realtime server. This is ideal for sending information like mouse moments where latency is important, but need not be persisted.
|
||||
|
||||
The broadcast JS client methods are supported from supabase-js v2 release candidate.
|
||||
|
||||
To broadcast a message:
|
||||
|
||||
```js
|
||||
const { createClient } = require('@supabase/supabase-js')
|
||||
|
||||
const supabase = createClient(
|
||||
process.env.SUPABASE_URL,
|
||||
process.env.SUPABASE_KEY
|
||||
)
|
||||
|
||||
// The name of the channel can be set to any string.
|
||||
// Set the same name across both the broadcasting and receiving clients.
|
||||
const channel = supabase.channel('user1-location', {
|
||||
configs: {
|
||||
broadcast: { ack: true },
|
||||
},
|
||||
})
|
||||
|
||||
// subscribe registers your client with the server
|
||||
channel.subscribe(async (status) => {
|
||||
if (status === 'SUBSCRIBED') {
|
||||
// now you can start broadcasting messages
|
||||
// sending a new message every second
|
||||
setInterval(async () => {
|
||||
const status = await channel.send({
|
||||
type: 'broadcast',
|
||||
event: 'location',
|
||||
payload: { x: Math.random(), y: Math.random() },
|
||||
})
|
||||
console.log(status)
|
||||
}, 1000)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
To receive the broadcasted message, run this script in another tab:
|
||||
|
||||
```js
|
||||
const { createClient } = require('@supabase/supabase-js')
|
||||
|
||||
const supabase = createClient(
|
||||
process.env.SUPABASE_URL,
|
||||
process.env.SUPABASE_KEY
|
||||
)
|
||||
|
||||
// use the same channel name as the client broadcasting the message is using
|
||||
const channel = supabase.channel('user1-location')
|
||||
|
||||
// listen to broadcasts
|
||||
channel
|
||||
.on('broadcast', { event: 'location' }, (payload) => console.log(payload))
|
||||
.subscribe(async (status) => {
|
||||
if (status === 'SUBSCRIBED') {
|
||||
console.log(status)
|
||||
// your callback function will now be called with the received messages
|
||||
}
|
||||
})
|
||||
```
|
||||
+16
-6
@@ -1,16 +1,26 @@
|
||||
---
|
||||
id: postgres-changes
|
||||
title: Postgres Changes
|
||||
description: Getting started with Realtime's Postgres Changes feature
|
||||
id: postgres-cdc
|
||||
title: Postgres CDC
|
||||
description: Getting started with Realtime's Postgres CDC feature
|
||||
---
|
||||
|
||||
Supabase Postgres Changes listen for database changes and sends them to clients. Clients are required to subscribe with a JWT dictating which changes they are allowed to receive based on the database's [Row Level Security](/docs/guides/auth/row-level-security).
|
||||
Realtime's Postgres Change Data Capture (CDC) feature listens for database changes and sends them to clients. Clients are required to subscribe with a JWT dictating which changes they are allowed to receive based on the database's [Row Level Security](/docs/guides/auth/row-level-security).
|
||||
|
||||
Anyone with access to a valid JWT signed with the project's JWT secret is able to listen to your database's changes, unless tables have [Row Level Security](/docs/guides/auth/row-level-security) enabled and policies in place.
|
||||
|
||||
Clients can choose to receive `INSERT`, `UPDATE`, `DELETE`, or `*` (all) changes for all changes in a schema, a table in a schema, or a column's value in a table. Your clients should only listen to tables in the `public` schema and you must first enable the tables you want your clients to listen to.
|
||||
|
||||
Postgres CDC works out of the box for tables in the `public` schema. You can listen to tables in your private schemas by granting table `SELECT` permissions to the database role found in your access token. You can run a query similar to the following:
|
||||
|
||||
```sql
|
||||
GRANT SELECT ON "private_schema"."table" TO authenticated;
|
||||
```
|
||||
|
||||
:::caution
|
||||
Anyone with access to a valid JWT signed with the project's JWT secret is able to listen to your database's changes, unless tables have [Row Level Security](/docs/guides/auth/row-level-security) enabled and policies in place.
|
||||
We strongly encourage you to enable RLS and create policies for tables in private schemas. Otherwise, any role you grant access to will have unfettered read access to the table.
|
||||
:::
|
||||
|
||||
Clients can choose to receive `INSERT`, `UPDATE`, `DELETE`, or `*` (all) changes for all changes in a schema, a table in a schema, or a column's value in a table.Your clients can only listen to tables in the `public` schema and you must first enable the tables you want your clients to listen to. You can do this in the [Replication](https://app.supabase.com/project/_/database/replication) section in the Dashboard or with the [SQL editor](https://app.supabase.com/project/_/sql):
|
||||
You can do this in the [Replication](https://app.supabase.com/project/_/database/replication) section in the Dashboard or with the [SQL editor](https://app.supabase.com/project/_/sql):
|
||||
|
||||
```sql
|
||||
begin;
|
||||
@@ -1,46 +0,0 @@
|
||||
---
|
||||
id: presence
|
||||
title: Presence
|
||||
description: Getting started with Realtime's presence feature
|
||||
---
|
||||
|
||||
Supabase Presence stores and synchronize shared state across clients. Presence is very similar to Broadcast, except that the state is “persisted” in the Channel so that new joiners can immediately get the state without waiting for another user to broadcast a message.
|
||||
|
||||
The `sync` event is triggered whenever the shared state changes. The `join` event is triggered when a new client joins the channel and `leave` when a client leaves.
|
||||
|
||||
Each client can use the `track` method to store an object in the shared state. Each client can only track one object in the shared state. If `track` is called again by the same client, then the new object will overwrite the previously tracked object in the shared state.
|
||||
|
||||
The presence JS client methods are supported from supabase-js v2 release candidate.
|
||||
|
||||
```js
|
||||
const { createClient } = require('@supabase/supabase-js')
|
||||
|
||||
const supabase = createClient(
|
||||
process.env.SUPABASE_URL,
|
||||
process.env.SUPABASE_KEY
|
||||
)
|
||||
|
||||
const channel = supabase.channel('online-users')
|
||||
|
||||
function getRandomUser() {
|
||||
const users = ['Alice', 'Bob', 'Mallory', 'Inian']
|
||||
return users[Math.floor(Math.random() * users.length)]
|
||||
}
|
||||
|
||||
channel
|
||||
.on('presence', { event: 'sync' }, () => {
|
||||
console.log('currently online users', channel.presenceState())
|
||||
})
|
||||
.on('presence', { event: 'join' }, ({ newPresences }) => {
|
||||
console.log('new users have joined', newPresences)
|
||||
})
|
||||
.on('presence', { event: 'leave' }, ({ leftPresences }) =>
|
||||
console.log('users have left', leftPresences)
|
||||
)
|
||||
.subscribe(async (status) => {
|
||||
if (status === 'SUBSCRIBED') {
|
||||
const status = await channel.track({ user_name: getRandomUser() })
|
||||
console.log(status)
|
||||
}
|
||||
})
|
||||
```
|
||||
@@ -0,0 +1,258 @@
|
||||
---
|
||||
id: quickstart
|
||||
title: Realtime Quickstart
|
||||
description: Getting started with Realtime's Features
|
||||
sidebar_label: Quickstart
|
||||
---
|
||||
|
||||
Learn how to build [multiplayer.dev](https://multiplayer.dev), a collaborative app that demonstrates Broadcast, Presence, and Postgres CDC using [Realtime](/docs/guides/realtime).
|
||||
|
||||
<div class="video-container">
|
||||
<iframe
|
||||
src="https://www.youtube-nocookie.com/embed/BelYEMJ2N00"
|
||||
frameBorder="1"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
</div>
|
||||
|
||||
## Install `supabase-js` Client
|
||||
|
||||
```bash
|
||||
npm install @supabase/supabase-js
|
||||
```
|
||||
|
||||
## Cursor Positions
|
||||
|
||||
[Broadcast](/docs/guides/realtime#broadcast) allows a client to send messages and multiple clients to receive the messages. The broadcasted messages are ephemeral. They are not persisted to the database and are directly relayed through the Realtime servers. This is ideal for sending information like cursor positions where minimal latency is important, but persisting them is not.
|
||||
|
||||
In [multiplayer.dev](https://multiplayer.dev), client's cursor positions are sent to other clients in the room. However, cursor positions will be randomly generated for this example.
|
||||
|
||||
You need to get the public `anon` access token from your project's [API settings](https://app.supabase.com/project/_/settings/api). Then you can set up the Suapbase client and start sending a client's cursor positions to other clients in channel `room1`:
|
||||
|
||||
```js
|
||||
const { createClient } = require('@supabase/supabase-js')
|
||||
|
||||
const supabase = createClient(
|
||||
'https://your-project-ref.supabase.co',
|
||||
'anon-key',
|
||||
{
|
||||
realtime: {
|
||||
params: {
|
||||
eventsPerSecond: 10,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// Channel name can be any string.
|
||||
// Create channels with the same name for both the broadcasting and receiving clients.
|
||||
const channel = supabase.channel('room1')
|
||||
|
||||
// Subscribe registers your client with the server
|
||||
channel.subscribe((status) => {
|
||||
if (status === 'SUBSCRIBED') {
|
||||
// now you can start broadcasting cursor positions
|
||||
setInterval(() => {
|
||||
channel.send({
|
||||
type: 'broadcast',
|
||||
event: 'cursor-pos',
|
||||
payload: { x: Math.random(), y: Math.random() },
|
||||
})
|
||||
console.log(status)
|
||||
}, 100)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
:::info
|
||||
JavaScript client has a default rate limit of 1 Realtime event every 100 milliseconds that's configured by `eventsPerSecond`.
|
||||
:::
|
||||
|
||||
Another client can subscribe to channel `room1` and receive cursor positions:
|
||||
|
||||
```js
|
||||
// Supabase client setup
|
||||
|
||||
// Listen to broadcast messages.
|
||||
supabase
|
||||
.channel('room1')
|
||||
.on('broadcast', { event: 'cursor-pos' }, (payload) => console.log(payload))
|
||||
.subscribe((status) => {
|
||||
if (status === 'SUBSCRIBED') {
|
||||
// your callback function will now be called with the messages broadcast by the other client
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
:::info
|
||||
`type` must be `broadcast` and the `event` must match for clients subscribed to the channel.
|
||||
:::
|
||||
|
||||
## Roundtrip Latency
|
||||
|
||||
You can also configure the channel so that the server must return an acknowledgement that it received the `broadcast` message. This is useful if you want to measure the roundtrip latency:
|
||||
|
||||
```js
|
||||
// Supabase client setup
|
||||
|
||||
const channel = supabase.channel('calc-latency', {
|
||||
config: {
|
||||
broadcast: { ack: true }
|
||||
}
|
||||
})
|
||||
|
||||
channel.subscribe(async (status) => {
|
||||
if (status === 'SUBSCRIBED') {
|
||||
const begin = performance.now()
|
||||
|
||||
await channel.send({
|
||||
type: 'broadcast',
|
||||
event: 'latency',
|
||||
payload: {},
|
||||
})
|
||||
|
||||
const end = performance.now()
|
||||
|
||||
console.log(`Latency is ${end - begin} milliseconds`)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Track and display which users are online
|
||||
|
||||
[Presence](/docs/guides/realtime#presence) stores and synchronize shared state across clients. The `sync` event is triggered whenever the shared state changes. The `join` event is triggered when new clients join the channel and `leave` event is triggered when clients leave.
|
||||
|
||||
Each client can use the channel's `track` method to store an object in shared state. Each client can only track one object, and if `track` is called again by the same client, then the new object overwrites the previously tracked object in the shared state. You can use one client to track and display users who are online:
|
||||
|
||||
```js
|
||||
// Supabase client setup
|
||||
|
||||
const channel = supabase.channel('online-users', {
|
||||
config: {
|
||||
presence: {
|
||||
key: 'user1',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
channel.on('presence', { event: 'sync' }, () => {
|
||||
console.log('Online users: ', channel.presenceState())
|
||||
})
|
||||
|
||||
channel.on('presence', { event: 'join' }, ({ newPresences }) => {
|
||||
console.log('New users have joined: ', newPresences)
|
||||
})
|
||||
|
||||
channel.on('presence', { event: 'leave' }, ({ leftPresences }) => {
|
||||
console.log('Users have left: ', newPresences)
|
||||
})
|
||||
|
||||
channel.subscribe(async (status) => {
|
||||
if (status === 'SUBSCRIBED') {
|
||||
const status = await channel.track({ online_at: new Date().toISOString() })
|
||||
console.log(status)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Then you can use another client to add another user to the channel's Presence state:
|
||||
|
||||
```js
|
||||
// Supabase client setup
|
||||
|
||||
const channel = supabase.channel('online-users', {
|
||||
config: {
|
||||
presence: {
|
||||
key: 'user2',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// Presence event handlers setup
|
||||
|
||||
channel.subscribe(async (status) => {
|
||||
if (status === 'SUBSCRIBED') {
|
||||
const status = await channel.track({ online_at: new Date().toISOString() })
|
||||
console.log(status)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
If a channel is set up without a presence key, the server generates a random UUID. `type` must be `presence` and `event` must be either `sync`, `join`, or `leave`.
|
||||
|
||||
## Insert and Receive Persisted Messages
|
||||
|
||||
[Postgres Change Data Capture (CDC)](/docs/guides/realtime#postgres-cdc) enables your client to insert, update, or delete database records and send the changes to clients. Create a `messages` table to keep track of messages created by users in specific rooms:
|
||||
|
||||
```sql
|
||||
create table messages (
|
||||
id serial primary key,
|
||||
message text,
|
||||
user_id text,
|
||||
room_id text,
|
||||
created_at timestamptz default now()
|
||||
)
|
||||
|
||||
alter table messages enable row level security;
|
||||
|
||||
create policy "anon_ins_policy"
|
||||
ON messages
|
||||
for insert
|
||||
to anon
|
||||
with check (true);
|
||||
|
||||
create policy "anon_sel_policy"
|
||||
ON messages
|
||||
for select
|
||||
to anon
|
||||
using (true);
|
||||
```
|
||||
|
||||
If it doesn't already exist, create a `supabase_realtime` publication and add `messages` table to the publication:
|
||||
|
||||
```sql
|
||||
begin;
|
||||
-- remove the supabase_realtime publication
|
||||
drop publication if exists supabase_realtime;
|
||||
|
||||
-- re-create the supabase_realtime publication with no tables and only for insert
|
||||
create publication supabase_realtime with (publish = 'insert');
|
||||
commit;
|
||||
|
||||
-- add a table to the publication
|
||||
alter publication supabase_realtime add table messages;
|
||||
```
|
||||
|
||||
You can then have a client listen for changes on the `messages` table for a specific room and send and receive persisted messages:
|
||||
|
||||
```js
|
||||
// Supabase client setup
|
||||
|
||||
const channel = supabase.channel('db-messages')
|
||||
|
||||
const roomId = 'room1'
|
||||
const userId = 'user1'
|
||||
|
||||
channel.on(
|
||||
'postgres_changes',
|
||||
{
|
||||
event: 'INSERT',
|
||||
schema: 'public',
|
||||
table: 'messages',
|
||||
filter: `room_id=eq.${roomId}`,
|
||||
},
|
||||
(payload) => console.log(payload)
|
||||
)
|
||||
|
||||
channel.subscribe(async (status) => {
|
||||
if (status === 'SUBSCRIBED') {
|
||||
const res = await supabase.from('messages').insert({
|
||||
room_id: roomId,
|
||||
user_id: userId,
|
||||
message: 'Welcome to Realtime!',
|
||||
})
|
||||
console.log(res)
|
||||
}
|
||||
})
|
||||
```
|
||||
@@ -19,7 +19,7 @@ Aside from performance, CDNs also help with security and availability by mitigat
|
||||
|
||||
By default, assets are cached both in the CDN and in the user’s browser for 1 hour. After this, the CDN nodes ping the storage server to see if an object has been updated.
|
||||
|
||||
You can modify this cache time when you are [uploading](https://supabase.com/docs/reference/javascript/next/storage-from-upload) or [updating](https://supabase.com/docs/reference/javascript/next/storage-from-update) an object by modifying the `cacheControl` parameter.
|
||||
You can modify this cache time when you are [uploading](https://supabase.com/docs/reference/javascript/storage-from-upload) or [updating](https://supabase.com/docs/reference/javascript/storage-from-update) an object by modifying the `cacheControl` parameter.
|
||||
|
||||
If you expect the object to not change at a given URL, setting a longer cache duration is preferable.
|
||||
|
||||
|
||||
@@ -272,7 +272,7 @@ You can access the storage API directly with the same endpoint. See the <a href=
|
||||
|
||||
Supabase Storage is integrated with your [Postgres Database](/docs/guides/database).
|
||||
This means that you can use the same [Row Level Security Policies](/docs/guides/auth#policies)
|
||||
for managing access to your files. Supabase Storage stores metadata in the `objects` and `buckets` table in the storage schema. To allow read access to files, the RLS policy must allow users to `SELECT` the `objects` table and for uploading a new object, the RLS policy must grant users access to `INSERT` into the `objects` table and so on. The mapping between the different API calls and the database permissions required is documented in the [Reference docs](/docs/reference/javascript/next/storage-createbucket).
|
||||
for managing access to your files. Supabase Storage stores metadata in the `objects` and `buckets` table in the storage schema. To allow read access to files, the RLS policy must allow users to `SELECT` the `objects` table and for uploading a new object, the RLS policy must grant users access to `INSERT` into the `objects` table and so on. The mapping between the different API calls and the database permissions required is documented in the [Reference docs](/docs/reference/javascript/storage-createbucket).
|
||||
|
||||
:::note
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ If you want to do it yourself, let's get started!
|
||||
|
||||
### GitHub
|
||||
|
||||
Whenever you get stuck at any point, take a look at [this repo](https://github.com/angular-supa/supabase-angular-user-management).
|
||||
Whenever you get stuck at any point, take a look at [this repo](https://github.com/supabase/examples/tree/main/supabase-js-v2/user-management/angular-user-management).
|
||||
|
||||
## Project set up
|
||||
|
||||
@@ -180,281 +180,365 @@ export const environment = {
|
||||
Now that we have the API credentials in place, let's create a **SupabaseService** with `ng g s supabase` to initialize the Supabase client and implement functions to communicate with the Supabase API.
|
||||
|
||||
```ts title="src/app/supabase.service.ts"
|
||||
import { Injectable } from '@angular/core'
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
AuthChangeEvent,
|
||||
AuthSession,
|
||||
createClient,
|
||||
Session,
|
||||
SupabaseClient,
|
||||
} from '@supabase/supabase-js'
|
||||
import { environment } from '../environments/environment'
|
||||
User,
|
||||
} from '@supabase/supabase-js';
|
||||
import { environment } from 'src/environments/environment';
|
||||
import { Database } from 'src/schema';
|
||||
|
||||
export interface Profile {
|
||||
username: string
|
||||
website: string
|
||||
avatar_url: string
|
||||
id?: string;
|
||||
username: string;
|
||||
website: string;
|
||||
avatar_url: string;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class SupabaseService {
|
||||
private supabase: SupabaseClient
|
||||
private supabase: SupabaseClient;
|
||||
_session: AuthSession | null = null;
|
||||
|
||||
constructor() {
|
||||
this.supabase = createClient(
|
||||
environment.supabaseUrl,
|
||||
environment.supabaseKey
|
||||
)
|
||||
}
|
||||
|
||||
get user() {
|
||||
return this.supabase.auth.user()
|
||||
);
|
||||
}
|
||||
|
||||
get session() {
|
||||
return this.supabase.auth.session()
|
||||
this.supabase.auth.getSession().then(({ data }) => {
|
||||
this._session = data.session;
|
||||
})
|
||||
return this._session;
|
||||
}
|
||||
|
||||
get profile() {
|
||||
profile(user: User) {
|
||||
return this.supabase
|
||||
.from('profiles')
|
||||
.select(`username, website, avatar_url`)
|
||||
.eq('id', this.user?.id)
|
||||
.single()
|
||||
.eq('id', user.id)
|
||||
.single();
|
||||
}
|
||||
|
||||
authChanges(
|
||||
callback: (event: AuthChangeEvent, session: Session | null) => void
|
||||
) {
|
||||
return this.supabase.auth.onAuthStateChange(callback)
|
||||
return this.supabase.auth.onAuthStateChange(callback);
|
||||
}
|
||||
|
||||
signIn(email: string) {
|
||||
return this.supabase.auth.signIn({ email })
|
||||
return this.supabase.auth.signInWithOtp({ email });
|
||||
}
|
||||
|
||||
signOut() {
|
||||
return this.supabase.auth.signOut()
|
||||
return this.supabase.auth.signOut();
|
||||
}
|
||||
|
||||
updateProfile(profile: Profile) {
|
||||
const update = {
|
||||
...profile,
|
||||
id: this.user?.id,
|
||||
updated_at: new Date(),
|
||||
}
|
||||
};
|
||||
|
||||
return this.supabase.from('profiles').upsert(update, {
|
||||
returning: 'minimal', // Don't return the value after inserting
|
||||
})
|
||||
return this.supabase.from('profiles').upsert(update);
|
||||
}
|
||||
|
||||
downLoadImage(path: string) {
|
||||
return this.supabase.storage.from('avatars').download(path)
|
||||
return this.supabase.storage.from('avatars').download(path);
|
||||
}
|
||||
|
||||
uploadAvatar(filePath: string, file: File) {
|
||||
return this.supabase.storage.from('avatars').upload(filePath, file)
|
||||
return this.supabase.storage.from('avatars').upload(filePath, file);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And one optional step is to update the CSS file `src/index.css` to make the app look nice.
|
||||
You can find the full contents of this file [here](https://raw.githubusercontent.com/angular-supa/supabase-angular-user-management/main/src/styles.css).
|
||||
Optionally, update [src/styles.css](https://raw.githubusercontent.com/supabase/examples/main/supabase-js-v2/user-management/angular-user-management/src/styles.css) to style the app.
|
||||
|
||||
### Set up a Login component
|
||||
|
||||
Let's set up an Angular component to manage logins and sign ups. We'll use Magic Links, so users can sign in with their email without using passwords.
|
||||
Create an **AuthComponent** with `ng g c auth` Angular CLI command.
|
||||
|
||||
```ts title="src/app/auth.component.ts"
|
||||
import { Component } from '@angular/core'
|
||||
import { SupabaseService } from './supabase.service'
|
||||
```ts title="src/app/auth/auth.component.ts"
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { FormBuilder } from '@angular/forms';
|
||||
import { SupabaseService } from '../supabase.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-auth',
|
||||
template: `
|
||||
<div class="row flex-center flex">
|
||||
<form class="col-6 form-widget">
|
||||
<h1 class="header">Supabase + Angular</h1>
|
||||
<p class="description">Sign in via magic link with your email below</p>
|
||||
<div>
|
||||
<input
|
||||
#input
|
||||
class="inputField"
|
||||
type="email"
|
||||
placeholder="Your email"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
type="submit"
|
||||
(click)="handleLogin(input.value)"
|
||||
class="button block"
|
||||
[disabled]="loading"
|
||||
>
|
||||
{{ loading ? 'Loading' : 'Send magic link' }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
`,
|
||||
templateUrl: './auth.component.html',
|
||||
styleUrls: ['./auth.component.css'],
|
||||
})
|
||||
export class AuthComponent {
|
||||
loading = false
|
||||
export class AuthComponent implements OnInit {
|
||||
loading = false;
|
||||
|
||||
constructor(private readonly supabase: SupabaseService) {}
|
||||
signInForm = this.formBuilder.group({
|
||||
email: '',
|
||||
});
|
||||
|
||||
async handleLogin(input: string) {
|
||||
constructor(
|
||||
private readonly supabase: SupabaseService,
|
||||
private readonly formBuilder: FormBuilder
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {}
|
||||
|
||||
async onSubmit(): Promise<void> {
|
||||
try {
|
||||
this.loading = true
|
||||
await this.supabase.signIn(input)
|
||||
alert('Check your email for the login link!')
|
||||
this.loading = true;
|
||||
const email = this.signInForm.value.email as string;
|
||||
const { error } = await this.supabase.signIn(email);
|
||||
if (error) throw error;
|
||||
alert('Check your email for the login link!');
|
||||
} catch (error) {
|
||||
alert(error.error_description || error.message)
|
||||
if (error instanceof Error) {
|
||||
alert(error.message);
|
||||
}
|
||||
} finally {
|
||||
this.loading = false
|
||||
this.signInForm.reset();
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```html title="src/app/auth/auth.component.html"
|
||||
<div class="row flex-center flex">
|
||||
<div class="col-6 form-widget" aria-live="polite">
|
||||
<h1 class="header">Supabase + Angular</h1>
|
||||
<p class="description">Sign in via magic link with your email below</p>
|
||||
<form [formGroup]="signInForm" (ngSubmit)="onSubmit()" class="form-widget">
|
||||
<div>
|
||||
<label for="email">Email</label>
|
||||
<input
|
||||
id="email"
|
||||
formControlName="email"
|
||||
class="inputField"
|
||||
type="email"
|
||||
placeholder="Your email"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
type="submit"
|
||||
class="button block"
|
||||
[disabled]="loading"
|
||||
>
|
||||
{{ loading ? 'Loading' : 'Send magic link' }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div
|
||||
```
|
||||
|
||||
### Account page
|
||||
|
||||
After a user is signed in we can allow them to edit their profile details and manage their account.
|
||||
Create an **AccountComponent** with `ng g c account` Angular CLI command.
|
||||
Users also need a way to edit their profile details and manage their accounts after signing in.
|
||||
Create an **AccountComponent** with the `ng g c account` Angular CLI command.
|
||||
|
||||
```ts title="src/app/account.component.ts"
|
||||
import { Component, Input, OnInit } from '@angular/core'
|
||||
import { Profile, SupabaseService } from './supabase.service'
|
||||
import { Session } from '@supabase/supabase-js'
|
||||
```ts title="src/app/account/account.component.ts"
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { FormBuilder } from '@angular/forms';
|
||||
import { AuthSession } from '@supabase/supabase-js';
|
||||
import { Profile, SupabaseService } from '../supabase.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-account',
|
||||
template: `
|
||||
<div class="form-widget">
|
||||
<div>
|
||||
<label for="email">Email</label>
|
||||
<input id="email" type="text" [value]="session?.user?.email" disabled />
|
||||
</div>
|
||||
<div>
|
||||
<label for="username">Name</label>
|
||||
<input
|
||||
#username
|
||||
id="username"
|
||||
type="text"
|
||||
[value]="profile?.username ?? ''"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="website">Website</label>
|
||||
<input
|
||||
#website
|
||||
id="website"
|
||||
type="url"
|
||||
[value]="profile?.website ?? ''"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
class="button primary block"
|
||||
(click)="updateProfile(username.value, website.value)"
|
||||
[disabled]="loading"
|
||||
>
|
||||
{{ loading ? 'Loading ...' : 'Update' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button class="button block" (click)="signOut()">Sign Out</button>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
templateUrl: './account.component.html',
|
||||
styleUrls: ['./account.component.css'],
|
||||
})
|
||||
export class AccountComponent implements OnInit {
|
||||
loading = false
|
||||
profile: Profile | undefined
|
||||
loading = false;
|
||||
profile!: Profile;
|
||||
|
||||
@Input() session: Session | undefined
|
||||
@Input()
|
||||
session!: AuthSession;
|
||||
|
||||
constructor(private readonly supabase: SupabaseService) {}
|
||||
updateProfileForm = this.formBuilder.group({
|
||||
username: '',
|
||||
website: '',
|
||||
avatar_url: '',
|
||||
});
|
||||
|
||||
ngOnInit() {
|
||||
this.getProfile()
|
||||
constructor(
|
||||
private readonly supabase: SupabaseService,
|
||||
private formBuilder: FormBuilder
|
||||
) {}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
await this.getProfile();
|
||||
|
||||
const { username, website, avatar_url } = this.profile;
|
||||
this.updateProfileForm.patchValue({
|
||||
username,
|
||||
website,
|
||||
avatar_url,
|
||||
});
|
||||
}
|
||||
|
||||
async getProfile() {
|
||||
try {
|
||||
this.loading = true
|
||||
let { data: profile, error, status } = await this.supabase.profile
|
||||
this.loading = true;
|
||||
const { user } = this.session;
|
||||
let { data: profile, error, status } = await this.supabase.profile(user);
|
||||
|
||||
if (error && status !== 406) {
|
||||
throw error
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (profile) {
|
||||
this.profile = profile
|
||||
this.profile = profile;
|
||||
}
|
||||
} catch (error) {
|
||||
alert(error.message)
|
||||
if (error instanceof Error) {
|
||||
alert(error.message);
|
||||
}
|
||||
} finally {
|
||||
this.loading = false
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
async updateProfile(
|
||||
username: string,
|
||||
website: string,
|
||||
avatar_url: string = ''
|
||||
) {
|
||||
async updateProfile(): Promise<void> {
|
||||
try {
|
||||
this.loading = true
|
||||
await this.supabase.updateProfile({ username, website, avatar_url })
|
||||
this.loading = true;
|
||||
const { user } = this.session;
|
||||
|
||||
const username = this.updateProfileForm.value.username as string;
|
||||
const website = this.updateProfileForm.value.website as string;
|
||||
const avatar_url = this.updateProfileForm.value.avatar_url as string;
|
||||
|
||||
const { error } = await this.supabase.updateProfile({
|
||||
id: user.id,
|
||||
username,
|
||||
website,
|
||||
avatar_url,
|
||||
});
|
||||
if (error) throw error;
|
||||
} catch (error) {
|
||||
alert(error.message)
|
||||
if (error instanceof Error) {
|
||||
alert(error.message);
|
||||
}
|
||||
} finally {
|
||||
this.loading = false
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
async signOut() {
|
||||
await this.supabase.signOut()
|
||||
await this.supabase.signOut();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```html title="src/app/account/account.component.html"
|
||||
<form [formGroup]="updateProfileForm" (ngSubmit)="updateProfile()" class="form-widget">
|
||||
<div>
|
||||
<label for="email">Email</label>
|
||||
<input id="email" type="text" [value]="session.user.email" disabled />
|
||||
</div>
|
||||
<div>
|
||||
<label for="username">Name</label>
|
||||
<input
|
||||
formControlName="username"
|
||||
id="username"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="website">Website</label>
|
||||
<input
|
||||
formControlName="website"
|
||||
id="website"
|
||||
type="url"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
type="submit"
|
||||
class="button primary block"
|
||||
[disabled]="loading"
|
||||
>
|
||||
{{ loading ? 'Loading ...' : 'Update' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button class="button block" (click)="signOut()">Sign Out</button>
|
||||
</div>
|
||||
</form>
|
||||
```
|
||||
|
||||
### Launch!
|
||||
|
||||
Now that we have all the components in place, let's update **AppComponent**:
|
||||
|
||||
```ts title="src/app/app.component.ts"
|
||||
import { Component, OnInit } from '@angular/core'
|
||||
import { SupabaseService } from './supabase.service'
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { SupabaseService } from './supabase.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
template: `
|
||||
<div class="container" style="padding: 50px 0 100px 0">
|
||||
<app-account *ngIf="session; else auth" [session]="session"></app-account>
|
||||
<ng-template #auth>
|
||||
<app-auth></app-auth>
|
||||
</ng-template>
|
||||
</div>
|
||||
`,
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.css'],
|
||||
})
|
||||
export class AppComponent implements OnInit {
|
||||
session = this.supabase.session
|
||||
title = 'angular-user-management';
|
||||
|
||||
session = this.supabase.session;
|
||||
|
||||
constructor(private readonly supabase: SupabaseService) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.supabase.authChanges((_, session) => (this.session = session))
|
||||
this.supabase.authChanges((_, session) => (this.session = session));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```html title="src/app/app.component.html"
|
||||
<div class="container" style="padding: 50px 0 100px 0">
|
||||
<app-account *ngIf="session; else auth" [session]="session"></app-account>
|
||||
<ng-template #auth>
|
||||
<app-auth></app-auth>
|
||||
</ng-template>
|
||||
</div>
|
||||
```
|
||||
|
||||
`app.module.ts` also needs to be modified to include the `ReactiveFormsModule` from the `@angular/forms` package.
|
||||
|
||||
```ts title="src/app/app.module.ts"
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { AuthComponent } from './auth/auth.component';
|
||||
import { AccountComponent } from './account/account.component';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { AvatarComponent } from './avatar/avatar.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
AuthComponent,
|
||||
AccountComponent,
|
||||
AvatarComponent,
|
||||
],
|
||||
imports: [BrowserModule, ReactiveFormsModule],
|
||||
providers: [],
|
||||
bootstrap: [AppComponent],
|
||||
})
|
||||
export class AppModule {}
|
||||
```
|
||||
|
||||
Once that's done, run this in a terminal window:
|
||||
|
||||
```bash
|
||||
@@ -474,110 +558,141 @@ Every Supabase project is configured with [Storage](/docs/guides/storage) for ma
|
||||
Let's create an avatar for the user so that they can upload a profile photo.
|
||||
Create an **AvatarComponent** with `ng g c avatar` Angular CLI command.
|
||||
|
||||
```ts title="src/app/avatar.component.ts"
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core'
|
||||
import { SupabaseService } from './supabase.service'
|
||||
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'
|
||||
```ts title="src/app/avatar/avatar.component.ts"
|
||||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
import { SafeResourceUrl, DomSanitizer } from '@angular/platform-browser';
|
||||
import { SupabaseService } from '../supabase.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-avatar',
|
||||
template: `
|
||||
<div>
|
||||
<img
|
||||
*ngIf="_avatarUrl"
|
||||
[src]="_avatarUrl"
|
||||
alt="Avatar"
|
||||
class="avatar image"
|
||||
style="height: 150px; width: 150px"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
*ngIf="!_avatarUrl"
|
||||
class="avatar no-image"
|
||||
style="height: 150px; width: 150px"
|
||||
></div>
|
||||
<div style="width: 150px">
|
||||
<label class="button primary block" for="single">
|
||||
{{ uploading ? 'Uploading ...' : 'Upload' }}
|
||||
</label>
|
||||
<input
|
||||
style="visibility: hidden;position: absolute"
|
||||
type="file"
|
||||
id="single"
|
||||
accept="image/*"
|
||||
(change)="uploadAvatar($event)"
|
||||
[disabled]="uploading"
|
||||
/>
|
||||
</div>
|
||||
`,
|
||||
templateUrl: './avatar.component.html',
|
||||
styleUrls: ['./avatar.component.css'],
|
||||
})
|
||||
export class AvatarComponent {
|
||||
_avatarUrl: SafeResourceUrl | undefined
|
||||
uploading = false
|
||||
export class AvatarComponent implements OnInit {
|
||||
_avatarUrl: SafeResourceUrl | undefined;
|
||||
uploading = false;
|
||||
|
||||
@Input()
|
||||
set avatarUrl(url: string | undefined) {
|
||||
set avatarUrl(url: string | null) {
|
||||
if (url) {
|
||||
this.downloadImage(url)
|
||||
this.downloadImage(url);
|
||||
}
|
||||
}
|
||||
|
||||
@Output() upload = new EventEmitter<string>()
|
||||
@Output() upload = new EventEmitter<string>();
|
||||
|
||||
constructor(
|
||||
private readonly supabase: SupabaseService,
|
||||
private readonly dom: DomSanitizer
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {}
|
||||
|
||||
async downloadImage(path: string) {
|
||||
try {
|
||||
const { data } = await this.supabase.downLoadImage(path)
|
||||
const { data } = await this.supabase.downLoadImage(path);
|
||||
if (data instanceof Blob) {
|
||||
this._avatarUrl = this.dom.bypassSecurityTrustResourceUrl(
|
||||
URL.createObjectURL(data)
|
||||
)
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error downloading image: ', error.message)
|
||||
if (error instanceof Error) {
|
||||
console.error('Error downloading image: ', error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async uploadAvatar(event: any) {
|
||||
try {
|
||||
this.uploading = true
|
||||
this.uploading = true;
|
||||
if (!event.target.files || event.target.files.length === 0) {
|
||||
throw new Error('You must select an image to upload.')
|
||||
throw new Error('You must select an image to upload.');
|
||||
}
|
||||
|
||||
const file = event.target.files[0]
|
||||
const fileExt = file.name.split('.').pop()
|
||||
const fileName = `${Math.random()}.${fileExt}`
|
||||
const filePath = `${fileName}`
|
||||
const file = event.target.files[0];
|
||||
const fileExt = file.name.split('.').pop();
|
||||
const filePath = `${Math.random()}.${fileExt}`;
|
||||
|
||||
await this.supabase.uploadAvatar(filePath, file)
|
||||
this.upload.emit(filePath)
|
||||
await this.supabase.uploadAvatar(filePath, file);
|
||||
this.upload.emit(filePath);
|
||||
} catch (error) {
|
||||
alert(error.message)
|
||||
if (error instanceof Error) {
|
||||
alert(error.message);
|
||||
}
|
||||
} finally {
|
||||
this.uploading = false
|
||||
this.uploading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```html title="src/app/avatar/avatar.component.html"
|
||||
<div>
|
||||
<img
|
||||
*ngIf="_avatarUrl"
|
||||
[src]="_avatarUrl"
|
||||
alt="Avatar"
|
||||
class="avatar image"
|
||||
style="height: 150px; width: 150px"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
*ngIf="!_avatarUrl"
|
||||
class="avatar no-image"
|
||||
style="height: 150px; width: 150px"
|
||||
></div>
|
||||
<div style="width: 150px">
|
||||
<label class="button primary block" for="single">
|
||||
{{ uploading ? 'Uploading ...' : 'Upload' }}
|
||||
</label>
|
||||
<input
|
||||
style="visibility: hidden;position: absolute"
|
||||
type="file"
|
||||
id="single"
|
||||
accept="image/*"
|
||||
(change)="uploadAvatar($event)"
|
||||
[disabled]="uploading"
|
||||
/>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Add the new widget
|
||||
|
||||
And then we can add the widget on top of the **AccountComponent** html template:
|
||||
|
||||
```ts title="src/app/account.component.ts"
|
||||
template: `
|
||||
<app-avatar
|
||||
[avatarUrl]="this.profile?.avatar_url"
|
||||
(upload)="updateProfile(username.value, website.value, $event)">
|
||||
</app-avatar>
|
||||
```html title="src/app/account.component.html"
|
||||
<form [formGroup]="updateProfileForm" (ngSubmit)="updateProfile()" class="form-widget">
|
||||
<app-avatar
|
||||
[avatarUrl]="this.avatarUrl"
|
||||
(upload)="updateAvatar($event)">
|
||||
</app-avatar>
|
||||
<!-- input fields -->
|
||||
</form>
|
||||
```
|
||||
|
||||
<!-- input fields -->
|
||||
`
|
||||
And add an `updateAvatar` function along with an `avatarUrl` getter to the **AccountComponent** typescript file:
|
||||
|
||||
```ts title="src/app/account.component.ts"
|
||||
@Component({
|
||||
selector: 'app-account',
|
||||
templateUrl: './account.component.html',
|
||||
styleUrls: ['./account.component.css'],
|
||||
})
|
||||
export class AccountComponent implements OnInit {
|
||||
// ...
|
||||
get avatarUrl() {
|
||||
return this.updateProfileForm.value.avatar_url as string;
|
||||
}
|
||||
|
||||
async updateAvatar(event: string): Promise<void> {
|
||||
this.updateProfileForm.patchValue({
|
||||
avatar_url: event,
|
||||
});
|
||||
await this.updateProfile();
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## Next steps
|
||||
|
||||
@@ -8,12 +8,6 @@ sidebar_label: Expo
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
|
||||
:::note
|
||||
|
||||
This guide uses supabase-js v2.0.0 Release Candidate (RC).
|
||||
|
||||
:::
|
||||
|
||||
## Intro
|
||||
|
||||
This example provides the steps to build a simple user management app (from scratch!) using Supabase and React Native. It includes:
|
||||
@@ -155,7 +149,7 @@ cd expo-user-management
|
||||
Then let's install the additional dependencies: [supabase-js](https://github.com/supabase/supabase-js)
|
||||
|
||||
```bash
|
||||
npm install @supabase/supabase-js@rc
|
||||
npm install @supabase/supabase-js
|
||||
npm install react-native-elements @react-native-async-storage/async-storage react-native-url-polyfill
|
||||
```
|
||||
|
||||
@@ -165,7 +159,7 @@ These variables will be exposed on the browser, and that's completely fine since
|
||||
[Row Level Security](/docs/guides/auth#row-level-security) enabled on our Database.
|
||||
|
||||
```ts title="lib/supabase.ts"
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage'
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
|
||||
const supabaseUrl = YOUR_REACT_NATIVE_SUPABASE_URL
|
||||
@@ -243,10 +237,18 @@ export default function Auth() {
|
||||
/>
|
||||
</View>
|
||||
<View style={[styles.verticallySpaced, styles.mt20]}>
|
||||
<Button title="Sign in" disabled={loading} onPress={() => signInWithEmail()} />
|
||||
<Button
|
||||
title="Sign in"
|
||||
disabled={loading}
|
||||
onPress={() => signInWithEmail()}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.verticallySpaced}>
|
||||
<Button title="Sign up" disabled={loading} onPress={() => signUpWithEmail()} />
|
||||
<Button
|
||||
title="Sign up"
|
||||
disabled={loading}
|
||||
onPress={() => signUpWithEmail()}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
@@ -360,16 +362,26 @@ export default function Account({ session }: { session: Session }) {
|
||||
<Input label="Email" value={session?.user?.email} disabled />
|
||||
</View>
|
||||
<View style={styles.verticallySpaced}>
|
||||
<Input label="Username" value={username || ''} onChangeText={(text) => setUsername(text)} />
|
||||
<Input
|
||||
label="Username"
|
||||
value={username || ''}
|
||||
onChangeText={(text) => setUsername(text)}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.verticallySpaced}>
|
||||
<Input label="Website" value={website || ''} onChangeText={(text) => setWebsite(text)} />
|
||||
<Input
|
||||
label="Website"
|
||||
value={website || ''}
|
||||
onChangeText={(text) => setWebsite(text)}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View style={[styles.verticallySpaced, styles.mt20]}>
|
||||
<Button
|
||||
title={loading ? 'Loading ...' : 'Update'}
|
||||
onPress={() => updateProfile({ username, website, avatar_url: avatarUrl })}
|
||||
onPress={() =>
|
||||
updateProfile({ username, website, avatar_url: avatarUrl })
|
||||
}
|
||||
disabled={loading}
|
||||
/>
|
||||
</View>
|
||||
@@ -425,7 +437,11 @@ export default function App() {
|
||||
|
||||
return (
|
||||
<View>
|
||||
{session && session.user ? <Account key={session.user.id} session={session} /> : <Auth />}
|
||||
{session && session.user ? (
|
||||
<Account key={session.user.id} session={session} />
|
||||
) : (
|
||||
<Auth />
|
||||
)}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
@@ -461,12 +477,16 @@ We can start by creating a new component:
|
||||
import { useState, useEffect } from 'react'
|
||||
import { supabase } from '../lib/supabase'
|
||||
import { StyleSheet, View, Alert, Image, Button } from 'react-native'
|
||||
import DocumentPicker, { isCancel, isInProgress, types } from 'react-native-document-picker'
|
||||
import DocumentPicker, {
|
||||
isCancel,
|
||||
isInProgress,
|
||||
types,
|
||||
} from 'react-native-document-picker'
|
||||
|
||||
interface Props {
|
||||
size: number
|
||||
url: string | null
|
||||
onUpload: (filePath: string) => void
|
||||
url: string | null
|
||||
onUpload: (filePath: string) => void
|
||||
}
|
||||
|
||||
export default function Avatar({ url, size = 150, onUpload }: Props) {
|
||||
@@ -483,7 +503,7 @@ export default function Avatar({ url, size = 150, onUpload }: Props) {
|
||||
const { data, error } = await supabase.storage
|
||||
.from('avatars')
|
||||
.download(path)
|
||||
|
||||
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
@@ -508,17 +528,17 @@ export default function Avatar({ url, size = 150, onUpload }: Props) {
|
||||
presentationStyle: 'fullScreen',
|
||||
copyTo: 'cachesDirectory',
|
||||
type: types.images,
|
||||
mode: 'open'
|
||||
mode: 'open',
|
||||
})
|
||||
|
||||
const photo = {
|
||||
uri: file.fileCopyUri,
|
||||
type: file.type,
|
||||
name: file.name
|
||||
name: file.name,
|
||||
}
|
||||
|
||||
const formData = new FormData()
|
||||
formData.append("file", photo)
|
||||
formData.append('file', photo)
|
||||
|
||||
const fileExt = file.name.split('.').pop()
|
||||
const filePath = `${Math.random()}.${fileExt}`
|
||||
@@ -537,7 +557,9 @@ export default function Avatar({ url, size = 150, onUpload }: Props) {
|
||||
console.warn('cancelled')
|
||||
// User cancelled the picker, exit any dialogs or menus and move on
|
||||
} else if (isInProgress(error)) {
|
||||
console.warn('multiple pickers were opened, only the last will be considered')
|
||||
console.warn(
|
||||
'multiple pickers were opened, only the last will be considered'
|
||||
)
|
||||
} else if (error instanceof Error) {
|
||||
Alert.alert(error.message)
|
||||
} else {
|
||||
@@ -551,12 +573,20 @@ export default function Avatar({ url, size = 150, onUpload }: Props) {
|
||||
return (
|
||||
<View>
|
||||
{avatarUrl ? (
|
||||
<Image source={{ uri: avatarUrl }} accessibilityLabel="Avatar" style={[avatarSize, styles.avatar, styles.image]} />
|
||||
<Image
|
||||
source={{ uri: avatarUrl }}
|
||||
accessibilityLabel="Avatar"
|
||||
style={[avatarSize, styles.avatar, styles.image]}
|
||||
/>
|
||||
) : (
|
||||
<View style={[avatarSize, styles.avatar, styles.noImage]} />
|
||||
)}
|
||||
<View>
|
||||
<Button title={uploading ? 'Uploading ...' : 'Upload'} onPress={uploadAvatar} disabled={uploading} />
|
||||
<Button
|
||||
title={uploading ? 'Uploading ...' : 'Upload'}
|
||||
onPress={uploadAvatar}
|
||||
disabled={uploading}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
@@ -566,7 +596,7 @@ const styles = StyleSheet.create({
|
||||
avatar: {
|
||||
borderRadius: 5,
|
||||
overflow: 'hidden',
|
||||
maxWidth: '100%'
|
||||
maxWidth: '100%',
|
||||
},
|
||||
image: {
|
||||
objectFit: 'cover',
|
||||
@@ -575,7 +605,7 @@ const styles = StyleSheet.create({
|
||||
noImage: {
|
||||
backgroundColor: '#333',
|
||||
border: '1px solid rgb(200, 200, 200)',
|
||||
borderRadius: 5
|
||||
borderRadius: 5,
|
||||
},
|
||||
})
|
||||
```
|
||||
@@ -593,13 +623,13 @@ import Avatar from './Avatar'
|
||||
<View>
|
||||
{/* Add to the body */}
|
||||
<View>
|
||||
<Avatar
|
||||
<Avatar
|
||||
size={200}
|
||||
url={avatarUrl}
|
||||
url={avatarUrl}
|
||||
onUpload={(url: string) => {
|
||||
setAvatarUrl(url)
|
||||
updateProfile({ username, website, avatar_url: url })
|
||||
}}
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
{/* ... */}
|
||||
|
||||
@@ -441,7 +441,7 @@ import { Session } from '@supabase/supabase-js'
|
||||
setupIonicReact()
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [session, setSession] = useState<Session | null>(null)
|
||||
const [session, setSession] = (useState < Session) | (null > null)
|
||||
useEffect(() => {
|
||||
setSession(supabase.auth.session())
|
||||
supabase.auth.onAuthStateChange((_event, session) => {
|
||||
|
||||
@@ -8,12 +8,6 @@ sidebar_label: 'Next.js'
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
|
||||
:::note
|
||||
|
||||
This guide uses supabase-js v2.0.0 Release Candidate (RC).
|
||||
|
||||
:::
|
||||
|
||||
## Intro
|
||||
|
||||
This example provides the steps to build a simple user management app (from scratch!) using Supabase and Next.js. It includes:
|
||||
@@ -67,6 +61,7 @@ Now we are going to set up the database schema. We can use the "User Management
|
||||
or you can just copy/paste the SQL from below and run it yourself.
|
||||
|
||||
<Tabs
|
||||
groupId="language"
|
||||
defaultValue="dashboard"
|
||||
values={[
|
||||
{label: 'Dashboard', value: 'dashboard'},
|
||||
@@ -91,51 +86,59 @@ or you can just copy/paste the SQL from below and run it yourself.
|
||||
<TabItem value="sql">
|
||||
|
||||
```sql
|
||||
-- Create a table for public "profiles"
|
||||
-- Create a table for public profiles
|
||||
create table profiles (
|
||||
id uuid references auth.users not null,
|
||||
id uuid references auth.users not null primary key,
|
||||
updated_at timestamp with time zone,
|
||||
username text unique,
|
||||
full_name text,
|
||||
avatar_url text,
|
||||
website text,
|
||||
|
||||
primary key (id),
|
||||
unique(username),
|
||||
constraint username_length check (char_length(username) >= 3)
|
||||
);
|
||||
-- Set up Row Level Security (RLS)
|
||||
-- See https://supabase.com/docs/guides/auth/row-level-security for more details.
|
||||
alter table profiles
|
||||
enable row level security;
|
||||
|
||||
alter table profiles enable row level security;
|
||||
create policy "Public profiles are viewable by everyone." on profiles
|
||||
for select using (true);
|
||||
|
||||
create policy "Public profiles are viewable by everyone."
|
||||
on profiles for select
|
||||
using ( true );
|
||||
create policy "Users can insert their own profile." on profiles
|
||||
for insert with check (auth.uid() = id);
|
||||
|
||||
create policy "Users can insert their own profile."
|
||||
on profiles for insert
|
||||
with check ( auth.uid() = id );
|
||||
create policy "Users can update own profile." on profiles
|
||||
for update using (auth.uid() = id);
|
||||
|
||||
create policy "Users can update own profile."
|
||||
on profiles for update
|
||||
using ( auth.uid() = id );
|
||||
|
||||
-- Set up Realtime!
|
||||
begin;
|
||||
drop publication if exists supabase_realtime;
|
||||
create publication supabase_realtime;
|
||||
commit;
|
||||
alter publication supabase_realtime add table profiles;
|
||||
-- This trigger automatically creates a profile entry when a new user signs up via Supabase Auth.
|
||||
-- See https://supabase.com/docs/guides/auth/managing-user-data#using-triggers for more details.
|
||||
create function public.handle_new_user()
|
||||
returns trigger as $$
|
||||
begin
|
||||
insert into public.profiles (id, full_name, avatar_url)
|
||||
values (new.id, new.raw_user_meta_data->>'full_name', new.raw_user_meta_data->>'avatar_url');
|
||||
return new;
|
||||
end;
|
||||
$$ language plpgsql security definer;
|
||||
create trigger on_auth_user_created
|
||||
after insert on auth.users
|
||||
for each row execute procedure public.handle_new_user();
|
||||
|
||||
-- Set up Storage!
|
||||
insert into storage.buckets (id, name)
|
||||
values ('avatars', 'avatars');
|
||||
values ('avatars', 'avatars');
|
||||
|
||||
create policy "Avatar images are publicly accessible."
|
||||
on storage.objects for select
|
||||
using ( bucket_id = 'avatars' );
|
||||
-- Set up access controls for storage.
|
||||
-- See https://supabase.com/docs/guides/storage#policy-examples for more details.
|
||||
create policy "Avatar images are publicly accessible." on storage.objects
|
||||
for select using (bucket_id = 'avatars');
|
||||
|
||||
create policy "Anyone can upload an avatar."
|
||||
on storage.objects for insert
|
||||
with check ( bucket_id = 'avatars' );
|
||||
create policy "Anyone can upload an avatar." on storage.objects
|
||||
for insert with check (bucket_id = 'avatars');
|
||||
|
||||
create policy "Anyone can update their own avatar." on storage.objects
|
||||
for update using ( auth.uid() = owner ) with check (bucket_id = 'avatars');
|
||||
|
||||
```
|
||||
|
||||
@@ -169,15 +172,35 @@ Let's start building the Next.js app from scratch.
|
||||
We can use [`create-next-app`](https://nextjs.org/docs/getting-started) to initialize
|
||||
an app called `supabase-nextjs`:
|
||||
|
||||
<Tabs
|
||||
groupId="language"
|
||||
defaultValue="js"
|
||||
values={[
|
||||
{label: 'JavaScript', value: 'js'},
|
||||
{label: 'TypeScript', value: 'ts'},
|
||||
]}>
|
||||
<TabItem value="js">
|
||||
|
||||
```bash
|
||||
npx create-next-app supabase-nextjs --use-npm
|
||||
npx create-next-app@latest --use-npm supabase-nextjs
|
||||
cd supabase-nextjs
|
||||
```
|
||||
|
||||
Then let's install the only additional dependency: [supabase-js](https://github.com/supabase/supabase-js)
|
||||
</TabItem>
|
||||
<TabItem value="ts">
|
||||
|
||||
```bash
|
||||
npm install @supabase/supabase-js@rc
|
||||
npx create-next-app@latest --ts --use-npm supabase-nextjs
|
||||
cd supabase-nextjs
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Then install the Supabase client library: [supabase-js](https://github.com/supabase/supabase-js)
|
||||
|
||||
```bash
|
||||
npm install @supabase/supabase-js
|
||||
```
|
||||
|
||||
And finally we want to save the environment variables in a `.env.local`.
|
||||
@@ -188,93 +211,148 @@ NEXT_PUBLIC_SUPABASE_URL=YOUR_SUPABASE_URL
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
|
||||
```
|
||||
|
||||
Now that we have the API credentials in place, let's create a helper file to initialize the Supabase client.
|
||||
These variables will be exposed on the browser, and that's completely fine since we have
|
||||
[Row Level Security](/docs/guides/auth#row-level-security) enabled on our Database.
|
||||
|
||||
```js title="utils/supabaseClient.js"
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
|
||||
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL
|
||||
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
|
||||
|
||||
export const supabase = createClient(supabaseUrl, supabaseAnonKey)
|
||||
```
|
||||
|
||||
And one optional step is to update the CSS file `styles/globals.css` to make the app look nice.
|
||||
You can find the full contents of this file [here](https://raw.githubusercontent.com/supabase/examples/main/supabase-js-v2/user-management/nextjs-ts-user-management/styles/globals.css).
|
||||
You can find the full contents of this file [here](https://raw.githubusercontent.com/supabase/supabase/master/examples/user-management/nextjs-ts-user-management/styles/globals.css).
|
||||
|
||||
### Set up a Login component
|
||||
|
||||
Let's set up a React component to manage logins and sign ups.
|
||||
We'll use Magic Links, so users can sign in with their email without using passwords.
|
||||
#### Supabase Auth Helpers
|
||||
|
||||
```jsx title="components/Auth.js"
|
||||
import { useState } from 'react'
|
||||
import { supabase } from '../utils/supabaseClient'
|
||||
Next.js is a highly versatile framework offering pre-rendering at build time (SSG), server-side rendering at request time (SSR), API routes, and middleware edge-functions.
|
||||
|
||||
export default function Auth() {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [email, setEmail] = useState('')
|
||||
It can be challenging to authenticate your users in all these different environments, that's why we've created the [Supabase Auth Helpers](https://supabase.com/docs/guides/auth/auth-helpers/nextjs) to make user management and data fetching within Next.js as easy as possible.
|
||||
|
||||
const handleLogin = async (email) => {
|
||||
try {
|
||||
setLoading(true)
|
||||
const { error } = await supabase.auth.signInWithOtp({ email })
|
||||
if (error) throw error
|
||||
alert('Check your email for the login link!')
|
||||
} catch (error) {
|
||||
alert(error.error_description || error.message)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
Install the auth helpers for React and Next.js
|
||||
|
||||
```bash
|
||||
npm install @supabase/auth-helpers-react @supabase/auth-helpers-nextjs
|
||||
```
|
||||
|
||||
<Tabs
|
||||
groupId="language"
|
||||
defaultValue="js"
|
||||
values={[
|
||||
{label: 'JavaScript', value: 'js'},
|
||||
{label: 'TypeScript', value: 'ts'},
|
||||
]}>
|
||||
<TabItem value="js">
|
||||
|
||||
Wrap your `pages/_app.js` component with the `SessionContextProvider` component:
|
||||
|
||||
```jsx title="pages/_app.js"
|
||||
import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'
|
||||
import { SessionContextProvider } from '@supabase/auth-helpers-react'
|
||||
|
||||
function MyApp({ Component, pageProps }) {
|
||||
const router = useRouter()
|
||||
const [supabaseClient] = useState(() => createBrowserSupabaseClient())
|
||||
|
||||
return (
|
||||
<div className="row flex-center flex">
|
||||
<div className="col-6 form-widget">
|
||||
<h1 className="header">Supabase + Next.js</h1>
|
||||
<p className="description">
|
||||
Sign in via magic link with your email below
|
||||
</p>
|
||||
<div>
|
||||
<input
|
||||
className="inputField"
|
||||
type="email"
|
||||
placeholder="Your email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
handleLogin(email)
|
||||
}}
|
||||
className="button block"
|
||||
disabled={loading}
|
||||
>
|
||||
<span>{loading ? 'Loading' : 'Send magic link'}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<SessionContextProvider
|
||||
supabaseClient={supabaseClient}
|
||||
initialSession={pageProps.initialSession}
|
||||
<Component {...pageProps} />
|
||||
</SessionContextProvider>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="ts">
|
||||
|
||||
Wrap your `pages/_app.tsx` component with the `SessionContextProvider` component:
|
||||
|
||||
```jsx title="pages/_app.tsx"
|
||||
import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'
|
||||
// highlight-next-line
|
||||
import { SessionContextProvider, Session } from '@supabase/auth-helpers-react'
|
||||
|
||||
function MyApp({
|
||||
Component,
|
||||
pageProps,
|
||||
}: AppProps<{
|
||||
// highlight-next-line
|
||||
initialSession: Session,
|
||||
}>) {
|
||||
const [supabaseClient] = useState(() => createBrowserSupabaseClient())
|
||||
|
||||
return (
|
||||
<SessionContextProvider
|
||||
supabaseClient={supabaseClient}
|
||||
initialSession={pageProps.initialSession}
|
||||
>
|
||||
<Component {...pageProps} />
|
||||
</SessionContextProvider>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
See the [Auth Helpers docs](/docs/guides/auth/auth-helpers/nextjs#usage-with-typescript) for more details on usage with TypeScript.
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
#### Supabase Auth UI
|
||||
|
||||
We can use the [Supabase Auth UI](https://supabase.com/docs/guides/auth/auth-helpers/auth-ui) a pre-built React component for authenticating users via OAuth, email, and magic links.
|
||||
|
||||
Install the Supabase Auth UI for React
|
||||
|
||||
```bash
|
||||
npm install @supabase/auth-ui-react
|
||||
```
|
||||
|
||||
Add the `Auth` component to your home page
|
||||
|
||||
```jsx title="pages/index.js"
|
||||
import type { NextPage } from 'next'
|
||||
import { Auth, ThemeSupa } from '@supabase/auth-ui-react'
|
||||
import { useSession, useSupabaseClient } from '@supabase/auth-helpers-react'
|
||||
|
||||
const Home: NextPage = () => {
|
||||
const session = useSession()
|
||||
const supabase = useSupabaseClient()
|
||||
|
||||
return (
|
||||
<div className="container" style={{ padding: '50px 0 100px 0' }}>
|
||||
{!session ? (
|
||||
<Auth
|
||||
supabaseClient={supabase}
|
||||
appearance={{ theme: ThemeSupa }}
|
||||
theme="dark"
|
||||
/>
|
||||
) : (
|
||||
<p>Account page will go here.</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Home
|
||||
```
|
||||
|
||||
### Account page
|
||||
|
||||
After a user is signed in we can allow them to edit their profile details and manage their account.
|
||||
|
||||
Let's create a new component for that called `Account.js`.
|
||||
<Tabs
|
||||
groupId="language"
|
||||
defaultValue="js"
|
||||
values={[
|
||||
{label: 'JavaScript', value: 'js'},
|
||||
{label: 'TypeScript', value: 'ts'},
|
||||
]}>
|
||||
<TabItem value="js">
|
||||
|
||||
```jsx title="components/Account.js"
|
||||
Let's create a new component for that called `Account.js` within a `components` folder.
|
||||
|
||||
```tsx title="components/Account.js"
|
||||
import { useState, useEffect } from 'react'
|
||||
import { supabase } from '../utils/supabaseClient'
|
||||
import { useUser, useSupabaseClient } from '@supabase/auth-helpers-react'
|
||||
|
||||
export default function Account({ session }) {
|
||||
const supabase = useSupabaseClient()
|
||||
const user = useUser()
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [username, setUsername] = useState(null)
|
||||
const [website, setWebsite] = useState(null)
|
||||
@@ -284,27 +362,9 @@ export default function Account({ session }) {
|
||||
getProfile()
|
||||
}, [session])
|
||||
|
||||
async function getCurrentUser() {
|
||||
const {
|
||||
data: { session },
|
||||
error,
|
||||
} = await supabase.auth.getSession()
|
||||
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
|
||||
if (!session?.user) {
|
||||
throw new Error('User not logged in')
|
||||
}
|
||||
|
||||
return session.user
|
||||
}
|
||||
|
||||
async function getProfile() {
|
||||
try {
|
||||
setLoading(true)
|
||||
const user = await getCurrentUser()
|
||||
|
||||
let { data, error, status } = await supabase
|
||||
.from('profiles')
|
||||
@@ -322,7 +382,8 @@ export default function Account({ session }) {
|
||||
setAvatarUrl(data.avatar_url)
|
||||
}
|
||||
} catch (error) {
|
||||
alert(error.message)
|
||||
alert('Error loading user data!')
|
||||
console.log(error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
@@ -331,23 +392,21 @@ export default function Account({ session }) {
|
||||
async function updateProfile({ username, website, avatar_url }) {
|
||||
try {
|
||||
setLoading(true)
|
||||
const user = await getCurrentUser()
|
||||
|
||||
const updates = {
|
||||
id: user.id,
|
||||
username,
|
||||
website,
|
||||
avatar_url,
|
||||
updated_at: new Date(),
|
||||
updated_at: new Date().toISOString(),
|
||||
}
|
||||
|
||||
let { error } = await supabase.from('profiles').upsert(updates)
|
||||
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
if (error) throw error
|
||||
alert('Profile updated!')
|
||||
} catch (error) {
|
||||
alert(error.message)
|
||||
alert('Error updating the data!')
|
||||
console.log(error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
@@ -360,7 +419,7 @@ export default function Account({ session }) {
|
||||
<input id="email" type="text" value={session.user.email} disabled />
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="username">Name</label>
|
||||
<label htmlFor="username">Username</label>
|
||||
<input
|
||||
id="username"
|
||||
type="text"
|
||||
@@ -401,63 +460,176 @@ export default function Account({ session }) {
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="ts">
|
||||
|
||||
Let's create a new component for that called `Account.tsx` within a `components` folder.
|
||||
|
||||
```tsx title="components/Account.tsx"
|
||||
import { useState, useEffect } from 'react'
|
||||
import {
|
||||
useUser,
|
||||
useSupabaseClient,
|
||||
Session,
|
||||
} from '@supabase/auth-helpers-react'
|
||||
import { Database } from '../utils/database.types'
|
||||
type Profiles = Database['public']['Tables']['profiles']['Row']
|
||||
|
||||
export default function Account({ session }: { session: Session }) {
|
||||
const supabase = useSupabaseClient<Database>()
|
||||
const user = useUser()
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [username, setUsername] = useState<Profiles['username']>(null)
|
||||
const [website, setWebsite] = useState<Profiles['website']>(null)
|
||||
const [avatar_url, setAvatarUrl] = useState<Profiles['avatar_url']>(null)
|
||||
|
||||
useEffect(() => {
|
||||
getProfile()
|
||||
}, [session])
|
||||
|
||||
async function getProfile() {
|
||||
try {
|
||||
setLoading(true)
|
||||
if (!user) throw new Error('No user')
|
||||
|
||||
let { data, error, status } = await supabase
|
||||
.from('profiles')
|
||||
.select(`username, website, avatar_url`)
|
||||
.eq('id', user.id)
|
||||
.single()
|
||||
|
||||
if (error && status !== 406) {
|
||||
throw error
|
||||
}
|
||||
|
||||
if (data) {
|
||||
setUsername(data.username)
|
||||
setWebsite(data.website)
|
||||
setAvatarUrl(data.avatar_url)
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Error loading user data!')
|
||||
console.log(error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
async function updateProfile({
|
||||
username,
|
||||
website,
|
||||
avatar_url,
|
||||
}: {
|
||||
username: Profiles['username']
|
||||
website: Profiles['website']
|
||||
avatar_url: Profiles['avatar_url']
|
||||
}) {
|
||||
try {
|
||||
setLoading(true)
|
||||
if (!user) throw new Error('No user')
|
||||
|
||||
const updates = {
|
||||
id: user.id,
|
||||
username,
|
||||
website,
|
||||
avatar_url,
|
||||
updated_at: new Date().toISOString(),
|
||||
}
|
||||
|
||||
let { error } = await supabase.from('profiles').upsert(updates)
|
||||
if (error) throw error
|
||||
alert('Profile updated!')
|
||||
} catch (error) {
|
||||
alert('Error updating the data!')
|
||||
console.log(error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="form-widget">
|
||||
<div>
|
||||
<label htmlFor="email">Email</label>
|
||||
<input id="email" type="text" value={session.user.email} disabled />
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="username">Username</label>
|
||||
<input
|
||||
id="username"
|
||||
type="text"
|
||||
value={username || ''}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="website">Website</label>
|
||||
<input
|
||||
id="website"
|
||||
type="website"
|
||||
value={website || ''}
|
||||
onChange={(e) => setWebsite(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
className="button primary block"
|
||||
onClick={() => updateProfile({ username, website, avatar_url })}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? 'Loading ...' : 'Update'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
className="button block"
|
||||
onClick={() => supabase.auth.signOut()}
|
||||
>
|
||||
Sign Out
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### Launch!
|
||||
|
||||
Now that we have all the components in place, let's update `pages/index.js`:
|
||||
|
||||
```jsx title="pages/index.js"
|
||||
import { useState, useEffect } from 'react'
|
||||
import { supabase } from '../utils/supabaseClient'
|
||||
import Auth from '../components/Auth'
|
||||
import type { NextPage } from 'next'
|
||||
import { Auth, ThemeSupa } from '@supabase/auth-ui-react'
|
||||
import { useSession, useSupabaseClient } from '@supabase/auth-helpers-react'
|
||||
// highlight-next-line
|
||||
import Account from '../components/Account'
|
||||
|
||||
export default function Home() {
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [session, setSession] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
let mounted = true
|
||||
|
||||
async function getInitialSession() {
|
||||
const {
|
||||
data: { session },
|
||||
} = await supabase.auth.getSession()
|
||||
|
||||
// only update the react state if the component is still mounted
|
||||
if (mounted) {
|
||||
if (session) {
|
||||
setSession(session)
|
||||
}
|
||||
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
getInitialSession()
|
||||
|
||||
const { subscription } = supabase.auth.onAuthStateChange(
|
||||
(_event, session) => {
|
||||
setSession(session)
|
||||
}
|
||||
)
|
||||
|
||||
return () => {
|
||||
mounted = false
|
||||
|
||||
subscription?.unsubscribe()
|
||||
}
|
||||
}, [])
|
||||
const Home: NextPage = () => {
|
||||
const session = useSession()
|
||||
const supabase = useSupabaseClient()
|
||||
|
||||
return (
|
||||
<div className="container" style={{ padding: '50px 0 100px 0' }}>
|
||||
{!session ? (
|
||||
<Auth />
|
||||
<Auth
|
||||
supabaseClient={supabase}
|
||||
appearance={{ theme: ThemeSupa }}
|
||||
theme="dark"
|
||||
/>
|
||||
) : (
|
||||
<Account key={session.user.id} session={session} />
|
||||
// highlight-next-line
|
||||
<Account session={session} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Home
|
||||
```
|
||||
|
||||
Once that's done, run this in a terminal window:
|
||||
@@ -468,8 +640,6 @@ npm run dev
|
||||
|
||||
And then open the browser to [localhost:3000](http://localhost:3000) and you should see the completed app.
|
||||
|
||||

|
||||
|
||||
## Bonus: Profile photos
|
||||
|
||||
Every Supabase project is configured with [Storage](/docs/guides/storage) for managing large files like
|
||||
@@ -477,14 +647,23 @@ photos and videos.
|
||||
|
||||
### Create an upload widget
|
||||
|
||||
Let's create an avatar for the user so that they can upload a profile photo.
|
||||
We can start by creating a new component:
|
||||
Let's create an avatar widget for the user so that they can upload a profile photo. We can start by creating a new component:
|
||||
|
||||
<Tabs
|
||||
groupId="language"
|
||||
defaultValue="js"
|
||||
values={[
|
||||
{label: 'JavaScript', value: 'js'},
|
||||
{label: 'TypeScript', value: 'ts'},
|
||||
]}>
|
||||
<TabItem value="js">
|
||||
|
||||
```jsx title="components/Avatar.js"
|
||||
import { useEffect, useState } from 'react'
|
||||
import { supabase } from '../utils/supabaseClient'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useSupabaseClient } from '@supabase/auth-helpers-react'
|
||||
|
||||
export default function Avatar({ url, size, onUpload }) {
|
||||
export default function Avatar({ uid, url, size, onUpload }) {
|
||||
const supabase = useSupabaseClient()
|
||||
const [avatarUrl, setAvatarUrl] = useState(null)
|
||||
const [uploading, setUploading] = useState(false)
|
||||
|
||||
@@ -503,11 +682,11 @@ export default function Avatar({ url, size, onUpload }) {
|
||||
const url = URL.createObjectURL(data)
|
||||
setAvatarUrl(url)
|
||||
} catch (error) {
|
||||
console.log('Error downloading image: ', error.message)
|
||||
console.log('Error downloading image: ', error)
|
||||
}
|
||||
}
|
||||
|
||||
async function uploadAvatar(event) {
|
||||
const uploadAvatar = async (event) => {
|
||||
try {
|
||||
setUploading(true)
|
||||
|
||||
@@ -517,12 +696,12 @@ export default function Avatar({ url, size, onUpload }) {
|
||||
|
||||
const file = event.target.files[0]
|
||||
const fileExt = file.name.split('.').pop()
|
||||
const fileName = `${Math.random()}.${fileExt}`
|
||||
const fileName = `${uid}.${fileExt}`
|
||||
const filePath = `${fileName}`
|
||||
|
||||
let { error: uploadError } = await supabase.storage
|
||||
.from('avatars')
|
||||
.upload(filePath, file)
|
||||
.upload(filePath, file, { upsert: true })
|
||||
|
||||
if (uploadError) {
|
||||
throw uploadError
|
||||
@@ -530,7 +709,8 @@ export default function Avatar({ url, size, onUpload }) {
|
||||
|
||||
onUpload(filePath)
|
||||
} catch (error) {
|
||||
alert(error.message)
|
||||
alert('Error uploading avatar!')
|
||||
console.log(error)
|
||||
} finally {
|
||||
setUploading(false)
|
||||
}
|
||||
@@ -572,6 +752,120 @@ export default function Avatar({ url, size, onUpload }) {
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="ts">
|
||||
|
||||
```tsx title="components/Avatar.tsx"
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useSupabaseClient } from '@supabase/auth-helpers-react'
|
||||
import { Database } from '../utils/database.types'
|
||||
type Profiles = Database['public']['Tables']['profiles']['Row']
|
||||
|
||||
export default function Avatar({
|
||||
uid,
|
||||
url,
|
||||
size,
|
||||
onUpload,
|
||||
}: {
|
||||
uid: string
|
||||
url: Profiles['avatar_url']
|
||||
size: number
|
||||
onUpload: (url: string) => void
|
||||
}) {
|
||||
const supabase = useSupabaseClient<Database>()
|
||||
const [avatarUrl, setAvatarUrl] = useState<Profiles['avatar_url']>(null)
|
||||
const [uploading, setUploading] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (url) downloadImage(url)
|
||||
}, [url])
|
||||
|
||||
async function downloadImage(path: string) {
|
||||
try {
|
||||
const { data, error } = await supabase.storage
|
||||
.from('avatars')
|
||||
.download(path)
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
const url = URL.createObjectURL(data)
|
||||
setAvatarUrl(url)
|
||||
} catch (error) {
|
||||
console.log('Error downloading image: ', error)
|
||||
}
|
||||
}
|
||||
|
||||
const uploadAvatar: React.ChangeEventHandler<HTMLInputElement> = async (
|
||||
event
|
||||
) => {
|
||||
try {
|
||||
setUploading(true)
|
||||
|
||||
if (!event.target.files || event.target.files.length === 0) {
|
||||
throw new Error('You must select an image to upload.')
|
||||
}
|
||||
|
||||
const file = event.target.files[0]
|
||||
const fileExt = file.name.split('.').pop()
|
||||
const fileName = `${uid}.${fileExt}`
|
||||
const filePath = `${fileName}`
|
||||
|
||||
let { error: uploadError } = await supabase.storage
|
||||
.from('avatars')
|
||||
.upload(filePath, file, { upsert: true })
|
||||
|
||||
if (uploadError) {
|
||||
throw uploadError
|
||||
}
|
||||
|
||||
onUpload(filePath)
|
||||
} catch (error) {
|
||||
alert('Error uploading avatar!')
|
||||
console.log(error)
|
||||
} finally {
|
||||
setUploading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{avatarUrl ? (
|
||||
<img
|
||||
src={avatarUrl}
|
||||
alt="Avatar"
|
||||
className="avatar image"
|
||||
style={{ height: size, width: size }}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
className="avatar no-image"
|
||||
style={{ height: size, width: size }}
|
||||
/>
|
||||
)}
|
||||
<div style={{ width: size }}>
|
||||
<label className="button primary block" htmlFor="single">
|
||||
{uploading ? 'Uploading ...' : 'Upload'}
|
||||
</label>
|
||||
<input
|
||||
style={{
|
||||
visibility: 'hidden',
|
||||
position: 'absolute',
|
||||
}}
|
||||
type="file"
|
||||
id="single"
|
||||
accept="image/*"
|
||||
onChange={uploadAvatar}
|
||||
disabled={uploading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### Add the new widget
|
||||
|
||||
And then we can add the widget to the Account page:
|
||||
@@ -586,6 +880,7 @@ return (
|
||||
<div className="form-widget">
|
||||
{/* Add to the body */}
|
||||
<Avatar
|
||||
uid={user.id}
|
||||
url={avatar_url}
|
||||
size={150}
|
||||
onUpload={(url) => {
|
||||
@@ -602,5 +897,8 @@ return (
|
||||
|
||||
At this stage you have a fully functional application!
|
||||
|
||||
- See the complete [example on GitHub](https://github.com/supabase/supabase/tree/master/examples/user-management/nextjs-ts-user-management) and deploy it to Vercel.
|
||||
- Explore the [Auth Helpers for Next.js](https://supabase.com/docs/guides/auth/auth-helpers/nextjs).
|
||||
- Explore the [pre-built Auth UI for React](https://supabase.com/docs/guides/auth/auth-helpers/auth-ui).
|
||||
- Got a question? [Ask here](https://github.com/supabase/supabase/discussions).
|
||||
- Sign in: [app.supabase.com](https://app.supabase.com)
|
||||
@@ -188,7 +188,7 @@ Let's set up a Vue component to manage logins and sign ups. We'll use Magic Link
|
||||
|
||||
```html title="/components/Auth.vue"
|
||||
<template>
|
||||
<form class="flex row flex-center" @submit.prevent="handleLogin">
|
||||
<form class="row flex-center flex" @submit.prevent="handleLogin">
|
||||
<div class="col-6 form-widget">
|
||||
<h1 class="header">Supabase + Nuxt 3</h1>
|
||||
<p class="description">Sign in via magic link with your email below</p>
|
||||
@@ -203,7 +203,7 @@ Let's set up a Vue component to manage logins and sign ups. We'll use Magic Link
|
||||
<div>
|
||||
<input
|
||||
type="submit"
|
||||
class="block button"
|
||||
class="button block"
|
||||
:value="loading ? 'Loading' : 'Send magic link'"
|
||||
:disabled="loading"
|
||||
/>
|
||||
@@ -260,14 +260,14 @@ Let's create a new component for that called `Account.vue`.
|
||||
<div>
|
||||
<input
|
||||
type="submit"
|
||||
class="block button primary"
|
||||
class="button primary block"
|
||||
:value="loading ? 'Loading ...' : 'Update'"
|
||||
:disabled="loading"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button class="block button" @click="signOut" :disabled="loading">
|
||||
<button class="button block" @click="signOut" :disabled="loading">
|
||||
Sign Out
|
||||
</button>
|
||||
</div>
|
||||
@@ -346,7 +346,7 @@ Now that we have all the components in place, let's update `app.vue`:
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const user = useSupabaseUser();
|
||||
const user = useSupabaseUser()
|
||||
</script>
|
||||
```
|
||||
|
||||
@@ -385,7 +385,7 @@ Let's create an avatar for the user so that they can upload a profile photo. We
|
||||
/>
|
||||
|
||||
<div style="width: 10em; position: relative;">
|
||||
<label class="block button primary" for="single">
|
||||
<label class="button primary block" for="single">
|
||||
{{ uploading ? "Uploading ..." : "Upload" }}
|
||||
</label>
|
||||
<input
|
||||
@@ -481,14 +481,14 @@ And then we can add the widget to the Account page:
|
||||
<div>
|
||||
<input
|
||||
type="submit"
|
||||
class="block button primary"
|
||||
class="button primary block"
|
||||
:value="loading ? 'Loading ...' : 'Update'"
|
||||
:disabled="loading"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button class="block button" @click="signOut" :disabled="loading">
|
||||
<button class="button block" @click="signOut" :disabled="loading">
|
||||
Sign Out
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -8,12 +8,6 @@ sidebar_label: React
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
|
||||
:::note
|
||||
|
||||
This guide uses supabase-js v2.0.0 Release Candidate (RC).
|
||||
|
||||
:::
|
||||
|
||||
## Intro
|
||||
|
||||
This example provides the steps to build a simple user management app (from scratch!) using Supabase and React. It includes:
|
||||
@@ -174,10 +168,10 @@ npx create-react-app supabase-react
|
||||
cd supabase-react
|
||||
```
|
||||
|
||||
Then let's install the only additional dependency: [supabase-js](https://github.com/supabase/supabase-js). We're going to install the "rc" (release candidate) version until this is released for general use.
|
||||
Then let's install the only additional dependency: [supabase-js](https://github.com/supabase/supabase-js).
|
||||
|
||||
```bash
|
||||
npm install @supabase/supabase-js@rc
|
||||
npm install @supabase/supabase-js
|
||||
```
|
||||
|
||||
And finally we want to save the environment variables in a `.env`.
|
||||
|
||||
@@ -8,12 +8,6 @@ sidebar_label: SolidJS
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
|
||||
:::note
|
||||
|
||||
This guide uses supabase-js v2.0.0 Release Candidate (RC).
|
||||
|
||||
:::
|
||||
|
||||
## Intro
|
||||
|
||||
This example provides the steps to build a simple user management app (from scratch!) using Supabase and Solid JS. It includes:
|
||||
@@ -176,7 +170,7 @@ cd supabase-solid
|
||||
Then let's install the only additional dependency: [supabase-js](https://github.com/supabase/supabase-js)
|
||||
|
||||
```bash
|
||||
npm install @supabase/supabase-js@rc
|
||||
npm install @supabase/supabase-js
|
||||
```
|
||||
|
||||
And finally we want to save the environment variables in a `.env`.
|
||||
@@ -272,7 +266,7 @@ import { Component, createEffect, createSignal } from 'solid-js'
|
||||
import { supabase } from './supabaseClient'
|
||||
|
||||
interface Props {
|
||||
session: AuthSession
|
||||
session: AuthSession;
|
||||
}
|
||||
|
||||
const Account: Component<Props> = ({ session }) => {
|
||||
@@ -366,11 +360,19 @@ const Account: Component<Props> = ({ session }) => {
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<button type="submit" class="button primary block" disabled={loading()}>
|
||||
<button
|
||||
type="submit"
|
||||
class="button primary block"
|
||||
disabled={loading()}
|
||||
>
|
||||
{loading() ? 'Saving ...' : 'Update profile'}
|
||||
</button>
|
||||
</div>
|
||||
<button type="button" class="button block" onClick={() => supabase.auth.signOut()}>
|
||||
<button
|
||||
type="button"
|
||||
class="button block"
|
||||
onClick={() => supabase.auth.signOut()}
|
||||
>
|
||||
Sign Out
|
||||
</button>
|
||||
</form>
|
||||
|
||||
@@ -8,12 +8,6 @@ sidebar_label: Svelte
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
|
||||
:::note
|
||||
|
||||
This guide uses supabase-js v2.0.0 Release Candidate (RC).
|
||||
|
||||
:::
|
||||
|
||||
## Intro
|
||||
|
||||
This example provides the steps to build a simple user management app (from scratch!) using Supabase and Svelte. It includes:
|
||||
@@ -160,7 +154,7 @@ npm install
|
||||
Then let's install the only additional dependency: [supabase-js](https://github.com/supabase/supabase-js)
|
||||
|
||||
```bash
|
||||
npm install @supabase/supabase-js@rc
|
||||
npm install @supabase/supabase-js
|
||||
```
|
||||
|
||||
And finally we want to save the environment variables in a `.env`.
|
||||
@@ -192,7 +186,7 @@ Let's set up a Svelte component to manage logins and sign ups. We'll use Magic L
|
||||
|
||||
```html title="src/lib/Auth.svelte"
|
||||
<script lang="ts">
|
||||
import { supabase } from "src/supabaseClient";
|
||||
import { supabase } from 'src/supabaseClient'
|
||||
|
||||
let loading = false
|
||||
let email = ''
|
||||
@@ -217,7 +211,7 @@ Let's set up a Svelte component to manage logins and sign ups. We'll use Magic L
|
||||
<div class="col-6 form-widget" aria-live="polite">
|
||||
<h1 class="header">Supabase + Svelte</h1>
|
||||
<p class="description">Sign in via magic link with your email below</p>
|
||||
<form class="form-widget" on:submit|preventDefault={handleLogin}>
|
||||
<form class="form-widget" on:submit|preventDefault="{handleLogin}">
|
||||
<div>
|
||||
<label for="email">Email</label>
|
||||
<input
|
||||
@@ -225,11 +219,16 @@ Let's set up a Svelte component to manage logins and sign ups. We'll use Magic L
|
||||
class="inputField"
|
||||
type="email"
|
||||
placeholder="Your email"
|
||||
bind:value={email}
|
||||
bind:value="{email}"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<button type="submit" class="button block" aria-live="polite" disabled={loading}>
|
||||
<button
|
||||
type="submit"
|
||||
class="button block"
|
||||
aria-live="polite"
|
||||
disabled="{loading}"
|
||||
>
|
||||
<span>{loading ? 'Loading' : 'Send magic link'}</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -264,13 +263,13 @@ Let's create a new component for that called `Account.svelte`.
|
||||
try {
|
||||
loading = true
|
||||
const { user } = session
|
||||
|
||||
|
||||
const { data, error, status } = await supabase
|
||||
.from('profiles')
|
||||
.select('username, website, avatar_url')
|
||||
.eq('id', user.id)
|
||||
.single()
|
||||
|
||||
|
||||
if (error && status !== 406) throw error
|
||||
|
||||
if (data) {
|
||||
@@ -301,7 +300,7 @@ Let's create a new component for that called `Account.svelte`.
|
||||
}
|
||||
|
||||
let { error } = await supabase.from('profiles').upsert(updates)
|
||||
|
||||
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
@@ -342,10 +341,10 @@ Now that we have all the components in place, let's update `App.svelte`:
|
||||
|
||||
```html title="src/App.svelte"
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { supabase } from './supabaseClient';
|
||||
import type { AuthSession } from '@supabase/supabase-js';
|
||||
import Account from './lib/Account.svelte';
|
||||
import { onMount } from 'svelte'
|
||||
import { supabase } from './supabaseClient'
|
||||
import type { AuthSession } from '@supabase/supabase-js'
|
||||
import Account from './lib/Account.svelte'
|
||||
import Auth from './lib/Auth.svelte'
|
||||
|
||||
let session: AuthSession
|
||||
@@ -363,9 +362,9 @@ Now that we have all the components in place, let's update `App.svelte`:
|
||||
|
||||
<div class="container" style="padding: 50px 0 100px 0">
|
||||
{#if !session}
|
||||
<Auth />
|
||||
<Auth />
|
||||
{:else}
|
||||
<Account {session} />
|
||||
<Account {session} />
|
||||
{/if}
|
||||
</div>
|
||||
```
|
||||
@@ -392,12 +391,12 @@ Let's create an avatar for the user so that they can upload a profile photo. We
|
||||
|
||||
```html title="src/lib/Avatar.svelte"
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { supabase } from "../supabaseClient"
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { supabase } from '../supabaseClient'
|
||||
|
||||
export let size: number
|
||||
export let url: string
|
||||
|
||||
|
||||
let avatarUrl: string = null
|
||||
let uploading = false
|
||||
let files: FileList
|
||||
@@ -406,7 +405,9 @@ Let's create an avatar for the user so that they can upload a profile photo. We
|
||||
|
||||
const downloadImage = async (path: string) => {
|
||||
try {
|
||||
const { data, error } = await supabase.storage.from('avatars').download(path)
|
||||
const { data, error } = await supabase.storage
|
||||
.from('avatars')
|
||||
.download(path)
|
||||
|
||||
if (error) {
|
||||
throw error
|
||||
@@ -436,7 +437,7 @@ Let's create an avatar for the user so that they can upload a profile photo. We
|
||||
let { error } = await supabase.storage
|
||||
.from('avatars')
|
||||
.upload(filePath, file)
|
||||
|
||||
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
@@ -456,18 +457,9 @@ Let's create an avatar for the user so that they can upload a profile photo. We
|
||||
</script>
|
||||
|
||||
<div style="width: {size}px" aria-live="polite">
|
||||
{#if avatarUrl}
|
||||
<img
|
||||
src={avatarUrl}
|
||||
alt={avatarUrl ? 'Avatar' : 'No image'}
|
||||
class="avatar image"
|
||||
style="height: {size}px, width: {size}px"
|
||||
/>
|
||||
{:else}
|
||||
<div
|
||||
class="avatar no-image"
|
||||
style="height: {size}px, width: {size}px"
|
||||
/>
|
||||
{#if avatarUrl} <img src={avatarUrl} alt={avatarUrl ? 'Avatar' : 'No image'}
|
||||
class="avatar image" style="height: {size}px, width: {size}px" /> {:else}
|
||||
<div class="avatar no-image" style="height: {size}px, width: {size}px" />
|
||||
{/if}
|
||||
<div style="width: {size}px">
|
||||
<label class="button primary block" for="single">
|
||||
@@ -479,8 +471,8 @@ Let's create an avatar for the user so that they can upload a profile photo. We
|
||||
id="single"
|
||||
accept="image/*"
|
||||
bind:files
|
||||
on:change={uploadAvatar}
|
||||
disabled={uploading}
|
||||
on:change="{uploadAvatar}"
|
||||
disabled="{uploading}"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
@@ -497,9 +489,9 @@ And then we can add the widget to the Account page:
|
||||
import Avatar from './Avatar.svelte'
|
||||
</script>
|
||||
|
||||
<form on:submit|preventDefault={updateProfile} class="form-widget">
|
||||
<form on:submit|preventDefault="{updateProfile}" class="form-widget">
|
||||
<!-- Add to body -->
|
||||
<Avatar bind:url={avatarUrl} size={150} on:upload={updateProfile} />
|
||||
<Avatar bind:url="{avatarUrl}" size="{150}" on:upload="{updateProfile}" />
|
||||
|
||||
<!-- Other form elements -->
|
||||
</form>
|
||||
|
||||
@@ -252,27 +252,27 @@ Let's create a new component for that called `Profile.svelte`.
|
||||
let avatar_url = null
|
||||
|
||||
function getProfile(node) {
|
||||
try {
|
||||
loading = true;
|
||||
const user = supabase.auth.user();
|
||||
try {
|
||||
loading = true
|
||||
const user = supabase.auth.user()
|
||||
|
||||
supabase
|
||||
.from("profiles")
|
||||
.select(`username, website, avatar_url`)
|
||||
.eq("id", user?.id)
|
||||
.single()
|
||||
.then(({ data, error, status }) => {
|
||||
if (data) {
|
||||
username = data.username;
|
||||
website = data.website;
|
||||
avatar_url = data.avatar_url;
|
||||
}
|
||||
if (error && status !== 406) throw error;
|
||||
});
|
||||
supabase
|
||||
.from('profiles')
|
||||
.select(`username, website, avatar_url`)
|
||||
.eq('id', user?.id)
|
||||
.single()
|
||||
.then(({ data, error, status }) => {
|
||||
if (data) {
|
||||
username = data.username
|
||||
website = data.website
|
||||
avatar_url = data.avatar_url
|
||||
}
|
||||
if (error && status !== 406) throw error
|
||||
})
|
||||
} catch (error) {
|
||||
alert(error.message);
|
||||
alert(error.message)
|
||||
} finally {
|
||||
loading = false;
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -311,7 +311,6 @@ Let's create a new component for that called `Profile.svelte`.
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ By the end of this guide you'll have an app which allows users to login and upda
|
||||
|
||||
### GitHub
|
||||
|
||||
Whenever you get stuck at any point, take a look at [this repo](https://github.com/zernonia/supabase-vue-3).
|
||||
Whenever you get stuck at any point, take a look at [this repo](https://github.com/supabase/examples/tree/main/supabase-js-v2/user-management/vue3-user-management).
|
||||
|
||||
## Project set up
|
||||
|
||||
@@ -182,102 +182,141 @@ const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY
|
||||
export const supabase = createClient(supabaseUrl, supabaseAnonKey)
|
||||
```
|
||||
|
||||
And one optional step is to update the CSS file `src/assets/main.css` to make the app look nice.
|
||||
You can find the full contents of this file [here](https://raw.githubusercontent.com/supabase/examples/main/supabase-js-v2/user-management/react-user-management/src/index.css).
|
||||
|
||||
```javascript title="src/main.js"
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import './assets/main.css'
|
||||
|
||||
createApp(App).mount('#app')
|
||||
```
|
||||
Optionally, update [src/style.css](https://raw.githubusercontent.com/supabase/examples/main/supabase-js-v2/user-management/vue3-user-management/src/style.css) to style the app.
|
||||
|
||||
### Set up a Login component
|
||||
|
||||
Let's set up a Vue component to manage logins and sign ups. We'll use Magic Links, so users can sign in with their email without using passwords.
|
||||
|
||||
```html title="/src/components/Auth.vue"
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { supabase } from '../supabase'
|
||||
|
||||
const loading = ref(false)
|
||||
const email = ref('')
|
||||
|
||||
const handleLogin = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const { error } = await supabase.auth.signInWithOtp({ email: email.value })
|
||||
if (error) throw error
|
||||
alert('Check your email for the login link!')
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
alert(error.message)
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form class="row flex-center flex" @submit.prevent="handleLogin">
|
||||
<div class="col-6 form-widget">
|
||||
<h1 class="header">Supabase + Vue 3</h1>
|
||||
<p class="description">Sign in via magic link with your email below</p>
|
||||
<div>
|
||||
<input
|
||||
class="inputField"
|
||||
type="email"
|
||||
placeholder="Your email"
|
||||
v-model="email"
|
||||
/>
|
||||
<input class="inputField" type="email" placeholder="Your email" v-model="email" />
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
type="submit"
|
||||
class="button block"
|
||||
:value="loading ? 'Loading' : 'Send magic link'"
|
||||
:disabled="loading"
|
||||
/>
|
||||
<input type="submit" class="button block" :value="loading ? 'Loading' : 'Send magic link'"
|
||||
:disabled="loading" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue'
|
||||
import { supabase } from '../supabase'
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const loading = ref(false)
|
||||
const email = ref('')
|
||||
|
||||
const handleLogin = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const { error } = await supabase.auth.signIn({ email: email.value })
|
||||
if (error) throw error
|
||||
alert('Check your email for the login link!')
|
||||
} catch (error) {
|
||||
alert(error.error_description || error.message)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
loading,
|
||||
email,
|
||||
handleLogin,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### User store
|
||||
|
||||
To access the user information in other places, we use a reactive store. Create a new file called `store.js` and utilize Vue 3 `reactive` functionalities.
|
||||
|
||||
```javascript title="src/store.js"
|
||||
import { reactive } from 'vue'
|
||||
|
||||
export const store = reactive({
|
||||
user: {},
|
||||
})
|
||||
```
|
||||
|
||||
### Account page
|
||||
|
||||
After a user is signed in we can allow them to edit their profile details and manage their account.
|
||||
Let's create a new component for that called `Profile.vue`.
|
||||
Let's create a new component for that called `Account.vue`.
|
||||
|
||||
```html title="src/components/Account.vue"
|
||||
<script setup>
|
||||
import { supabase } from '../supabase'
|
||||
import { onMounted, ref, toRefs } from 'vue'
|
||||
|
||||
const props = defineProps(['session'])
|
||||
const { session } = toRefs(props)
|
||||
|
||||
const loading = ref(true)
|
||||
const username = ref('')
|
||||
const website = ref('')
|
||||
const avatar_url = ref('')
|
||||
|
||||
onMounted(() => {
|
||||
getProfile()
|
||||
})
|
||||
|
||||
async function getProfile() {
|
||||
try {
|
||||
loading.value = true
|
||||
const { user } = session.value
|
||||
|
||||
let { data, error, status } = await supabase
|
||||
.from('profiles')
|
||||
.select(`username, website, avatar_url`)
|
||||
.eq('id', user.id)
|
||||
.single()
|
||||
|
||||
if (error && status !== 406) throw error
|
||||
|
||||
if (data) {
|
||||
username.value = data.username
|
||||
website.value = data.website
|
||||
avatar_url.value = data.avatar_url
|
||||
}
|
||||
} catch (error) {
|
||||
alert(error.message)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function updateProfile() {
|
||||
try {
|
||||
loading.value = true
|
||||
const { user } = session.value
|
||||
|
||||
const updates = {
|
||||
id: user.id,
|
||||
username: username.value,
|
||||
website: website.value,
|
||||
avatar_url: avatar_url.value,
|
||||
updated_at: new Date(),
|
||||
}
|
||||
|
||||
let { error } = await supabase.from('profiles').upsert(updates)
|
||||
|
||||
if (error) throw error
|
||||
} catch (error) {
|
||||
alert(error.message)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function signOut() {
|
||||
try {
|
||||
loading.value = true
|
||||
let { error } = await supabase.auth.signOut()
|
||||
if (error) throw error
|
||||
} catch (error) {
|
||||
alert(error.message)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
```html title="src/components/Profile.vue"
|
||||
<template>
|
||||
<form class="form-widget" @submit.prevent="updateProfile">
|
||||
<div>
|
||||
<label for="email">Email</label>
|
||||
<input id="email" type="text" :value="store.user.email" disabled />
|
||||
<input id="email" type="text" :value="session.user.email" disabled />
|
||||
</div>
|
||||
<div>
|
||||
<label for="username">Name</label>
|
||||
@@ -289,12 +328,8 @@ Let's create a new component for that called `Profile.vue`.
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input
|
||||
type="submit"
|
||||
class="button primary block"
|
||||
:value="loading ? 'Loading ...' : 'Update'"
|
||||
:disabled="loading"
|
||||
/>
|
||||
<input type="submit" class="button primary block" :value="loading ? 'Loading ...' : 'Update'"
|
||||
:disabled="loading" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@@ -304,98 +339,6 @@ Let's create a new component for that called `Profile.vue`.
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { supabase } from '../supabase'
|
||||
import { store } from '../store'
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const loading = ref(true)
|
||||
const username = ref('')
|
||||
const website = ref('')
|
||||
const avatar_url = ref('')
|
||||
|
||||
async function getProfile() {
|
||||
try {
|
||||
loading.value = true
|
||||
store.user = supabase.auth.user()
|
||||
|
||||
let { data, error, status } = await supabase
|
||||
.from('profiles')
|
||||
.select(`username, website, avatar_url`)
|
||||
.eq('id', store.user.id)
|
||||
.single()
|
||||
|
||||
if (error && status !== 406) throw error
|
||||
|
||||
if (data) {
|
||||
username.value = data.username
|
||||
website.value = data.website
|
||||
avatar_url.value = data.avatar_url
|
||||
}
|
||||
} catch (error) {
|
||||
alert(error.message)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function updateProfile() {
|
||||
try {
|
||||
loading.value = true
|
||||
store.user = supabase.auth.user()
|
||||
|
||||
const updates = {
|
||||
id: store.user.id,
|
||||
username: username.value,
|
||||
website: website.value,
|
||||
avatar_url: avatar_url.value,
|
||||
updated_at: new Date(),
|
||||
}
|
||||
|
||||
let { error } = await supabase.from('profiles').upsert(updates, {
|
||||
returning: 'minimal', // Don't return the value after inserting
|
||||
})
|
||||
|
||||
if (error) throw error
|
||||
} catch (error) {
|
||||
alert(error.message)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function signOut() {
|
||||
try {
|
||||
loading.value = true
|
||||
let { error } = await supabase.auth.signOut()
|
||||
if (error) throw error
|
||||
} catch (error) {
|
||||
alert(error.message)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getProfile()
|
||||
})
|
||||
|
||||
return {
|
||||
store,
|
||||
loading,
|
||||
username,
|
||||
website,
|
||||
avatar_url,
|
||||
|
||||
updateProfile,
|
||||
signOut,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### Launch!
|
||||
@@ -403,37 +346,31 @@ Let's create a new component for that called `Profile.vue`.
|
||||
Now that we have all the components in place, let's update `App.vue`:
|
||||
|
||||
```html title="src/App.vue"
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue'
|
||||
import Account from './components/Account.vue'
|
||||
import Auth from './components/Auth.vue'
|
||||
import { supabase } from './supabase'
|
||||
|
||||
const session = ref()
|
||||
|
||||
onMounted(() => {
|
||||
supabase.auth.getSession().then(({ data }) => {
|
||||
session.value = data.session
|
||||
})
|
||||
|
||||
supabase.auth.onAuthStateChange((_, _session) => {
|
||||
session.value = _session
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container" style="padding: 50px 0 100px 0">
|
||||
<Profile v-if="store.user" />
|
||||
<Account v-if="session" :session="session" />
|
||||
<Auth v-else />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { store } from './store'
|
||||
import { supabase } from './supabase'
|
||||
import Auth from './components/Auth.vue'
|
||||
import Profile from './components/Profile.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Auth,
|
||||
Profile,
|
||||
},
|
||||
|
||||
setup() {
|
||||
store.user = supabase.auth.user()
|
||||
supabase.auth.onAuthStateChange((_, session) => {
|
||||
store.user = session.user
|
||||
})
|
||||
|
||||
return {
|
||||
store,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
Once that's done, run this in a terminal window:
|
||||
@@ -442,7 +379,7 @@ Once that's done, run this in a terminal window:
|
||||
npm run dev
|
||||
```
|
||||
|
||||
And then open the browser to [localhost:3000](http://localhost:3000) and you should see the completed app.
|
||||
And then open the browser to [localhost:5173](http://localhost:5173) and you should see the completed app.
|
||||
|
||||

|
||||
|
||||
@@ -455,132 +392,96 @@ Every Supabase project is configured with [Storage](/docs/guides/storage) for ma
|
||||
Let's create an avatar for the user so that they can upload a profile photo. We can start by creating a new component:
|
||||
|
||||
```html title="src/components/Avatar.vue"
|
||||
<script setup>
|
||||
import { ref, toRefs, watch } from 'vue'
|
||||
import { supabase } from '../supabase'
|
||||
|
||||
const prop = defineProps(['path', 'size'])
|
||||
const { path, size } = toRefs(prop)
|
||||
|
||||
const emit = defineEmits(['upload', 'update:path'])
|
||||
const uploading = ref(false)
|
||||
const src = ref('')
|
||||
const files = ref()
|
||||
|
||||
const downloadImage = async () => {
|
||||
try {
|
||||
const { data, error } = await supabase.storage
|
||||
.from('avatars')
|
||||
.download(path.value)
|
||||
if (error) throw error
|
||||
src.value = URL.createObjectURL(data)
|
||||
} catch (error) {
|
||||
console.error('Error downloading image: ', error.message)
|
||||
}
|
||||
}
|
||||
|
||||
const uploadAvatar = async (evt) => {
|
||||
files.value = evt.target.files
|
||||
try {
|
||||
uploading.value = true
|
||||
if (!files.value || files.value.length === 0) {
|
||||
throw new Error('You must select an image to upload.')
|
||||
}
|
||||
|
||||
const file = files.value[0]
|
||||
const fileExt = file.name.split('.').pop()
|
||||
const filePath = `${Math.random()}.${fileExt}`
|
||||
|
||||
let { error: uploadError } = await supabase.storage
|
||||
.from('avatars')
|
||||
.upload(filePath, file)
|
||||
|
||||
if (uploadError) throw uploadError
|
||||
emit('update:path', filePath)
|
||||
emit('upload')
|
||||
} catch (error) {
|
||||
alert(error.message)
|
||||
} finally {
|
||||
uploading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
watch(path, () => {
|
||||
if (path.value) downloadImage()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<img
|
||||
v-if="src"
|
||||
:src="src"
|
||||
alt="Avatar"
|
||||
class="avatar image"
|
||||
:style="{ height: size, width: size }"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
class="avatar no-image"
|
||||
:style="{ height: size, width: size }"
|
||||
/>
|
||||
<img v-if="src" :src="src" alt="Avatar" class="avatar image"
|
||||
:style="{ height: size + 'em', width: size + 'em' }" />
|
||||
<div v-else class="avatar no-image" :style="{ height: size + 'em', width: size + 'em' }" />
|
||||
|
||||
<div :style="{ width: size }">
|
||||
<div :style="{ width: size + 'em' }">
|
||||
<label class="button primary block" for="single">
|
||||
{{ uploading ? "Uploading ..." : "Upload" }}
|
||||
</label>
|
||||
<input
|
||||
style="visibility: hidden; position: absolute"
|
||||
type="file"
|
||||
id="single"
|
||||
accept="image/*"
|
||||
@change="uploadAvatar"
|
||||
:disabled="uploading"
|
||||
/>
|
||||
<input style="visibility: hidden; position: absolute" type="file" id="single" accept="image/*"
|
||||
@change="uploadAvatar" :disabled="uploading" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, toRefs, watch } from 'vue'
|
||||
import { supabase } from '../supabase'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
path: String,
|
||||
},
|
||||
emits: ['upload', 'update:path'],
|
||||
setup(prop, { emit }) {
|
||||
const { path } = toRefs(prop)
|
||||
const size = ref('10em')
|
||||
const uploading = ref(false)
|
||||
const src = ref('')
|
||||
const files = ref()
|
||||
|
||||
const downloadImage = async () => {
|
||||
try {
|
||||
const { data, error } = await supabase.storage
|
||||
.from('avatars')
|
||||
.download(path.value)
|
||||
if (error) throw error
|
||||
src.value = URL.createObjectURL(data)
|
||||
} catch (error) {
|
||||
console.error('Error downloading image: ', error.message)
|
||||
}
|
||||
}
|
||||
|
||||
const uploadAvatar = async (evt) => {
|
||||
files.value = evt.target.files
|
||||
try {
|
||||
uploading.value = true
|
||||
if (!files.value || files.value.length === 0) {
|
||||
throw new Error('You must select an image to upload.')
|
||||
}
|
||||
|
||||
const file = files.value[0]
|
||||
const fileExt = file.name.split('.').pop()
|
||||
const fileName = `${Math.random()}.${fileExt}`
|
||||
const filePath = `${fileName}`
|
||||
|
||||
let { error: uploadError } = await supabase.storage
|
||||
.from('avatars')
|
||||
.upload(filePath, file)
|
||||
|
||||
if (uploadError) throw uploadError
|
||||
emit('update:path', filePath)
|
||||
emit('upload')
|
||||
} catch (error) {
|
||||
alert(error.message)
|
||||
} finally {
|
||||
uploading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
watch(path, () => {
|
||||
if (path.value) downloadImage()
|
||||
})
|
||||
|
||||
return {
|
||||
size,
|
||||
uploading,
|
||||
src,
|
||||
files,
|
||||
|
||||
uploadAvatar,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### Add the new widget
|
||||
|
||||
And then we can add the widget to the Account page:
|
||||
|
||||
```html title="src/Profile.vue"
|
||||
```html title="src/components/Account.vue"
|
||||
<script>
|
||||
// Import the new component
|
||||
import Avatar from './Avatar.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form class="form-widget" @submit.prevent="updateProfile">
|
||||
<!-- Add to body -->
|
||||
<Avatar v-model:path="avatar_url" @upload="updateProfile" />
|
||||
<Avatar v-model:path="avatar_url" @upload="updateProfile" size="10" />
|
||||
|
||||
<!-- Other form elements -->
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// Import the new component
|
||||
import Avatar from './Avatar.vue'
|
||||
|
||||
//
|
||||
components: {
|
||||
Avatar,
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
## Next steps
|
||||
|
||||
@@ -82,13 +82,13 @@ For the curious, try heading to the SQL editor and querying:
|
||||
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()](../../reference/javascript/auth-session) method to see if there is a valid session:
|
||||
If supabase-js is loaded on your site (in this case http://localhost:3000) it automatically plucks the `access_token` out of the URL and initiates a session. You can retrieve the [session](/docs/reference/javascript/auth-getsession) to see if there is a valid session:
|
||||
|
||||
```jsx
|
||||
console.log(supabase.auth.session())
|
||||
console.log(supabase.auth.getSession())
|
||||
```
|
||||
|
||||
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:
|
||||
Now that we can use methods 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 (
|
||||
|
||||
@@ -41,26 +41,6 @@ Reference documentation for the official Supabase client libraries, APIs, and to
|
||||
</div>
|
||||
</div>
|
||||
|
||||
## Community
|
||||
|
||||
Reference documentation for community-contributed utilities.
|
||||
|
||||
<div class="container" style={{ padding: 0 }}>
|
||||
<div class="row is-multiline">
|
||||
{/* Auth */}
|
||||
<div class="col col--6">
|
||||
<ButtonCard
|
||||
class="card"
|
||||
to={useBaseUrl('/reference/auth-helpers')}
|
||||
style={{ height: '100%' }}
|
||||
title="Auth Helpers"
|
||||
description="Collection of framework-specific auth utilities for working with Supabase."
|
||||
/>
|
||||
</div>
|
||||
{/* */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
## Self-hosting
|
||||
|
||||
Reference documentation for self-hosting Supabase features.
|
||||
|
||||
@@ -12,10 +12,10 @@ For help and questions about best practices, we have a discussion forum set up o
|
||||
|
||||
## Business Support
|
||||
|
||||
We offer email based support for business users. You can email us at support@supabase.io.
|
||||
We offer email based support for business users. You can email us at support@supabase.com.
|
||||
|
||||
If you need SLAs, guaranteed response times, or other enterprise level services, please contact us at this email address.
|
||||
|
||||
## Slack Support
|
||||
|
||||
If you are building with Supabase and you would like to set up a shared Slack channel (via [Slack Connect](https://slack.com/connect)), please reach out to us at support@supabase.io.
|
||||
If you are building with Supabase and you would like to set up a shared Slack channel (via [Slack Connect](https://slack.com/connect)), please reach out to us at support@supabase.com.
|
||||
@@ -14,7 +14,7 @@ const config = {
|
||||
url: 'https://supabase.com',
|
||||
baseUrl: baseUrl,
|
||||
onBrokenLinks: 'throw',
|
||||
onBrokenMarkdownLinks: 'warn',
|
||||
onBrokenMarkdownLinks: 'throw',
|
||||
favicon: '/favicon.ico',
|
||||
themes: ['docusaurus-theme-search-typesense'],
|
||||
|
||||
@@ -112,11 +112,10 @@ const config = {
|
||||
breadcrumbs: false,
|
||||
editUrl:
|
||||
'https://github.com/supabase/supabase/edit/master/apps/reference/',
|
||||
lastVersion: 'v1',
|
||||
lastVersion: 'current',
|
||||
versions: {
|
||||
current: {
|
||||
label: 'v2 RC',
|
||||
path: '/next',
|
||||
label: 'v2',
|
||||
},
|
||||
v1: {
|
||||
label: 'v1',
|
||||
@@ -124,18 +123,6 @@ const config = {
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
'@docusaurus/plugin-content-docs',
|
||||
{
|
||||
id: '_auth_helpers',
|
||||
path: '_auth_helpers',
|
||||
routeBasePath: '/reference/auth-helpers',
|
||||
sidebarPath: require.resolve('./nav/auth_helpers_sidebars.js'),
|
||||
breadcrumbs: false,
|
||||
editUrl:
|
||||
'https://github.com/supabase/supabase/edit/master/apps/reference/',
|
||||
},
|
||||
],
|
||||
],
|
||||
|
||||
presets: [
|
||||
|
||||
@@ -10,7 +10,7 @@ export default async function gen(
|
||||
apiUrl: string
|
||||
) {
|
||||
const specRaw = fs.readFileSync(inputFileName, 'utf8')
|
||||
const spec = JSON.parse(specRaw)
|
||||
const spec = JSON.parse(specRaw) as any
|
||||
// console.log('spec', spec)
|
||||
|
||||
switch (spec.openapi || spec.swagger) {
|
||||
|
||||
@@ -6,8 +6,7 @@ import template from './templates/CliTemplate'
|
||||
import type { CliSpec } from './types/CliSpec'
|
||||
|
||||
export default async function gen(inputFileName: string, outputDir: string) {
|
||||
const spec = yaml.load(fs.readFileSync(inputFileName, 'utf8'))
|
||||
// console.log('spec', spec)
|
||||
const spec = yaml.load(fs.readFileSync(inputFileName, 'utf8')) as any
|
||||
|
||||
switch (spec.clispec) {
|
||||
case '001':
|
||||
|
||||
@@ -12,7 +12,7 @@ type Section = {
|
||||
}
|
||||
|
||||
export default async function gen(inputFileName: string, outputDir: string) {
|
||||
const spec = yaml.load(fs.readFileSync(inputFileName, 'utf8'))
|
||||
const spec = yaml.load(fs.readFileSync(inputFileName, 'utf8')) as any
|
||||
// console.log('spec', spec)
|
||||
|
||||
switch (spec.configspec) {
|
||||
|
||||
@@ -130,7 +130,16 @@ function recurseThroughParams(paramDefinition: TsDoc.TypeDefinition) {
|
||||
if (param.type?.declaration?.children) {
|
||||
children = param.type?.declaration?.children
|
||||
} else if (isUnion(param)) {
|
||||
children = param.type.types
|
||||
// We don't want to show the union types if it's a literal
|
||||
const nonLiteralVariants = param.type.types.filter(
|
||||
({ type }) => type !== 'literal'
|
||||
)
|
||||
|
||||
if (nonLiteralVariants.length === 0) {
|
||||
children = null
|
||||
} else {
|
||||
children = nonLiteralVariants
|
||||
}
|
||||
} else if (param.type === 'reflection') {
|
||||
children = param.declaration.children
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { writeToDisk } from './helpers'
|
||||
import type { SdkSpec } from './types/SdkSpec'
|
||||
|
||||
export default async function gen(inputFileName: string, outputDir: string) {
|
||||
const spec = yaml.load(fs.readFileSync(inputFileName, 'utf8'))
|
||||
const spec = yaml.load(fs.readFileSync(inputFileName, 'utf8')) as any
|
||||
// console.log('spec', spec)
|
||||
|
||||
switch (spec.sdkspec) {
|
||||
|
||||
@@ -154,9 +154,14 @@ const sidebars = {
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Functions',
|
||||
label: 'Edge Functions',
|
||||
collapsed: true,
|
||||
items: ['guides/functions'],
|
||||
items: [
|
||||
'guides/functions',
|
||||
'guides/functions/auth',
|
||||
'guides/functions/examples',
|
||||
'guides/functions/cicd-workflow',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
@@ -164,9 +169,8 @@ const sidebars = {
|
||||
collapsed: true,
|
||||
items: [
|
||||
'guides/realtime',
|
||||
'guides/realtime/broadcast',
|
||||
'guides/realtime/presence',
|
||||
'guides/realtime/postgres-changes',
|
||||
'guides/realtime/quickstart',
|
||||
'guides/realtime/postgres-cdc',
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -211,22 +215,50 @@ const sidebars = {
|
||||
label: 'Integrations',
|
||||
collapsed: true,
|
||||
items: [
|
||||
'guides/integrations/appsmith',
|
||||
'guides/integrations/auth0',
|
||||
'guides/integrations/authsignal',
|
||||
'guides/integrations/clerk',
|
||||
'guides/integrations/dashibase',
|
||||
'guides/integrations/directus',
|
||||
'guides/integrations/draftbit',
|
||||
'guides/integrations/pgmustard',
|
||||
'guides/integrations/plasmic',
|
||||
'guides/integrations/polyscale',
|
||||
'guides/integrations/prisma',
|
||||
'guides/integrations/sequin',
|
||||
'guides/integrations/snaplet',
|
||||
'guides/integrations/stytch',
|
||||
'guides/integrations/supertokens',
|
||||
'guides/integrations/vercel',
|
||||
'guides/integrations/integrations',
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Auth',
|
||||
collapsed: true,
|
||||
items: [
|
||||
'guides/integrations/auth0',
|
||||
'guides/integrations/authsignal',
|
||||
'guides/integrations/clerk',
|
||||
'guides/integrations/stytch',
|
||||
'guides/integrations/supertokens',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Caching / Offline-first',
|
||||
collapsed: true,
|
||||
items: ['guides/integrations/polyscale'],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Developer Tools',
|
||||
collapsed: true,
|
||||
items: [
|
||||
'guides/integrations/pgmustard',
|
||||
'guides/integrations/prisma',
|
||||
'guides/integrations/sequin',
|
||||
'guides/integrations/snaplet',
|
||||
'guides/integrations/vercel',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Low-code',
|
||||
collapsed: true,
|
||||
items: [
|
||||
'guides/integrations/appsmith',
|
||||
'guides/integrations/dashibase',
|
||||
'guides/integrations/dhiwise',
|
||||
'guides/integrations/directus',
|
||||
'guides/integrations/draftbit',
|
||||
'guides/integrations/plasmic',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -252,19 +284,20 @@ const sidebars = {
|
||||
{ type: 'link', label: 'Supabase API', href: '/reference/api' },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Community',
|
||||
// link: { type: 'doc', id: 'about' },
|
||||
collapsed: false,
|
||||
items: [
|
||||
{
|
||||
type: 'link',
|
||||
label: 'Supabase Auth Helpers',
|
||||
href: '/reference/auth-helpers',
|
||||
},
|
||||
],
|
||||
},
|
||||
// TODO: add back in with typedocs
|
||||
// {
|
||||
// type: 'category',
|
||||
// label: 'Community',
|
||||
// // link: { type: 'doc', id: 'about' },
|
||||
// collapsed: false,
|
||||
// items: [
|
||||
// {
|
||||
// type: 'link',
|
||||
// label: 'Supabase Auth Helpers',
|
||||
// href: '/reference/auth-helpers',
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Self-hosting',
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
/**
|
||||
* Creating a sidebar enables you to:
|
||||
- create an ordered group of docs
|
||||
- render a sidebar for each doc of that group
|
||||
- provide next/previous navigation
|
||||
|
||||
The sidebars can be generated from the filesystem, or explicitly defined here.
|
||||
|
||||
Create as many sidebars as you want.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
|
||||
const sidebars = {
|
||||
// By default, Docusaurus generates a sidebar from the docs folder structure
|
||||
// tutorialSidebar: [{ type: 'autogenerated', dirName: '.' }],
|
||||
|
||||
sidebar: [
|
||||
'intro',
|
||||
'next-js',
|
||||
'sveltekit',
|
||||
// 'usage',
|
||||
// 'config',
|
||||
// 'release-notes',
|
||||
// {
|
||||
// type: "category",
|
||||
// label: "Release Notes",
|
||||
// items: ["release-notes"],
|
||||
// },
|
||||
],
|
||||
}
|
||||
|
||||
module.exports = sidebars
|
||||
@@ -18,6 +18,7 @@ const sidebars = {
|
||||
|
||||
sidebar: [
|
||||
'intro',
|
||||
// 'usage', // Hide until the OpenAPI spec is updated to v3
|
||||
'config',
|
||||
'release-notes',
|
||||
// {
|
||||
|
||||
Loaded 100 of 1509 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user