Merge branch 'master' of github.com:supabase/supabase

This commit is contained in:
Terry Sutton committed 2022-10-17 13:12:27 -02:30
commit bafbf9aaae
10 files changed
+582 -231

No files matched your search

@@ -348,6 +348,24 @@ Once pushed, check that the migration version is up to date for both local and r
supabase migration list
```
### Permission denied on db remote commit
If you have been using Supabase hosted projects for a long time, you might encounter the following permission error when executing `db remote commit`.
```bash
Error: Error running pg_dump on remote database: pg_dump: error: query failed: ERROR: permission denied for table _type
pg_dump: error: query was: LOCK TABLE "graphql"."_type" IN ACCESS SHARE MODE
```
To resolve this error, you need to grant `postgres` role permissions to `graphql` schema. You can do that by running the following query from Supabase dashboard's SQL Editor.
```sql
grant all on all tables in schema graphql to postgres, anon, authenticated, service_role;
grant all on all functions in schema graphql to postgres, anon, authenticated, service_role;
grant all on all sequences in schema graphql to postgres, anon, authenticated, service_role;
```
### Permission denied on db push
If you created a table through Supabase dashboard, and your new migration script contains `ALTER TABLE` statements, you might run into permission error when applying them on staging or production databases.
@@ -358,12 +376,14 @@ ERROR: must be owner of table employees (SQLSTATE 42501); while executing migrat
This is because tables created through Supabase dashboard are owned by `supabase_admin` role while the migration scripts executed through CLI are under `postgres` role.
One way to solve this is to grant `postgres` role additional privileges through the SQL Editor available on Supabase dashboard. For example, the following command grants postgres permissions to alter any table in the public schema.
One way to solve this is to reassign the owner of those tables to `postgres` role. For example, if your table is named `users` in the public schema, you can run the following command to reassign owner.
```sql
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO postgres;
ALTER TABLE users OWNER TO postgres;
```
Apart from tables, you also need to reassign owner of other entities using their respective commands, including [types](https://www.postgresql.org/docs/current/sql-alterschema.html), [functions](https://www.postgresql.org/docs/current/sql-alterroutine.html), and [schemas](https://www.postgresql.org/docs/current/sql-altertype.html).
### Rebasing new migrations
Sometimes your teammate may merge a new migration file to git main branch, and now you need to rebase your local schema changes on top.
@@ -62,7 +62,7 @@ This project comes with TypeScript configured and has the following structure.
### Configuring the project to use PostgreSQL
Go ahead and delete the `prisma/dev.db` file because we will be switching to PostgreSQL.
Next, inside the `prisma/.env` file, update the value of the `DATABASE_URL` variable to the connection string you got in **step 3**. The URL might look as follows:
Next, inside the `prisma/.env` file, update the value of the `DATABASE_URL` variable to the connection string you got in **step 1**. The URL might look as follows:
```env
# prisma/.env
@@ -122,7 +122,7 @@ This will create a `prisma/migrations` folder inside your `prisma` directory and
If you’re working in a serverless environment (for example Node.js functions hosted on AWS Lambda, Vercel or Netlify Functions), you need to set up [connection pooling](https://www.prisma.io/docs/guides/performance-and-optimization/connection-management#serverless-environments-faas) using a tool like [PgBouncer](https://www.pgbouncer.org/). That’s because every function invocation may result in a [new connection to the database](https://www.prisma.io/docs/guides/performance-and-optimization/connection-management#the-serverless-challenge). Supabase [supports connection management using PgBouncer](https://supabase.io/blog/2021/04/02/supabase-pgbouncer#what-is-connection-pooling) and are enabled by default.
Go to the **Database** page from the sidebar in the Supabase dashboard and navigate to **connection pool** settings
![Connection pool settings](/img/guides/integrations/prisma/w0oowg8vq435ob5c3gf0.png)
When running migrations you need to use the non pooled connection URL (like the one we used in **step 4**). However, when deploying your app, you’ll use the pooled connection URL and add the `?pgbouncer=true` flag to the PostgreSQL connection URL. To minimize the number of concurrent connections, setting the `connection_limit` to `1` is also recommended. So the URL might look as follows:
When running migrations you need to use the non pooled connection URL (like the one we used in **step 1**). However, when deploying your app, you’ll use the pooled connection URL and add the `?pgbouncer=true` flag to the PostgreSQL connection URL. To minimize the number of concurrent connections, setting the `connection_limit` to `1` is also recommended. So the URL might look as follows:
```env
# prisma/.env
+322 -207
View File
@@ -33,7 +33,7 @@ If you want to do it yourself, let's get started!
### GitHub
Whenever you get stuck at any point, take a look at [this repo](https://github.com/angular-supa/supabase-angular-user-management).
Whenever you get stuck at any point, take a look at [this repo](https://github.com/supabase/examples/tree/main/supabase-js-v2/user-management/angular-user-management).
## Project set up
@@ -180,281 +180,365 @@ export const environment = {
Now that we have the API credentials in place, let's create a **SupabaseService** with `ng g s supabase` to initialize the Supabase client and implement functions to communicate with the Supabase API.
```ts title="src/app/supabase.service.ts"
import { Injectable } from '@angular/core'
import { Injectable } from '@angular/core';
import {
AuthChangeEvent,
AuthSession,
createClient,
Session,
SupabaseClient,
} from '@supabase/supabase-js'
import { environment } from '../environments/environment'
User,
} from '@supabase/supabase-js';
import { environment } from 'src/environments/environment';
import { Database } from 'src/schema';
export interface Profile {
username: string
website: string
avatar_url: string
id?: string;
username: string;
website: string;
avatar_url: string;
}
@Injectable({
providedIn: 'root',
})
export class SupabaseService {
private supabase: SupabaseClient
private supabase: SupabaseClient;
_session: AuthSession | null = null;
constructor() {
this.supabase = createClient(
environment.supabaseUrl,
environment.supabaseKey
)
}
get user() {
return this.supabase.auth.user()
);
}
get session() {
return this.supabase.auth.session()
this.supabase.auth.getSession().then(({ data }) => {
this._session = data.session;
})
return this._session;
}
get profile() {
profile(user: User) {
return this.supabase
.from('profiles')
.select(`username, website, avatar_url`)
.eq('id', this.user?.id)
.single()
.eq('id', user.id)
.single();
}
authChanges(
callback: (event: AuthChangeEvent, session: Session | null) => void
) {
return this.supabase.auth.onAuthStateChange(callback)
return this.supabase.auth.onAuthStateChange(callback);
}
signIn(email: string) {
return this.supabase.auth.signIn({ email })
return this.supabase.auth.signInWithOtp({ email });
}
signOut() {
return this.supabase.auth.signOut()
return this.supabase.auth.signOut();
}
updateProfile(profile: Profile) {
const update = {
...profile,
id: this.user?.id,
updated_at: new Date(),
}
};
return this.supabase.from('profiles').upsert(update, {
returning: 'minimal', // Don't return the value after inserting
})
return this.supabase.from('profiles').upsert(update);
}
downLoadImage(path: string) {
return this.supabase.storage.from('avatars').download(path)
return this.supabase.storage.from('avatars').download(path);
}
uploadAvatar(filePath: string, file: File) {
return this.supabase.storage.from('avatars').upload(filePath, file)
return this.supabase.storage.from('avatars').upload(filePath, file);
}
}
```
And one optional step is to update the CSS file `src/index.css` to make the app look nice.
You can find the full contents of this file [here](https://raw.githubusercontent.com/angular-supa/supabase-angular-user-management/main/src/styles.css).
Optionally, update [src/styles.css](https://raw.githubusercontent.com/supabase/examples/main/supabase-js-v2/user-management/angular-user-management/src/styles.css) to style the app.
### Set up a Login component
Let's set up an Angular component to manage logins and sign ups. We'll use Magic Links, so users can sign in with their email without using passwords.
Create an **AuthComponent** with `ng g c auth` Angular CLI command.
```ts title="src/app/auth.component.ts"
import { Component } from '@angular/core'
import { SupabaseService } from './supabase.service'
```ts title="src/app/auth/auth.component.ts"
import { Component, OnInit } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { SupabaseService } from '../supabase.service';
@Component({
selector: 'app-auth',
template: `
<div class="row flex-center flex">
<form class="col-6 form-widget">
<h1 class="header">Supabase + Angular</h1>
<p class="description">Sign in via magic link with your email below</p>
<div>
<input
#input
class="inputField"
type="email"
placeholder="Your email"
/>
</div>
<div>
<button
type="submit"
(click)="handleLogin(input.value)"
class="button block"
[disabled]="loading"
>
{{ loading ? 'Loading' : 'Send magic link' }}
</button>
</div>
</form>
</div>
`,
templateUrl: './auth.component.html',
styleUrls: ['./auth.component.css'],
})
export class AuthComponent {
loading = false
export class AuthComponent implements OnInit {
loading = false;
constructor(private readonly supabase: SupabaseService) {}
signInForm = this.formBuilder.group({
email: '',
});
async handleLogin(input: string) {
constructor(
private readonly supabase: SupabaseService,
private readonly formBuilder: FormBuilder
) {}
ngOnInit(): void {}
async onSubmit(): Promise<void> {
try {
this.loading = true
await this.supabase.signIn(input)
alert('Check your email for the login link!')
this.loading = true;
const email = this.signInForm.value.email as string;
const { error } = await this.supabase.signIn(email);
if (error) throw error;
alert('Check your email for the login link!');
} catch (error) {
alert(error.error_description || error.message)
if (error instanceof Error) {
alert(error.message);
}
} finally {
this.loading = false
this.signInForm.reset();
this.loading = false;
}
}
}
```
```html title="src/app/auth/auth.component.html"
<div class="row flex-center flex">
<div class="col-6 form-widget" aria-live="polite">
<h1 class="header">Supabase + Angular</h1>
<p class="description">Sign in via magic link with your email below</p>
<form [formGroup]="signInForm" (ngSubmit)="onSubmit()" class="form-widget">
<div>
<label for="email">Email</label>
<input
id="email"
formControlName="email"
class="inputField"
type="email"
placeholder="Your email"
/>
</div>
<div>
<button
type="submit"
class="button block"
[disabled]="loading"
>
{{ loading ? 'Loading' : 'Send magic link' }}
</button>
</div>
</form>
</div>
</div
```
### Account page
After a user is signed in we can allow them to edit their profile details and manage their account.
Create an **AccountComponent** with `ng g c account` Angular CLI command.
Users also need a way to edit their profile details and manage their accounts after signing in.
Create an **AccountComponent** with the `ng g c account` Angular CLI command.
```ts title="src/app/account.component.ts"
import { Component, Input, OnInit } from '@angular/core'
import { Profile, SupabaseService } from './supabase.service'
import { Session } from '@supabase/supabase-js'
```ts title="src/app/account/account.component.ts"
import { Component, Input, OnInit } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { AuthSession } from '@supabase/supabase-js';
import { Profile, SupabaseService } from '../supabase.service';
@Component({
selector: 'app-account',
template: `
<div class="form-widget">
<div>
<label for="email">Email</label>
<input id="email" type="text" [value]="session?.user?.email" disabled />
</div>
<div>
<label for="username">Name</label>
<input
#username
id="username"
type="text"
[value]="profile?.username ?? ''"
/>
</div>
<div>
<label for="website">Website</label>
<input
#website
id="website"
type="url"
[value]="profile?.website ?? ''"
/>
</div>
<div>
<button
class="button primary block"
(click)="updateProfile(username.value, website.value)"
[disabled]="loading"
>
{{ loading ? 'Loading ...' : 'Update' }}
</button>
</div>
<div>
<button class="button block" (click)="signOut()">Sign Out</button>
</div>
</div>
`,
templateUrl: './account.component.html',
styleUrls: ['./account.component.css'],
})
export class AccountComponent implements OnInit {
loading = false
profile: Profile | undefined
loading = false;
profile!: Profile;
@Input() session: Session | undefined
@Input()
session!: AuthSession;
constructor(private readonly supabase: SupabaseService) {}
updateProfileForm = this.formBuilder.group({
username: '',
website: '',
avatar_url: '',
});
ngOnInit() {
this.getProfile()
constructor(
private readonly supabase: SupabaseService,
private formBuilder: FormBuilder
) {}
async ngOnInit(): Promise<void> {
await this.getProfile();
const { username, website, avatar_url } = this.profile;
this.updateProfileForm.patchValue({
username,
website,
avatar_url,
});
}
async getProfile() {
try {
this.loading = true
let { data: profile, error, status } = await this.supabase.profile
this.loading = true;
const { user } = this.session;
let { data: profile, error, status } = await this.supabase.profile(user);
if (error && status !== 406) {
throw error
throw error;
}
if (profile) {
this.profile = profile
this.profile = profile;
}
} catch (error) {
alert(error.message)
if (error instanceof Error) {
alert(error.message);
}
} finally {
this.loading = false
this.loading = false;
}
}
async updateProfile(
username: string,
website: string,
avatar_url: string = ''
) {
async updateProfile(): Promise<void> {
try {
this.loading = true
await this.supabase.updateProfile({ username, website, avatar_url })
this.loading = true;
const { user } = this.session;
const username = this.updateProfileForm.value.username as string;
const website = this.updateProfileForm.value.website as string;
const avatar_url = this.updateProfileForm.value.avatar_url as string;
const { error } = await this.supabase.updateProfile({
id: user.id,
username,
website,
avatar_url,
});
if (error) throw error;
} catch (error) {
alert(error.message)
if (error instanceof Error) {
alert(error.message);
}
} finally {
this.loading = false
this.loading = false;
}
}
async signOut() {
await this.supabase.signOut()
await this.supabase.signOut();
}
}
```
```html title="src/app/account/account.component.html"
<form [formGroup]="updateProfileForm" (ngSubmit)="updateProfile()" class="form-widget">
<div>
<label for="email">Email</label>
<input id="email" type="text" [value]="session.user.email" disabled />
</div>
<div>
<label for="username">Name</label>
<input
formControlName="username"
id="username"
type="text"
/>
</div>
<div>
<label for="website">Website</label>
<input
formControlName="website"
id="website"
type="url"
/>
</div>
<div>
<button
type="submit"
class="button primary block"
[disabled]="loading"
>
{{ loading ? 'Loading ...' : 'Update' }}
</button>
</div>
<div>
<button class="button block" (click)="signOut()">Sign Out</button>
</div>
</form>
```
### Launch!
Now that we have all the components in place, let's update **AppComponent**:
```ts title="src/app/app.component.ts"
import { Component, OnInit } from '@angular/core'
import { SupabaseService } from './supabase.service'
import { Component, OnInit } from '@angular/core';
import { SupabaseService } from './supabase.service';
@Component({
selector: 'app-root',
template: `
<div class="container" style="padding: 50px 0 100px 0">
<app-account *ngIf="session; else auth" [session]="session"></app-account>
<ng-template #auth>
<app-auth></app-auth>
</ng-template>
</div>
`,
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnInit {
session = this.supabase.session
title = 'angular-user-management';
session = this.supabase.session;
constructor(private readonly supabase: SupabaseService) {}
ngOnInit() {
this.supabase.authChanges((_, session) => (this.session = session))
this.supabase.authChanges((_, session) => (this.session = session));
}
}
```
```html title="src/app/app.component.html"
<div class="container" style="padding: 50px 0 100px 0">
<app-account *ngIf="session; else auth" [session]="session"></app-account>
<ng-template #auth>
<app-auth></app-auth>
</ng-template>
</div>
```
`app.module.ts` also needs to be modified to include the `ReactiveFormsModule` from the `@angular/forms` package.
```ts title="src/app/app.module.ts"
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { AuthComponent } from './auth/auth.component';
import { AccountComponent } from './account/account.component';
import { ReactiveFormsModule } from '@angular/forms';
import { AvatarComponent } from './avatar/avatar.component';
@NgModule({
declarations: [
AppComponent,
AuthComponent,
AccountComponent,
AvatarComponent,
],
imports: [BrowserModule, ReactiveFormsModule],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
```
Once that's done, run this in a terminal window:
```bash
@@ -474,110 +558,141 @@ Every Supabase project is configured with [Storage](/docs/guides/storage) for ma
Let's create an avatar for the user so that they can upload a profile photo.
Create an **AvatarComponent** with `ng g c avatar` Angular CLI command.
```ts title="src/app/avatar.component.ts"
import { Component, EventEmitter, Input, Output } from '@angular/core'
import { SupabaseService } from './supabase.service'
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'
```ts title="src/app/avatar/avatar.component.ts"
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { SafeResourceUrl, DomSanitizer } from '@angular/platform-browser';
import { SupabaseService } from '../supabase.service';
@Component({
selector: 'app-avatar',
template: `
<div>
<img
*ngIf="_avatarUrl"
[src]="_avatarUrl"
alt="Avatar"
class="avatar image"
style="height: 150px; width: 150px"
/>
</div>
<div
*ngIf="!_avatarUrl"
class="avatar no-image"
style="height: 150px; width: 150px"
></div>
<div style="width: 150px">
<label class="button primary block" for="single">
{{ uploading ? 'Uploading ...' : 'Upload' }}
</label>
<input
style="visibility: hidden;position: absolute"
type="file"
id="single"
accept="image/*"
(change)="uploadAvatar($event)"
[disabled]="uploading"
/>
</div>
`,
templateUrl: './avatar.component.html',
styleUrls: ['./avatar.component.css'],
})
export class AvatarComponent {
_avatarUrl: SafeResourceUrl | undefined
uploading = false
export class AvatarComponent implements OnInit {
_avatarUrl: SafeResourceUrl | undefined;
uploading = false;
@Input()
set avatarUrl(url: string | undefined) {
set avatarUrl(url: string | null) {
if (url) {
this.downloadImage(url)
this.downloadImage(url);
}
}
@Output() upload = new EventEmitter<string>()
@Output() upload = new EventEmitter<string>();
constructor(
private readonly supabase: SupabaseService,
private readonly dom: DomSanitizer
) {}
ngOnInit(): void {}
async downloadImage(path: string) {
try {
const { data } = await this.supabase.downLoadImage(path)
const { data } = await this.supabase.downLoadImage(path);
if (data instanceof Blob) {
this._avatarUrl = this.dom.bypassSecurityTrustResourceUrl(
URL.createObjectURL(data)
)
);
}
} catch (error) {
console.error('Error downloading image: ', error.message)
if (error instanceof Error) {
console.error('Error downloading image: ', error.message);
}
}
}
async uploadAvatar(event: any) {
try {
this.uploading = true
this.uploading = true;
if (!event.target.files || event.target.files.length === 0) {
throw new Error('You must select an image to upload.')
throw new Error('You must select an image to upload.');
}
const file = event.target.files[0]
const fileExt = file.name.split('.').pop()
const fileName = `${Math.random()}.${fileExt}`
const filePath = `${fileName}`
const file = event.target.files[0];
const fileExt = file.name.split('.').pop();
const filePath = `${Math.random()}.${fileExt}`;
await this.supabase.uploadAvatar(filePath, file)
this.upload.emit(filePath)
await this.supabase.uploadAvatar(filePath, file);
this.upload.emit(filePath);
} catch (error) {
alert(error.message)
if (error instanceof Error) {
alert(error.message);
}
} finally {
this.uploading = false
this.uploading = false;
}
}
}
```
```html title="src/app/avatar/avatar.component.html"
<div>
<img
*ngIf="_avatarUrl"
[src]="_avatarUrl"
alt="Avatar"
class="avatar image"
style="height: 150px; width: 150px"
/>
</div>
<div
*ngIf="!_avatarUrl"
class="avatar no-image"
style="height: 150px; width: 150px"
></div>
<div style="width: 150px">
<label class="button primary block" for="single">
{{ uploading ? 'Uploading ...' : 'Upload' }}
</label>
<input
style="visibility: hidden;position: absolute"
type="file"
id="single"
accept="image/*"
(change)="uploadAvatar($event)"
[disabled]="uploading"
/>
</div>
```
### Add the new widget
And then we can add the widget on top of the **AccountComponent** html template:
```ts title="src/app/account.component.ts"
template: `
<app-avatar
[avatarUrl]="this.profile?.avatar_url"
(upload)="updateProfile(username.value, website.value, $event)">
</app-avatar>
```html title="src/app/account.component.html"
<form [formGroup]="updateProfileForm" (ngSubmit)="updateProfile()" class="form-widget">
<app-avatar
[avatarUrl]="this.avatarUrl"
(upload)="updateAvatar($event)">
</app-avatar>
<!-- input fields -->
</form>
```
<!-- input fields -->
`
And add an `updateAvatar` function along with an `avatarUrl` getter to the **AccountComponent** typescript file:
```ts title="src/app/account.component.ts"
@Component({
selector: 'app-account',
templateUrl: './account.component.html',
styleUrls: ['./account.component.css'],
})
export class AccountComponent implements OnInit {
// ...
get avatarUrl() {
return this.updateProfileForm.value.avatar_url as string;
}
async updateAvatar(event: string): Promise<void> {
this.updateProfileForm.patchValue({
avatar_url: event,
});
await this.updateProfile();
}
// ...
}
```
## Next steps
@@ -206,7 +206,7 @@ We'll focus on the end-user-centric meaning of performance here (query speed).
Postgres is capable of loading CSVs, but since we'll be using other search engines as well, let's convert to format that's much easier to use and process - [Newline Delimited JSON](https://dataprotocols.org/ndjson/).
After a few lines of code and one `[csv2ndjson.mjs` script](https://github.com/VADOSWARE/fts-benchmark/blob/main/src/util/csv2ndjson.mjs) later, we have a `movies.ndjson.json` full of JSON documents that we can easily ingest into any search engine (or other database for that matter!).
After a few lines of code and one [`csv2ndjson.mjs` script](https://github.com/VADOSWARE/fts-benchmark/blob/main/src/util/csv2ndjson.mjs) later, we have a `movies.ndjson.json` full of JSON documents that we can easily ingest into any search engine (or other database for that matter!).
### Inserting the data
@@ -332,7 +332,7 @@ There's a _lot_ of tuning left undone here, but a few points stand out right awa
- Postgres FTS is quite close performance-wise to many other solutions, at least in their default configuration.
- Only Typesense and MeiliSearch properly handled mis-spellings (the "suprman" query).
- Typesense was relatively strict with matches compared to other engines.
- OpenSearch was _very_ fast with ingest, but also failed with the misspelling out of the box.
- OpenSearch was _very_ fast with ingest, but the default configuration doesn't index misspellings.
- In-memory SQLite is by far the fastest, and PG isn't too far behind for this small data set.
## Feedback from the other products
@@ -31,11 +31,6 @@ const Benchmark = () => {
</p>
</div>
</div>
<div className="col-span-1 rounded-r-md px-12 pb-12 lg:col-span-2 lg:px-0 lg:pb-0">
<a href="https://supabasesubscribe.vercel.app/subscription/enterpriseBenchmarks">
<Button>Get notified</Button>
</a>
</div>
</div>
</div>
)
+46
View File
@@ -1415,4 +1415,50 @@ module.exports = [
// source: '/docs/reference/javascript/auth-api-updateuserbyid',
// destination: '/docs/reference/javascript/auth-admin-updateuserbyid',
// },
// v1 to v2 redirects
// signIn method is now split into signInWithPassword ,signInWithPasswordless ,signInWithOAuth
// send traffic to v1 docs instead
{
permanent: true,
source: '/docs/reference/javascript/auth-signin',
destination: '/docs/reference/javascript/v1/auth-signin',
},
// auth.session renamed to auth.getSession
// send to new v2 docs version
{
permanent: true,
source: '/docs/reference/javascript/auth-session',
destination: '/docs/reference/javascript/auth-getsession',
},
// Removed sendMobileOTP in favor of signInWithOtp
// send traffic to new v2 docs instead
{
permanent: true,
source: '/docs/reference/javascript/auth-api-sendmobileotp',
destination: '/docs/reference/javascript/auth-signinwithotp',
},
// Replaced update with updateUser
{
permanent: true,
source: '/docs/reference/javascript/auth-update',
destination: '/docs/reference/javascript/auth-updateuser',
},
// realtime methods been replaced with new names
{
permanent: true,
source: '/docs/reference/javascript/removesubscription',
destination: '/docs/reference/javascript/v1/removesubscription',
},
{
permanent: true,
source: '/docs/reference/javascript/removeallsubscriptions',
destination: '/docs/reference/javascript/v1/removeallsubscriptions',
},
{
permanent: true,
source: '/docs/reference/javascript/getsubscriptions',
destination: '/docs/reference/javascript/v1/getsubscriptions',
},
]
+2 -3
View File
@@ -52,7 +52,7 @@ export const Section = ({ children, icon, img }) => (
### SOC 2
Supabase is SOC2 Type 1 compliant. Enterprise customers can request a copy of our SOC2 through our [Security Portal](https://security.supabase.com).
Supabase is SOC2 Type 1 compliant. Enterprise customers can request a copy of our SOC2 [here](https://supabase.com/legal/soc2).
<div className="w-16">
<img src="/images/security/21972-312_SOC_NonCPA.png" />
@@ -126,8 +126,7 @@ In addition to internal security reviews, we use various tools to scan our code
<h3 className="text-3xl">Security Portal</h3>
Access Supabase’s security and compliance documents on our Security Portal, such as
penetration testing and audit reports.
Access more of Supabase’s security on our Security Portal, such as penetration testing and audit reports.
<Link passHref href="https://security.supabase.com">
<Button type="default" size="medium" as="a" className="no-underline" target="_blank">
+2
View File
@@ -38,6 +38,8 @@ services:
# https://github.com/supabase/cli/issues/14
KONG_DNS_ORDER: LAST,A,CNAME
KONG_PLUGINS: request-transformer,cors,key-auth,acl
KONG_NGINX_PROXY_PROXY_BUFFER_SIZE: 160k
KONG_NGINX_PROXY_PROXY_BUFFERS: 64 160k
volumes:
- ./volumes/api:/var/lib/kong
+172 -8
View File
@@ -1,7 +1,7 @@
clispec: '001'
info:
id: cli
version: 1.0.0
version: 1.8.5
title: Supabase CLI
language: sh
source: https://github.com/supabase/cli
@@ -11,8 +11,9 @@ info:
Supabase CLI provides you with tools to develop your application locally, and deploy your application to the Supabase platform.
options: |-
```sh
--debug output debug logs to stderr
-h, --help help for supabase
--debug output debug logs to stderr
-h, --help help for supabase
--workdir string path to a Supabase project directory
```
commands:
- id: supabase-stop
@@ -27,7 +28,8 @@ commands:
subcommands: []
options: |-
```sh
-h, --help help for stop
--backup Backs up the current database before stopping.
-h, --help help for stop
```
- id: supabase-status
title: supabase status
@@ -299,7 +301,7 @@ commands:
title: supabase gen types typescript
summary: Generate types for TypeScript
description: |
Generate types for TypeScript. Must specify either --local or --db-url
Generate types for TypeScript. Must specify one of --local, --linked, --project-id, or --db-url
tags: []
links: []
usage: |-
@@ -309,9 +311,12 @@ commands:
subcommands: []
options: |-
```sh
--db-url string Generate types from a database url.
-h, --help help for typescript
--local Generate types from the local dev database.
--db-url string Generate types from a database url.
-h, --help help for typescript
--linked Generate types from the linked project.
--local Generate types from the local dev database.
--project-id string Generate types from a project ID.
--schema stringArray Schemas to generate types for.
```
- id: supabase-functions
title: supabase functions
@@ -399,6 +404,7 @@ commands:
subcommands:
- supabase-db-branch
- supabase-db-diff
- supabase-db-lint
- supabase-db-push
- supabase-db-remote
- supabase-db-reset
@@ -478,6 +484,22 @@ commands:
-h, --help help for push
-p, --password string Password to your remote Postgres database.
```
- id: supabase-db-lint
title: supabase db lint
summary: Checks local database for typing error
tags: []
links: []
usage: |-
```sh
supabase db lint [flags]
```
subcommands: []
options: |-
```sh
-h, --help help for lint
--level string Error level to emit. (default "warning")
-s, --schema strings List of schema to include. (default [public])
```
- id: supabase-db-diff
title: supabase db diff
summary: Diffs the local database for schema changes
@@ -567,3 +589,145 @@ commands:
```sh
-h, --help help for create
```
- id: supabase-completion
title: supabase completion
summary: Generate the autocompletion script for the specified shell
description: |
Generate the autocompletion script for supabase for the specified shell.
See each sub-command's help for details on how to use the generated script.
tags: []
links: []
subcommands:
- supabase-completion-bash
- supabase-completion-fish
- supabase-completion-powershell
- supabase-completion-zsh
options: |-
```sh
-h, --help help for completion
```
- id: supabase-completion-zsh
title: supabase completion zsh
summary: Generate the autocompletion script for zsh
description: |
Generate the autocompletion script for the zsh shell.
If shell completion is not already enabled in your environment you will need
to enable it. You can execute the following once:
echo "autoload -U compinit; compinit" >> ~/.zshrc
To load completions in your current shell session:
source <(supabase completion zsh); compdef _supabase supabase
To load completions for every new session, execute once:
#### Linux:
supabase completion zsh > "${fpath[1]}/_supabase"
#### macOS:
supabase completion zsh > $(brew --prefix)/share/zsh/site-functions/_supabase
You will need to start a new shell for this setup to take effect.
tags: []
links: []
usage: |-
```sh
supabase completion zsh [flags]
```
subcommands: []
options: |-
```sh
-h, --help help for zsh
--no-descriptions disable completion descriptions
```
- id: supabase-completion-powershell
title: supabase completion powershell
summary: Generate the autocompletion script for powershell
description: |
Generate the autocompletion script for powershell.
To load completions in your current shell session:
supabase completion powershell | Out-String | Invoke-Expression
To load completions for every new session, add the output of the above command
to your powershell profile.
tags: []
links: []
usage: |-
```sh
supabase completion powershell [flags]
```
subcommands: []
options: |-
```sh
-h, --help help for powershell
--no-descriptions disable completion descriptions
```
- id: supabase-completion-fish
title: supabase completion fish
summary: Generate the autocompletion script for fish
description: |
Generate the autocompletion script for the fish shell.
To load completions in your current shell session:
supabase completion fish | source
To load completions for every new session, execute once:
supabase completion fish > ~/.config/fish/completions/supabase.fish
You will need to start a new shell for this setup to take effect.
tags: []
links: []
usage: |-
```sh
supabase completion fish [flags]
```
subcommands: []
options: |-
```sh
-h, --help help for fish
--no-descriptions disable completion descriptions
```
- id: supabase-completion-bash
title: supabase completion bash
summary: Generate the autocompletion script for bash
description: |
Generate the autocompletion script for the bash shell.
This script depends on the 'bash-completion' package.
If it is not installed already, you can install it via your OS's package manager.
To load completions in your current shell session:
source <(supabase completion bash)
To load completions for every new session, execute once:
#### Linux:
supabase completion bash > /etc/bash_completion.d/supabase
#### macOS:
supabase completion bash > $(brew --prefix)/etc/bash_completion.d/supabase
You will need to start a new shell for this setup to take effect.
tags: []
links: []
usage: |-
```sh
supabase completion bash
```
subcommands: []
options: |-
```sh
-h, --help help for bash
--no-descriptions disable completion descriptions
```
+12 -2
View File
@@ -1,6 +1,13 @@
import { useRef, useEffect, useState, FormEvent, KeyboardEvent, ReactNode } from 'react'
import { orderBy, filter, without } from 'lodash'
import { Popover, IconCheck, IconAlertCircle, IconSearch, IconPlus } from 'ui'
import {
Popover,
IconCheck,
IconAlertCircle,
IconSearch,
IconPlus,
IconChevronDown,
} from 'ui'
import { BadgeDisabled, BadgeSelected } from './Badges'
@@ -223,7 +230,7 @@ export default function MultiSelect({
>
<div
className={[
'flex w-full flex-wrap items-start items-center gap-1.5 p-1.5',
'flex w-full flex-wrap items-start gap-1.5 p-1.5 cursor-default',
`${selectedOptions.length === 0 ? 'h-9' : ''}`,
].join(' ')}
>
@@ -250,6 +257,9 @@ export default function MultiSelect({
)
}
})}
<div className="absolute inset-y-0 right-0 pl-3 pr-1 flex space-x-1 items-center cursor-pointer ">
<IconChevronDown className="text-scale-900" />
</div>
</div>
</Popover>
</div>