mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 01:45:10 +03:00
Fix all missing images
This commit is contained in:
1 parent
a7f7cae6ff
commit
3a1d8ab8e4
50 files changed
+504
-768
No files matched your search
@@ -16,7 +16,7 @@ while maintaining the scalability required to go beyond it.
|
||||
|
||||
Each Supabase project consists of several tools:
|
||||
|
||||

|
||||

|
||||
|
||||
### PostgreSQL (Database)
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ Apple OAuth consists of six broad steps:
|
||||
- Go to [developer.apple.com](https://developer.apple.com).
|
||||
- Click on `Account` at the top right to log in.
|
||||
|
||||

|
||||

|
||||
|
||||
## Obtain an App ID
|
||||
|
||||
@@ -69,12 +69,7 @@ The next step requires a callback URL, which looks like this:
|
||||
- Now just add `/auth/v1/callback` to the end of that to get your full `OAuth Redirect URI`.
|
||||
|
||||
<video width="99%" muted playsInline controls="true">
|
||||
<source
|
||||
src="/docs/videos/api/api-url-and-key.mp4"
|
||||
type="video/mp4"
|
||||
muted
|
||||
playsInline
|
||||
/>
|
||||
<source src="/docs/videos/api/api-url-and-key.mp4" type="video/mp4" muted playsInline />
|
||||
</video>
|
||||
|
||||
## Configure your Services ID
|
||||
|
||||
@@ -32,7 +32,7 @@ Azure OAuth consists of four broad steps:
|
||||
- The redirect / callback URI should look like this: `https://<project-ref>.supabase.co/auth/v1/callback`
|
||||
- Click "Register" at the bottom of the form.
|
||||
|
||||

|
||||

|
||||
|
||||
## Obtain a Client ID
|
||||
|
||||
@@ -40,7 +40,7 @@ This will serve as the `client_id` when you make API calls to authenticate the u
|
||||
|
||||
- Once your app has been registered, the client id can be found under the [list of app registrations](https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/RegisteredApps) under the column titled "Application (client) ID".
|
||||
|
||||

|
||||

|
||||
|
||||
## Obtain a Secret ID
|
||||
|
||||
@@ -52,7 +52,7 @@ This will serve as the `client_secret` when you make API calls to authenticate t
|
||||
- Enter a description and choose your preferred expiry for the secret.
|
||||
- Once the secret is generated, save the `value` (not the secret ID).
|
||||
|
||||

|
||||

|
||||
|
||||
## Obtain the Tenant URL
|
||||
|
||||
@@ -61,7 +61,7 @@ This will allow your users to use your custom Azure login page when logging in.
|
||||
- Select the Directory (Tenant) ID value.
|
||||
- The Azure Tenant URL should look like this: `https://login.microsoftonline.com/<tenant-id>`
|
||||
|
||||

|
||||

|
||||
|
||||
## Add login code to your client app
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ Setting up Bitbucket logins for your application consists of 3 parts:
|
||||
- Go to [bitbucket.org](https://bitbucket.org/).
|
||||
- Click on `Login` at the top right to log in.
|
||||
|
||||

|
||||

|
||||
|
||||
## Find your callback URL
|
||||
|
||||
@@ -37,12 +37,7 @@ The next step requires a callback URL, which looks like this:
|
||||
- Now just add `/auth/v1/callback` to the end of that to get your full `OAuth Redirect URI`.
|
||||
|
||||
<video width="99%" muted playsInline controls="true">
|
||||
<source
|
||||
src="/docs/videos/api/api-url-and-key.mp4"
|
||||
type="video/mp4"
|
||||
muted
|
||||
playsInline
|
||||
/>
|
||||
<source src="/docs/videos/api/api-url-and-key.mp4" type="video/mp4" muted playsInline />
|
||||
</video>
|
||||
|
||||
## Create a Bitbucket OAuth app
|
||||
|
||||
@@ -17,21 +17,21 @@ Go to the [hCaptcha](https://www.hcaptcha.com/) website and sign up for an accou
|
||||
|
||||
If you have already signed up and didn't copy this information from the welcome page, you can get the **Secret key** from the settings page.
|
||||
|
||||

|
||||

|
||||
|
||||
The **Sitekey** can be found in the **Settings** of the active site you created.
|
||||
|
||||

|
||||

|
||||
|
||||
In the Settings page, look for the **Sitekey** section and copy the key.
|
||||
|
||||

|
||||

|
||||
|
||||
### Enable hCaptcha protection for your Supabase project
|
||||
|
||||
Navigate to the **[Authentication](https://app.supabase.com/project/_/auth/settings)** page in the Supabase Dashboard and find the **Enable hCaptcha protection** toggle under the **Security and Protection** section.
|
||||
|
||||

|
||||

|
||||
|
||||
Enter your hCaptcha **Secret key** and click **Save**.
|
||||
|
||||
|
||||
@@ -22,11 +22,11 @@ Setting up Discord logins for your application consists of 3 parts:
|
||||
- Go to [discord.com](https://discord.com/).
|
||||
- Click on `Login` at the top right to log in.
|
||||
|
||||

|
||||

|
||||
|
||||
- Once logged in, go to [discord.com/developers](https://discord.com/developers).
|
||||
|
||||

|
||||

|
||||
|
||||
## Find your callback URL
|
||||
|
||||
@@ -41,12 +41,7 @@ In the next step you require a callback URL, which looks like this:
|
||||
- Now just add `/auth/v1/callback` to the end of that to get your full `OAuth Redirect URI`.
|
||||
|
||||
<video width="99%" muted playsInline controls="true">
|
||||
<source
|
||||
src="/docs/videos/api/api-url-and-key.mp4"
|
||||
type="video/mp4"
|
||||
muted
|
||||
playsInline
|
||||
/>
|
||||
<source src="/docs/videos/api/api-url-and-key.mp4" type="video/mp4" muted playsInline />
|
||||
</video>
|
||||
|
||||
## Create a Discord Application
|
||||
|
||||
@@ -22,7 +22,7 @@ Setting up Facebook logins for your application consists of 3 parts:
|
||||
- Go to [developers.facebook.com](https://developers.facebook.com).
|
||||
- Click on `Log In` at the top right to log in.
|
||||
|
||||

|
||||

|
||||
|
||||
## Create a Facebook App
|
||||
|
||||
@@ -45,12 +45,7 @@ The next step requires a callback URI, which looks like this:
|
||||
- Now just add `/auth/v1/callback` to the end of that to get your full `OAuth Redirect URI`.
|
||||
|
||||
<video width="99%" muted playsInline controls="true">
|
||||
<source
|
||||
src="/docs/videos/api/api-url-and-key.mp4"
|
||||
type="video/mp4"
|
||||
muted
|
||||
playsInline
|
||||
/>
|
||||
<source src="/docs/videos/api/api-url-and-key.mp4" type="video/mp4" muted playsInline />
|
||||
</video>
|
||||
|
||||
## Set up FaceBook Login for your Facebook App
|
||||
|
||||
@@ -22,7 +22,7 @@ Setting up GitHub logins for your application consists of 3 parts:
|
||||
- Go to [github.com](https://github.com).
|
||||
- Click on `Sign In` at the top right to log in.
|
||||
|
||||

|
||||

|
||||
|
||||
## Create a GitHub Oauth App
|
||||
|
||||
@@ -46,12 +46,7 @@ The next step requires a callback URL, which looks like this:
|
||||
- Now just add `/auth/v1/callback` to the end of that to get your full `OAuth Redirect URI`.
|
||||
|
||||
<video width="99%" muted playsInline controls="true">
|
||||
<source
|
||||
src="/docs/videos/api/api-url-and-key.mp4"
|
||||
type="video/mp4"
|
||||
muted
|
||||
playsInline
|
||||
/>
|
||||
<source src="/docs/videos/api/api-url-and-key.mp4" type="video/mp4" muted playsInline />
|
||||
</video>
|
||||
|
||||
## Register a new OAuth application
|
||||
|
||||
@@ -22,7 +22,7 @@ Setting up GitLab logins for your application consists of 3 parts:
|
||||
- Go to [gitlab.com](https://gitlab.com).
|
||||
- Click on `Login` at the top right to log in.
|
||||
|
||||

|
||||

|
||||
|
||||
## Find your callback URL
|
||||
|
||||
@@ -37,12 +37,7 @@ The next step requires a callback URL, which looks like this:
|
||||
- Now just add `/auth/v1/callback` to the end of that to get your full `OAuth Redirect URI`.
|
||||
|
||||
<video width="99%" muted playsInline controls="true">
|
||||
<source
|
||||
src="/docs/videos/api/api-url-and-key.mp4"
|
||||
type="video/mp4"
|
||||
muted
|
||||
playsInline
|
||||
/>
|
||||
<source src="/docs/videos/api/api-url-and-key.mp4" type="video/mp4" muted playsInline />
|
||||
</video>
|
||||
|
||||
## Create your GitLab Application
|
||||
|
||||
@@ -22,7 +22,7 @@ Setting up Google logins for your application consists of 3 parts:
|
||||
- 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
|
||||
|
||||
@@ -60,12 +60,7 @@ The next step requires a callback URL, which looks like this:
|
||||
- Now just add `/auth/v1/callback` to the end of that to get your full `OAuth Redirect URI`.
|
||||
|
||||
<video width="99%" muted playsInline controls="true">
|
||||
<source
|
||||
src="/docs/videos/api/api-url-and-key.mp4"
|
||||
type="video/mp4"
|
||||
muted
|
||||
playsInline
|
||||
/>
|
||||
<source src="/docs/videos/api/api-url-and-key.mp4" type="video/mp4" muted playsInline />
|
||||
</video>
|
||||
|
||||
## Create your Google credentials
|
||||
|
||||
@@ -34,13 +34,13 @@ Keycloak OAuth consists of five broad steps:
|
||||
- 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`.
|
||||
- You can find this endpoint from the realm settings under the "General Tab" or visit [`http://localhost:8080/realms/my_realm_name/.well-known/openid-configuration`](http://localhost:8080/realms/my_realm_name/.well-known/openid-configuration)
|
||||
|
||||

|
||||

|
||||
|
||||
## 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
|
||||
|
||||
@@ -50,15 +50,15 @@ After you've created the client successfully, ensure that you set the following
|
||||
2. The "Access Type" should be set to "confidential".
|
||||
3. The "Valid Redirect URIs" should be set to: `https://<project-ref>.supabase.co/auth/v1/callback`.
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
## 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
|
||||
|
||||
@@ -83,4 +83,4 @@ async function signout() {
|
||||
## Resources
|
||||
|
||||
- You can find the keycloak openid endpoint configuration under the realm settings.
|
||||

|
||||

|
||||
@@ -22,7 +22,7 @@ Setting up LinkedIn logins for your application consists of 3 parts:
|
||||
- Go to [LinkedIn Developer Dashboard](https://www.linkedin.com/developers/apps).
|
||||
- Log in (if necessary.)
|
||||
|
||||

|
||||

|
||||
|
||||
## Find your callback URL
|
||||
|
||||
@@ -37,12 +37,7 @@ The next step requires a callback URL, which looks like this:
|
||||
- Now just add `/auth/v1/callback` to the end of that to get your full `OAuth Redirect URI`.
|
||||
|
||||
<video width="99%" muted playsInline controls="true">
|
||||
<source
|
||||
src="/docs/videos/api/api-url-and-key.mp4"
|
||||
type="video/mp4"
|
||||
muted
|
||||
playsInline
|
||||
/>
|
||||
<source src="/docs/videos/api/api-url-and-key.mp4" type="video/mp4" muted playsInline />
|
||||
</video>
|
||||
|
||||
## Create a LinkedIn OAuth app
|
||||
|
||||
@@ -22,20 +22,20 @@ Setting up Notion logins for your application consists of 3 parts:
|
||||
- Go to [developers.notion.com](https://developers.notion.com/).
|
||||
- Click "View my integrations" and login.
|
||||
|
||||

|
||||

|
||||
|
||||
- Once logged in, go to [notion.so/my-integrations](https://notion.so/my-integrations) and create a new integration.
|
||||
- When creating your integration, ensure that you select "Public integration" under "Integration type" and "Read user information including email addresses" under "Capabilities".
|
||||
- You will need to add a redirect uri, see [Add the redirect uri](#add-the-redirect-uri)
|
||||
- Once you've filled in the necessary fields, click "Submit" to finish creating the integration.
|
||||
|
||||

|
||||

|
||||
|
||||
## Add the redirect URI
|
||||
|
||||
- After selecting "Public integration", you should see an option to add "Redirect URIs".
|
||||
|
||||

|
||||

|
||||
|
||||
You can retrieve the redirect uri with the following steps:
|
||||
|
||||
@@ -48,19 +48,14 @@ You can retrieve the redirect uri with the following steps:
|
||||
Your redirect uri should look like the following: `https://<project-ref>.supabase.co/auth/v1/callback`
|
||||
|
||||
<video width="99%" muted playsInline controls="true">
|
||||
<source
|
||||
src="/docs/videos/api/api-url-and-key.mp4"
|
||||
type="video/mp4"
|
||||
muted
|
||||
playsInline
|
||||
/>
|
||||
<source src="/docs/videos/api/api-url-and-key.mp4" type="video/mp4" muted playsInline />
|
||||
</video>
|
||||
|
||||
## 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.
|
||||
|
||||

|
||||

|
||||
|
||||
- Go to your [Supabase Project Dashboard](https://app.supabase.com)
|
||||
- In the left sidebar, click the `Authentication` icon (near the top)
|
||||
|
||||
@@ -22,7 +22,7 @@ Setting up Slack logins for your application consists of 3 parts:
|
||||
- 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
|
||||
|
||||
@@ -37,12 +37,7 @@ The next step requires a callback URL, which looks like this:
|
||||
- Now just add `/auth/v1/callback` to the end of that to get your full `OAuth Redirect URI`.
|
||||
|
||||
<video width="99%" muted playsInline controls="true">
|
||||
<source
|
||||
src="/docs/videos/api/api-url-and-key.mp4"
|
||||
type="video/mp4"
|
||||
muted
|
||||
playsInline
|
||||
/>
|
||||
<source src="/docs/videos/api/api-url-and-key.mp4" type="video/mp4" muted playsInline />
|
||||
</video>
|
||||
|
||||
## Create a Slack OAuth app
|
||||
|
||||
@@ -22,7 +22,7 @@ Setting up Spotify logins for your application consists of 3 parts:
|
||||
- Log into [Spotify](https://spotify.com)
|
||||
- Access the [Spotify Developer Dashboard](https://developer.spotify.com/dashboard)
|
||||
|
||||

|
||||

|
||||
|
||||
## Find your callback URL
|
||||
|
||||
@@ -37,12 +37,7 @@ The next step requires a callback URL, which looks like this:
|
||||
- Now just add `/auth/v1/callback` to the end of that to get your full `OAuth Redirect URI`.
|
||||
|
||||
<video width="99%" muted playsInline controls="true">
|
||||
<source
|
||||
src="/docs/videos/api/api-url-and-key.mp4"
|
||||
type="video/mp4"
|
||||
muted
|
||||
playsInline
|
||||
/>
|
||||
<source src="/docs/videos/api/api-url-and-key.mp4" type="video/mp4" muted playsInline />
|
||||
</video>
|
||||
|
||||
## Create a Spotify OAuth app
|
||||
|
||||
@@ -23,11 +23,11 @@ Setting up Twitch logins for your application consists of 3 parts:
|
||||
- Click on `Log in with Twitch` at the top right to log in.
|
||||
- If you have not already enabled 2-Factor Authentication for your Twitch Account, you will need to do that at [Twitch Security Settings](https://www.twitch.tv/settings/security) before you can continue.
|
||||
|
||||

|
||||

|
||||
|
||||
- Once logged in, go to the [Twitch Developer Console](https://dev.twitch.tv/console).
|
||||
|
||||

|
||||

|
||||
|
||||
## Find your callback URL
|
||||
|
||||
@@ -42,21 +42,16 @@ In the next step you require a callback URL, which looks like this:
|
||||
- Now just add `/auth/v1/callback` to the end of that to get your full `OAuth Redirect URI`.
|
||||
|
||||
<video width="99%" muted playsInline controls="true">
|
||||
<source
|
||||
src="/docs/videos/api/api-url-and-key.mp4"
|
||||
type="video/mp4"
|
||||
muted
|
||||
playsInline
|
||||
/>
|
||||
<source src="/docs/videos/api/api-url-and-key.mp4" type="video/mp4" muted playsInline />
|
||||
</video>
|
||||
|
||||
## Create a Twitch Application
|
||||
|
||||

|
||||

|
||||
|
||||
- Click on `+ Register Your Application` at the top right.
|
||||
|
||||

|
||||

|
||||
|
||||
- Enter the name of your application.
|
||||
- Type or paste your `OAuth Redirect URL` (the callback URL from the previous step.)
|
||||
@@ -67,13 +62,13 @@ In the next step you require a callback URL, which looks like this:
|
||||
|
||||
- Click `Manage` at the right of your application entry in the list.
|
||||
|
||||

|
||||

|
||||
|
||||
- Copy your Client ID.
|
||||
- Click `New Secret` to create a new Client Secret.
|
||||
- Copy your Client Secret.
|
||||
|
||||

|
||||

|
||||
|
||||
## Add your Twitch credentials into your Supabase Project
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ Setting up Twitter logins for your application consists of 3 parts:
|
||||
- Go to [developer.twitter.com](https://developer.twitter.com).
|
||||
- Click on `Sign in` at the top right to log in.
|
||||
|
||||

|
||||

|
||||
|
||||
## Find your callback URL
|
||||
|
||||
@@ -37,12 +37,7 @@ The next step requires a callback URL, which looks like this:
|
||||
- Now just add `/auth/v1/callback` to the end of that to get your full `OAuth Redirect URI`.
|
||||
|
||||
<video width="99%" muted playsInline controls="true">
|
||||
<source
|
||||
src="/docs/videos/api/api-url-and-key.mp4"
|
||||
type="video/mp4"
|
||||
muted
|
||||
playsInline
|
||||
/>
|
||||
<source src="/docs/videos/api/api-url-and-key.mp4" type="video/mp4" muted playsInline />
|
||||
</video>
|
||||
|
||||
## Create a Twitter OAuth app
|
||||
|
||||
@@ -24,26 +24,26 @@ The procedure consists of five broad steps:
|
||||
## 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
|
||||
|
||||
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
|
||||
|
||||
Head over to the API Keys page and obtain the secret key.
|
||||
|
||||

|
||||

|
||||
|
||||
## Connect a WorkOS Supported Identity Provider
|
||||
|
||||
Set up the identity provider by visiting the setup link.
|
||||
|
||||

|
||||

|
||||
|
||||
You can pick between any one of the many identity providers that WorkOS supports.
|
||||
|
||||
@@ -62,18 +62,16 @@ When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/aut
|
||||
|
||||
```js
|
||||
async function signInWithWorkOS() {
|
||||
const { data, error } = await supabase.auth.signInWithOAuth(
|
||||
{
|
||||
provider: 'workos',
|
||||
options: {
|
||||
queryParams: {
|
||||
connection: '<your_connection>',
|
||||
organization: '<your_organization',
|
||||
provider: '<your_provider>',
|
||||
}
|
||||
}
|
||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||
provider: 'workos',
|
||||
options: {
|
||||
queryParams: {
|
||||
connection: '<your_connection>',
|
||||
organization: '<your_organization',
|
||||
provider: '<your_provider>',
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ Setting up Zoom logins for your application consists of 3 parts:
|
||||
- Go to [marketplace.zoom.us](https://marketplace.zoom.us/).
|
||||
- Click on `Sign In` at the top right to log in.
|
||||
|
||||

|
||||

|
||||
|
||||
## Find your callback URL
|
||||
|
||||
@@ -37,12 +37,7 @@ The next step requires a callback URL, which looks like this:
|
||||
- Now just add `/auth/v1/callback` to the end of that to get your full `OAuth Redirect URI`.
|
||||
|
||||
<video width="99%" muted playsInline controls="true">
|
||||
<source
|
||||
src="/docs/videos/api/api-url-and-key.mp4"
|
||||
type="video/mp4"
|
||||
muted
|
||||
playsInline
|
||||
/>
|
||||
<source src="/docs/videos/api/api-url-and-key.mp4" type="video/mp4" muted playsInline />
|
||||
</video>
|
||||
|
||||
## Create a Zoom Oauth App
|
||||
|
||||
@@ -68,7 +68,7 @@ There are two ways to make schema changes:
|
||||
|
||||
### Manual migration
|
||||
|
||||
<!--  -->
|
||||
<!--  -->
|
||||
|
||||
Create a new migration script by running:
|
||||
|
||||
@@ -106,7 +106,7 @@ This allows you to pipe in an existing script from another file or stdout:
|
||||
|
||||
Unlike manual migrations, auto schema diff creates a new migration script from changes **already** applied to your local database.
|
||||
|
||||
<!--  -->
|
||||
<!--  -->
|
||||
|
||||
Create an `employees` table under the `public` schema using Studio UI, accessible at [localhost:54323](http://localhost:54323/) by default.
|
||||
|
||||
@@ -163,7 +163,7 @@ Without the `-f` file flag, the output is written to stdout by default.
|
||||
|
||||
In a production environment, we recommend using a CI/CD pipeline to deploy new migrations with GitHub Actions rather than deploying from your local machine.
|
||||
|
||||

|
||||

|
||||
|
||||
This example uses two Supabase projects, one for production and one for staging.
|
||||
|
||||
@@ -308,7 +308,7 @@ Commit these files to git and push to your `main` branch on GitHub. Update these
|
||||
|
||||
When configured correctly, your repository will have CI and Release workflows that trigger on new commits pushed to `main` and `develop` branches.
|
||||
|
||||

|
||||

|
||||
|
||||
### Open a PR with new migration
|
||||
|
||||
@@ -331,7 +331,7 @@ Once the test error is resolved, merge this PR and watch the deployment in actio
|
||||
|
||||
After verifying your staging project has successfully migrated, create another PR from `develop` to `main` and merge it to deploy the migration to the production project.
|
||||
|
||||
<!--  -->
|
||||
<!--  -->
|
||||
|
||||
The `release` job applies all new migration scripts merged in `supabase/migrations` directory to a linked Supabase project. You can control which project the job links to via `PROJECT_ID` environment variable.
|
||||
|
||||
@@ -400,7 +400,7 @@ Apart from tables, you also need to reassign owner of other entities using their
|
||||
|
||||
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.
|
||||
|
||||
<!--  -->
|
||||
<!--  -->
|
||||
|
||||
We can handle this scenario gracefully by renaming your old migration file with a new timestamp.
|
||||
|
||||
|
||||
@@ -43,12 +43,7 @@ You cannot manage the database schema via the API (for security reasons). To do
|
||||
You can find the API URL and Keys in the [Dashboard](https://app.supabase.com/project/_/settings/api).
|
||||
|
||||
<video width="99%" muted playsInline controls="true">
|
||||
<source
|
||||
src="/docs/videos/api/api-url-and-key.mp4"
|
||||
type="video/mp4"
|
||||
muted
|
||||
playsInline
|
||||
/>
|
||||
<source src="/docs/videos/api/api-url-and-key.mp4" type="video/mp4" muted playsInline />
|
||||
</video>
|
||||
|
||||
## Direct connections
|
||||
@@ -62,12 +57,7 @@ Every Supabase project provides a full Postgres database. You can connect to the
|
||||
3. Find your Connection Info and Connection String. Direct connections are on port `5432`.
|
||||
|
||||
<video width="99%" muted playsInline controls="true">
|
||||
<source
|
||||
src="/docs/videos/postgres-connection.mp4"
|
||||
type="video/mp4"
|
||||
muted
|
||||
playsInline
|
||||
/>
|
||||
<source src="/docs/videos/postgres-connection.mp4" type="video/mp4" muted playsInline />
|
||||
</video>
|
||||
|
||||
## Connection Pool
|
||||
@@ -81,7 +71,7 @@ A "connection pool" is a system (external to Postgres) which manages connections
|
||||
When a client makes a request, PgBouncer "allocates" an available connection to the client.
|
||||
When the client transaction or session is completed the connection is returned to the pool and is free to be used by another client.
|
||||
|
||||

|
||||

|
||||
|
||||
### Pool modes
|
||||
|
||||
@@ -112,12 +102,7 @@ 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
|
||||
@@ -125,7 +110,7 @@ This is the most granular option. Connections are returned to the pool after eve
|
||||
Use this when connecting to your database to prevent snooping and man-in-the-middle attacks.
|
||||
|
||||
Obtain your connection info and Server root certificate from your application’s dashboard.
|
||||

|
||||

|
||||
|
||||
Assuming you’ve downloaded your certificate and it’s located at `$HOME/Downloads/prod-ca-2021.cer`, and your Host address is `db.abcdefghijklm.supabase.co` you can connect to the DB with
|
||||
SSL enabled as illustrated below:
|
||||
@@ -138,13 +123,13 @@ psql "sslmode=verify-full sslrootcert=$HOME/Downloads/prod-ca-2021.cer host=db.a
|
||||
|
||||
2. With `pgAdmin`
|
||||
a. Register a new Postgres server
|
||||

|
||||

|
||||
|
||||
b. Name your server to your liking and add the connection info.
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
3. Navigate to the SSL tab and change the SSL mode to Require. Next navigate to the Root certificate input, it will open up a
|
||||
file-picker modal. Select the certificate you downloaded from your Supabase dashboard and save the server details. PgAdmin
|
||||
should now be able to connect to your Postgres via SSL.
|
||||

|
||||

|
||||
@@ -20,7 +20,7 @@ Supabase is a combination of open source tools, each specifically chosen for Ent
|
||||
If the tools and communities already exist, with an MIT, Apache 2, or equivalent open license, we will use and support that tool.
|
||||
If the tool doesn't exist, we build and open source it ourselves.
|
||||
|
||||

|
||||

|
||||
|
||||
- [Kong](https://github.com/Kong/kong) is a cloud-native API gateway.
|
||||
- [GoTrue](https://github.com/netlify/gotrue) is an SWT based API for managing users and issuing SWT tokens.
|
||||
|
||||
@@ -16,12 +16,12 @@ Let’s get started!
|
||||
|
||||
- On the [Supabase dashboard](https://app.supabase.com), click `New project` and set the name to **Support Dashboard**
|
||||
|
||||

|
||||

|
||||
|
||||
- Create a new table by clicking on the Create Table option on the side navigation.
|
||||
- Supabase provides many ways to add data to the tables, from writing queries to creating schemas using UI to simply uploading CSV files. For our support dashboard, we will be creating the **tickets** table by uploading the [CSV file](https://raw.githubusercontent.com/vihar/datasets/master/tickets.csv) on Supabase.
|
||||
|
||||

|
||||

|
||||
|
||||
The database is now set up.
|
||||
|
||||
@@ -29,40 +29,40 @@ The database is now set up.
|
||||
|
||||
- Note down the database connection information under Project Settings in Supabase.
|
||||
|
||||

|
||||

|
||||
|
||||
- On Appsmith, create a new application under the dashboard under your preferred organization.
|
||||
- Click on the `+` icon next to Datasources on the left navigation bar under Page1
|
||||
- Next, click on Create New tab and choose PostgreSQL datasource, you’ll see the following screenshot:
|
||||
|
||||

|
||||

|
||||
|
||||
- Fill out the form to connect to your Supabase instance. Click **Test** to test connection and then **Save** to save the datasource
|
||||
|
||||

|
||||

|
||||
|
||||
## Step 3: Build UI on Appsmith
|
||||
|
||||
- Click on the + icon next to widgets and drag and drop a Tab widget. We can configure using the property pane by clicking on the cog icon on the top-right corner.
|
||||
- As seen in the below screenshot, we have added four tabs to support the dashboard.
|
||||
|
||||

|
||||

|
||||
|
||||
- Add widgets to the **Home** tab to create the dashboard as shown in the screenshot below. For eg: **Critical Open Issues** is a **Text** widget and below it is an **Input** widget which we will bind later to display the number of open tickets.
|
||||
|
||||
- Set up the **New** button to open a modal which will have a form to raise a new ticket.
|
||||
|
||||

|
||||

|
||||
|
||||
- In the modal widget, add a few widgets to accept input when creating a new ticket. Please refer to the screenshot below.
|
||||
|
||||

|
||||

|
||||
|
||||
## Step 4: Writing Queries in Appsmith and binding data to widgets
|
||||
|
||||
- Click on the + icon next to Datasources on the navigation bar and click New Query next to the Supabase connection here to create a new query.
|
||||
|
||||

|
||||

|
||||
|
||||
- Rename the query to create_new_ticket under the query pane; here we can write SQL that can collect the data from the widgets using mustache templates.
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ You will also need a Supabase account, which can be created by signing in [here]
|
||||
|
||||
From the Auth0 dashboard, click the menu to the right of the Auth0 logo, and select `Create tenant`.
|
||||
|
||||

|
||||

|
||||
|
||||
Enter a `Domain` for your tenant - this will need to be unique.
|
||||
|
||||
@@ -32,7 +32,7 @@ Select a `Region` - this should be geographically close to the majority of your
|
||||
|
||||
Select `Development` for `Environment Tag` - this should be production when you're ready to go live.
|
||||
|
||||

|
||||

|
||||
|
||||
## Step 2: Setting up an Auth0 application
|
||||
|
||||
@@ -40,7 +40,7 @@ From the sidebar menu, select `Applications` > `Applications` and click `Create
|
||||
|
||||
Give your application a name, select the `Regular Web Applications` option and click `Create`.
|
||||
|
||||

|
||||

|
||||
|
||||
Select `Settings` and navigate to the `Application URIs` section, and update the following:
|
||||
|
||||
@@ -68,7 +68,7 @@ Select the same `Region` you selected for your Auth0 tenant.
|
||||
|
||||
Click `Create new project`.
|
||||
|
||||

|
||||

|
||||
|
||||
## Step 4: Creating data in Supabase
|
||||
|
||||
@@ -86,17 +86,17 @@ Create two new columns:
|
||||
|
||||
Click `Save` to create the new table.
|
||||
|
||||

|
||||

|
||||
|
||||
From the `Table editor` view, select the `todo` table and click `Insert row`.
|
||||
|
||||
Fill out the `title` field and click `Save`.
|
||||
|
||||

|
||||

|
||||
|
||||
Click `Insert row` and add a couple of extra todos.
|
||||
|
||||

|
||||

|
||||
|
||||
## Step 5: Building a Next.js app
|
||||
|
||||
@@ -121,11 +121,11 @@ SUPABASE_SIGNING_SECRET=get-from-supabase-dashboard
|
||||
|
||||
> Note: Auth0 values can be found under `Settings > Basic Information` for your application.
|
||||
|
||||

|
||||

|
||||
|
||||
> Note: Supabase values can be found under `Settings > API` for your project.
|
||||
|
||||

|
||||

|
||||
|
||||
Restart your Next.js development server to read in the new values from `.env.local`.
|
||||
|
||||
@@ -203,13 +203,13 @@ export default Index
|
||||
|
||||
Test this is working by navigating to `http://localhost:3000` which should redirect you to an Auth0 sign in screen.
|
||||
|
||||

|
||||

|
||||
|
||||
Either `Sign up` for a new account, or click `Continue with Google` to sign in.
|
||||
|
||||
You should now be able to view the landing page.
|
||||
|
||||

|
||||

|
||||
|
||||
## Step 7: Sign Auth0 token for Supabase
|
||||
|
||||
@@ -239,10 +239,7 @@ const afterCallback = async (req, res, session) => {
|
||||
exp: Math.floor(Date.now() / 1000) + 60 * 60,
|
||||
}
|
||||
|
||||
session.user.accessToken = jwt.sign(
|
||||
payload,
|
||||
process.env.SUPABASE_SIGNING_SECRET
|
||||
)
|
||||
session.user.accessToken = jwt.sign(payload, process.env.SUPABASE_SIGNING_SECRET)
|
||||
|
||||
return session
|
||||
}
|
||||
@@ -390,7 +387,7 @@ export default Index
|
||||
|
||||
Either way, when we reload our application, we are still getting the empty state for todos.
|
||||
|
||||

|
||||

|
||||
|
||||
This is because we enabled Row Level Security, which blocks all requests by default. To enable our user to select their `todos` we need to write a policy.
|
||||
|
||||
@@ -416,11 +413,11 @@ Let's create a policy that checks whether this user is the owner of the todo.
|
||||
|
||||
Select `Authentication` from the Supabase sidebar menu, click `Policies`, and then `New Policy` on the `todo` table.
|
||||
|
||||

|
||||

|
||||
|
||||
From the modal, select `Create a policy from scratch` and add the following.
|
||||
|
||||

|
||||

|
||||
|
||||
This policy is calling the function we just created to get the currently logged in user's ID `auth.user_id()` and checking whether this matches the `user_id` column for the current `todo`. If it does, then it will allow the user to select it, otherwise it will continue to deny.
|
||||
|
||||
@@ -432,21 +429,21 @@ The last thing we need to do is update the `user_id` columns for our existing `t
|
||||
|
||||
Head back to the Supabase dashboard, and select `Table editor` from the sidebar.
|
||||
|
||||

|
||||

|
||||
|
||||
Each of our `user_id` columns are set to `NULL`!
|
||||
|
||||
To get the ID for our Auth0 user, head over to the Auth0 dashboard, select `User Management` from the sidebar, click `Users` and select your test user.
|
||||
|
||||

|
||||

|
||||
|
||||
Copy their `user_id`.
|
||||
|
||||

|
||||

|
||||
|
||||
Update each row in Supabase.
|
||||
|
||||

|
||||

|
||||
|
||||
Now when we refresh our application, we should finally see our list of `todos`!
|
||||
|
||||
|
||||
@@ -20,23 +20,23 @@ After your Clerk application has been created, use the lefthand menu to navigate
|
||||
|
||||
Click on the button to create a new template based on Supabase.
|
||||
|
||||

|
||||

|
||||
|
||||
This will pre-populate the default claims required by Supabase. You can include additional claims or modify them as necessary. [Shortcodes](https://docs.clerk.dev/popular-guides/jwt-templates#shortcodes) are also available for adding dynamic values.
|
||||
|
||||
ℹ️ Note the name of the JWT template (which you can change) because this will be needed later.
|
||||
|
||||

|
||||

|
||||
|
||||
## Step 2: Sign JWT with Supabase secret
|
||||
|
||||
Supabase requires JWTs be signed with the HS256 signing algorithm and use their signing key. Find the JWT secret key in your Supabase project under **Settings** > **API** in the **Config** section.
|
||||
|
||||

|
||||

|
||||
|
||||
Click to reveal the JWT secret, copy it, and then paste it in the Signing key field in the Clerk JWT template.
|
||||
|
||||

|
||||

|
||||
|
||||
After the key is added, click the **Apply Changes** button to save your template.
|
||||
|
||||
@@ -58,11 +58,11 @@ NEXT_PUBLIC_SUPABASE_KEY=your-supabase-anon-key
|
||||
|
||||
Your Clerk Frontend API can be found on the [API Keys](https://dashboard.clerk.dev/last-active?path=api-keys) screen.
|
||||
|
||||

|
||||

|
||||
|
||||
To get the ones needed for Supabase, navigate to the same Settings > API page as before and locate the anon public key and URL.
|
||||
|
||||

|
||||

|
||||
|
||||
**Note**: It is recommended that you enable [Row Level Security](https://supabase.com/docs/guides/auth/row-level-security) (RLS) for your database tables and configure access policies as needed.
|
||||
|
||||
@@ -174,7 +174,7 @@ For example, this policy would check that the user making the request is authent
|
||||
|
||||
If you would like the requesting user ID from the JWT to automatically populate a text type column in your database table, you can set the **Default Value** field to the previously defined `requesting_user_id()` function.
|
||||
|
||||

|
||||

|
||||
|
||||
## Resources
|
||||
|
||||
|
||||
@@ -26,11 +26,11 @@ First, in Dashibase, go to “Data Sources” via the left sidebar and click on
|
||||
|
||||
Note: We do not download or store any of your database data.
|
||||
|
||||

|
||||

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

|
||||

|
||||
|
||||
## Step 2. Set up your data access policy
|
||||
|
||||
@@ -38,15 +38,15 @@ Once you have connected your database, you can also control what your team can s
|
||||
|
||||
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).
|
||||
|
||||

|
||||

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

|
||||

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

|
||||

|
||||
|
||||
## Step 3. Build your dashboard
|
||||
|
||||
@@ -56,25 +56,25 @@ Here’s where the fun begins. There are many ways to build your dashboards in D
|
||||
|
||||
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.
|
||||
|
||||

|
||||

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

|
||||

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

|
||||

|
||||
|
||||
For all the tables in your dashboard, you can rename the column headers, add filter and sort, adjust the column width, and more.
|
||||
|
||||
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.
|
||||
|
||||

|
||||

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

|
||||

|
||||
|
||||
## Step 4. Share your dashboard with your team
|
||||
|
||||
@@ -85,7 +85,7 @@ Finally, save and share the dashboard with your team. There are two levels of ac
|
||||
|
||||
Click on the “Share” button in the upper-right corner of your dashboard to share access:
|
||||
|
||||

|
||||

|
||||
|
||||
Now your team can use this customer admin panel to find and update customer information easily!
|
||||
|
||||
|
||||
@@ -8,11 +8,11 @@ In this guide, we will demonstrate how to create a new Supabase project, install
|
||||
|
||||
## Introduction
|
||||
|
||||

|
||||

|
||||
|
||||
[Supabase](https://supabase.com/) is an open-source Firebase alternative that provides a PostgreSQL database, storage, authentication, and a dynamic REST API based on your schema. While it is possible to self-host Supabase on your own infrastructure, this article will focus on Supabase Cloud's Free tier, which is the fastest and easiest way to get started.
|
||||
|
||||

|
||||

|
||||
|
||||
[Directus](https://directus.io/) is an open-source data platform that layers on top of any SQL database, providing a powerful suite of tools. The Directus Engine provides dynamic REST and GraphQL APIs based on your schema, hooks and automation, authentication and access control, and file transformations. Directus Studio enables engineers and non-technical users alike to browse, manage, and visualize database content through a no-code app.
|
||||
|
||||
@@ -49,7 +49,7 @@ As mentioned, while you can [deploy Supabase locally](https://supabase.com/docs/
|
||||
|
||||
To take full advantage of the built-in geometry and mapping features Directus offers, we recommend enabling Geometric Data Support. To add PostGIS, follow these steps:
|
||||
|
||||

|
||||

|
||||
|
||||
1. From the sidebar, navigate to **Database > Extensions**.
|
||||
2. Use the search bar to look up `PostGIS`.
|
||||
|
||||
@@ -12,7 +12,7 @@ Draftbit is back-end agnostic and connects to Supabase via REST API.
|
||||
|
||||
> Note: For the demonstration purpose of this guide, we are using a pre-populated database in Supabase. We are calling `Groceries`. To follow along, rename it any way you prefer.
|
||||
|
||||

|
||||

|
||||
|
||||
If you don’t have a Draftbit account, create one [here](https://draftb.it/3Fkbask). Once you’ve got your account set up, Create a New App. You can select `Start From a Blank App` for this demo and proceed to the Builder interface.
|
||||
|
||||
@@ -30,7 +30,7 @@ You can find these unique values in the API settings of your Supabase account.
|
||||
- In the **Project URL** section, select and copy the URL. It is the Base URL of your Supabase REST API. It will be required to make a connection to the Draftbit app.
|
||||
- Also, under `Project API keys`, select and copy the API key under `anon`. It is required for every request made to the Supabase database.
|
||||
|
||||

|
||||

|
||||
|
||||
## Step 2: Save Supabase API key as Authorization Header in Draftbit
|
||||
|
||||
@@ -44,7 +44,7 @@ To authorize your Draftbit app with Supabse, in the builder interface:
|
||||
- The value of this key requires you to enter an authorization token that starts with syntax is `<your-api-key>`. Click **Add** after adding the value.
|
||||
- Click **Save** to save these keys and close the modal.
|
||||
|
||||

|
||||

|
||||
|
||||
## Step 3: Add Supabase RESTful endpoint in Draftbit
|
||||
|
||||
@@ -57,7 +57,7 @@ In your Draftbit builder interface:
|
||||
- In Step 2: Under **Key** add `Authorization` and `apikey`. Then, under **Value**, select the global variables (from the previous section) to add the actual values for both keys.
|
||||
- Click Save.
|
||||
|
||||

|
||||

|
||||
|
||||
## Making API requests with Supabase & Draftbit
|
||||
|
||||
@@ -67,7 +67,7 @@ In this section, let's populate a Fetch component with all the data from a simpl
|
||||
|
||||
For reference, here is a how the Components tree looks like for this screen:
|
||||
|
||||

|
||||

|
||||
|
||||
The next step is to create an endpoint. Let's try fetching all the data using a `GET` HTTP request. Select the Supabase service in the **API & Cloud Services** modal, and then:
|
||||
|
||||
@@ -77,7 +77,7 @@ The next step is to create an endpoint. Let's try fetching all the data using a
|
||||
- In Step 4: click the **Test** button next to the Endpoint input to verify the response coming from the Supabase.
|
||||
- Click Save.
|
||||
|
||||

|
||||

|
||||
|
||||
In the Builder, on the app screen:
|
||||
|
||||
@@ -89,7 +89,7 @@ In the Builder, on the app screen:
|
||||
- Add a `{{varName}}` value (inside the curly braces) to represent a column field from the Supabase. For example, add `{{title}}` to represent the column name from the Supabase Base.
|
||||
- Under **Variables**, you will see the variable name defined in the previous step. From the dropdown menu, select the appropriate field that represents the data field.
|
||||
|
||||

|
||||

|
||||
|
||||
### GET request to fetch single row
|
||||
|
||||
@@ -101,7 +101,7 @@ Open the **API & Cloud services** modal from the top menu, select the Supabase s
|
||||
- In Step 4: click the **Test** button next to the Endpoint input to verify the response coming from the Supabase.
|
||||
- Click Save.
|
||||
|
||||

|
||||

|
||||
|
||||
On app screen:
|
||||
|
||||
@@ -114,7 +114,7 @@ On app screen:
|
||||
- Add a `{{varName}}` value (inside the curly braces) to represent the column field from the Supabase. For example, add `{{title}}` to represent the field and value from the Supabase data table.
|
||||
- Under **Variables**, you will see the variable name defined in the previous step. From the dropdown menu, select the appropriate field that represents the data field.
|
||||
|
||||

|
||||

|
||||
|
||||
### POST request to submit a new row
|
||||
|
||||
@@ -140,7 +140,7 @@ In addition, you need a Touchable component like a Button to attach the POST act
|
||||
- In Step 4: to see the new row added to the Supabase data table as JSON response inside the Builder, you have to pass a new header called `Prefer` with its value as `return=representation`.
|
||||
- In Step 5: click the **Test** button next to the Endpoint input to verify the response coming from the Supabase and click Save.
|
||||
|
||||

|
||||

|
||||
|
||||
Once you follow the above steps, you should get a 200 OK response with exactly the new record as a JSON you have entered for your schema.
|
||||
|
||||
@@ -160,7 +160,7 @@ Now, there is a working `POST` request in Draftbit. Map its response to the comp
|
||||
|
||||
First, for each input component, make sure you have set the Field Names (found in the Configs tab, second from the left) to unique values. For example, in the screen below, there is one TextInput field component with the value of the `Field Name` prop of `textInputValue`.
|
||||
|
||||

|
||||

|
||||
|
||||
Next, on your Button component, go to the Interactions tab in the Properties panel located on the far-right-hand side. Select an Action called `API request`.
|
||||
|
||||
@@ -170,7 +170,7 @@ In the API request action:
|
||||
- In **Endpoint**, select the name of the Endpoint.
|
||||
- Then add the configuration for the body request to be sent by selecting the values for `{{inputValue}}`.
|
||||
|
||||

|
||||

|
||||
|
||||
After completing the above steps, you can trigger the API request to submit new data to the Supabase database.
|
||||
|
||||
@@ -186,7 +186,7 @@ After you have created your screen components in the Draftbit builder, open the
|
||||
- In Step 3: add a valid Body structure to submit a PATCH request. Add one or many `{{variable}}` for test values depending on the structure of your app. Click Body Preview to validate the structure of the Body in the request. For the example, let's create a variable called `{{inputValue}}`.
|
||||
- In Step 5: click the **Test** button next to the Endpoint input to verify the response coming from the Supabase and click Save.
|
||||
|
||||

|
||||

|
||||
|
||||
Next, on your Button component, go to the Interactions tab in the Properties panel located on the far-right-hand side. Select an Action called `API request`.
|
||||
|
||||
@@ -196,7 +196,7 @@ In the API request action:
|
||||
- In **Endpoint**, select the name of the Endpoint.
|
||||
- Then add the configuration for the query param, and the body request to be sent by selecting the values for `{{inputValue}}`.
|
||||
|
||||

|
||||

|
||||
|
||||
After completing the above steps, you can trigger the API request to update existing data in the Supabase database.
|
||||
|
||||
@@ -212,7 +212,7 @@ After you have created your screen components in the Draftbit builder, open the
|
||||
- In Step 4: click the **Test** button next to the Endpoint input to verify the response from the Supabase.
|
||||
- Click Save.
|
||||
|
||||

|
||||

|
||||
|
||||
Next, on your Button component, go to the Interactions tab in the Properties panel located on the far-right-hand side. Select an Action called `API request`.
|
||||
|
||||
@@ -222,7 +222,7 @@ In the API Request action:
|
||||
- In **Endpoint**, select the name of the Endpoint.
|
||||
- Then, add the configuration for the query request to be sent by selecting a value. For example, in this case it will be the `id` of the record coming from the Navigation parameter.
|
||||
|
||||

|
||||

|
||||
|
||||
## Resources
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ explain (analyze, format json, buffers, verbose, settings)
|
||||
|
||||
Run the query, and copy the output.
|
||||
|
||||

|
||||

|
||||
|
||||
If you’re using the Supabase SQL Editor, this is easily copied from the cell titled `QUERY PLAN`, as seen above.
|
||||
|
||||
@@ -32,17 +32,17 @@ If you have any trouble, check out pgMustard’s guide for [getting a query plan
|
||||
|
||||
Paste the json output into pgMustard and press Submit.
|
||||
|
||||

|
||||

|
||||
|
||||
## Step 3: Look through the top tips and slowest operations
|
||||
|
||||
Review the top tips in pgMustard. These are scored on a scale of 0 to 5 stars, based on how much time-saving potential they have (5 stars meaning lots of potential).
|
||||
|
||||

|
||||

|
||||
|
||||
Click one of the tips, or one of the operations, to see more information.
|
||||
|
||||

|
||||

|
||||
|
||||
## Step 4: Consider your options
|
||||
|
||||
@@ -52,11 +52,11 @@ If you don’t get any tips, your query might be quite fast for the amount of wo
|
||||
|
||||
For the example we saw in Step 3, let's try adding an index on the `customer_name` field in Supabase.
|
||||
|
||||

|
||||

|
||||
|
||||
Going through Steps 1-3 again, we now get an efficient index scan, that will scale nicely as our data grows.
|
||||
|
||||

|
||||

|
||||
|
||||
We could look into why Postgres isn’t choosing to do an index-only scan here, but pgMustard is letting us know that it doesn’t think we’ll gain much by doing so, by scoring the tip 0.3 out of 5.
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ You can also take a look at the Plasmic project here:
|
||||
|
||||
> You’ll need to enable 3rd-party cookies in your browser for the project to properly load.
|
||||
|
||||

|
||||

|
||||
|
||||
At a high level,
|
||||
|
||||
@@ -27,7 +27,7 @@ At a high level,
|
||||
- On the [Supabase dashboard](https://app.supabase.com/), click `New project` and set the name of the project.
|
||||
By default, Supabase will already be set up for user signups with email, with users being stored in a `users` table.
|
||||
|
||||

|
||||

|
||||
|
||||
- Navigate to the `Table Editor` on the left side navigation bar. Here we can create a `New table` to store our Pokemon entries. Make sure you are in the `schema public` view. Create a new table called `entries`, with 6 columns:
|
||||
- `id`: is a unique ID for the entry. This column should be generated automatically as the primary column.
|
||||
@@ -37,11 +37,11 @@ At a high level,
|
||||
|
||||
> Note: In this tutorial we’ve turned off “[Row Level Security (RLS)](https://supabase.com/docs/guides/auth/row-level-security)”. In practice, you will want to create policies that restrict who gets to create, edit, and delete posts. By turning this off, any user can modify the database without restrictions.
|
||||
|
||||

|
||||

|
||||
|
||||
For your convenience, feel free to import the following CSV into Supabase to pre-populate your database. In order to import, you must select `Import data via spreadsheet`, in the new table dialog box. (It does not work on existing tables.)
|
||||
|
||||
[pokedex-export.csv](/img/guides/integrations/plasmic/pokedex-export.csv)
|
||||
[pokedex-export.csv](/docs/img/guides/integrations/plasmic/pokedex-export.csv)
|
||||
|
||||
## Step 2: Set up your codebase
|
||||
|
||||
@@ -71,7 +71,7 @@ Navigate to [http://localhost:3000](http://localhost:3000) in your web browser.
|
||||
|
||||
If you pre-populated the database in Step 1. you should see the following homepage after logging in. Otherwise, feel free to add Pokemon manually via the UI.
|
||||
|
||||

|
||||

|
||||
|
||||
## Step 4: Clone the Plasmic project
|
||||
|
||||
@@ -80,7 +80,7 @@ Now let’s try to make some additions! The code base is currently configured to
|
||||
Open the default starter Plasmic project here:
|
||||
[https://studio.plasmic.app/projects/66RKaSPCwKxYjCfXWHCxn6](https://studio.plasmic.app/projects/66RKaSPCwKxYjCfXWHCxn6)
|
||||
|
||||

|
||||

|
||||
|
||||
To make an editable copy, click on the `Copy Project` button in the blue bar. This will clone the project and redirect you to your copy.
|
||||
|
||||
@@ -91,7 +91,7 @@ Take note of the `project ID` and `API token`. You can find the project ID in th
|
||||
|
||||
The API token can be found by clicking the `Code` button in the top bar.
|
||||
|
||||

|
||||

|
||||
|
||||
Now go back to `.env.local` and update the corresponding project ID and token fields.
|
||||
|
||||
@@ -131,7 +131,7 @@ Insert a `SupabaseGrid` by searching the AddDrawer (by clicking the blue + butto
|
||||
|
||||
> For source see `components/CodeComponents/DatabaseComponents.tsx`
|
||||
|
||||

|
||||

|
||||
|
||||
Then in the right-hand panel, configure the props on `SupabaseGrid`.
|
||||
|
||||
@@ -139,13 +139,13 @@ Then in the right-hand panel, configure the props on `SupabaseGrid`.
|
||||
- `tableColumns` are a comma-delimited list of columns you want to select from the table
|
||||
- We also set the number of columns and spacing shown in the grid
|
||||
|
||||

|
||||

|
||||
|
||||
The `SupabaseGrid` will loop over the rows from the query.
|
||||
|
||||
Now customize the repeated content by inserting instances of `SupabaseField`. Select the type of content and a selector string to fetch a single value. In the example below, we use `{{row.imageUrl}}` to retrieve the `imageUrl` column of the row. Apply any styling and layout you want on these elements.
|
||||
|
||||

|
||||

|
||||
|
||||
### Putting it all together (video)
|
||||
|
||||
@@ -266,16 +266,7 @@ Note that this code component is defined in your codebase. Feel free to augment
|
||||
|
||||
export function SupabaseGridCollection(props: SupabaseGridCollectionProps) {
|
||||
const supabaseQuery = React.useContext(SupabaseQueryContext)
|
||||
const {
|
||||
children,
|
||||
columns,
|
||||
columnGap,
|
||||
rowGap,
|
||||
count,
|
||||
className,
|
||||
loading,
|
||||
testLoading,
|
||||
} = props
|
||||
const { children, columns, columnGap, rowGap, count, className, loading, testLoading } = props
|
||||
|
||||
const result = supabaseQuery
|
||||
if (!result || testLoading) {
|
||||
@@ -309,13 +300,7 @@ export function SupabaseGridCollection(props: SupabaseGridCollectionProps) {
|
||||
```tsx
|
||||
// supabase-demo/components/CodeComponents/DisplayCollections.tsx
|
||||
|
||||
export function SupabaseTextField({
|
||||
name,
|
||||
className,
|
||||
}: {
|
||||
name?: string
|
||||
className?: string
|
||||
}) {
|
||||
export function SupabaseTextField({ name, className }: { name?: string; className?: string }) {
|
||||
const contexts = useAllContexts()
|
||||
if (!name) {
|
||||
return <p>You need to set the name prop</p>
|
||||
|
||||
@@ -31,7 +31,7 @@ If you do not already have a PolyScale account, you can create an account [here]
|
||||
|
||||
In your Supabase project, click on `Settings > Database` and scroll down to the `Connection info` section to copy your database `Host`.
|
||||
|
||||

|
||||

|
||||
|
||||
### 1.2 Configure your PolyScale Cache
|
||||
|
||||
@@ -42,7 +42,7 @@ In your Supabase project, click on `Settings > Database` and scroll down to the
|
||||
- Enter `5432` for the **Port**
|
||||
- Click **Create**
|
||||
|
||||

|
||||

|
||||
|
||||
Your cache is now created. PolyScale automatically checks to see that your database is accessible from all our global PoPs.
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ This guide explains how to quickly connect the Postgres database provided by Sup
|
||||
## Step 1: Get the connection string from Supabase project settings
|
||||
|
||||
Go to the settings page from the sidebar and navigate to the **Database** tab. You’ll find the database’s connection string with a placeholder for the password you provided when you created the project.
|
||||

|
||||

|
||||
|
||||
## Step 2: Testing the connection
|
||||
|
||||
@@ -114,14 +114,14 @@ This will create a `prisma/migrations` folder inside your `prisma` directory and
|
||||
|
||||
> Note: if you want to skip the process of creating a migration history, you can use the [`db push`](https://www.prisma.io/docs/concepts/components/prisma-migrate/db-push) command instead of `migrate dev`.
|
||||
> If you go to your Supabase project, in the table editor, you should see that two tables have been created, a `Post` and a `User` table.
|
||||
> 
|
||||
> 
|
||||
> That’s it! You have now successfully connected a Prisma project to a PostgreSQL database hosted on Supabase and ran your first migration.
|
||||
|
||||
## Connection pooling with Supabase
|
||||
|
||||
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 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
|
||||
|
||||
@@ -14,29 +14,29 @@ To connect Supabase to Sequin, you'll first need to retrieve the credentials for
|
||||
|
||||
1. In the Supabase dashboard, go to the settings page and open up your **Database** settings. In the **Connection info** section, you'll find the credentials you need - like `host` and `user`:
|
||||
|
||||

|
||||

|
||||
|
||||
2. In the [Sequin console](https://app.sequin.io), go to your sync's configuration and open the **Destination** section. Select **Launch or Connect** and then click **Connect** to configure the connection to your Supabase Postgres:
|
||||
|
||||

|
||||

|
||||
|
||||
3. In the connection modal that appears, enter the `Host` and `Port` for your Supabase database and click **Continue**.
|
||||
|
||||

|
||||

|
||||
|
||||
4. Now, enter the `Database name` and set the `schema` name for your sync. For instance, if your syncing Stripe, you'll likely want to name your synced schema something like `stripe`. Finally, enter the `user` and `password` for your Supabase database and then click **Continue**. Sequin will verify it can properly connect to your database with the correct permissions.
|
||||
|
||||

|
||||

|
||||
|
||||
5. Sequin is now connected to your Supabase Postgres database and will ask you to confirm which database users should be able to access your synced schema. Select all of the users and click **Continue**:
|
||||
|
||||

|
||||

|
||||
|
||||
6. That's it. Sequin will now create a new schema and permissions group in your Supabase database. Name the database connection in Sequin something like `Supabase` and your done!
|
||||
|
||||
In the Supabase dashboard, you can go to the **Table Editor** and you'll see a new schema full of your synced platform data.
|
||||
|
||||

|
||||

|
||||
|
||||
## Step 2: Grant Permissions
|
||||
|
||||
@@ -44,7 +44,7 @@ To ensure the right users can access the synced schema Sequin manages, you'll ne
|
||||
|
||||
1. In the Sequin console, click the **Connect** button next to your sync and copy down your `Schema` and unique `Read Group`.
|
||||
|
||||

|
||||

|
||||
|
||||
2. Now, in the Supabase dashboard, go to the **SQL Editor** and run the following permission grants:
|
||||
|
||||
|
||||
@@ -40,29 +40,29 @@ Follow along in the video below as the founder of Snaplet, Peter Pistorius, take
|
||||
|
||||
Navigate to [https://www.snaplet.dev/](https://www.snaplet.dev/) and sign up for a new account (it’s free). Once you have successfully signed up for a new account, create a team, and start by connecting to your Supabase project.
|
||||
|
||||

|
||||

|
||||
|
||||
Enter the credentials of your production Supabase project. Find the "Connection string" in Supabase via `Organization > Project > Settings > Database > Connection string` (at the bottom of the page).
|
||||
|
||||
The password is the same password you used when creating the Supabase project.
|
||||
|
||||

|
||||

|
||||
|
||||
You’ll have to confirm providing Snaplet access to your database. Snaplet will prompt you to only provide `read-only` access to your database. Snaplet has a guide in their documentation on how to do so [here](https://docs.snaplet.dev/postgresql/create-read-only-role).
|
||||
|
||||
> Note that whatever connection string you provide here will be that of your Data Source – essentially the production database in a real-life scenario
|
||||
|
||||

|
||||

|
||||
|
||||
### 2.2. Transform your data
|
||||
|
||||

|
||||

|
||||
|
||||
The next step is to exclude any schemas that you do not require. You are able to exclude an entire schema by clicking on the drop-down at the top, selecting the schema you would like to exclude and clicking ‘Exclude schema’. Alternatively, you can select a given schema and exclude only specific tables from that particular schema. Exclude any non-required table data (such as logs) and extensions and view your columns.
|
||||
|
||||
At this point, Snaplet will automatically detect any columns that have Personally Identifiable Information (PII) and mark them in purple. If there are any additional columns that hold data you would like to anonymise, you can click on the respective column name and provide a replacement value for the data in that column. To complete the onboarding, click on `Review and Save` and proceed to the dashboard.
|
||||
|
||||

|
||||

|
||||
|
||||
### 2.3. Create a Snapshot
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ If you already have a Stytch account you may skip this step.
|
||||
|
||||
Go to [Stytch](https://stytch.com?utm_source=supabase&utm_medium=guide), and create an account. Note that Stytch provides two ways to create an account, either via Google OAuth, or through Email magic links. This is the same user experience we will be building in this guide!
|
||||
|
||||

|
||||

|
||||
|
||||
## Step 1: Set up Stytch redirect URLs
|
||||
|
||||
@@ -28,11 +28,11 @@ First we need to add the redirect URLs that will be used during the Email magic
|
||||
|
||||
Navigate to your [redirect URL settings](https://stytch.com/dashboard/redirect-urls?utm_source=supabase&utm_medium=guide) in the Stytch dashboard, and under **Test environment** create an entry where the **URL** is `http://localhost:3000/api/authenticate` and the **Type** is `All`.
|
||||
|
||||

|
||||

|
||||
|
||||
After pressing **Confirm**, the redirect URLs dashboard will update to show your new entry. We will use this URL later on.
|
||||
|
||||

|
||||

|
||||
|
||||
## Step 2: Create a Supabase project
|
||||
|
||||
@@ -44,7 +44,7 @@ Enter a secure `Database Password`.
|
||||
|
||||
Click **Create new project**. It may take a couple minutes for your project to be provisioned.
|
||||
|
||||

|
||||

|
||||
|
||||
## Step 3: Creating data in Supabase
|
||||
|
||||
@@ -64,17 +64,17 @@ Add three new columns:
|
||||
|
||||
Click **Save** to create the new table.
|
||||
|
||||

|
||||

|
||||
|
||||
From the Table editor view, select the expenses table and click **Insert row**.
|
||||
|
||||
Fill out the title and value fields (leave user_id blank for now) and click **Save**.
|
||||
|
||||

|
||||

|
||||
|
||||
Use **Insert Row** to further populate the table with expenses.
|
||||
|
||||

|
||||

|
||||
|
||||
## Step 4: Building a Next.js app
|
||||
|
||||
@@ -98,11 +98,11 @@ SUPABASE_SIGNING_SECRET=GET_FROM_SUPABASE_DASHBOARD
|
||||
|
||||
> Note: Stytch values can be found in the project [dashboard](https://stytch.com/dashboard/api-keys?utm_source=supabase&utm_medium=guide) under **API Keys**.
|
||||
|
||||

|
||||

|
||||
|
||||
> Note: Supabase values can be found under **Settings** > **API** for your project.
|
||||
|
||||

|
||||

|
||||
|
||||
Start your Next.js development server to read in the new values from `.env.local`.
|
||||
|
||||
@@ -225,15 +225,15 @@ export default Index
|
||||
|
||||
On `localhost:3000` there is now a login form prompting for your email address.
|
||||
|
||||

|
||||

|
||||
|
||||
Enter your email address and press **Continue with email**.
|
||||
|
||||

|
||||

|
||||
|
||||
In your inbox you will find a login request from your app.
|
||||
|
||||

|
||||

|
||||
|
||||
However, if you click the link in the email you will get a 404. We need to build an API route to handle the email magic link authentication.
|
||||
|
||||
@@ -266,10 +266,7 @@ const loadStytch = () => {
|
||||
client = new stytch.Client({
|
||||
project_id: process.env.STYTCH_PROJECT_ID,
|
||||
secret: process.env.STYTCH_SECRET,
|
||||
env:
|
||||
process.env.STYTCH_PROJECT_ENV === 'live'
|
||||
? stytch.envs.live
|
||||
: stytch.envs.test,
|
||||
env: process.env.STYTCH_PROJECT_ENV === 'live' ? stytch.envs.live : stytch.envs.test,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -400,7 +397,7 @@ export default Index
|
||||
|
||||
Return to `localhost:3000`, and login again by sending yourself a new email. Upon clicking through in the email you should be presented with “Welcome $USER_ID”. If you refresh the page, you should remain in an authenticated state. If you press **Logout** then you should return to the login screen.
|
||||
|
||||

|
||||

|
||||
|
||||
Now that we have a working login flow with persistent authentication it is time to pull in our expense data from Supabase.
|
||||
|
||||
@@ -520,11 +517,11 @@ Let's create a policy that checks whether this user is the owner of an expense.
|
||||
|
||||
Select **Authentication** from the Supabase sidebar menu, click **Policies**, then **New Policy**.
|
||||
|
||||

|
||||

|
||||
|
||||
From the modal, select **For full customization create a policy from scratch** and add the following.
|
||||
|
||||

|
||||

|
||||
|
||||
This policy is calling the function we just created to get the currently logged in user's `user_id` `auth.user_id()` and checking whether this matches the `user_id` column for the current expense. If it does, then it will allow the user to select it, otherwise it will continue to deny.
|
||||
|
||||
@@ -536,19 +533,19 @@ The last thing we need to do is update the `user_id` columns for our existing ex
|
||||
|
||||
Head back to the Supabase dashboard, and select Table editor from the sidebar. You will notice each entry has `user_id` set to `NULL`. We need to update this value to the proper `user_id`.
|
||||
|
||||

|
||||

|
||||
|
||||
To get the `user_id` for our Stytch user, you can pull it from the welcome page in our example app (eg `user-test-61497d40-f957-45cd-a6c8-5408d22e93bc`).
|
||||
|
||||

|
||||

|
||||
|
||||
Update each row in Supabase to this `user_id`.
|
||||
|
||||

|
||||

|
||||
|
||||
Return to `localhost:3000`, and you will see your expenses listed.
|
||||
|
||||

|
||||

|
||||
|
||||
We now have a basic expense tracker application powered by Stytch, Supabase, and Next.js. From here you could add additional features like adding, editing, and organizing your expenses further.
|
||||
|
||||
@@ -651,7 +648,7 @@ export const authenticateTokenStartSession = async (req, res) => {
|
||||
|
||||
With these two changes you will now have a working Google One Tap authentication method along with email magic links.
|
||||
|
||||

|
||||

|
||||
|
||||
## Resources
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ Select the same `Region` you host your app's backend in.
|
||||
|
||||
Click `Create new project`.
|
||||
|
||||

|
||||

|
||||
|
||||
## Step 2: Creating tables in Supabase
|
||||
|
||||
@@ -45,7 +45,7 @@ Create two new columns:
|
||||
|
||||
Click `Save` to create the new table.
|
||||
|
||||

|
||||

|
||||
|
||||
## Step 3: Setup your Next.js App with SuperTokens.
|
||||
|
||||
@@ -53,7 +53,7 @@ Since the scope of this guide is limited to the integration between SuperTokens
|
||||
|
||||
Once you finish setting up your app, you will be greeted with the following screen
|
||||
|
||||

|
||||

|
||||
|
||||
## Step 4: Creating a Supabase JWT to access Supabase
|
||||
|
||||
@@ -294,10 +294,7 @@ function ProtectedPage() {
|
||||
const supabase = getSupabase(accessTokenPayload.supabase_token)
|
||||
|
||||
// retrieve the user's name from the users table whose email matches the email in the JWT
|
||||
const { data } = await supabase
|
||||
.from('users')
|
||||
.select('email')
|
||||
.eq('user_id', userId)
|
||||
const { data } = await supabase.from('users').select('email').eq('user_id', userId)
|
||||
|
||||
if (data.length > 0) {
|
||||
setEmail(data[0].email)
|
||||
@@ -363,11 +360,11 @@ Our first policy will check whether the user is the owner of the email.
|
||||
|
||||
Select `Authentication` from the Supabase sidebar menu, click `Policies`, and then `New Policy` on the `Users` table.
|
||||
|
||||

|
||||

|
||||
|
||||
From the modal, select `Create a policy from scratch` and add the following.
|
||||
|
||||

|
||||

|
||||
|
||||
This policy is calling the PostgreSQL function we just created to get the currently logged in user's ID `auth.user_id()` and checking whether this matches the `user_id` column for the current `email`. If it does, then it will allow the user to select it, otherwise it will continue to deny.
|
||||
|
||||
@@ -379,7 +376,7 @@ Our second policy will check whether the `user_id` being inserted is the same as
|
||||
|
||||
Create another policy and add the following:
|
||||
|
||||

|
||||

|
||||
|
||||
Similar to the previous policy we are calling the PostgreSQL function we created to get the currently logged in user's ID `auth.user_id()` and check whether this matches the `user_id` column for the row we are trying to insert. If it does, then it will allow the user to insert the row, otherwise it will continue to deny.
|
||||
|
||||
@@ -389,11 +386,11 @@ Click `Review` and then `Save policy`.
|
||||
|
||||
You can now sign up and you should see the following screen:
|
||||
|
||||

|
||||

|
||||
|
||||
If you navigate to your table you should see a new row with the user's `user_id` and `email`.
|
||||
|
||||

|
||||

|
||||
|
||||
## Resources
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ From the Supabase dashboard, click `New project` and select an organization.
|
||||
|
||||
Give your project a `name`, `password`, select a `region` close to your potential users and click `Create new project`.
|
||||
|
||||

|
||||

|
||||
|
||||
Supabase will take a couple of minutes to configure the infrastructure.
|
||||
|
||||
@@ -57,15 +57,15 @@ This will create a new todos table, enable row level security, add policies for
|
||||
|
||||
From your [Vercel dashboard](https://vercel.com/dashboard), click `New Project`.
|
||||
|
||||

|
||||

|
||||
|
||||
Under the `Clone Template` menu, click `Next.js`.
|
||||
|
||||

|
||||

|
||||
|
||||
In the `Create Git Repository` section, click `GitHub`, select your username under `GIT SCOPE`, enter a name for your project, choose whether you want your repo `private` or `public`, and click `Create`.
|
||||
|
||||

|
||||

|
||||
|
||||
This will create a new GitHub repository, clone and commit the Next.js starter project, then build and deploy your new project to Vercel.
|
||||
|
||||
@@ -75,19 +75,19 @@ Navigate to `Settings`, `Integrations`, then click `Browse Marketplace`.
|
||||
|
||||
Search for `Supabase` and click the Supabase integration.
|
||||
|
||||

|
||||

|
||||
|
||||
Click `Add Integration`. Select your account from the `Vercel Scope` dropdown, and click `CONTINUE`.
|
||||
|
||||

|
||||

|
||||
|
||||
Choose `Specific Projects` and select your new Vercel project from the dropdown, and click `Add Integration`.
|
||||
|
||||

|
||||

|
||||
|
||||
From the Supabase popup, select your new Vercel Project and Supabase project from the dropdowns.
|
||||
|
||||

|
||||

|
||||
|
||||
## Step 3: Clone GitHub repo
|
||||
|
||||
@@ -95,11 +95,11 @@ The fastest way to get this project running locally is to clone the repo that Ve
|
||||
|
||||
Navigate back to the Vercel project `Overview` page, and click `View Git Repository`.
|
||||
|
||||

|
||||

|
||||
|
||||
This will open the GitHub repo. From here, click the arrow next to `Code` and copy the url from the dropdown.
|
||||
|
||||

|
||||

|
||||
|
||||
Open a terminal window or CLI and run the following command to clone the GitHub repo.
|
||||
|
||||
@@ -147,7 +147,7 @@ npx vercel link
|
||||
|
||||
Step through the prompts to link the Vercel project.
|
||||
|
||||

|
||||

|
||||
|
||||
Copy the environment variables from our Vercel project.
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import ProjectSetup from './_partials/project_setup.mdx'
|
||||
|
||||
<QuickstartIntro />
|
||||
|
||||

|
||||

|
||||
|
||||
### GitHub
|
||||
|
||||
@@ -414,7 +414,7 @@ npm run start
|
||||
|
||||
And then open the browser to [localhost:4200](http://localhost:4200) and you should see the completed app.
|
||||
|
||||

|
||||

|
||||
|
||||
## Bonus: Profile photos
|
||||
|
||||
|
||||
@@ -5,14 +5,9 @@ description: Learn how to use Supabase in your React Native App.
|
||||
sidebar_label: Expo
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
import QuickstartIntro from './_partials/quickstart_intro.md'
|
||||
import ProjectSetup from './_partials/project_setup.mdx'
|
||||
|
||||
<QuickstartIntro />
|
||||
|
||||

|
||||

|
||||
|
||||
### GitHub
|
||||
|
||||
@@ -126,18 +121,10 @@ 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>
|
||||
)
|
||||
@@ -251,26 +238,16 @@ 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>
|
||||
@@ -326,11 +303,7 @@ 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>
|
||||
)
|
||||
}
|
||||
@@ -366,11 +339,7 @@ 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
|
||||
@@ -389,9 +358,7 @@ export default function Avatar({ url, size = 150, onUpload }: Props) {
|
||||
|
||||
async function downloadImage(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
|
||||
@@ -432,9 +399,7 @@ export default function Avatar({ url, size = 150, onUpload }: Props) {
|
||||
const fileExt = file.name.split('.').pop()
|
||||
const filePath = `${Math.random()}.${fileExt}`
|
||||
|
||||
let { error } = await supabase.storage
|
||||
.from('avatars')
|
||||
.upload(filePath, formData)
|
||||
let { error } = await supabase.storage.from('avatars').upload(filePath, formData)
|
||||
|
||||
if (error) {
|
||||
throw error
|
||||
@@ -446,9 +411,7 @@ 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 {
|
||||
|
||||
@@ -5,14 +5,9 @@ description: Learn how to use Supabase in your Flutter App.
|
||||
sidebar_label: Flutter
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
import QuickstartIntro from './_partials/quickstart_intro.md'
|
||||
import ProjectSetup from './_partials/project_setup.mdx'
|
||||
|
||||
<QuickstartIntro />
|
||||
|
||||

|
||||

|
||||
|
||||
### GitHub
|
||||
|
||||
@@ -50,7 +45,7 @@ Now that we have the dependencies installed let's setup deep links so users who
|
||||
1. Go to [Authentication Settings](https://app.supabase.com/project/_/auth/settings) in the Dashboard.
|
||||
1. In **Redirect URLs**, add `io.supabase.flutterquickstart://login-callback/` as a new domain.
|
||||
|
||||

|
||||

|
||||
|
||||
That is it on Supabase's end and the rest are platform specific settings:
|
||||
|
||||
@@ -503,7 +498,7 @@ flutter run -d web-server --web-hostname localhost --web-port 3000
|
||||
|
||||
And then open the browser to [localhost:3000](http://localhost:3000) and you should see the completed app.
|
||||
|
||||

|
||||

|
||||
|
||||
## Bonus: Profile photos
|
||||
|
||||
|
||||
@@ -5,14 +5,9 @@ description: Learn how to use Supabase in your Ionic Angular App.
|
||||
sidebar_label: Ionic Angular
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
import QuickstartIntro from './_partials/quickstart_intro.md'
|
||||
import ProjectSetup from './_partials/project_setup.mdx'
|
||||
|
||||
<QuickstartIntro />
|
||||
|
||||

|
||||

|
||||
|
||||
### GitHub
|
||||
|
||||
@@ -391,7 +386,7 @@ ionic serve
|
||||
|
||||
And the browser will auomatically open to show the app.
|
||||
|
||||

|
||||

|
||||
|
||||
## Bonus: Profile photos
|
||||
|
||||
|
||||
@@ -5,14 +5,9 @@ description: Learn how to use Supabase in your Ionic React App.
|
||||
sidebar_label: Ionic React
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
import QuickstartIntro from './_partials/quickstart_intro.md'
|
||||
import ProjectSetup from './_partials/project_setup.mdx'
|
||||
|
||||
<QuickstartIntro />
|
||||
|
||||

|
||||

|
||||
|
||||
### GitHub
|
||||
|
||||
@@ -316,9 +311,9 @@ import { Session } from '@supabase/supabase-js'
|
||||
setupIonicReact()
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [session, setSession] = useState<Session>(null)
|
||||
const [session, setSession] = useState < Session > null
|
||||
useEffect(() => {
|
||||
setSession(supabase.auth.session())
|
||||
setSession(supabase.auth.session())
|
||||
supabase.auth.onAuthStateChange((_event, session) => {
|
||||
setSession(session)
|
||||
})
|
||||
@@ -354,7 +349,7 @@ ionic serve
|
||||
|
||||
And then open the browser to [localhost:3000](http://localhost:3000) and you should see the completed app.
|
||||
|
||||

|
||||

|
||||
|
||||
## Bonus: Profile photos
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import ProjectSetup from './_partials/project_setup.mdx'
|
||||
|
||||
<QuickstartIntro />
|
||||
|
||||

|
||||

|
||||
|
||||
### GitHub
|
||||
|
||||
@@ -83,12 +83,7 @@ Let's set up a Vue component to manage logins and sign ups. We'll use Magic Link
|
||||
<form @submit.prevent="handleLogin">
|
||||
<ion-item>
|
||||
<ion-label position="stacked">Email</ion-label>
|
||||
<ion-input
|
||||
v-model="email"
|
||||
name="email"
|
||||
autocomplete
|
||||
type="email"
|
||||
></ion-input>
|
||||
<ion-input v-model="email" name="email" autocomplete type="email"></ion-input>
|
||||
</ion-item>
|
||||
<div class="ion-text-center">
|
||||
<ion-button type="submit" fill="clear">Login</ion-button>
|
||||
@@ -185,20 +180,12 @@ Let's create a new component for that called `Account.vue`.
|
||||
|
||||
<ion-item>
|
||||
<ion-label position="stacked">Name</ion-label>
|
||||
<ion-input
|
||||
type="text"
|
||||
name="username"
|
||||
v-model="profile.username"
|
||||
></ion-input>
|
||||
<ion-input type="text" name="username" v-model="profile.username"></ion-input>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label position="stacked">Website</ion-label>
|
||||
<ion-input
|
||||
type="url"
|
||||
name="website"
|
||||
v-model="profile.website"
|
||||
></ion-input>
|
||||
<ion-input type="url" name="website" v-model="profile.website"></ion-input>
|
||||
</ion-item>
|
||||
<div class="ion-text-center">
|
||||
<ion-button fill="clear" type="submit">Update Profile</ion-button>
|
||||
@@ -400,7 +387,7 @@ ionic serve
|
||||
|
||||
And then open the browser to [localhost:3000](http://localhost:3000) and you should see the completed app.
|
||||
|
||||

|
||||

|
||||
|
||||
## Bonus: Profile photos
|
||||
|
||||
@@ -470,9 +457,7 @@ Then create an **AvatarComponent**.
|
||||
|
||||
const downloadImage = async () => {
|
||||
try {
|
||||
const { data, error } = await supabase.storage
|
||||
.from('avatars')
|
||||
.download(path.value)
|
||||
const { data, error } = await supabase.storage.from('avatars').download(path.value)
|
||||
if (error) throw error
|
||||
avatarUrl.value = URL.createObjectURL(data!)
|
||||
} catch (error: any) {
|
||||
@@ -488,14 +473,9 @@ Then create an **AvatarComponent**.
|
||||
if (photo.dataUrl) {
|
||||
const file = await fetch(photo.dataUrl)
|
||||
.then((res) => res.blob())
|
||||
.then(
|
||||
(blob) =>
|
||||
new File([blob], 'my-file', { type: `image/${photo.format}` })
|
||||
)
|
||||
.then((blob) => new File([blob], 'my-file', { type: `image/${photo.format}` }))
|
||||
|
||||
const fileName = `${Math.random()}-${new Date().getTime()}.${
|
||||
photo.format
|
||||
}`
|
||||
const fileName = `${Math.random()}-${new Date().getTime()}.${photo.format}`
|
||||
let { error: uploadError } = await supabase.storage
|
||||
.from('avatars')
|
||||
.upload(fileName, file)
|
||||
|
||||
@@ -12,7 +12,7 @@ import ProjectSetup from './_partials/project_setup.mdx'
|
||||
|
||||
<QuickstartIntro />
|
||||
|
||||

|
||||

|
||||
|
||||
### Github
|
||||
|
||||
@@ -76,12 +76,7 @@ Let's set up a Vue component to manage logins and sign ups. We'll use Magic Link
|
||||
<h1 class="header">Supabase + Nuxt 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
|
||||
@@ -150,9 +145,7 @@ Let's create a new component for that called `Account.vue`.
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button class="button block" @click="signOut" :disabled="loading">
|
||||
Sign Out
|
||||
</button>
|
||||
<button class="button block" @click="signOut" :disabled="loading">Sign Out</button>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
@@ -242,7 +235,7 @@ npm run dev
|
||||
|
||||
And then open the browser to [localhost:3000](http://localhost:3000) and you should see the completed app.
|
||||
|
||||

|
||||

|
||||
|
||||
## Bonus: Profile photos
|
||||
|
||||
@@ -262,11 +255,7 @@ Let's create an avatar for the user so that they can upload a profile photo. We
|
||||
class="avatar image"
|
||||
style="width: 10em; height: 10em;"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
class="avatar no-image"
|
||||
:style="{ height: size, width: size }"
|
||||
/>
|
||||
<div v-else class="avatar no-image" :style="{ height: size, width: size }" />
|
||||
|
||||
<div style="width: 10em; position: relative;">
|
||||
<label class="button primary block" for="single">
|
||||
@@ -297,9 +286,7 @@ Let's create an avatar for the user so that they can upload a profile photo. We
|
||||
const files = ref()
|
||||
const downloadImage = async () => {
|
||||
try {
|
||||
const { data, error } = await supabase.storage
|
||||
.from('avatars')
|
||||
.download(path.value)
|
||||
const { data, error } = await supabase.storage.from('avatars').download(path.value)
|
||||
if (error) throw error
|
||||
src.value = URL.createObjectURL(data)
|
||||
} catch (error) {
|
||||
@@ -318,9 +305,7 @@ Let's create an avatar for the user so that they can upload a profile photo. We
|
||||
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)
|
||||
let { error: uploadError } = await supabase.storage.from('avatars').upload(filePath, file)
|
||||
if (uploadError) throw uploadError
|
||||
emit('update:path', filePath)
|
||||
emit('upload')
|
||||
@@ -372,9 +357,7 @@ And then we can add the widget to the Account page:
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button class="button block" @click="signOut" :disabled="loading">
|
||||
Sign Out
|
||||
</button>
|
||||
<button class="button block" @click="signOut" :disabled="loading">Sign Out</button>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
@@ -12,7 +12,7 @@ import ProjectSetup from './_partials/project_setup.mdx'
|
||||
|
||||
<QuickstartIntro />
|
||||
|
||||

|
||||

|
||||
|
||||
### GitHub
|
||||
|
||||
@@ -94,9 +94,7 @@ export default function Auth() {
|
||||
<div className="row flex-center flex">
|
||||
<div className="col-6 form-widget" aria-live="polite">
|
||||
<h1 className="header">Supabase + React</h1>
|
||||
<p className="description">
|
||||
Sign in via magic link with your email below
|
||||
</p>
|
||||
<p className="description">Sign in via magic link with your email below</p>
|
||||
{loading ? (
|
||||
'Sending magic link...'
|
||||
) : (
|
||||
@@ -227,11 +225,7 @@ const Account = ({ session }) => {
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="button block"
|
||||
onClick={() => supabase.auth.signOut()}
|
||||
>
|
||||
<button type="button" className="button block" onClick={() => supabase.auth.signOut()}>
|
||||
Sign Out
|
||||
</button>
|
||||
</div>
|
||||
@@ -267,11 +261,7 @@ export default function App() {
|
||||
|
||||
return (
|
||||
<div className="container" style={{ padding: '50px 0 100px 0' }}>
|
||||
{!session ? (
|
||||
<Auth />
|
||||
) : (
|
||||
<Account key={session.user.id} session={session} />
|
||||
)}
|
||||
{!session ? <Auth /> : <Account key={session.user.id} session={session} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -285,7 +275,7 @@ npm start
|
||||
|
||||
And then open the browser to [localhost:3000](http://localhost:3000) and you should see the completed app.
|
||||
|
||||

|
||||

|
||||
|
||||
## Bonus: Profile photos
|
||||
|
||||
@@ -309,9 +299,7 @@ export default function Avatar({ url, size, onUpload }) {
|
||||
|
||||
const downloadImage = async (path) => {
|
||||
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
|
||||
}
|
||||
@@ -335,9 +323,7 @@ export default function Avatar({ url, size, onUpload }) {
|
||||
const fileName = `${Math.random()}.${fileExt}`
|
||||
const filePath = `${fileName}`
|
||||
|
||||
let { error: uploadError } = await supabase.storage
|
||||
.from('avatars')
|
||||
.upload(filePath, file)
|
||||
let { error: uploadError } = await supabase.storage.from('avatars').upload(filePath, file)
|
||||
|
||||
if (uploadError) {
|
||||
throw uploadError
|
||||
|
||||
@@ -639,7 +639,7 @@ yarn rw dev
|
||||
|
||||
And then open the browser to [localhost:8910](http://localhost:8910) and you should see the completed app.
|
||||
|
||||

|
||||

|
||||
|
||||
## Bonus: Profile photos
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import ProjectSetup from './_partials/project_setup.mdx'
|
||||
|
||||
<QuickstartIntro />
|
||||
|
||||

|
||||

|
||||
|
||||
### GitHub
|
||||
|
||||
@@ -226,19 +226,11 @@ 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>
|
||||
@@ -291,7 +283,7 @@ npm start
|
||||
|
||||
And then open the browser to [localhost:3000](http://localhost:3000) and you should see the completed app.
|
||||
|
||||

|
||||

|
||||
|
||||
## Bonus: Profile photos
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import ProjectSetup from './_partials/project_setup.mdx'
|
||||
|
||||
<QuickstartIntro />
|
||||
|
||||

|
||||

|
||||
|
||||
### GitHub
|
||||
|
||||
@@ -106,12 +106,7 @@ Let's set up a Svelte component to manage logins and sign ups. We'll use Magic L
|
||||
/>
|
||||
</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>
|
||||
@@ -262,7 +257,7 @@ And then open the browser to [localhost:5173](http://localhost:5173) and you sho
|
||||
|
||||
> ⚠️ WARNING: Svelte uses Vite and the default port is `5173`, Supabase uses `port 3000`. To change the redirection port for supabase go to: `Authentication > Settings` and change the `Site Url` to `localhost:5173`
|
||||
|
||||

|
||||

|
||||
|
||||
## Bonus: Profile photos
|
||||
|
||||
@@ -288,9 +283,7 @@ 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
|
||||
@@ -317,9 +310,7 @@ Let's create an avatar for the user so that they can upload a profile photo. We
|
||||
const fileExt = file.name.split('.').pop()
|
||||
const filePath = `${Math.random()}.${fileExt}`
|
||||
|
||||
let { error } = await supabase.storage
|
||||
.from('avatars')
|
||||
.upload(filePath, file)
|
||||
let { error } = await supabase.storage.from('avatars').upload(filePath, file)
|
||||
|
||||
if (error) {
|
||||
throw error
|
||||
@@ -340,8 +331,8 @@ 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}
|
||||
{#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">
|
||||
|
||||
@@ -12,7 +12,7 @@ import ProjectSetup from './_partials/project_setup.mdx'
|
||||
|
||||
<QuickstartIntro />
|
||||
|
||||

|
||||

|
||||
|
||||
### GitHub
|
||||
|
||||
@@ -78,52 +78,52 @@ Update your `src/routes/+layout.svelte`:
|
||||
|
||||
```html title="src/routes/+layout.svelte"
|
||||
<script lang="ts">
|
||||
import { supabaseClient } from '$lib/supabaseClient'
|
||||
import { invalidate } from '$app/navigation'
|
||||
import { onMount } from 'svelte'
|
||||
import './styles.css'
|
||||
import { supabaseClient } from '$lib/supabaseClient'
|
||||
import { invalidate } from '$app/navigation'
|
||||
import { onMount } from 'svelte'
|
||||
import './styles.css'
|
||||
|
||||
onMount(() => {
|
||||
const {
|
||||
data: { subscription }
|
||||
} = supabaseClient.auth.onAuthStateChange(() => {
|
||||
invalidate('supabase:auth')
|
||||
})
|
||||
onMount(() => {
|
||||
const {
|
||||
data: { subscription },
|
||||
} = supabaseClient.auth.onAuthStateChange(() => {
|
||||
invalidate('supabase:auth')
|
||||
})
|
||||
|
||||
return () => {
|
||||
subscription.unsubscribe()
|
||||
}
|
||||
})
|
||||
return () => {
|
||||
subscription.unsubscribe()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="container" style="padding: 50px 0 100px 0">
|
||||
<slot />
|
||||
<slot />
|
||||
</div>
|
||||
```
|
||||
|
||||
Create a new `src/routes/+layout.ts` file to handle the session on the client-side.
|
||||
|
||||
```ts title="src/routes/+layout.ts"
|
||||
import type { LayoutLoad } from './$types';
|
||||
import { getSupabase } from '@supabase/auth-helpers-sveltekit';
|
||||
import type { LayoutLoad } from './$types'
|
||||
import { getSupabase } from '@supabase/auth-helpers-sveltekit'
|
||||
|
||||
export const load: LayoutLoad = async (event) => {
|
||||
const { session } = await getSupabase(event);
|
||||
return { session };
|
||||
};
|
||||
const { session } = await getSupabase(event)
|
||||
return { session }
|
||||
}
|
||||
```
|
||||
|
||||
Create a new `src/routes/+layout.server.ts` file to handle the session on the server-side.
|
||||
|
||||
```ts title="src/routes/+layout.server.ts"
|
||||
import type { LayoutServerLoad } from './$types';
|
||||
import { getServerSession } from '@supabase/auth-helpers-sveltekit';
|
||||
import type { LayoutServerLoad } from './$types'
|
||||
import { getServerSession } from '@supabase/auth-helpers-sveltekit'
|
||||
|
||||
export const load: LayoutServerLoad = async (event) => {
|
||||
return {
|
||||
session: await getServerSession(event)
|
||||
};
|
||||
};
|
||||
return {
|
||||
session: await getServerSession(event),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Be sure to create `src/hooks.client.ts` and `src/hooks.server.ts` in order to get the auth helper started on the client and server-side.
|
||||
@@ -142,43 +142,39 @@ Let's set up a Svelte component to manage logins and sign ups. We'll use Magic L
|
||||
|
||||
```html title="src/routes/Auth.svelte"
|
||||
<script lang="ts">
|
||||
import { supabaseClient } from '$lib/supabaseClient'
|
||||
import { supabaseClient } from '$lib/supabaseClient'
|
||||
|
||||
let loading = false
|
||||
let email: string
|
||||
let loading = false
|
||||
let email: string
|
||||
|
||||
const handleLogin = async () => {
|
||||
try {
|
||||
loading = true
|
||||
const { error } = await supabaseClient.auth.signInWithOtp({ email })
|
||||
if (error) throw error
|
||||
alert('Check your email for the login link!')
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
alert(error.message)
|
||||
}
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
const handleLogin = async () => {
|
||||
try {
|
||||
loading = true
|
||||
const { error } = await supabaseClient.auth.signInWithOtp({ email })
|
||||
if (error) throw error
|
||||
alert('Check your email for the login link!')
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
alert(error.message)
|
||||
}
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<form class="row flex-center flex" on:submit|preventDefault={handleLogin}>
|
||||
<div class="col-6 form-widget">
|
||||
<h1 class="header">Supabase + SvelteKit</h1>
|
||||
<p class="description">Sign in via magic link with your email below</p>
|
||||
<div>
|
||||
<input class="inputField" type="email" placeholder="Your email" bind:value={email} />
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
type="submit"
|
||||
class="button block"
|
||||
value={loading ? 'Loading' : 'Send magic link'}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<form class="row flex-center flex" on:submit|preventDefault="{handleLogin}">
|
||||
<div class="col-6 form-widget">
|
||||
<h1 class="header">Supabase + SvelteKit</h1>
|
||||
<p class="description">Sign in via magic link with your email below</p>
|
||||
<div>
|
||||
<input class="inputField" type="email" placeholder="Your email" bind:value="{email}" />
|
||||
</div>
|
||||
<div>
|
||||
<input type="submit" class="button block" value={loading ? 'Loading' : 'Send magic link'}
|
||||
disabled={loading} />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
```
|
||||
|
||||
@@ -189,114 +185,110 @@ Create a new `Account.svelte` component to handle this functionality.
|
||||
|
||||
```html title="src/routes/Account.svelte"
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte'
|
||||
import type { AuthSession } from '@supabase/supabase-js'
|
||||
import { supabaseClient } from '$lib/supabaseClient'
|
||||
import { onMount } from 'svelte'
|
||||
import type { AuthSession } from '@supabase/supabase-js'
|
||||
import { supabaseClient } from '$lib/supabaseClient'
|
||||
|
||||
export let session: AuthSession
|
||||
export let session: AuthSession
|
||||
|
||||
let loading = false
|
||||
let username: string | null = null
|
||||
let website: string | null = null
|
||||
let avatarUrl: string | null = null
|
||||
let loading = false
|
||||
let username: string | null = null
|
||||
let website: string | null = null
|
||||
let avatarUrl: string | null = null
|
||||
|
||||
onMount(() => {
|
||||
getProfile()
|
||||
})
|
||||
onMount(() => {
|
||||
getProfile()
|
||||
})
|
||||
|
||||
const getProfile = async () => {
|
||||
try {
|
||||
loading = true
|
||||
const { user } = session
|
||||
const getProfile = async () => {
|
||||
try {
|
||||
loading = true
|
||||
const { user } = session
|
||||
|
||||
const { data, error, status } = await supabaseClient
|
||||
.from('profiles')
|
||||
.select(`username, website, avatar_url`)
|
||||
.eq('id', user.id)
|
||||
.single()
|
||||
const { data, error, status } = await supabaseClient
|
||||
.from('profiles')
|
||||
.select(`username, website, avatar_url`)
|
||||
.eq('id', user.id)
|
||||
.single()
|
||||
|
||||
if (data) {
|
||||
username = data.username
|
||||
website = data.website
|
||||
avatarUrl = data.avatar_url
|
||||
}
|
||||
if (data) {
|
||||
username = data.username
|
||||
website = data.website
|
||||
avatarUrl = data.avatar_url
|
||||
}
|
||||
|
||||
if (error && status !== 406) throw error
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
alert(error.message)
|
||||
}
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
if (error && status !== 406) throw error
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
alert(error.message)
|
||||
}
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
|
||||
async function updateProfile() {
|
||||
try {
|
||||
loading = true
|
||||
const { user } = session
|
||||
async function updateProfile() {
|
||||
try {
|
||||
loading = true
|
||||
const { user } = session
|
||||
|
||||
const updates = {
|
||||
id: user.id,
|
||||
username,
|
||||
website,
|
||||
avatar_url: avatarUrl,
|
||||
updated_at: new Date()
|
||||
}
|
||||
const updates = {
|
||||
id: user.id,
|
||||
username,
|
||||
website,
|
||||
avatar_url: avatarUrl,
|
||||
updated_at: new Date(),
|
||||
}
|
||||
|
||||
let { error } = await supabaseClient.from('profiles').upsert(updates)
|
||||
let { error } = await supabaseClient.from('profiles').upsert(updates)
|
||||
|
||||
if (error) throw error
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
alert(error.message)
|
||||
}
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
if (error) throw error
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
alert(error.message)
|
||||
}
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
|
||||
async function signOut() {
|
||||
try {
|
||||
loading = true
|
||||
let { error } = await supabaseClient.auth.signOut()
|
||||
if (error) throw error
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
alert(error.message)
|
||||
}
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
async function signOut() {
|
||||
try {
|
||||
loading = true
|
||||
let { error } = await supabaseClient.auth.signOut()
|
||||
if (error) throw error
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
alert(error.message)
|
||||
}
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<form class="form-widget" on:submit|preventDefault={updateProfile}>
|
||||
<div>
|
||||
<label for="email">Email</label>
|
||||
<input id="email" type="text" value={session.user.email} disabled />
|
||||
</div>
|
||||
<div>
|
||||
<label for="username">Name</label>
|
||||
<input id="username" type="text" bind:value={username} />
|
||||
</div>
|
||||
<div>
|
||||
<label for="website">Website</label>
|
||||
<input id="website" type="website" bind:value={website} />
|
||||
</div>
|
||||
<form class="form-widget" on:submit|preventDefault="{updateProfile}">
|
||||
<div>
|
||||
<label for="email">Email</label>
|
||||
<input id="email" type="text" value="{session.user.email}" disabled />
|
||||
</div>
|
||||
<div>
|
||||
<label for="username">Name</label>
|
||||
<input id="username" type="text" bind:value="{username}" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="website">Website</label>
|
||||
<input id="website" type="website" bind:value="{website}" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input
|
||||
type="submit"
|
||||
class="button block primary"
|
||||
value={loading ? 'Loading...' : 'Update'}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<input type="submit" class="button block primary" value={loading ? 'Loading...' : 'Update'}
|
||||
disabled={loading} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button class="button block" on:click={signOut} disabled={loading}> Sign Out </button>
|
||||
</div>
|
||||
<div>
|
||||
<button class="button block" on:click="{signOut}" disabled="{loading}">Sign Out</button>
|
||||
</div>
|
||||
</form>
|
||||
```
|
||||
|
||||
@@ -306,20 +298,20 @@ Now that we have all the components in place, let's update `src/routes/+page.sve
|
||||
|
||||
```html title="src/routes/+page.svelte"
|
||||
<script>
|
||||
import { page } from '$app/stores'
|
||||
import Account from './Account.svelte'
|
||||
import Auth from './Auth.svelte'
|
||||
import { page } from '$app/stores'
|
||||
import Account from './Account.svelte'
|
||||
import Auth from './Auth.svelte'
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Supabase + SvelteKit</title>
|
||||
<meta name="description" content="SvelteKit using supabase-js v2" />
|
||||
<title>Supabase + SvelteKit</title>
|
||||
<meta name="description" content="SvelteKit using supabase-js v2" />
|
||||
</svelte:head>
|
||||
|
||||
{#if !$page.data.session}
|
||||
<Auth />
|
||||
<Auth />
|
||||
{:else}
|
||||
<Account session={$page.data.session} />
|
||||
<Account session="{$page.data.session}" />
|
||||
{/if}
|
||||
```
|
||||
|
||||
@@ -331,7 +323,7 @@ npm run dev
|
||||
|
||||
And then open the browser to [localhost:5173](http://localhost:5173) and you should see the completed app.
|
||||
|
||||

|
||||

|
||||
|
||||
## Bonus: Profile photos
|
||||
|
||||
@@ -343,93 +335,87 @@ Let's create an avatar for the user so that they can upload a profile photo. We
|
||||
|
||||
```html title="src/routes/Avatar.svelte"
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { supabaseClient } from '$lib/supabaseClient'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { supabaseClient } from '$lib/supabaseClient'
|
||||
|
||||
export let size = 10
|
||||
export let url: string
|
||||
export let size = 10
|
||||
export let url: string
|
||||
|
||||
let avatarUrl: string | null = null
|
||||
let uploading = false
|
||||
let files: FileList
|
||||
let avatarUrl: string | null = null
|
||||
let uploading = false
|
||||
let files: FileList
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const downloadImage = async (path: string) => {
|
||||
try {
|
||||
const { data, error } = await supabaseClient.storage.from('avatars').download(path)
|
||||
const downloadImage = async (path: string) => {
|
||||
try {
|
||||
const { data, error } = await supabaseClient.storage.from('avatars').download(path)
|
||||
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
|
||||
const url = URL.createObjectURL(data)
|
||||
avatarUrl = url
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
console.log('Error downloading image: ', error.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
const url = URL.createObjectURL(data)
|
||||
avatarUrl = url
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
console.log('Error downloading image: ', error.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const uploadAvatar = async () => {
|
||||
try {
|
||||
uploading = true
|
||||
const uploadAvatar = async () => {
|
||||
try {
|
||||
uploading = true
|
||||
|
||||
if (!files || files.length === 0) {
|
||||
throw new Error('You must select an image to upload.')
|
||||
}
|
||||
if (!files || files.length === 0) {
|
||||
throw new Error('You must select an image to upload.')
|
||||
}
|
||||
|
||||
const file = files[0]
|
||||
const fileExt = file.name.split('.').pop()
|
||||
const filePath = `${Math.random()}.${fileExt}`
|
||||
const file = files[0]
|
||||
const fileExt = file.name.split('.').pop()
|
||||
const filePath = `${Math.random()}.${fileExt}`
|
||||
|
||||
let { error } = await supabaseClient.storage.from('avatars').upload(filePath, file)
|
||||
let { error } = await supabaseClient.storage.from('avatars').upload(filePath, file)
|
||||
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
|
||||
url = filePath
|
||||
dispatch('upload')
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
alert(error.message)
|
||||
}
|
||||
} finally {
|
||||
uploading = false
|
||||
}
|
||||
}
|
||||
url = filePath
|
||||
dispatch('upload')
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
alert(error.message)
|
||||
}
|
||||
} finally {
|
||||
uploading = false
|
||||
}
|
||||
}
|
||||
|
||||
$: if (url) downloadImage(url)
|
||||
$: if (url) downloadImage(url)
|
||||
</script>
|
||||
|
||||
<div>
|
||||
{#if avatarUrl}
|
||||
<img
|
||||
src={avatarUrl}
|
||||
alt={avatarUrl ? 'Avatar' : 'No image'}
|
||||
class="avatar image"
|
||||
style="height: {size}em; width: {size}em;"
|
||||
/>
|
||||
{:else}
|
||||
<div class="avatar no-image" style="height: {size}em; width: {size}em;" />
|
||||
{/if}
|
||||
{#if avatarUrl} <img src={avatarUrl} alt={avatarUrl ? 'Avatar' : 'No image'} class="avatar image"
|
||||
style="height: {size}em; width: {size}em;" /> {:else}
|
||||
<div class="avatar no-image" style="height: {size}em; width: {size}em;" />
|
||||
{/if}
|
||||
|
||||
<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/*"
|
||||
bind:files
|
||||
on:change={uploadAvatar}
|
||||
disabled={uploading}
|
||||
/>
|
||||
</div>
|
||||
<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/*"
|
||||
bind:files
|
||||
on:change="{uploadAvatar}"
|
||||
disabled="{uploading}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
@@ -443,13 +429,9 @@ And then we can add the widget to the Account page:
|
||||
import Avatar from './Avatar.svelte'
|
||||
</script>
|
||||
|
||||
<form
|
||||
use:getProfile
|
||||
class="form-widget"
|
||||
on:submit|preventDefault="{updateProfile}"
|
||||
>
|
||||
<form use:getProfile class="form-widget" on:submit|preventDefault="{updateProfile}">
|
||||
<!-- Add to body -->
|
||||
<Avatar bind:url={avatarUrl} size={10} on:upload={updateProfile} />
|
||||
<Avatar bind:url="{avatarUrl}" size="{10}" on:upload="{updateProfile}" />
|
||||
|
||||
<!-- Other form elements -->
|
||||
</form>
|
||||
|
||||
@@ -12,7 +12,7 @@ import ProjectSetup from './_partials/project_setup.mdx'
|
||||
|
||||
<QuickstartIntro />
|
||||
|
||||

|
||||

|
||||
|
||||
### GitHub
|
||||
|
||||
@@ -103,12 +103,7 @@ Let's set up a Vue component to manage logins and sign ups. We'll use Magic Link
|
||||
<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
|
||||
@@ -231,9 +226,7 @@ Let's create a new component for that called `Account.vue`.
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button class="button block" @click="signOut" :disabled="loading">
|
||||
Sign Out
|
||||
</button>
|
||||
<button class="button block" @click="signOut" :disabled="loading">Sign Out</button>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
@@ -279,7 +272,7 @@ npm run dev
|
||||
|
||||
And then open the browser to [localhost:5173](http://localhost:5173) and you should see the completed app.
|
||||
|
||||

|
||||

|
||||
|
||||
## Bonus: Profile photos
|
||||
|
||||
@@ -304,9 +297,7 @@ Let's create an avatar for the user so that they can upload a profile photo. We
|
||||
|
||||
const downloadImage = async () => {
|
||||
try {
|
||||
const { data, error } = await supabase.storage
|
||||
.from('avatars')
|
||||
.download(path.value)
|
||||
const { data, error } = await supabase.storage.from('avatars').download(path.value)
|
||||
if (error) throw error
|
||||
src.value = URL.createObjectURL(data)
|
||||
} catch (error) {
|
||||
@@ -326,9 +317,7 @@ Let's create an avatar for the user so that they can upload a profile photo. We
|
||||
const fileExt = file.name.split('.').pop()
|
||||
const filePath = `${Math.random()}.${fileExt}`
|
||||
|
||||
let { error: uploadError } = await supabase.storage
|
||||
.from('avatars')
|
||||
.upload(filePath, file)
|
||||
let { error: uploadError } = await supabase.storage.from('avatars').upload(filePath, file)
|
||||
|
||||
if (uploadError) throw uploadError
|
||||
emit('update:path', filePath)
|
||||
@@ -354,11 +343,7 @@ Let's create an avatar for the user so that they can upload a profile photo. We
|
||||
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 v-else class="avatar no-image" :style="{ height: size + 'em', width: size + 'em' }" />
|
||||
|
||||
<div :style="{ width: size + 'em' }">
|
||||
<label class="button primary block" for="single">
|
||||
|
||||
Reference in new issue
Block a user