mirror of
https://github.com/supabase/supabase.git
synced 2026-10-19 08:09:11 +03:00
merge master
This commit is contained in:
commit
3e916eebf7
127 files changed
+19176
-33822
No files matched your search
@@ -23,3 +23,6 @@ yarn-error.log*
|
||||
**/*/generated/**/*
|
||||
!**/*/generated/.gitkeep
|
||||
!**/*/generated/**/.gitkeep
|
||||
|
||||
# Sitemap
|
||||
public/sitemap.xml
|
||||
@@ -16,7 +16,7 @@ import { Button, cn } from 'ui'
|
||||
import { useSendFeedbackMutation } from '~/lib/fetch/feedback'
|
||||
import { useSendTelemetryEvent } from '~/lib/telemetry'
|
||||
import { FeedbackModal, type FeedbackFields } from './FeedbackModal'
|
||||
import { getSanitizedTabParams } from './Feedback.utils'
|
||||
import { getNotionTeam, getSanitizedTabParams } from './Feedback.utils'
|
||||
|
||||
const FeedbackButton = forwardRef<
|
||||
HTMLButtonElement,
|
||||
@@ -125,6 +125,7 @@ function Feedback() {
|
||||
title,
|
||||
// @ts-expect-error -- can't click this button without having a state.response
|
||||
isHelpful: state.response === 'yes',
|
||||
team: getNotionTeam(pathname),
|
||||
})
|
||||
setModalOpen(false)
|
||||
refocusButton()
|
||||
|
||||
@@ -1,5 +1,39 @@
|
||||
import { pick } from 'lodash'
|
||||
|
||||
/**
|
||||
* Gets the Notion team to send feedback to based on the pathname.
|
||||
*/
|
||||
const getNotionTeam = (pathname: string) => {
|
||||
const DEFAULT_TEAM = 'team-docs'
|
||||
|
||||
// Pathname has format `/guides/(team)/**`
|
||||
const pathParts = pathname.split('/')
|
||||
|
||||
if (pathParts[1] !== 'guides' || !pathParts[2]) return DEFAULT_TEAM
|
||||
|
||||
switch (pathParts[2]) {
|
||||
case 'database':
|
||||
return 'team-postgres'
|
||||
case 'auth':
|
||||
return 'team-auth'
|
||||
case 'storage':
|
||||
return 'team-storage'
|
||||
case 'functions':
|
||||
return 'team-functions'
|
||||
case 'realtime':
|
||||
return 'team-realtime'
|
||||
case 'ai':
|
||||
return 'team-ai'
|
||||
case 'cli':
|
||||
return 'team-cli'
|
||||
|
||||
// Ignoring platform for now because that section is a mix of teams.
|
||||
case 'platform':
|
||||
default:
|
||||
return DEFAULT_TEAM
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the tab selection state from the URL search params.
|
||||
*
|
||||
@@ -13,4 +47,4 @@ const getSanitizedTabParams = () => {
|
||||
return pick(Object.fromEntries(searchParams.entries()), queryGroups)
|
||||
}
|
||||
|
||||
export { getSanitizedTabParams }
|
||||
export { getNotionTeam, getSanitizedTabParams }
|
||||
@@ -4,3 +4,9 @@ The next step requires a callback URL, which looks like this: `https://<project-
|
||||
- Click on the `Authentication` icon in the left sidebar
|
||||
- Click on [`Providers`](https://supabase.com/dashboard/project/_/auth/providers) under the Configuration section
|
||||
- Click on **{props.provider}** from the accordion list to expand and you'll find your **Callback URL**, you can click `Copy` to copy it to the clipboard
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
For testing OAuth locally with the Supabase CLI please see the [local development docs](/docs/guides/cli/local-development#use-auth-locally).
|
||||
|
||||
</Admonition>
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
IconMenuDevCli,
|
||||
} from './HomeMenuIcons'
|
||||
|
||||
function getMenuIcon(menuKey: string, width: number = 16, height: number = 16, className) {
|
||||
function getMenuIcon(menuKey: string, width: number = 16, height: number = 16, className: string) {
|
||||
switch (menuKey) {
|
||||
case 'home':
|
||||
return <IconMenuHome width={width} height={height} className={className} />
|
||||
|
||||
@@ -195,7 +195,7 @@ export const REFERENCES: References = {
|
||||
csharp: {
|
||||
name: 'C#',
|
||||
library: 'supabase-csharp',
|
||||
versions: ['v0'],
|
||||
versions: ['v1', 'v0'],
|
||||
icon: '/docs/img/libraries/c-sharp-icon.svg',
|
||||
},
|
||||
swift: {
|
||||
@@ -624,6 +624,10 @@ export const auth = {
|
||||
name: 'Next.js guide',
|
||||
url: '/guides/auth/server-side/nextjs',
|
||||
},
|
||||
{
|
||||
name: 'SvelteKit guide',
|
||||
url: '/guides/auth/server-side/sveltekit',
|
||||
},
|
||||
{
|
||||
name: 'Creating a client',
|
||||
url: '/guides/auth/server-side/creating-a-client',
|
||||
@@ -715,6 +719,10 @@ export const database: NavMenuConstant = {
|
||||
name: 'Managing tables, views, and data',
|
||||
url: '/guides/database/tables',
|
||||
},
|
||||
{
|
||||
name: 'Working with arrays',
|
||||
url: '/guides/database/arrays',
|
||||
},
|
||||
{ name: 'Managing indexes', url: '/guides/database/postgres/indexes' },
|
||||
{
|
||||
name: 'Querying joins and nested tables',
|
||||
@@ -890,7 +898,7 @@ export const database: NavMenuConstant = {
|
||||
url: '/guides/database/extensions/pg-safeupdate',
|
||||
},
|
||||
{
|
||||
name: 'pgsodium: Encryption Features',
|
||||
name: 'pgsodium (pending deprecation): Encryption Features',
|
||||
url: '/guides/database/extensions/pgsodium',
|
||||
},
|
||||
{
|
||||
@@ -1057,6 +1065,10 @@ export const api: NavMenuConstant = {
|
||||
name: 'Using custom schemas',
|
||||
url: '/guides/api/using-custom-schemas',
|
||||
},
|
||||
{
|
||||
name: 'Converting from SQL to JavaScript API',
|
||||
url: '/guides/api/sql-to-api',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -2081,6 +2093,13 @@ export const reference_csharp_v0 = {
|
||||
parent: '/reference',
|
||||
}
|
||||
|
||||
export const reference_csharp_v1 = {
|
||||
icon: 'reference-csharp',
|
||||
title: 'C#',
|
||||
url: 'guides/reference/csharp',
|
||||
parent: '/reference',
|
||||
}
|
||||
|
||||
export const reference_python_v2 = {
|
||||
icon: 'reference-python',
|
||||
title: 'Python',
|
||||
|
||||
@@ -24,6 +24,7 @@ enum MenuId {
|
||||
RefDartV1 = 'reference_dart_v1',
|
||||
RefDartV2 = 'reference_dart_v2',
|
||||
RefCSharpV0 = 'reference_csharp_v0',
|
||||
RefCSharpV1 = 'reference_csharp_v1',
|
||||
RefPythonV2 = 'reference_python_v2',
|
||||
RefSwiftV1 = 'reference_swift_v1',
|
||||
RefSwiftV2 = 'reference_swift_v2',
|
||||
@@ -154,6 +155,13 @@ const menus: Menu[] = [
|
||||
commonSectionsFile: 'common-client-libs-sections.json',
|
||||
specFile: 'supabase_csharp_v0.yml',
|
||||
type: 'reference',
|
||||
path: '/reference/csharp/v0',
|
||||
},
|
||||
{
|
||||
id: MenuId.RefCSharpV1,
|
||||
commonSectionsFile: 'common-client-libs-sections.json',
|
||||
specFile: 'supabase_csharp_v1.yml',
|
||||
type: 'reference',
|
||||
path: '/reference/csharp',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -109,7 +109,7 @@ Similarity search is the most common use case for vectors. `pgvector` support 3
|
||||
|
||||
Choosing the right operator depends on your needs. Dot product tends to be the fastest if your vectors are normalized. For more information on how embeddings work and how they relate to each other, see [What are Embeddings?](/docs/guides/ai/concepts#what-are-embeddings).
|
||||
|
||||
Supabase client libraries like `supabase-js` connect to your Postgres instance via [PostgREST](docs/guides/getting-started/architecture#postgrest-api). PostgREST does not currently support `pgvector` similarity operators, so we'll need to wrap our query in a Postgres function and call it via the `rpc()` method:
|
||||
Supabase client libraries like `supabase-js` connect to your Postgres instance via [PostgREST](/docs/guides/getting-started/architecture#postgrest-api). PostgREST does not currently support `pgvector` similarity operators, so we'll need to wrap our query in a Postgres function and call it via the `rpc()` method:
|
||||
|
||||
```sql
|
||||
create or replace function match_documents (
|
||||
|
||||
@@ -195,7 +195,7 @@ Read more about [PostgREST's pre-request function](https://postgrest.org/en/stab
|
||||
>
|
||||
<TabPanel id="rate-limit-per-ip" label="Rate limit per IP">
|
||||
|
||||
You can only rate-limit `POST`, `PUT`, `PATCH` and `DELETE` requests. This is because `GET` and `HEAD` requests run in read-only mode, and will be served by [Read Replicas](/guides/platform/read-replicas) which do not support writing to the database.
|
||||
You can only rate-limit `POST`, `PUT`, `PATCH` and `DELETE` requests. This is because `GET` and `HEAD` requests run in read-only mode, and will be served by [Read Replicas](/docs/guides/platform/read-replicas) which do not support writing to the database.
|
||||
|
||||
Outline:
|
||||
|
||||
@@ -265,7 +265,7 @@ alter role authenticator
|
||||
notify pgrst, 'reload config';
|
||||
```
|
||||
|
||||
To clear old entries in the `private.rate_limits` table, set up a [pg_cron](/guides/database/extensions/pg_cron) job to clean them up.
|
||||
To clear old entries in the `private.rate_limits` table, set up a [pg_cron](/docs/guides/database/extensions/pg_cron) job to clean them up.
|
||||
|
||||
</TabPanel>
|
||||
|
||||
|
||||
+5
-17
@@ -4,7 +4,9 @@ title: 'Converting SQL to JavaScript API'
|
||||
description: 'Implementing common SQL patterns in the JavaScript API'
|
||||
---
|
||||
|
||||
{/* @TODO: this one is a very daunting read on first attempt. Need to check what is the purpose of it */}
|
||||
Many common SQL queries can be written using the JavaScript API, provided by the SDK to wrap Data API calls. Below are a few examples of conversions between SQL and JavaScript patterns.
|
||||
|
||||
## Select statement with basic clauses
|
||||
|
||||
Select a set of columns from a single table with where, order by, and limit clauses.
|
||||
|
||||
@@ -28,6 +30,8 @@ const { data, error } = await supabase
|
||||
.limit(20)
|
||||
```
|
||||
|
||||
## Select statement with complex Boolean Logic clause
|
||||
|
||||
Select all columns from a single table with a complex where clause: OR AND OR
|
||||
|
||||
```sql
|
||||
@@ -42,7 +46,6 @@ const { data, error } = await supabase
|
||||
.select() // or .select('*')
|
||||
.or('team_id.eq.CHN,team_id.is.null')
|
||||
.or('age.gt.35,age.is.null') // additional filters imply "AND"
|
||||
.not('team_id', 'eq', 'STL')
|
||||
```
|
||||
|
||||
Select all columns from a single table with a complex where clause: AND OR AND
|
||||
@@ -60,21 +63,6 @@ const { data, error } = await supabase
|
||||
.or('and(team_id.eq.CHN,age.gt.35),and(team_id.neq.CHN,.not.age.is.null)')
|
||||
```
|
||||
|
||||
Get a count of rows, but don't return any data.
|
||||
|
||||
```sql
|
||||
select count(*)
|
||||
from players
|
||||
where team_id = 'NYM';
|
||||
```
|
||||
|
||||
```js
|
||||
const { count, error } = await supabase
|
||||
.from('players')
|
||||
.select('*', { count: 'exact', head: true }) // exact, planned, or executed
|
||||
.eq('team_id', 'NYM')
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [Supabase - Get started for free](https://supabase.com)
|
||||
@@ -27,7 +27,11 @@ The `auth-helpers` package has been replaced with the `@supabase/ssr` package. W
|
||||
|
||||
This submodule provides convenience helpers for implementing user authentication in Next.js applications using the pages directory.
|
||||
|
||||
> Note: As of [Next.js 13.4](https://nextjs.org/blog/next-13-4), the App Router has reached stable status. This is now the recommended path for new Next.js app. Check out our guide on using [Auth Helpers with the Next.js App Directory](/docs/guides/auth/auth-helpers/nextjs).
|
||||
<Admonition type="note">
|
||||
|
||||
Note: As of [Next.js 13.4](https://nextjs.org/blog/next-13-4), the App Router has reached stable status. This is now the recommended path for new Next.js app. Check out our guide on using [Auth Helpers with the Next.js App Directory](/docs/guides/auth/auth-helpers/nextjs).
|
||||
|
||||
</Admonition>
|
||||
|
||||
## Install the Next.js helper library
|
||||
|
||||
|
||||
@@ -36,7 +36,11 @@ This submodule provides convenience helpers for implementing user authentication
|
||||
></iframe>
|
||||
</div>
|
||||
|
||||
> For a complete implementation example, check out [this free egghead course](https://egghead.io/courses/build-a-realtime-chat-app-with-remix-and-supabase-d36e2618) or [this GitHub repo](https://github.com/supabase/auth-helpers/tree/main/examples/remix).
|
||||
<Admonition type="tip">
|
||||
|
||||
For a complete implementation example, check out [this free egghead course](https://egghead.io/courses/build-a-realtime-chat-app-with-remix-and-supabase-d36e2618) or [this GitHub repo](https://github.com/supabase/auth-helpers/tree/main/examples/remix).
|
||||
|
||||
</Admonition>
|
||||
|
||||
## Install the Remix helper library
|
||||
|
||||
@@ -179,7 +183,11 @@ export const loader = async ({ request }) => {
|
||||
}
|
||||
```
|
||||
|
||||
> Supabase will set cookie headers to manage the user's auth session, therefore, the `response.headers` must be returned from the `Loader` function.
|
||||
<Admonition type="tip">
|
||||
|
||||
Supabase will set cookie headers to manage the user's auth session, therefore, the `response.headers` must be returned from the `Loader` function.
|
||||
|
||||
</Admonition>
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="ts" label="TypeScript">
|
||||
@@ -209,7 +217,11 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
|
||||
}
|
||||
```
|
||||
|
||||
> Supabase will set cookie headers to manage the user's auth session, therefore, the `response.headers` must be returned from the `Loader` function.
|
||||
<Admonition type="tip">
|
||||
|
||||
Supabase will set cookie headers to manage the user's auth session, therefore, the `response.headers` must be returned from the `Loader` function.
|
||||
|
||||
</Admonition>
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
@@ -251,7 +263,11 @@ export const action = async ({ request }) => {
|
||||
}
|
||||
```
|
||||
|
||||
> Supabase will set cookie headers to manage the user's auth session, therefore, the `response.headers` must be returned from the `Action` function.
|
||||
<Admonition type="tip">
|
||||
|
||||
Supabase will set cookie headers to manage the user's auth session, therefore, the `response.headers` must be returned from the `Action` function.
|
||||
|
||||
</Admonition>
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="ts" label="TypeScript">
|
||||
@@ -282,7 +298,11 @@ export const action = async ({ request }: ActionFunctionArgs) => {
|
||||
}
|
||||
```
|
||||
|
||||
> Supabase will set cookie headers to manage the user's auth session, therefore, the `response.headers` must be returned from the `Action` function.
|
||||
<Admonition type="tip">
|
||||
|
||||
Supabase will set cookie headers to manage the user's auth session, therefore, the `response.headers` must be returned from the `Action` function.
|
||||
|
||||
</Admonition>
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
@@ -341,7 +361,11 @@ export const loader = () => {
|
||||
}
|
||||
```
|
||||
|
||||
> These may not be stored in `process.env` for environments other than Node.
|
||||
<Admonition type="tip">
|
||||
|
||||
These may not be stored in `process.env` for environments other than Node.
|
||||
|
||||
</Admonition>
|
||||
|
||||
Next, we call the `useLoaderData` hook in our component to get the `env` object.
|
||||
|
||||
@@ -375,7 +399,11 @@ export const loader = ({}: LoaderFunctionArgs) => {
|
||||
}
|
||||
```
|
||||
|
||||
> These may not be stored in `process.env` for environments other than Node.
|
||||
<Admonition type="tip">
|
||||
|
||||
These may not be stored in `process.env` for environments other than Node.
|
||||
|
||||
</Admonition>
|
||||
|
||||
Next, we call the `useLoaderData` hook in our component to get the `env` object.
|
||||
|
||||
@@ -558,7 +586,11 @@ useEffect(() => {
|
||||
|
||||
</Tabs>
|
||||
|
||||
> Check out [this repo](https://github.com/supabase/auth-helpers/tree/main/examples/remix) for full implementation example
|
||||
<Admonition type="tip">
|
||||
|
||||
Check out [this repo](https://github.com/supabase/auth-helpers/tree/main/examples/remix) for full implementation example
|
||||
|
||||
</Admonition>
|
||||
|
||||
### Authentication
|
||||
|
||||
@@ -771,7 +803,11 @@ export default function Index() {
|
||||
|
||||
</Tabs>
|
||||
|
||||
> Ensure you have [enabled replication](https://supabase.com/dashboard/project/_/database/publications) on the table you are subscribing to.
|
||||
<Admonition type="tip">
|
||||
|
||||
Ensure you have [enabled replication](https://supabase.com/dashboard/project/_/database/publications) on the table you are subscribing to.
|
||||
|
||||
</Admonition>
|
||||
|
||||
## Migration guide
|
||||
|
||||
|
||||
@@ -125,6 +125,20 @@ on my_table for select
|
||||
using ( true );
|
||||
```
|
||||
|
||||
### Protect views using `security_invoker`
|
||||
|
||||
Views bypass RLS by default because they are usually created with the `postgres` user. This is a feature of Postgres, which automatically creates views with `security definer`.
|
||||
|
||||
In Postgres 15 and above, you can make a view obey the RLS policies of the underlying tables when invoked by `anon` and `authenticated` roles by setting `security_invoker = true`.
|
||||
|
||||
```sql
|
||||
create view <VIEW_NAME>
|
||||
with(security_invoker = true)
|
||||
as select <QUERY>
|
||||
```
|
||||
|
||||
In older versions of Postgres, protect your views by revoking access from the `anon` and `authenticated` roles, or by putting them in an unexposed schema.
|
||||
|
||||
### Using external authorization systems
|
||||
|
||||
If you want to use another authorization method for your applications that's also fine. Supabase is "just Postgres", so if your application works with Postgres, then it also works with Supabase. If you take this path, don't put your tables in the `public` schema - instead create a new schema for your tables and functions:
|
||||
|
||||
@@ -469,22 +469,27 @@ export const handle: Handle = async ({ event, resolve }) => {
|
||||
})
|
||||
|
||||
/**
|
||||
* Unlike `supabase.auth.getSession`, which is unsafe on the server because it
|
||||
* doesn't validate the JWT, this function validates the JWT by first calling
|
||||
* `getUser` and aborts early if the JWT signature is invalid.
|
||||
* Unlike `supabase.auth.getSession()`, which returns the session _without_
|
||||
* validating the JWT, this function also calls `getUser()` to validate the
|
||||
* JWT before returning the session.
|
||||
*/
|
||||
event.locals.safeGetSession = async () => {
|
||||
const {
|
||||
data: { session },
|
||||
} = await event.locals.supabase.auth.getSession()
|
||||
if (!session) {
|
||||
return { session: null, user: null }
|
||||
}
|
||||
|
||||
const {
|
||||
data: { user },
|
||||
error,
|
||||
} = await event.locals.supabase.auth.getUser()
|
||||
if (error) {
|
||||
// JWT validation has failed
|
||||
return { session: null, user: null }
|
||||
}
|
||||
|
||||
const {
|
||||
data: { session },
|
||||
} = await event.locals.supabase.auth.getSession()
|
||||
return { session, user }
|
||||
}
|
||||
|
||||
|
||||
@@ -9,15 +9,22 @@ When using Supabase with server-side languages and frameworks - such as Next.js,
|
||||
|
||||
## Framework quickstarts
|
||||
|
||||
<div className="grid cols-[repeat(auto-fit,min-max(250px,1fr)] gap-6 mb-6 not-prose">
|
||||
<div className="grid grid-cols-[repeat(auto-fit,minmax(300px,1fr))] gap-6 mb-6 not-prose">
|
||||
{[
|
||||
{
|
||||
title: 'Next.js',
|
||||
href: '/guides/auth/server-side/nextjs',
|
||||
description:
|
||||
'Automatically configure Supabase to use cookies, making your user and their session available on the client and server.',
|
||||
'Automatically configure Supabase in Next.js to use cookies, making your user and their session available on the client and server.',
|
||||
icon: '/docs/img/icons/nextjs-icon',
|
||||
},
|
||||
{
|
||||
title: 'SvelteKit',
|
||||
href: '/guides/auth/server-side/sveltekit',
|
||||
description:
|
||||
'Automatically configure Supabase in SvelteKit to use cookies, making your user and their session available on the client and server.',
|
||||
icon: '/docs/img/icons/svelte-icon',
|
||||
},
|
||||
].map((item) => {
|
||||
return (
|
||||
<Link href={`${item.href}`} key={item.title} passHref>
|
||||
|
||||
@@ -0,0 +1,636 @@
|
||||
---
|
||||
title: 'Setting up Server-Side Auth for SvelteKit'
|
||||
hideToc: true
|
||||
---
|
||||
|
||||
Set up Server-Side Auth to use cookie-based authentication with SvelteKit.
|
||||
|
||||
<StepHikeCompact>
|
||||
|
||||
<StepHikeCompact.Step step={1}>
|
||||
|
||||
<StepHikeCompact.Details title="Install Supabase packages">
|
||||
|
||||
Install the `@supabase/supabase-js` package and the helper `@supabase/ssr` package.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```sh
|
||||
npm install @supabase/supabase-js @supabase/ssr
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={2}>
|
||||
|
||||
<StepHikeCompact.Details title="Set up environment variables">
|
||||
|
||||
Create a `.env.local` file in your project root directory.
|
||||
|
||||
Fill in your `PUBLIC_SUPABASE_URL` and `PUBLIC_SUPABASE_ANON_KEY`:
|
||||
|
||||
<ProjectConfigVariables variable="url" />
|
||||
<ProjectConfigVariables variable="anonKey" />
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```txt .env.local
|
||||
PUBLIC_SUPABASE_URL=<your_supabase_project_url>
|
||||
PUBLIC_SUPABASE_ANON_KEY=<your_supabase_anon_key>
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={3}>
|
||||
|
||||
<StepHikeCompact.Details title="Set up server-side hooks">
|
||||
|
||||
Set up server-side hooks in `src/hooks.server.ts`. The hooks:
|
||||
|
||||
- Create a request-specific Supabase client, using the user credentials from the request cookie. This client is used for server-only code.
|
||||
- Check user authentication.
|
||||
- Guard protected pages.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```ts src/hooks.server.ts
|
||||
import { createServerClient } from '@supabase/ssr'
|
||||
import { type Handle, redirect } from '@sveltejs/kit'
|
||||
import { sequence } from '@sveltejs/kit/hooks'
|
||||
|
||||
import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public'
|
||||
|
||||
const supabase: Handle = async ({ event, resolve }) => {
|
||||
/**
|
||||
* Creates a Supabase client specific to this server request.
|
||||
*
|
||||
* The Supabase client gets the Auth token from the request cookies.
|
||||
*/
|
||||
event.locals.supabase = createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, {
|
||||
cookies: {
|
||||
get: (key) => event.cookies.get(key),
|
||||
/**
|
||||
* SvelteKit's cookies API requires `path` to be explicitly set in
|
||||
* the cookie options. Setting `path` to `/` replicates previous/
|
||||
* standard behavior.
|
||||
*/
|
||||
set: (key, value, options) => {
|
||||
event.cookies.set(key, value, { ...options, path: '/' })
|
||||
},
|
||||
remove: (key, options) => {
|
||||
event.cookies.delete(key, { ...options, path: '/' })
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
/**
|
||||
* Unlike `supabase.auth.getSession()`, which returns the session _without_
|
||||
* validating the JWT, this function also calls `getUser()` to validate the
|
||||
* JWT before returning the session.
|
||||
*/
|
||||
event.locals.safeGetSession = async () => {
|
||||
const {
|
||||
data: { session },
|
||||
} = await event.locals.supabase.auth.getSession()
|
||||
if (!session) {
|
||||
return { session: null, user: null }
|
||||
}
|
||||
|
||||
const {
|
||||
data: { user },
|
||||
error,
|
||||
} = await event.locals.supabase.auth.getUser()
|
||||
if (error) {
|
||||
// JWT validation has failed
|
||||
return { session: null, user: null }
|
||||
}
|
||||
|
||||
return { session, user }
|
||||
}
|
||||
|
||||
return resolve(event, {
|
||||
filterSerializedResponseHeaders(name) {
|
||||
/**
|
||||
* Supabase libraries use the `content-range` header, so we need to
|
||||
* tell SvelteKit to pass it through.
|
||||
*/
|
||||
return name === 'content-range'
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const authGuard: Handle = async ({ event, resolve }) => {
|
||||
const { session, user } = await event.locals.safeGetSession()
|
||||
event.locals.session = session
|
||||
event.locals.user = user
|
||||
|
||||
if (!event.locals.session && event.url.pathname.startsWith('/private')) {
|
||||
return redirect(303, '/auth')
|
||||
}
|
||||
|
||||
if (event.locals.session && event.url.pathname === '/auth') {
|
||||
return redirect(303, '/private')
|
||||
}
|
||||
|
||||
return resolve(event)
|
||||
}
|
||||
|
||||
export const handle: Handle = sequence(supabase, authGuard)
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={4}>
|
||||
|
||||
<StepHikeCompact.Details title="Create TypeScript definitions">
|
||||
|
||||
To prevent TypeScript errors, add type definitions for the new `event.locals` properties.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```ts src/app.d.ts
|
||||
import type { Session, SupabaseClient, User } from '@supabase/supabase-js'
|
||||
|
||||
declare global {
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
interface Locals {
|
||||
supabase: SupabaseClient
|
||||
safeGetSession: () => Promise<{ session: Session | null; user: User | null }>
|
||||
session: Session | null
|
||||
user: User | null
|
||||
}
|
||||
// interface PageData {}
|
||||
// interface PageState {}
|
||||
// interface Platform {}
|
||||
}
|
||||
}
|
||||
|
||||
export {}
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={5}>
|
||||
|
||||
<StepHikeCompact.Details title="Create a Supabase client in your root layout">
|
||||
|
||||
Create a Supabase client in your root `+layout.ts`. This client can be used to access Supabase from the client or the server. In order to get access to the Auth token on the server, use a `+layout.server.ts` file to pass in the session from `event.locals`.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
<CH.Code>
|
||||
|
||||
```ts src/routes/+layout.ts
|
||||
import { createBrowserClient, createServerClient, isBrowser, parse } from '@supabase/ssr'
|
||||
|
||||
import { PUBLIC_SUPABASE_ANON_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public'
|
||||
|
||||
import type { LayoutLoad } from './$types'
|
||||
|
||||
export const load: LayoutLoad = async ({ data, depends, fetch }) => {
|
||||
/**
|
||||
* Declare a dependency so the layout can be invalidated, for example, on
|
||||
* session refresh.
|
||||
*/
|
||||
depends('supabase:auth')
|
||||
|
||||
const supabase = isBrowser()
|
||||
? createBrowserClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, {
|
||||
global: {
|
||||
fetch,
|
||||
},
|
||||
cookies: {
|
||||
get(key) {
|
||||
const cookie = parse(document.cookie)
|
||||
return cookie[key]
|
||||
},
|
||||
},
|
||||
})
|
||||
: createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, {
|
||||
global: {
|
||||
fetch,
|
||||
},
|
||||
cookies: {
|
||||
get() {
|
||||
return JSON.stringify(data.session)
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
/**
|
||||
* It's fine to use `getSession` here, because on the client, `getSession` is
|
||||
* safe, and on the server, it reads `session` from the `LayoutData`, which
|
||||
* safely checked the session using `safeGetSession`.
|
||||
*/
|
||||
const {
|
||||
data: { session },
|
||||
} = await supabase.auth.getSession()
|
||||
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabase.auth.getUser()
|
||||
|
||||
return { session, supabase, user }
|
||||
}
|
||||
```
|
||||
|
||||
```ts src/routes/+layout.server.ts
|
||||
import type { LayoutServerLoad } from './$types'
|
||||
|
||||
export const load: LayoutServerLoad = async ({ locals: { session } }) => {
|
||||
return {
|
||||
session,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</CH.Code>
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={6}>
|
||||
|
||||
<StepHikeCompact.Details title="Listen to Auth events">
|
||||
|
||||
Set up a listener for Auth events on the client, to handle session refreshes and signouts.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```svelte src/routes/+layout.svelte
|
||||
<script>
|
||||
import { goto, invalidate } from '$app/navigation';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
export let data;
|
||||
$: ({ session, supabase } = data);
|
||||
|
||||
onMount(() => {
|
||||
const { data } = supabase.auth.onAuthStateChange((_, newSession) => {
|
||||
if (!newSession) {
|
||||
/**
|
||||
* Queue this as a task so the navigation won't prevent the
|
||||
* triggering function from completing
|
||||
*/
|
||||
setTimeout(() => {
|
||||
goto('/', { invalidateAll: true });
|
||||
});
|
||||
}
|
||||
if (newSession?.expires_at !== session?.expires_at) {
|
||||
invalidate('supabase:auth');
|
||||
}
|
||||
});
|
||||
|
||||
return () => data.subscription.unsubscribe();
|
||||
});
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={7}>
|
||||
|
||||
<StepHikeCompact.Details title="Create your first page">
|
||||
|
||||
Create your first page. This example page calls Supabase from the server to get a list of countries from the database.
|
||||
|
||||
This is an example of a public page that uses publicly readable data.
|
||||
|
||||
To populate your database, run the [countries quickstart](/dashboard/project/_/sql/quickstarts) from your dashboard.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
<CH.Code>
|
||||
|
||||
```ts src/routes/+page.server.ts
|
||||
import type { PageServerLoad } from './$types'
|
||||
|
||||
export const load: PageServerLoad = async ({ locals: { supabase } }) => {
|
||||
const { data: countries } = await supabase.from('countries').select('name').limit(5).order('name')
|
||||
return { countries: countries ?? [] }
|
||||
}
|
||||
```
|
||||
|
||||
```svelte src/routes/+page.svelte
|
||||
<script>
|
||||
export let data;
|
||||
$: ({ countries } = data);
|
||||
</script>
|
||||
|
||||
<h1>Welcome to Supabase!</h1>
|
||||
<ul>
|
||||
{#each countries as country}
|
||||
<li>{country.name}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
```
|
||||
|
||||
</CH.Code>
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={8}>
|
||||
|
||||
<StepHikeCompact.Details title="Change the Auth confirmation path">
|
||||
|
||||
If you have email confirmation turned on (the default), a new user will receive an email confirmation after signing up.
|
||||
|
||||
Change the email template to support a server-side authentication flow.
|
||||
|
||||
Go to the [Auth templates](https://supabase.com/dashboard/project/_/auth/templates) page in your dashboard. In the `Confirm signup` template, change `{{ .ConfirmationURL }}` to `{{ .SiteURL }}/api/auth/confirm?token_hash={{ .TokenHash }}&type=signup`.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={9}>
|
||||
|
||||
<StepHikeCompact.Details title="Create a login page">
|
||||
|
||||
Next, create a login page to let users sign up and log in.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
<CH.Code>
|
||||
|
||||
```ts src/routes/auth/+page.server.ts
|
||||
import { redirect } from '@sveltejs/kit'
|
||||
|
||||
import type { Actions } from './$types'
|
||||
|
||||
export const actions: Actions = {
|
||||
signup: async ({ request, locals: { supabase } }) => {
|
||||
const formData = await request.formData()
|
||||
const email = formData.get('email') as string
|
||||
const password = formData.get('password') as string
|
||||
|
||||
const { error } = await supabase.auth.signUp({ email, password })
|
||||
if (error) {
|
||||
console.error(error)
|
||||
return redirect(303, '/auth/error')
|
||||
} else {
|
||||
return redirect(303, '/')
|
||||
}
|
||||
},
|
||||
login: async ({ request, locals: { supabase } }) => {
|
||||
const formData = await request.formData()
|
||||
const email = formData.get('email') as string
|
||||
const password = formData.get('password') as string
|
||||
|
||||
const { error } = await supabase.auth.signInWithPassword({ email, password })
|
||||
if (error) {
|
||||
console.error(error)
|
||||
return redirect(303, '/auth/error')
|
||||
} else {
|
||||
return redirect(303, '/private')
|
||||
}
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
```svelte src/routes/auth/+page.svelte
|
||||
<form method="POST" action="?/login">
|
||||
<label>
|
||||
Email
|
||||
<input name="email" type="email" />
|
||||
</label>
|
||||
<label>
|
||||
Password
|
||||
<input name="password" type="password" />
|
||||
</label>
|
||||
<button>Login</button>
|
||||
<button formaction="?/signup">Sign up</button>
|
||||
</form>
|
||||
```
|
||||
|
||||
```svelte src/routes/auth/+layout.svelte
|
||||
<header>
|
||||
<nav>
|
||||
<a href="/">Home</a>
|
||||
</nav>
|
||||
</header>
|
||||
<slot />
|
||||
```
|
||||
|
||||
```svelte src/routes/auth/error/+page.svelte
|
||||
<p>Login error</p>
|
||||
```
|
||||
|
||||
</CH.Code>
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={10}>
|
||||
|
||||
<StepHikeCompact.Details title="Create the signup confirmation route">
|
||||
|
||||
Finish the signup flow by creating the API route to handle email verification.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```ts src/routes/api/auth/confirm/+server.ts
|
||||
import type { EmailOtpType } from '@supabase/supabase-js'
|
||||
import { redirect } from '@sveltejs/kit'
|
||||
|
||||
import type { RequestHandler } from './$types'
|
||||
|
||||
export const GET: RequestHandler = async ({ url, locals: { supabase } }) => {
|
||||
const token_hash = url.searchParams.get('token_hash')
|
||||
const type = url.searchParams.get('type') as EmailOtpType | null
|
||||
const next = url.searchParams.get('next') ?? '/'
|
||||
|
||||
/**
|
||||
* Clean up the redirect URL by deleting the Auth flow parameters.
|
||||
*
|
||||
* `next` is preserved for now, because it's needed in the error case.
|
||||
*/
|
||||
const redirectTo = new URL(url)
|
||||
redirectTo.pathname = next
|
||||
redirectTo.searchParams.delete('token_hash')
|
||||
redirectTo.searchParams.delete('type')
|
||||
|
||||
if (token_hash && type) {
|
||||
const { error } = await supabase.auth.verifyOtp({ type, token_hash })
|
||||
if (!error) {
|
||||
redirectTo.searchParams.delete('next')
|
||||
return redirect(303, redirectTo)
|
||||
}
|
||||
}
|
||||
|
||||
redirectTo.pathname = '/auth/error'
|
||||
return redirect(303, redirectTo)
|
||||
}
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={11}>
|
||||
|
||||
<StepHikeCompact.Details title="Create private routes">
|
||||
|
||||
Create private routes that can only be accessed by authenticated users. The routes in the `private` directory are protected by the route guard in `hooks.server.ts`.
|
||||
|
||||
To ensure that `hooks.server.ts` runs for every nested path, put a `+layout.server.ts` file in the `private` directory. This file can be empty, but must exist to protect routes that don't have their own `+layout|page.server.ts`.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
<CH.Code className="min-h-[30rem]">
|
||||
|
||||
```ts src/routes/private/+layout.server.ts
|
||||
/**
|
||||
* This file is necessary to ensure protection of all routes in the `private`
|
||||
* directory. It makes the routes in this directory _dynamic_ routes, which
|
||||
* send a server request, and thus trigger `hooks.server.ts`.
|
||||
**/
|
||||
```
|
||||
|
||||
```svelte src/routes/private/+layout.svelte
|
||||
<script>
|
||||
export let data;
|
||||
$: ({ supabase } = data);
|
||||
|
||||
$: logout = async () => {
|
||||
const { error } = await supabase.auth.signOut();
|
||||
if (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<header>
|
||||
<nav>
|
||||
<a href="/">Home</a>
|
||||
</nav>
|
||||
<button on:click={logout}>Logout</button>
|
||||
</header>
|
||||
<main>
|
||||
<slot />
|
||||
</main>
|
||||
```
|
||||
|
||||
```sql SQL
|
||||
-- Run this SQL against your database to create a `notes` table.
|
||||
|
||||
create table notes (
|
||||
id bigint primary key generated always as identity,
|
||||
created_at timestamp with time zone not null default now(),
|
||||
user_id uuid references auth.users on delete cascade not null default auth.uid(),
|
||||
note text not null
|
||||
);
|
||||
|
||||
alter table notes enable row level security;
|
||||
|
||||
revoke all on table notes from authenticated;
|
||||
revoke all on table notes from anon;
|
||||
|
||||
grant all (note) on table notes to authenticated;
|
||||
grant select (id) on table notes to authenticated;
|
||||
grant delete on table notes to authenticated;
|
||||
|
||||
create policy "Users can access and modify their own notes"
|
||||
on notes
|
||||
for all
|
||||
to authenticated
|
||||
using ((select auth.uid()) = user_id);
|
||||
```
|
||||
|
||||
```svelte src/routes/private/+page.server.ts
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ depends, locals: { supabase } }) => {
|
||||
depends('supabase:db:notes');
|
||||
const { data: notes } = await supabase.from('notes').select('id,note').order('id');
|
||||
return { notes: notes ?? [] };
|
||||
};
|
||||
```
|
||||
|
||||
```svelte src/routes/private/+page.svelte
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import type { EventHandler } from 'svelte/elements';
|
||||
|
||||
import type { PageData } from './$types';
|
||||
|
||||
export let data: PageData;
|
||||
$: ({ notes, supabase, user } = data);
|
||||
|
||||
let handleSubmit: EventHandler<SubmitEvent, HTMLFormElement>;
|
||||
$: handleSubmit = async (evt) => {
|
||||
evt.preventDefault();
|
||||
if (!evt.target) return;
|
||||
|
||||
const form = evt.target as HTMLFormElement;
|
||||
|
||||
const note = (new FormData(form).get('note') ?? '') as string;
|
||||
if (!note) return;
|
||||
|
||||
const { error } = await supabase.from('notes').insert({ note });
|
||||
if (error) console.error(error);
|
||||
|
||||
invalidate('supabase:db:notes');
|
||||
form.reset();
|
||||
};
|
||||
</script>
|
||||
|
||||
<h1>Private page for user: {user?.email}</h1>
|
||||
<h2>Notes</h2>
|
||||
<ul>
|
||||
{#each notes as note}
|
||||
<li>{note.note}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<form on:submit={handleSubmit}>
|
||||
<label>
|
||||
Add a note
|
||||
<input name="note" type="text" />
|
||||
</label>
|
||||
</form>
|
||||
```
|
||||
|
||||
</CH.Code>
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
</StepHikeCompact>
|
||||
@@ -311,7 +311,7 @@ Before you can use Sign in with Google, you need to obtain a [Google Cloud Platf
|
||||
|
||||
## Google sign-in for Web, macOS, Windows, and Linux
|
||||
|
||||
Google sign-in with Supabase on Web, macOS, Windows, and Linux is done through the [signInWithOAuth](docs/reference/dart/auth-signinwithoauth) method.
|
||||
Google sign-in with Supabase on Web, macOS, Windows, and Linux is done through the [signInWithOAuth](/docs/reference/dart/auth-signinwithoauth) method.
|
||||
|
||||
This method of signing in is web based, and will open a browser window to perform the sign in. For non-web platforms, the user is brought back to the app via [deep linking](/docs/guides/auth/native-mobile-deep-linking?platform=flutter).
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ subtitle: 'Developing locally using the Supabase CLI.'
|
||||
sidebar_label: 'Overview'
|
||||
---
|
||||
|
||||
You can use the Supabase CLI to run the entire Supabase stack locally on your machine, simply by running `supabase init` and then `supabase start`.
|
||||
You can use the Supabase CLI to run the entire Supabase stack locally on your machine, simply by running `supabase init` and then `supabase start`. To install the CLI, see the [installation guide](/docs/guides/cli/getting-started#installing-the-supabase-cli).
|
||||
|
||||
The Supabase CLI provides tools to develop your project locally, deploy to the Supabase Platform, handle database migrations, and generate types directly from your database schema.
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ Usage:
|
||||
supabase test db [flags]
|
||||
```
|
||||
|
||||
This is powered by the [pgTAP](https://supabase.com/docs/guides/database/extensions/pgtap) extension. You can find a full guide to writing an running tests in the [Testing your database](/docs/guides/database/testing) section.
|
||||
This is powered by the [pgTAP](https://supabase.com/docs/guides/database/extensions/pgtap) extension. You can find a full guide to writing and running tests in the [Testing your database](/docs/guides/database/testing) section.
|
||||
|
||||
### Test helpers
|
||||
|
||||
@@ -28,7 +28,7 @@ Our friends at [Basejump](https://usebasejump.com/) have created a useful set of
|
||||
|
||||
### Running database tests in CI
|
||||
|
||||
Use our GitHub Action to [automate your database tests](docs/guides/cli/github-action/testing#testing-your-database).
|
||||
Use our GitHub Action to [automate your database tests](/docs/guides/cli/github-action/testing#testing-your-database).
|
||||
|
||||
## Testing your Edge Functions
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ subtitle: 'Connecting to your Postgres database in serverless environments.'
|
||||
|
||||
Supabase provides several options for connecting to your Postgres database from serverless environments.
|
||||
|
||||
[supabase-js](/reference/javascript/introduction) is an isomorphic JavaScript client that uses the [auto-generated REST API](/guides/api) and therefore works in any environment that supports HTTPS connections. This API has a built-in [connection pooler](/guides/database/connecting-to-postgres#connection-pooler) and can serve thousands of simultaneous requests, and therefore is ideal for Serverless workloads.
|
||||
[supabase-js](/docs/reference/javascript/introduction) is an isomorphic JavaScript client that uses the [auto-generated REST API](/docs/guides/api) and therefore works in any environment that supports HTTPS connections. This API has a built-in [connection pooler](/docs/guides/database/connecting-to-postgres#connection-pooler) and can serve thousands of simultaneous requests, and therefore is ideal for Serverless workloads.
|
||||
|
||||
## Vercel Edge Functions
|
||||
|
||||
|
||||
@@ -214,7 +214,11 @@ where rolname = 'postgres';
|
||||
|
||||
PGAudit was designed for storing logs as CSV files with the following headers:
|
||||
|
||||
> Referenced from the [PGAudit official docs](https://github.com/pgaudit/pgaudit/blob/master/README.md#format)
|
||||
<Admonition type="note">
|
||||
|
||||
Referenced from the [PGAudit official docs](https://github.com/pgaudit/pgaudit/blob/master/README.md#format)
|
||||
|
||||
</Admonition>
|
||||
|
||||
| header | Description |
|
||||
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
@@ -1,90 +1,23 @@
|
||||
---
|
||||
id: 'pgsodium'
|
||||
title: 'pgsodium: Encryption Features'
|
||||
title: 'pgsodium (pending deprecation): Encryption Features'
|
||||
description: 'Encryption library for PostgreSQL'
|
||||
---
|
||||
|
||||
[`pgsodium`](https://github.com/michelp/pgsodium) is a PostgreSQL extension which provides SQL access to [`libsodium`'s](https://doc.libsodium.org/) high-level cryptographic algorithms.
|
||||
Supabase DOES NOT RECOMMEND any new usage of [`pgsodium`](https://github.com/michelp/pgsodium).
|
||||
|
||||
`libsodium` is a modern, easy-to-use software library for encryption, decryption, signatures, password hashing, and more. It is a portable, cross-compilable, installable, and packageable fork of the [NaCl](http://nacl.cr.yp.to/) library, with a compatible but extended API to improve usability even further.
|
||||
The [`pgsodium`](https://github.com/michelp/pgsodium) extension is expected to go through a deprecation cycle in the near future. We will reach out to owners of impacted projects to assist with migrations away from [`pgsodium`](https://github.com/michelp/pgsodium) once the deprecation process begins.
|
||||
|
||||
The design choices emphasize security and ease of use. But despite the emphasis on high security, primitives are faster across-the-board than most implementations.
|
||||
[`pgsodium`](https://github.com/michelp/pgsodium) is a Postgres extension which provides SQL access to [`libsodium`'s](https://doc.libsodium.org/) high-level cryptographic algorithms.
|
||||
|
||||
`pgsodium` exposes the following `libsodium` APIs to SQL:
|
||||
Supabase previously documented two features derived from pgsodium. Namely [Server Key Management](https://github.com/michelp/pgsodium#server-key-management) and [Transparent Column Encryption](https://github.com/michelp/pgsodium#transparent-column-encryption). At this time, we do not recommend using either on the Supabase platform due to their high level of operational complexity and misconfiguration risk.
|
||||
|
||||
- [Generating Random Data](https://github.com/michelp/pgsodium/#generating-random-data)
|
||||
- [Secret key cryptography](https://github.com/michelp/pgsodium/#secret-key-cryptography)
|
||||
- [Authenticated encryption](https://github.com/michelp/pgsodium/#authenticated-encryption)
|
||||
- [Authentication](https://github.com/michelp/pgsodium/#authentication)
|
||||
- [Public key cryptography](https://github.com/michelp/pgsodium/#public-key-cryptography)
|
||||
- [Authenticated encryption](https://github.com/michelp/pgsodium/#authenticated-encryption-1)
|
||||
- [Public key signatures](https://github.com/michelp/pgsodium/#public-key-signatures)
|
||||
- [Sealed boxes](https://github.com/michelp/pgsodium/#sealed-boxes)
|
||||
- [Hashing](https://github.com/michelp/pgsodium/#hashing)
|
||||
- [Password hashing](https://github.com/michelp/pgsodium/#password-hashing)
|
||||
- [Key Derivation](https://github.com/michelp/pgsodium/#key-derivation)
|
||||
- [Key Exchange](https://github.com/michelp/pgsodium/#key-exchange)
|
||||
- [HMAC512](https://github.com/michelp/pgsodium/#hmac512)
|
||||
- [Advanced Stream API](https://github.com/michelp/pgsodium/#advanced-stream-api)
|
||||
- [XChaCha20-SIV](https://github.com/michelp/pgsodium/#xchacha20-siv)
|
||||
- [Signcryption](https://github.com/michelp/pgsodium/#signcryption)
|
||||
|
||||
It also enables some Postgres specific features including:
|
||||
|
||||
- [Server Key Management](https://github.com/michelp/pgsodium#server-key-management)
|
||||
|
||||
- [Transparent Column Encryption](https://github.com/michelp/pgsodium#transparent-column-encryption)
|
||||
|
||||
Note that column encryption should only be used in highly sensitive scenarios as it has a meaningful impact on statement performance and flexibility.
|
||||
|
||||
Specifically:
|
||||
|
||||
- Encryption and decryption both take time. Inserting and selecting encrypted data takes more time than a "plain" column of data.
|
||||
|
||||
- Encrypted columns should never be indexed. This is because the index will store the encrypted value of a column, which would not be useful.
|
||||
|
||||
- Encrypted columns can be queried in a `WHERE` clause, but this can also have some negative performance consequences, since the value must be decrypted in order to matched to any `WHERE` qualifiers.
|
||||
|
||||
- While you can encrypt multiple columns in the same table, each column must go through a full encryption cycle, so two columns will take twice the time as one etc.
|
||||
|
||||
In general, it is a bad idea to over-use column encryption for mundane data or data that you need to search against such as names, user or account types, addresses, country codes, etc. Column encryption is intended to be used for very sensitive data that would cause serious issues if it were to leak, such as API keys, payment keys, highly sensitive personal information, etc.
|
||||
|
||||
Note that Supabase projects are already encrypted at rest by default.
|
||||
Note that Supabase projects are encrypted at rest by default which likely is sufficient for your compliance needs e.g. SOC2 & HIPAA.
|
||||
|
||||
## Get the root encryption key for your Supabase project
|
||||
|
||||
Encryption requires keys. Keeping the keys in the same database as the encrypted data would be unsafe. For more information about managing the `pgsodium` root encryption key on your Supabase project see **[encryption key location](/docs/guides/database/vault#encryption-key-location)**. This key is required to decrypt values stored in [Supabase Vault](/docs/guides/database/vault) and data encrypted with Transparent Column Encryption.
|
||||
|
||||
## Enable the extension
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="dashboard"
|
||||
queryGroup="database-method"
|
||||
>
|
||||
<TabPanel id="dashboard" label="Dashboard">
|
||||
|
||||
1. Go to the [Database](https://supabase.com/dashboard/project/_/database/tables) page in the Dashboard.
|
||||
2. Click on **Extensions** in the sidebar.
|
||||
3. Search for `pgsodium` and enable the extension.
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="sql" label="SQL">
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
-- Enable the "pgsodium" extension
|
||||
create extension pgsodium;
|
||||
|
||||
-- Disable the "pgsodium" extension
|
||||
drop extension if exists pgsodium;
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
## Resources
|
||||
|
||||
- [Supabase Vault](/docs/guides/database/vault)
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
---
|
||||
id: 'managing-passwords'
|
||||
title: 'Passwords'
|
||||
description: 'How to change your PostgreSQL database password.'
|
||||
---
|
||||
|
||||
Your Postgres database is the core of your Supabase project, so it's important that it has a strong, secure password at all times.
|
||||
|
||||
If you use special symbols in your postgres password, you must remember to [percent-encode](https://en.wikipedia.org/wiki/Percent-encoding) your password later if using the postgres connection string e.g. `postgresql://postgres.projectref:p%3Dword@aws-0-us-east-1.pooler.supabase.com:6543/postgres`
|
||||
|
||||
## Changing your project password
|
||||
|
||||
When you created your project you were also asked to enter a password. This is actually the password for your database, specifically for the `postgres` user.
|
||||
You can update this from the Dashboard under the [database settings](https://supabase.com/dashboard/project/_/settings/database) page.
|
||||
|
||||
## Creating a secure password
|
||||
|
||||
It's absolutely critical that you store your customers' data safely. Here are some tips for creating a secure password.
|
||||
|
||||
- Use a password manager to generate it.
|
||||
- Make a long password (12 characters at least).
|
||||
- Don't use any common dictionary words.
|
||||
- Use both upper and lower case characters, numbers, and special symbols.
|
||||
|
||||
## Resources
|
||||
|
||||
- [PostgreSQL `ALTER USER` Documentation](https://www.postgresql.org/docs/12/sql-alteruser.html)
|
||||
@@ -1,77 +0,0 @@
|
||||
---
|
||||
id: 'managing-timezones'
|
||||
title: 'Timezones'
|
||||
slug: 'managing-timezones'
|
||||
description: 'How to change your database timezone.'
|
||||
---
|
||||
|
||||
Every Supabase database is set to UTC timezone by default. We strongly recommend keeping it this way, even if your users are in a different location.
|
||||
This is because it makes it much easier to calculate differences between timezones if you adopt the mental model that "everything in my database is in UTC time."
|
||||
|
||||
### Change timezone
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="sql"
|
||||
queryGroup="database-method"
|
||||
>
|
||||
<TabPanel id="sql" label="SQL">
|
||||
|
||||
```sql
|
||||
alter database postgres
|
||||
set timezone to 'America/New_York';
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
### Full list of timezones
|
||||
|
||||
Get a full list of timezones supported by your database. This will return the following columns:
|
||||
|
||||
- `name`: Time zone name
|
||||
- `abbrev`: Time zone abbreviation
|
||||
- `utc_offset`: Offset from UTC (positive means east of Greenwich)
|
||||
- `is_dst`: True if currently observing daylight savings
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="sql"
|
||||
queryGroup="database-method"
|
||||
>
|
||||
<TabPanel id="sql" label="SQL">
|
||||
|
||||
```sql
|
||||
select name, abbrev, utc_offset, is_dst
|
||||
from pg_timezone_names()
|
||||
order by name;
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
### Search for a specific timezone
|
||||
|
||||
Use `ilike` (case insensitive search) to find specific timezones.
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="sql"
|
||||
queryGroup="database-method"
|
||||
>
|
||||
<TabPanel id="sql" label="SQL">
|
||||
|
||||
```sql
|
||||
select *
|
||||
from pg_timezone_names()
|
||||
where name ilike '%york%';
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
@@ -79,8 +79,7 @@ You must define the "data type" when you create a column.
|
||||
|
||||
### Data types
|
||||
|
||||
Every column is a predefined type. PostgreSQL provides many [default types](https://www.postgresql.org/docs/current/datatype.html), and you can even design your own (or use extensions)
|
||||
if the default types don't fit your needs. You can use any data type that Postgres supports via the SQL editor. We only support a subset of these in the Table Editor in an effort to keep the experience simple for people with less experience with databases.
|
||||
Every column is a predefined type. PostgreSQL provides many [default types](https://www.postgresql.org/docs/current/datatype.html), and you can even design your own (or use extensions) if the default types don't fit your needs. You can use any data type that Postgres supports via the SQL editor. We only support a subset of these in the Table Editor in an effort to keep the experience simple for people with less experience with databases.
|
||||
|
||||
<details>
|
||||
<summary>Show/Hide default data types</summary>
|
||||
|
||||
@@ -70,7 +70,7 @@ Inference via larger models is supported via [Ollama](https://ollama.com/). In t
|
||||
|
||||
### Running locally
|
||||
|
||||
1. Install Ollama and pull the Mistral model
|
||||
1. [Install Ollama](https://github.com/ollama/ollama?tab=readme-ov-file#ollama) and pull the Mistral model
|
||||
|
||||
```
|
||||
ollama pull mistral
|
||||
@@ -85,7 +85,7 @@ Inference via larger models is supported via [Ollama](https://ollama.com/). In t
|
||||
1. Set a function secret called AI_INFERENCE_API_HOST to point to the Ollama server
|
||||
|
||||
```
|
||||
echo "AI_INFERENCE_API_HOST=http://host.docker.internal:11434/api/generate" >> supabase/functions/.env
|
||||
echo "AI_INFERENCE_API_HOST=http://host.docker.internal:11434" >> supabase/functions/.env
|
||||
```
|
||||
|
||||
1. Create a new function with the following code
|
||||
|
||||
@@ -172,7 +172,7 @@ Manage your projects programmatically. [Docs](/docs/reference/api).
|
||||
|
||||
## Client libraries
|
||||
|
||||
Official client libraries for [JavaScript](/docs/reference/javascript/start), [Flutter](docs/reference/dart/initializing) and [Swift](/docs/reference/swift/introduction).
|
||||
Official client libraries for [JavaScript](/docs/reference/javascript/start), [Flutter](/docs/reference/dart/initializing) and [Swift](/docs/reference/swift/introduction).
|
||||
Unofficial libraries are supported by the community.
|
||||
|
||||
## Feature status
|
||||
|
||||
@@ -138,7 +138,11 @@ export class SupabaseService {
|
||||
Let's set up a route to manage logins and signups. We'll use Magic Links so users can sign in with their email without using passwords.
|
||||
Create a **LoginPage** with the `ionic g page login` Ionic CLI command.
|
||||
|
||||
> This guide will show the template inline, but the example app will have `templateUrl`s
|
||||
<Admonition type="tip">
|
||||
|
||||
This guide will show the template inline, but the example app will have `templateUrl`s
|
||||
|
||||
</Admonition>
|
||||
|
||||
```ts src/app/login/login.page.ts
|
||||
import { Component, OnInit } from '@angular/core'
|
||||
|
||||
@@ -456,7 +456,11 @@ const HomePage = () => {
|
||||
export default HomePage
|
||||
```
|
||||
|
||||
> What we're doing here is showing the sign in form if you aren't logged in and your account profile if you are.
|
||||
<Admonition type="note">
|
||||
|
||||
What we're doing here is showing the sign in form if you aren't logged in and your account profile if you are.
|
||||
|
||||
</Admonition>
|
||||
|
||||
### Launch!
|
||||
|
||||
|
||||
@@ -85,22 +85,27 @@ export const handle: Handle = async ({ event, resolve }) => {
|
||||
})
|
||||
|
||||
/**
|
||||
* Unlike `supabase.auth.getSession`, which is unsafe on the server because it
|
||||
* doesn't validate the JWT, this function validates the JWT by first calling
|
||||
* `getUser` and aborts early if the JWT signature is invalid.
|
||||
* Unlike `supabase.auth.getSession()`, which returns the session _without_
|
||||
* validating the JWT, this function also calls `getUser()` to validate the
|
||||
* JWT before returning the session.
|
||||
*/
|
||||
event.locals.safeGetSession = async () => {
|
||||
const {
|
||||
data: { session },
|
||||
} = await event.locals.supabase.auth.getSession()
|
||||
if (!session) {
|
||||
return { session: null, user: null }
|
||||
}
|
||||
|
||||
const {
|
||||
data: { user },
|
||||
error,
|
||||
} = await event.locals.supabase.auth.getUser()
|
||||
if (error) {
|
||||
// JWT validation has failed
|
||||
return { session: null, user: null }
|
||||
}
|
||||
|
||||
const {
|
||||
data: { session },
|
||||
} = await event.locals.supabase.auth.getSession()
|
||||
return { session, user }
|
||||
}
|
||||
|
||||
@@ -153,7 +158,11 @@ export const load = (async ({ locals: { safeGetSession } }) => {
|
||||
}) satisfies LayoutServerLoad
|
||||
```
|
||||
|
||||
> Start your dev server (`npm run dev`) in order to generate the `./$types` files we are referencing in our project.
|
||||
<Admonition type="tip">
|
||||
|
||||
Start your dev server (`npm run dev`) in order to generate the `./$types` files we are referencing in our project.
|
||||
|
||||
</Admonition>
|
||||
|
||||
Create a new `src/routes/+layout.ts` file to handle the session and the supabase object on the client-side.
|
||||
|
||||
|
||||
@@ -24,6 +24,14 @@ Supabase Branching works with Git. You can test changes in a separate, temporary
|
||||
|
||||
You can run multiple Preview Branches for every Supabase project. Branches contain all the Supabase features with their own API credentials. Preview Environments pause automatically after 5 minutes of inactivity.
|
||||
|
||||
<Admonition type="caution" label="Some features may not work as expected on Preview Branches">
|
||||
|
||||
Due to Preview Branches running on Fly Postgres instances, some features like `pg-cron` are not fully supported.
|
||||
|
||||
For a full list of limitations, see [Fly Postgres limitations](/docs/guides/platform/fly-postgres#limitations).
|
||||
|
||||
</Admonition>
|
||||
|
||||
<figure className="max-w-[700px] mx-auto">
|
||||
<Image
|
||||
className="hidden dark:block"
|
||||
|
||||
@@ -212,7 +212,6 @@ Sentry.init({
|
||||
}),
|
||||
new Sentry.Integrations.Undici({
|
||||
shouldCreateSpanForRequest: (url) => {
|
||||
console.log('server', `${process.env.NEXT_PUBLIC_SUPABASE_URL}/rest`, url)
|
||||
return !url.startsWith(`${process.env.NEXT_PUBLIC_SUPABASE_URL}/rest`)
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
---
|
||||
id: 'realtime'
|
||||
title: 'Throttling messages'
|
||||
description: 'Use client-side throttling to manage message frequency.'
|
||||
subtitle: 'Use client-side throttling to manage message frequency.'
|
||||
---
|
||||
|
||||
The Supabase clients include functionality for throttling messages.
|
||||
|
||||
## Managing client-side throttling
|
||||
|
||||
You can customize the client-side throttling when creating the client:
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="js"
|
||||
queryGroup="language"
|
||||
>
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
```js
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
|
||||
const SUPABASE_URL = 'https://<project>.supabase.co'
|
||||
const SUPABASE_ANON_KEY = '<your-anon-key>'
|
||||
|
||||
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
|
||||
realtime: {
|
||||
params: {
|
||||
eventsPerSecond: 2,
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="dart" label="Dart">
|
||||
|
||||
```dart
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
void main() async {
|
||||
Supabase.initialize(
|
||||
url: 'https://<project>.supabase.co',
|
||||
anonKey: '<your-anon-key>',
|
||||
realtimeClientOptions: const RealtimeClientOptions(
|
||||
eventsPerSecond: 2,
|
||||
),
|
||||
);
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
final supabase = Supabase.instance.client;
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="kotlin" label="Kotlin">
|
||||
|
||||
```kotlin
|
||||
val supabase = createSupabaseClient(
|
||||
"https://<project>.supabase.co",
|
||||
"<your-anon-key>"
|
||||
) {
|
||||
install(Realtime) {
|
||||
eventsPerSecond = 2
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
## Default client throttling
|
||||
|
||||
By default the Supabase clients throttles messages to 10 messages per-second (1 message every 100 milliseconds). This is a soft-limit provided as a safe-guard when you're getting started. You'll rarely need to send more messages than this.
|
||||
|
||||
Each client has their own throttling behavior. If you instantiate two clients, by default you would send 20 messages per-second to your project.
|
||||
|
||||
## Project quotas
|
||||
|
||||
Each Broadcast and Presence message counts towards your [Project Quotas](/docs/guides/realtime/quotas).
|
||||
|
||||
It's common to unintentionally flood the Realtime service with messages. For example, tracking mouse movements without throttling would send hundreds of events per second. It's rare that you need so many messages. Updating a mouse movement even a few times per second is usually enough for the human eye.
|
||||
|
||||
The throttling parameter protects against these unintended floods.
|
||||
@@ -32,6 +32,6 @@ on storage.objects
|
||||
for delete
|
||||
TO authenticated
|
||||
USING (
|
||||
owner_id = (select auth.uid())
|
||||
owner = (select auth.uid())
|
||||
);
|
||||
```
|
||||
@@ -10,7 +10,7 @@ custom_edit_url: https://github.com/supabase/supabase/edit/master/web/spec/supab
|
||||
<RefSubLayout.EducationRow>
|
||||
<RefSubLayout.Details>
|
||||
|
||||
You can install Supabase package from [nuget.org](https://www.nuget.org/packages/supabase-csharp/)
|
||||
You can install Supabase package from [nuget.org](https://www.nuget.org/packages/supabase/)
|
||||
|
||||
</RefSubLayout.Details>
|
||||
|
||||
@@ -25,7 +25,7 @@ custom_edit_url: https://github.com/supabase/supabase/edit/master/web/spec/supab
|
||||
<TabPanel id="csharp" label="Terminal">
|
||||
|
||||
```sh Terminal
|
||||
dotnet add package supabase-csharp
|
||||
dotnet add package supabase
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
|
||||
@@ -16,7 +16,7 @@ hideTitle: true
|
||||
|
||||
{/* prettier-ignore */}
|
||||
<div className="max-w-xl">
|
||||
This reference documents every object and method available in Supabase's C# library, [supabase-csharp](https://www.nuget.org/packages/supabase-csharp). You can use supabase-csharp to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
|
||||
This reference documents every object and method available in Supabase's C# library, [supabase](https://www.nuget.org/packages/supabase). You can use `Supabase` to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
|
||||
</div>
|
||||
|
||||
{/* prettier-ignore */}
|
||||
@@ -24,6 +24,6 @@ hideTitle: true
|
||||
|
||||
The C# client library is created and maintained by the Supabase community, and is not an official library. Please be tolerant of areas where the library is still being developed, and — as with all the libraries — feel free to contribute wherever you find issues.
|
||||
|
||||
Huge thanks to official maintainer, [Joseph Schultz](https://github.com/acupofjose), and to [Ben Randall](https://github.com/veleek) and [Rhuan Barros](https://github.com/rhuanbarros) for their help.
|
||||
Huge thanks to official maintainer, [Joseph Schultz](https://github.com/acupofjose). As well as [Will Iverson](https://github.com/wiverson), [Ben Randall](https://github.com/veleek), and [Rhuan Barros](https://github.com/rhuanbarros) for their help.
|
||||
|
||||
</div>
|
||||
@@ -3,372 +3,36 @@ id: release-notes
|
||||
title: Release Notes
|
||||
---
|
||||
|
||||
## 0.11.0 - 2023-05-24
|
||||
|
||||
- Update dependency: postgrest-csharp@3.2.0
|
||||
- General codebase and QOL improvements. Exceptions are generally thrown through `PostgrestException` now instead
|
||||
of `Exception`. A `FailureHint.Reason` is provided with failures if possible to parse.
|
||||
- `AddDebugListener` is now available on the client to help with debugging
|
||||
- Merges [#65](https://github.com/supabase-community/postgrest-csharp/pull/65) Cleanup + Add better exception handling
|
||||
- Merges [#66](https://github.com/supabase-community/postgrest-csharp/pull/66) Local test Fixes
|
||||
- Fixes [#67](https://github.com/supabase-community/postgrest-csharp/issues/67) Postgrest Reference attribute is
|
||||
producing StackOverflow for circular references
|
||||
- Update dependency: gotrue-csharp@4.0.2
|
||||
- [#58](https://github.com/supabase-community/gotrue-csharp/issues/58) - Add support for the `reauthentication` endpoint which allows for secure password changes.
|
||||
- Update dependency: realtime-csharp@6.0.1
|
||||
- Updates publishing action for future packages, includes README and icon.
|
||||
- Merges [#28](https://github.com/supabase-community/realtime-csharp/pull/28) and [#30](https://github.com/supabase-community/realtime-csharp/pull/30)
|
||||
- The realtime client now takes a "fail-fast" approach. On establishing an initial connection, client will throw
|
||||
a `RealtimeException` in `ConnectAsync()` if the socket server is unreachable. After an initial connection has been
|
||||
established, the **client will continue attempting reconnections indefinitely until disconnected.**
|
||||
- [Major, New] C# `EventHandlers` have been changed to `delegates`. This should allow for cleaner event data access over
|
||||
the previous subclassed `EventArgs` setup. Events are scoped accordingly. For example, the `RealtimeSocket` error
|
||||
handlers will receive events regarding socket connectivity; whereas the `RealtimeChannel` error handlers will receive
|
||||
events according to `Channel` joining/leaving/etc. This is implemented with the following methods prefixed by (
|
||||
Add/Remove/Clear):
|
||||
- `RealtimeBroadcast.AddBroadcastEventHandler`
|
||||
- `RealtimePresence.AddPresenceEventHandler`
|
||||
- `RealtimeSocket.AddStateChangedHandler`
|
||||
- `RealtimeSocket.AddMessageReceivedHandler`
|
||||
- `RealtimeSocket.AddHeartbeatHandler`
|
||||
- `RealtimeSocket.AddErrorHandler`
|
||||
- `RealtimeClient.AddDebugHandler`
|
||||
- `RealtimeClient.AddStateChangedHandler`
|
||||
- `RealtimeChannel.AddPostgresChangeHandler`
|
||||
- `RealtimeChannel.AddMessageReceivedHandler`
|
||||
- `RealtimeChannel.AddErrorHandler`
|
||||
- `Push.AddMessageReceivedHandler`
|
||||
- [Major, new] `ClientOptions.Logger` has been removed in favor of `Client.AddDebugHandler()` which allows for
|
||||
implementing custom logging solutions if desired.
|
||||
- A simple logger can be set up with the following:
|
||||
```c#
|
||||
client.AddDebugHandler((sender, message, exception) => Debug.WriteLine(message));
|
||||
```
|
||||
- [Major] `Connect()` has been marked `Obsolete` in favor of `ConnectAsync()`
|
||||
- Custom reconnection logic has been removed in favor of using the built-in logic from `Websocket.Client@4.6.1`.
|
||||
- Exceptions that are handled within this library have been marked as `RealtimeException`s.
|
||||
- The local, docker-composed test suite has been brought back (as opposed to remotely testing on live supabase servers)
|
||||
to test against.
|
||||
- Comments have been added throughout the entire codebase and an `XML` file is now generated on build.
|
||||
|
||||
## 0.10.0 - 2023-05-14
|
||||
|
||||
- Changes options to require `Supabase.SupabaseOptions.SessionPersistor` from using `ISupabaseSessionHandler`
|
||||
to `IGotrueSessionPersistance<Session>` (these are now synchronous operations).
|
||||
- Update dependency: gotrue-csharp@4.0.1
|
||||
- [#60](https://github.com/supabase-community/gotrue-csharp/pull/60) - Add interfaces, bug fixes, additional error
|
||||
reason detection. Thanks [@wiverson](https://github.com/wiverson)!
|
||||
- [#57](https://github.com/supabase-community/gotrue-csharp/pull/57) Refactor exceptions, code cleanup, and move to
|
||||
delegate auth state changes
|
||||
- Huge thank you to [@wiverson](https://github.com/wiverson) for his help on this refactor and release!
|
||||
- Changes
|
||||
- Exceptions have been simplified to a single `GotrueException`. A `Reason` field has been added
|
||||
to `GotrueException` to clarify what happened. This should also be easier to manage as the Gotrue
|
||||
server API & messages evolve.
|
||||
- The session delegates for `Save`/`Load`/`Destroy` have been simplified to no longer require `async`.
|
||||
- Console logging in a few places (most notable the background refresh thread) has been removed
|
||||
in favor of a notification method. See `Client.AddDebugListener()` and the test cases for examples.
|
||||
This will allow you to implement your own logging strategy (write to temp file, console, user visible
|
||||
err console, etc).
|
||||
- The client now more reliably emits AuthState changes.
|
||||
- There is now a single source of truth for headers in the stateful Client - the `Options` headers.
|
||||
- New feature:
|
||||
- Added a `Settings` request to the stateless API only - you can now query the server instance to
|
||||
determine if it's got the settings you need. This might allow for things like a visual
|
||||
component in a tool to verify the GoTrue settings are working correctly, or tests that run differently
|
||||
depending on the server configuration.
|
||||
- Implementation notes:
|
||||
- Test cases have been added to help ensure reliability of auth state change notifications
|
||||
and persistence.
|
||||
- Persistence is now managed via the same notifications as auth state change
|
||||
|
||||
## 0.9.1 - 2023-04-28
|
||||
|
||||
- Update dependency: gotrue-csharp@3.1.1
|
||||
- Implements `SignInWithIdToken` for Apple/Google signing from LW7. A HUGE thank you
|
||||
to [@wiverson](https://github.com/wiverson)!
|
||||
- Update dependency: realtime-csharp@5.0.5
|
||||
- Re: [#27](https://github.com/supabase-community/realtime-csharp/issues/27) `PostgresChangesOptions` was not
|
||||
setting `listenType` in constructor. Thanks [@Kuffs2205](https://github.com/Kuffs2205)
|
||||
- Update dependency: supabase-storage-csharp@1.2.10
|
||||
- Re: [#7](https://github.com/supabase-community/storage-csharp/issues/7) Implements a `DownloadPublicFile` method.
|
||||
|
||||
## 0.9.0 - 2023-04-12
|
||||
|
||||
- Update dependency: gotrue-csharp@3.1.0
|
||||
|
||||
- [Minor] Implements PKCE auth flow. SignIn using a provider now returns an instance of `ProviderAuthState` rather
|
||||
than a `string`.
|
||||
|
||||
- Update dependency: supabase-storage-csharp@1.2.9
|
||||
- Implements storage features from LW7:
|
||||
- feat: custom file size limit and mime types at bucket
|
||||
level [supabase/storage-js#151](https://github.com/supabase/storage-js/pull/151) file size and mime type
|
||||
limits per bucket
|
||||
- feat: quality option, image
|
||||
transformation [supabase/storage-js#145](https://github.com/supabase/storage-js/pull/152) quality option for
|
||||
image transformations
|
||||
- feat: format option for webp
|
||||
support [supabase/storage-js#142](https://github.com/supabase/storage-js/pull/142) format option for image
|
||||
transformation
|
||||
|
||||
## 0.8.8 - 2023-03-29
|
||||
|
||||
- Update dependency: gotrue-csharp@3.0.6
|
||||
- Supports adding `SignInOptions` (i.e. `RedirectTo`) on `OAuth Provider` SignIn requests.
|
||||
|
||||
## 0.8.7 - 2023-03-23
|
||||
|
||||
- Update dependency: realtime-csharp@5.0.4
|
||||
- Re: [#26](https://github.com/supabase-community/realtime-csharp/pull/26) - Fixes Connect() not returning callback
|
||||
result when the socket isn't null. Thanks [@BlueWaterCrystal](https://github.com/BlueWaterCrystal)!
|
||||
|
||||
## 0.8.6 - 2023-03-23
|
||||
|
||||
- Update dependency: supabase-storage-csharp@1.2.8
|
||||
- [Merge #5](https://github.com/supabase-community/storage-csharp/pull/5) Added search string as an optional search
|
||||
parameter. Thanks [@ElectroKnight22](https://github.com/ElectroKnight22)!
|
||||
|
||||
## 0.8.5 - 2023-03-10
|
||||
|
||||
- Update dependency: realtime-csharp@5.0.3
|
||||
- Re: [#25](https://github.com/supabase-community/realtime-csharp/issues/25) - Support Channel being resubscribed
|
||||
after having been unsubscribed, fixes rejoin timer being erroneously called on channel `Unsubscribe`.
|
||||
Thanks [@Kuffs2205](https://github.com/Kuffs2205)!
|
||||
|
||||
## 0.8.4 - 2023-03-03
|
||||
|
||||
- Update dependency: supabase-storage-csharp@1.2.7
|
||||
- Re: [#4](https://github.com/supabase-community/storage-csharp/issues/4) Implementation for `ClientOptions` which
|
||||
supports specifying Upload, Download, and Request timeouts.
|
||||
- Update dependency: realtime-csharp@5.0.2
|
||||
- Re: [#24](https://github.com/supabase-community/realtime-csharp/issues/24) - Fixes join failing until reconnect
|
||||
happened + adds access token push on channel join. Big thank you to [@Honeyhead](https://github.com/honeyhead) for
|
||||
the help debugging and identifying!
|
||||
|
||||
## 0.8.3 - 2023-02-26
|
||||
|
||||
- Update dependency: supabase-storage-csharp@1.2.5
|
||||
- Provides fix
|
||||
for [supabase-community/supabase-csharp#54](https://github.com/supabase-community/supabase-csharp/issues/54) -
|
||||
Dynamic headers were always being overwritten by initialized token headers, so the storage client would not
|
||||
receive user's access token as expected.
|
||||
- Provides fix for upload progress not reporting
|
||||
in [supabase-community/storage-csharp#3](https://github.com/supabase-community/storage-csharp/issues/3)
|
||||
- Update dependency: gotrue-csharp@3.0.5
|
||||
- Fixes [#44](https://github.com/supabase-community/gotrue-csharp/issues/44) - refresh timer should automatically
|
||||
reattempt (interval of 5s) for HTTP exceptions - gracefully exits on invalid refresh and triggers
|
||||
an `AuthState.Changed` event
|
||||
|
||||
## 0.8.2 - 2023-02-26
|
||||
|
||||
- Update dependency: supabase-storage-csharp@1.2.4
|
||||
- `UploadOrUpdate` now appropriately throws request exceptions
|
||||
|
||||
## 0.8.1 - 2023-02-06
|
||||
|
||||
- Update dependency: realtime-csharp@5.0.1
|
||||
- Re: [#22](https://github.com/supabase-community/realtime-csharp/issues/22) - `SerializerSettings` were not being
|
||||
passed to `PostgresChangesResponse` - Thanks [@Shenrak](https://github.com/Shenrak) for the help debugging!
|
||||
|
||||
## 0.8.0 - 2023-01-31
|
||||
|
||||
- Update dependency: realtime-csharp@5.0.0
|
||||
- Re: [#21](https://github.com/supabase-community/realtime-csharp/pull/21) Provide API for `presence`, `broadcast` and `postgres_changes`
|
||||
- [Major, New] `Channel.PostgresChanges` event will receive the wildcard `*` changes event, not `Channel.OnMessage`.
|
||||
- [Major] `Channel.OnInsert`, `Channel.OnUpdate`, and `Channel.OnDelete` now conform to the server's payload of `Response.Payload.**Data**`
|
||||
- [Major] `Channel.OnInsert`, `Channel.OnUpdate`, and `Channel.OnDelete` now return `PostgresChangesEventArgs`
|
||||
- [Minor] Rename `Channel` to `RealtimeChannel`
|
||||
- Supports better handling of disconnects in `RealtimeSocket` and adds a `Client.OnReconnect` event.
|
||||
- [Minor] Moves `ChannelOptions` to `Channel.ChannelOptions`
|
||||
- [Minor] Moves `ChannelStateChangedEventArgs` to `Channel.ChannelStateChangedEventArgs`
|
||||
- [Minor] Moves `Push` to `Channel.Push`
|
||||
- [Minor] Moves `Channel.ChannelState` to `Constants.ChannelState`
|
||||
- [Minor] Moves `SocketResponse`, `SocketRequest`, `SocketResponsePayload`, `SocketResponseEventArgs`, and `SocketStateChangedEventArgs` to `Socket` namespace.
|
||||
- [New] Adds `RealtimeBroadcast`
|
||||
- [New] Adds `RealtimePresence`
|
||||
- [Improvement] Better handling of disconnection/reconnection
|
||||
- Update dependency: postgrest-csharp@3.1.3
|
||||
- Another fix for [#61](https://github.com/supabase-community/postgrest-csharp/issues/61) which further typechecks nullable values.
|
||||
|
||||
## 0.7.2 - 2023-01-27
|
||||
|
||||
- Update dependency: gotrue-csharp@3.0.4
|
||||
- Makes `Session.CreatedAt` a publicly settable property, which should fix incorrect dates on retrieved `Session`s.
|
||||
- Update dependency: postgrest-csharp@3.1.2
|
||||
- Fix [#61](https://github.com/supabase-community/postgrest-csharp/issues/61) which did not correctly parse Linq `Where` when encountering a nullable type.
|
||||
- Add missing support for transforming for `== null` and `!= null`
|
||||
|
||||
## 0.7.1 - 2023-01-17
|
||||
|
||||
- Update dependency: postgrest-csharp@3.1.1
|
||||
- Fix issue from supabase-community/supabase-csharp#48 where boolean model properties would not be evaluated in predicate expressions
|
||||
|
||||
## 0.7.0 - 2023-01-16
|
||||
|
||||
- Update dependency: postgrest-csharp@3.1.0
|
||||
- [Minor] Breaking API Change: `PrimaryKey` attribute defaults to `shouldInsert: false` as most uses will have the Database generate the primary key.
|
||||
- Merged [#60](https://github.com/supabase-community/postgrest-csharp/pull/60) which Added linq support for `Select`, `Where`, `OnConflict`, `Columns`, `Order`, `Update`, `Set`, and `Delete`
|
||||
|
||||
## 0.6.2 - 2022-11-22
|
||||
|
||||
- Update dependency: postgrest-csharp@3.0.4
|
||||
- `GetHeaders` is now passed to `ModeledResponse` and `BaseModel` so that the default `Update` and `Delete` methods use the latest credentials
|
||||
- `GetHeaders` is used in `Rpc` calls (re: [#39](https://github.com/supabase-community/supabase-csharp/issues/39))
|
||||
|
||||
## 0.6.1 - 2022-11-12
|
||||
|
||||
- [Hotfix] `GetHeaders` was not passing properly to `SupabaseTable` and `Gotrue.Api`
|
||||
|
||||
## 0.6.0 - 2022-11-12
|
||||
|
||||
[BREAKING CHANGES]
|
||||
|
||||
- `Client` is no longer a singleton, singleton interactions (if desired) are left to the developer to implement.
|
||||
- `Client` supports injection of dependent clients after initialization via property:
|
||||
- `Auth`
|
||||
- `Functions`
|
||||
- `Realtime`
|
||||
- `Postgrest`
|
||||
- `Storage`
|
||||
- `SupabaseModel` contains no logic but remains for backwards compatibility. (Marked `Obsolete`)
|
||||
- `ClientOptions.ShouldInitializeRealtime` was removed (no longer auto initialized)
|
||||
- `ClientOptions` now references an `ISupabaseSessionHandler` which specifies expected functionality for session persistence on Gotrue (replaces `ClientOptions.SessionPersistor`, `ClientOptions.SessionRetriever`, and `ClientOptions.SessionDestroyer`).
|
||||
- `supabase-csharp` and all child libraries now have support `nullity`
|
||||
|
||||
Other Changes:
|
||||
|
||||
- Update dependency: functions-csharp@1.2.1
|
||||
- Update dependency: gotrue-csharp@3.0.2
|
||||
- Update dependency: postgrest-csharp@3.0.2
|
||||
- Update dependency: realtime-csharp@4.0.1
|
||||
- Update dependency: supabase-storage-csharp@1.2.3
|
||||
- Update dependency: supabase-core@0.0.2
|
||||
|
||||
Big thank you to [@veleek](https://github.com/veleek) for his insight into these changes.
|
||||
|
||||
Re: [#35](https://github.com/supabase-community/supabase-csharp/issues/35), [#34](https://github.com/supabase-community/supabase-csharp/issues/34), [#23](https://github.com/supabase-community/supabase-csharp/issues/23), [#36](https://github.com/supabase-community/supabase-csharp/pull/36)
|
||||
|
||||
## 0.5.3 - 2022-10-11
|
||||
|
||||
- Update dependency: postgrest-csharp@2.1.0
|
||||
- [Minor] Breaking API change: Remove `BaseModel.PrimaryKeyValue` and `BaseModel.PrimaryKeyColumn` in favor of a `PrimaryKey` dictionary with support for composite keys.
|
||||
- Re: [#48](https://github.com/supabase-community/postgrest-csharp/issues/48) - Add support for derived models on `ReferenceAttribute`
|
||||
- Re: [#49](https://github.com/supabase-community/postgrest-csharp/issues/49) - Added `Match(T model)`
|
||||
|
||||
## 0.5.2 - 2022-9-13
|
||||
|
||||
- Update dependency: postgrest-csharp@2.0.12
|
||||
- Merged [#47](https://github.com/supabase-community/postgrest-csharp/pull/49) which added cancellation token support to `Table<T>` methods. Thanks [@devpikachu](https://github.com/devpikachu)!
|
||||
|
||||
## 0.5.1 - 2022-8-1
|
||||
|
||||
- Update dependency: postgrest-csharp@2.0.11
|
||||
- Update dependency: supabase-storage-csharp@1.1.1
|
||||
|
||||
## 0.5.0 - 2022-7-17
|
||||
|
||||
- Update dependency: postgrest-csharp@2.0.9
|
||||
- Update dependency: realtime-csharp@3.0.1
|
||||
- Update dependency: supabase-storage-csharp@1.1.0
|
||||
- API Change [Breaking/Minor] Library no longer uses `WebClient` and instead leverages `HttpClient`. Progress events on `Upload` and `Download` are now handled with `EventHandler<float>` instead of `WebClient` EventHandlers.
|
||||
|
||||
## 0.4.4 - 2022-5-24
|
||||
|
||||
- Update dependency: gotrue-csharp@2.4.5
|
||||
- Update dependency: postgrest-csharp@2.0.8
|
||||
|
||||
## 0.4.3 - 2022-5-13
|
||||
|
||||
- Update dependency: gotrue-csharp@2.4.4
|
||||
|
||||
## 0.4.2 - 2022-4-30
|
||||
|
||||
- Update dependency: gotrue-csharp@2.4.3
|
||||
|
||||
## 0.4.1 - 2022-4-23
|
||||
|
||||
- Update dependency: gotrue-csharp@2.4.2
|
||||
|
||||
## 0.4.0 - 2022-4-12
|
||||
|
||||
- Add support for functions-csharp@1.0.1, giving access to invoking Supabase's edge functions.
|
||||
- Update dependency: gotrue-csharp@2.4.1
|
||||
|
||||
## 0.3.5 - 2022-4-11
|
||||
|
||||
- Update dependency: postgres-csharp@2.0.7
|
||||
|
||||
## 0.3.4 - 2022-03-28
|
||||
|
||||
- Update dependency: gotrue-csharp@2.4.0
|
||||
|
||||
## 0.3.3 - 2022-02-27
|
||||
|
||||
- Update dependency: gotrue-csharp@2.3.6
|
||||
- Update dependency: supabase-storage-csharp@1.0.2
|
||||
|
||||
## 0.3.2 - 2022-02-18
|
||||
|
||||
- Update dependency: realtime-csharp@3.0.0
|
||||
- Exchange existing websocket client: [WebSocketSharp](https://github.com/sta/websocket-sharp) for [Marfusios/websocket-client](https://github.com/Marfusios/websocket-client) which adds support for Blazor WASM apps.
|
||||
Ref: [#14](https://github.com/supabase-community/realtime-csharp/pull/14)
|
||||
|
||||
## 0.3.1 - 2022-01-20
|
||||
|
||||
- Update dependency: gotrue-csharp@2.3.5
|
||||
- [#23](https://github.com/supabase-community/gotrue-csharp/pull/23) Added `redirect_url` option for MagicLink sign in (Thanks [@MisterJimson](https://github.com/MisterJimson))
|
||||
- [#21](https://github.com/supabase-community/gotrue-csharp/pull/21) Added SignOut method to Stateless Client (Thanks [@fplaras](https://github.com/fplaras))
|
||||
|
||||
## 0.3.0 - 2021-12-30
|
||||
|
||||
- Update dependency: postgrest-csharp@2.0.6
|
||||
- Add support for `NullValueHandling` to be specified on a `Column` Attribute and for it to be honored on Inserts and Updates. Defaults to: `NullValueHandling.Include`.
|
||||
- Implements [#38](https://github.com/supabase-community/postgrest-csharp/issues/38)
|
||||
- Update dependency: realtime-csharp@2.0.8
|
||||
- Implement Upstream Realtime RLS Error Broadcast Handler
|
||||
- Implements [#12](https://github.com/supabase-community/realtime-csharp/issues/12)
|
||||
- `SocketResponse` now exposes a method: `OldModel`, that hydrates the `OldRecord` property into a model.
|
||||
|
||||
## 0.2.12 - 2021-12-29
|
||||
|
||||
- Update dependency: gotrue-csharp@2.3.3
|
||||
- `SignUp` will return a `Session` with a _populated `User` object_ on an unconfirmed signup.
|
||||
- Fixes [#19](https://github.com/supabase-community/gotrue-csharp/issues/19)
|
||||
- Developers who were using a `null` check on `Session.User` will need to adjust accordingly.
|
||||
- Update dependency: postgrest-csharp@2.0.5
|
||||
- Fix for [#37](https://github.com/supabase-community/postgrest-csharp/issues/37) - Return Type `minimal` would fail to resolve because of incorrect `Accept` headers. Added header and test to verify for future.
|
||||
- Fix for [#36](https://github.com/supabase-community/postgrest-csharp/issues/36) - Inserting/Upserting bulk records would fail while doing an unnecessary generic coercion.
|
||||
|
||||
## 0.2.11 - 2021-12-24
|
||||
|
||||
- Update dependency: gotrue-csharp@2.3.2 (changes CreateUser parameters to conform to `AdminUserAttributes`)
|
||||
- See [#15](https://github.com/supabase-community/supabase-csharp/issues/15)
|
||||
- See [#16](https://github.com/supabase-community/supabase-csharp/issues/16)
|
||||
- Update dependency: realtime-csharp@2.0.7
|
||||
- See [#13](https://github.com/supabase-community/supabase-csharp/issues/13)
|
||||
|
||||
## 0.2.10 - 2021-12-23
|
||||
|
||||
- Update dependency: gotrue-csharp@2.3.0 (adds metadata support for user signup, see [#14](https://github.com/supabase/community/issues/14))
|
||||
|
||||
## 0.2.9 - 2021-12-9
|
||||
|
||||
- Separate Storage client from Supabase repo and into `storage-csharp`, `supabase-csharp` now references new repo.
|
||||
|
||||
## 0.2.8 - 2021-12-4
|
||||
|
||||
- Update gotrue-csharp to 2.2.4
|
||||
- Adds support for `ListUsers` (paginate, sort, filter), `GetUserById`, `CreateUser`, and `UpdateById`
|
||||
|
||||
## 0.2.7 - 2021-12-2
|
||||
|
||||
- Update gotrue-csharp to 2.2.3
|
||||
- Adds support for sending password resets to users.
|
||||
|
||||
## 0.2.6 - 2021-11-29
|
||||
|
||||
- Support for [#12](https://github.com/supabase-community/supabase-csharp/issues/12)
|
||||
- Update realtime-csharp to 2.0.6
|
||||
- Update gotrue-csharp to 2.2.2
|
||||
- Add `StatelessClient` re:[#7](https://github.com/supabase-community/supabase-csharp/issues/7)
|
||||
## 1.0.0 - 2024-04-21
|
||||
|
||||
- Assembly Name has been changed to `Supabase.dll`
|
||||
- Update dependency: `postgrest-csharp@5.0.0`
|
||||
- [MAJOR] Moves namespaces from `Postgrest` to `Supabase.Postgrest`
|
||||
- Re: [#135](https://github.com/supabase-community/supabase-csharp/issues/135) Update nuget package
|
||||
name `postgrest-csharp` to `Supabase.Postgrest`
|
||||
- Update dependency: `gotrue-csharp@5.0.0`
|
||||
- Re: [#135](supabase-community/supabase-csharp#135) Update nuget package name `gotrue-csharp` to `Supabase.Gotrue`
|
||||
- Re: [#89](https://github.com/supabase-community/gotrue-csharp/issues/89), Only add `access_token` to request body
|
||||
when it is explicitly declared.
|
||||
- [MINOR] Re: [#89](https://github.com/supabase-community/gotrue-csharp/issues/89) Update signature
|
||||
for `SignInWithIdToken` which adds an optional `accessToken` parameter, update doc comments, and
|
||||
call `DestroySession` in method
|
||||
- Re: [#88](https://github.com/supabase-community/gotrue-csharp/issues/88), Add `IsAnonymous` property to `User`
|
||||
- Re: [#90](https://github.com/supabase-community/gotrue-csharp/issues/90) Implement `LinkIdentity`
|
||||
and `UnlinkIdentity`
|
||||
- Update dependency: `realtime-csharp@7.0.0`
|
||||
- Merges [#45](https://github.com/supabase-community/realtime-csharp/pull/45) - Updating
|
||||
the `Websocket.Client@5.1.1`
|
||||
- Re: [#135](https://github.com/supabase-community/supabase-csharp/issues/135) Update nuget package
|
||||
name `realtime-csharp` to `Supabase.Realtime`
|
||||
- Update dependency: `storage-csharp@2.0.0`
|
||||
- Re: [#135](https://github.com/supabase-community/supabase-csharp/issues/135) Update nuget package
|
||||
name `storage-csharp` to `Supabase.Storage`
|
||||
- Update dependency: `functions-csharp@2.0.0`
|
||||
- Re: [#135](https://github.com/supabase-community/supabase-csharp/issues/135) Update nuget package
|
||||
name `functions-csharp` to `Supabase.Functions`
|
||||
- Update dependency: `core-csharp@1.0.0`
|
||||
- Re: [#135](https://github.com/supabase-community/supabase-csharp/issues/135) Update nuget package
|
||||
name `supabase-core`
|
||||
to `Supabase.Core`
|
||||
- Adds comments to the remaining undocumented code.
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
id: installing
|
||||
title: 'Installing & Initialization'
|
||||
slug: installing
|
||||
custom_edit_url: https://github.com/supabase/supabase/edit/master/web/spec/supabase.yml
|
||||
---
|
||||
|
||||
### Install from NuGet
|
||||
|
||||
<RefSubLayout.EducationRow>
|
||||
<RefSubLayout.Details>
|
||||
|
||||
You can install Supabase package from [nuget.org](https://www.nuget.org/packages/supabase-csharp/)
|
||||
|
||||
</RefSubLayout.Details>
|
||||
|
||||
<RefSubLayout.Examples>
|
||||
|
||||
<Tabs
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="csharp"
|
||||
queryGroup="language"
|
||||
>
|
||||
<TabPanel id="csharp" label="Terminal">
|
||||
|
||||
```sh Terminal
|
||||
dotnet add package supabase-csharp
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
</RefSubLayout.Examples>
|
||||
</RefSubLayout.EducationRow>
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
id: introduction
|
||||
title: Introduction
|
||||
hideTitle: true
|
||||
---
|
||||
|
||||
<div className="flex items-start gap-6 not-prose" id="introduction">
|
||||
<IconMenuCsharp width={35} height={35} />
|
||||
<div className="flex flex-col gap-2">
|
||||
<h1 className="text-3xl text-foreground m-0">C# Client Library</h1>
|
||||
<h2 className="text-base font-mono text-foreground-light">
|
||||
@supabase-community/supabase-csharp
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* prettier-ignore */}
|
||||
<div className="max-w-xl">
|
||||
This reference documents every object and method available in Supabase's C# library, [supabase-csharp](https://www.nuget.org/packages/supabase-csharp). You can use `Supabase` to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
|
||||
</div>
|
||||
|
||||
{/* prettier-ignore */}
|
||||
<div className="max-w-xl bg-slate-300 px-4 py-2 rounded-md">
|
||||
|
||||
The C# client library is created and maintained by the Supabase community, and is not an official library. Please be tolerant of areas where the library is still being developed, and — as with all the libraries — feel free to contribute wherever you find issues.
|
||||
|
||||
Huge thanks to official maintainer, [Joseph Schultz](https://github.com/acupofjose). As well as [Will Iverson](https://github.com/wiverson), [Ben Randall](https://github.com/veleek), and [Rhuan Barros](https://github.com/rhuanbarros) for their help.
|
||||
|
||||
</div>
|
||||
@@ -3,4 +3,565 @@ id: release-notes
|
||||
title: Release Notes
|
||||
---
|
||||
|
||||
## dart v0 this is the release notes file.
|
||||
## 0.16.2 - 2024-04-02
|
||||
|
||||
- Update dependency: `gotrue-csharp@4.2.7`
|
||||
- [#88](https://github.com/supabase-community/gotrue-csharp/issues/88) Implement `signInAnonymously` from the JS
|
||||
client
|
||||
- Include additional 3rd party providers in constants.
|
||||
|
||||
## 0.16.1 - 2024-03-15
|
||||
|
||||
- Update dependency: `postgrest-csharp@3.5.1`
|
||||
- Re: [#147](https://github.com/supabase-community/supabase-csharp/issues/147) - Supports `Rpc` specifying a generic
|
||||
type for its return.
|
||||
|
||||
## 0.16.0 - 2024-03-12
|
||||
|
||||
- Update dependency: `postgrest-csharp@3.5.0`
|
||||
- Re: [#78](https://github.com/supabase-community/postgrest-csharp/issues/78), Generalize query filtering creation
|
||||
in `Table` so that it matches new generic signatures.
|
||||
- Move from `QueryFilter` parameters to a more generic `IPosgrestQueryFilter` to support constructing new
|
||||
QueryFilters from a LINQ expression.
|
||||
- Note: Lists of `QueryFilter`s will now need to be defined
|
||||
as: `new List<IPostgrestQueryFilter> { new QueryFilter(), ... }`
|
||||
- Adjust serialization of timestamps within a `QueryFilter` to support `DateTime` and `DateTimeOffset` using the
|
||||
ISO-8601 (https://stackoverflow.com/a/115002)
|
||||
- Update dependency: `functions-csharp@1.3.2`
|
||||
- Re: [#5](https://github.com/supabase-community/functions-csharp/issues/5) Add support for specifying Http Timeout
|
||||
on a function call by adding `HttpTimeout` to `InvokeFunctionOptions`
|
||||
|
||||
## 0.15.0 - 2024-01-08
|
||||
|
||||
- Update Dependency: `gotrue-csharp@4.2.6`
|
||||
- [#83](https://github.com/supabase-community/gotrue-csharp/pull/83) Replaces JWTDecoder package with
|
||||
System.IdentityModel.Tokens.Jwt. Thanks [@FantasyTeddy](https://github.com/FantasyTeddy)!
|
||||
- Update Dependency: `postgrest-csharp@3.4.1`
|
||||
- Re: [#85](https://github.com/supabase-community/postgrest-csharp/issues/85) Fixes problem when using multiple
|
||||
.Order()
|
||||
methods by merging [#86](https://github.com/supabase-community/postgrest-csharp/pull/86).
|
||||
Thanks [@hunsra](https://github.com/hunsra)!
|
||||
- Re: [#81](https://github.com/supabase-community/postgrest-csharp/issues/81)
|
||||
- [Minor] Removes `IgnoreOnInsert`and `IgnoreOnUpdate` from `ReferenceAttribute` as changing these properties
|
||||
to `false` does not currently provide the expected functionality.
|
||||
- Fixes `Insert` and `Update` not working on models that have `Reference` specified on a property with a
|
||||
non-null
|
||||
value.
|
||||
|
||||
## 0.14.0 - 2023-12-15
|
||||
|
||||
- Update Dependency: `gotrue-csharp@4.2.5`
|
||||
- [#82](https://github.com/supabase-community/gotrue-csharp/issues/81) - Implements #82 - Creates a `GenerateLink`
|
||||
method on the `AdminClient` that supports `signup`, `invite`, `magiclink`, `recovery`, `email_change_new`
|
||||
and `email_change_current`
|
||||
- [#81](https://github.com/supabase-community/gotrue-csharp/issues/81) - Adds `InviteUserByEmailOptions` as a
|
||||
parameter to the Gotrue Admin Client
|
||||
- Update Dependency: `postgrest-csharp@3.3.0`
|
||||
- Re: [#78](https://github.com/supabase-community/postgrest-csharp/issues/78) Updates signatures for `Not`
|
||||
and `Filter` to include generic types for a better development experience.
|
||||
- Updates internal generic type names to be more descriptive.
|
||||
- Add support for LINQ predicates on `Table<TModel>.Not()` signatures
|
||||
|
||||
## 0.13.7 - 2023-11-13
|
||||
|
||||
- Update Dependency: `postgrest-csharp@3.2.10`
|
||||
- Re: [#76](https://github.com/supabase-community/postgrest-csharp/issues/76) Removes the
|
||||
incorrect `ToUniversalTime` conversion in the LINQ `Where` parser.
|
||||
|
||||
## 0.13.6 - 2023-10-12
|
||||
|
||||
- Update Dependency: `gotrue-csharp@4.2.3`
|
||||
- Re: [#80](https://github.com/supabase-community/gotrue-csharp/pull/80) Fixes `Session.Expires()` not being
|
||||
calculated correctly. Thanks [@dayjay](https://github.com/Dayjay)!
|
||||
|
||||
## 0.13.5 - 2023-10-09
|
||||
|
||||
- Update Dependency: `postgrest-csharp@3.2.9`
|
||||
- Re: [supabase-csharp#115](https://github.com/supabase-community/supabase-csharp/discussions/115) Additional
|
||||
support for a model referencing another model with multiple foreign keys.
|
||||
- Re: [supabase-csharp#115](https://github.com/supabase-community/supabase-csharp/discussions/115) Adds support for
|
||||
multiple references attached to the same model (foreign keys) on a single C# Model.
|
||||
|
||||
## 0.13.4 - 2023-10-08
|
||||
|
||||
- Update Dependency: `gotrue-csharp@4.2.2`
|
||||
- Re: [#78](https://github.com/supabase-community/gotrue-csharp/issues/78) - Implements PKCE flow support
|
||||
for `ResetPasswordForEmail`.
|
||||
|
||||
## 0.13.3 - 2023-09-15
|
||||
|
||||
- Re: [#107](https://github.com/supabase-community/supabase-csharp/issues/107) - removes Realtime socket being
|
||||
disconnected on a User sign-out - only the subscriptions should be removed.
|
||||
|
||||
## 0.13.2 - 2023-09-15
|
||||
|
||||
- Update dependency: `postgrest-csharp@3.2.7`
|
||||
- Implements a `TableWithCache` for `Get` requests that can pull reactive Models from cache before making a remote
|
||||
request.
|
||||
- Re: [supabase-csharp#85](https://github.com/supabase-community/supabase-csharp/issues/85) Includes sourcelink
|
||||
support.
|
||||
- Re: [#75](https://github.com/supabase-community/postgrest-csharp/pull/75) Fix issue with marshalling of stored
|
||||
procedure arguments. Big thank you to [@corrideat](https://github.com/corrideat)!
|
||||
|
||||
## 0.13.1 - 2023-08-26
|
||||
|
||||
- Update dependency: `supabase-storage-csharp@1.4.0`
|
||||
- Fixes [#11](https://github.com/supabase-community/storage-csharp/issues/11) - Which implements
|
||||
missing `SupabaseStorageException` on failure status codes for `Upload`, `Download`, `Move`, `CreateSignedUrl`
|
||||
and `CreateSignedUrls`.
|
||||
|
||||
## 0.13.0 - 2023-08-26
|
||||
|
||||
- Update dependency: `gotrue-csharp@4.2.1`
|
||||
- [#74](https://github.com/supabase-community/gotrue-csharp/pull/74) - Fixes bug where token refresh interval was
|
||||
not honored by client. Thanks [@slater1](https://github.com/slater1)!
|
||||
- **Minor Breaking changes:** [#72](https://github.com/supabase-community/gotrue-csharp/pull/72) - Fixes
|
||||
Calling `SetAuth` does not actually set Authorization Headers for subsequent requests by implementing `SetSession`
|
||||
- Removes `RefreshToken(string refreshToken)` and `SetAuth(string accessToken` in favor
|
||||
of `SetSession(string accessToken, string refreshToken)`
|
||||
- Makes `RefreshAccessToken` require `accessToken` and `refreshToken` as parameters - overrides the
|
||||
authorization
|
||||
headers to use the supplied token
|
||||
- Migrates project internal times to use `DateTime.UtcNow` over `DateTime.Now`.
|
||||
|
||||
## 0.12.2 - 2023-07-28
|
||||
|
||||
- Update dependency: `realtime-csharp@6.0.4`
|
||||
- Fixes [#29](https://github.com/supabase-community/realtime-csharp/issues/29) Where the Realtime client could
|
||||
disconnect from channels after a few hours and fail to reconnect by removing the case where the `IsSubscribe` flag
|
||||
is flipped when encountering a channel error.
|
||||
- Update dependency: `postgrest-csharp@3.2.5`
|
||||
- Re: [supabase-community/supabase-csharp#81](https://github.com/supabase-community/supabase-csharp/discussions/81):
|
||||
Clarifies `ReferenceAttribute` by changing `shouldFilterTopLevel` to `useInnerJoin` and adds an additional
|
||||
constructor for `ReferenceAttribute` with a shortcut for specifying the `JoinType`
|
||||
|
||||
## 0.12.1 - 2023-06-29
|
||||
|
||||
- Update dependency: `gotrue-csharp@4.1.1`
|
||||
- [#68](https://github.com/supabase-community/gotrue-csharp/pull/68) Changes Network Status to use the interface
|
||||
instead of client
|
||||
- Update dependency: `postgrest-csharp@3.2.4`
|
||||
- [#70](https://github.com/supabase-community/postgrest-csharp/pull/70) Minor Unity related fixes
|
||||
|
||||
## 0.12.0 - 2023-06-25
|
||||
|
||||
- Update dependency: `gotrue-csharp@4.1.0`
|
||||
- **Minor** [#66](https://github.com/supabase-community/gotrue-csharp/pull/66) - Separates out Admin JWT
|
||||
functionality into a separate `AdminClient`
|
||||
- [#67](https://github.com/supabase-community/gotrue-csharp/pull/67) - Adds shutdown method which terminates the
|
||||
background refresh threads.
|
||||
- Movement of much of the documentation for methods out of their classes and into their interfaces.
|
||||
- Language features locked to C#9
|
||||
- Update dependency: `postgrest-csharp@3.2.3`
|
||||
- [#69](https://github.com/supabase-community/postgrest-csharp/pull/69) Locks language version to C#9
|
||||
- [#68](https://github.com/supabase-community/postgrest-csharp/pull/68) Makes RPC parameters optional
|
||||
|
||||
Thanks [@wiverson](https://github.com/wiverson) for the work in this release!
|
||||
|
||||
## 0.11.1 - 2023-06-10
|
||||
|
||||
- Update dependencies: `functions-csharp@1.3.1`, `gotrue-csharp@4.0.4`, `postgrest-csharp@3.2.2`,
|
||||
`realtime-csharp@6.0.3`, `supabase-storage-csharp@1.3.2`, `supabase-core@0.0.3`
|
||||
- Namespaces assembly names to make them unique among other dependencies, i.e: `Core.dll`
|
||||
becomes `Supabase.Core.dll` which will hopefully prevent future collisions.
|
||||
|
||||
## 0.11.0 - 2023-05-24
|
||||
|
||||
- Update dependency: postgrest-csharp@3.2.0
|
||||
- General codebase and QOL improvements. Exceptions are generally thrown through `PostgrestException` now instead
|
||||
of `Exception`. A `FailureHint.Reason` is provided with failures if possible to parse.
|
||||
- `AddDebugListener` is now available on the client to help with debugging
|
||||
- Merges [#65](https://github.com/supabase-community/postgrest-csharp/pull/65) Cleanup + Add better exception
|
||||
handling
|
||||
- Merges [#66](https://github.com/supabase-community/postgrest-csharp/pull/66) Local test Fixes
|
||||
- Fixes [#67](https://github.com/supabase-community/postgrest-csharp/issues/67) Postgrest Reference attribute is
|
||||
producing StackOverflow for circular references
|
||||
- Update dependency: gotrue-csharp@4.0.2
|
||||
- [#58](https://github.com/supabase-community/gotrue-csharp/issues/58) - Add support for the `reauthentication`
|
||||
endpoint which allows for secure password changes.
|
||||
- Update dependency: realtime-csharp@6.0.1
|
||||
- Updates publishing action for future packages, includes README and icon.
|
||||
- Merges [#28](https://github.com/supabase-community/realtime-csharp/pull/28)
|
||||
and [#30](https://github.com/supabase-community/realtime-csharp/pull/30)
|
||||
- The realtime client now takes a "fail-fast" approach. On establishing an initial connection, client will throw
|
||||
a `RealtimeException` in `ConnectAsync()` if the socket server is unreachable. After an initial connection has
|
||||
been
|
||||
established, the **client will continue attempting reconnections indefinitely until disconnected.**
|
||||
- [Major, New] C# `EventHandlers` have been changed to `delegates`. This should allow for cleaner event data access
|
||||
over
|
||||
the previous subclassed `EventArgs` setup. Events are scoped accordingly. For example, the `RealtimeSocket` error
|
||||
handlers will receive events regarding socket connectivity; whereas the `RealtimeChannel` error handlers will
|
||||
receive
|
||||
events according to `Channel` joining/leaving/etc. This is implemented with the following methods prefixed by (
|
||||
Add/Remove/Clear):
|
||||
- `RealtimeBroadcast.AddBroadcastEventHandler`
|
||||
- `RealtimePresence.AddPresenceEventHandler`
|
||||
- `RealtimeSocket.AddStateChangedHandler`
|
||||
- `RealtimeSocket.AddMessageReceivedHandler`
|
||||
- `RealtimeSocket.AddHeartbeatHandler`
|
||||
- `RealtimeSocket.AddErrorHandler`
|
||||
- `RealtimeClient.AddDebugHandler`
|
||||
- `RealtimeClient.AddStateChangedHandler`
|
||||
- `RealtimeChannel.AddPostgresChangeHandler`
|
||||
- `RealtimeChannel.AddMessageReceivedHandler`
|
||||
- `RealtimeChannel.AddErrorHandler`
|
||||
- `Push.AddMessageReceivedHandler`
|
||||
- [Major, new] `ClientOptions.Logger` has been removed in favor of `Client.AddDebugHandler()` which allows for
|
||||
implementing custom logging solutions if desired.
|
||||
- A simple logger can be set up with the following:
|
||||
```c#
|
||||
client.AddDebugHandler((sender, message, exception) => Debug.WriteLine(message));
|
||||
```
|
||||
- [Major] `Connect()` has been marked `Obsolete` in favor of `ConnectAsync()`
|
||||
- Custom reconnection logic has been removed in favor of using the built-in logic from `Websocket.Client@4.6.1`.
|
||||
- Exceptions that are handled within this library have been marked as `RealtimeException`s.
|
||||
- The local, docker-composed test suite has been brought back (as opposed to remotely testing on live supabase
|
||||
servers)
|
||||
to test against.
|
||||
- Comments have been added throughout the entire codebase and an `XML` file is now generated on build.
|
||||
|
||||
## 0.10.0 - 2023-05-14
|
||||
|
||||
- Changes options to require `Supabase.SupabaseOptions.SessionPersistor` from using `ISupabaseSessionHandler`
|
||||
to `IGotrueSessionPersistance<Session>` (these are now synchronous operations).
|
||||
- Update dependency: gotrue-csharp@4.0.1
|
||||
- [#60](https://github.com/supabase-community/gotrue-csharp/pull/60) - Add interfaces, bug fixes, additional error
|
||||
reason detection. Thanks [@wiverson](https://github.com/wiverson)!
|
||||
- [#57](https://github.com/supabase-community/gotrue-csharp/pull/57) Refactor exceptions, code cleanup, and move to
|
||||
delegate auth state changes
|
||||
- Huge thank you to [@wiverson](https://github.com/wiverson) for his help on this refactor and release!
|
||||
- Changes
|
||||
- Exceptions have been simplified to a single `GotrueException`. A `Reason` field has been added
|
||||
to `GotrueException` to clarify what happened. This should also be easier to manage as the Gotrue
|
||||
server API & messages evolve.
|
||||
- The session delegates for `Save`/`Load`/`Destroy` have been simplified to no longer require `async`.
|
||||
- Console logging in a few places (most notable the background refresh thread) has been removed
|
||||
in favor of a notification method. See `Client.AddDebugListener()` and the test cases for examples.
|
||||
This will allow you to implement your own logging strategy (write to temp file, console, user visible
|
||||
err console, etc).
|
||||
- The client now more reliably emits AuthState changes.
|
||||
- There is now a single source of truth for headers in the stateful Client - the `Options` headers.
|
||||
- New feature:
|
||||
- Added a `Settings` request to the stateless API only - you can now query the server instance to
|
||||
determine if it's got the settings you need. This might allow for things like a visual
|
||||
component in a tool to verify the GoTrue settings are working correctly, or tests that run differently
|
||||
depending on the server configuration.
|
||||
- Implementation notes:
|
||||
- Test cases have been added to help ensure reliability of auth state change notifications
|
||||
and persistence.
|
||||
- Persistence is now managed via the same notifications as auth state change
|
||||
|
||||
## 0.9.1 - 2023-04-28
|
||||
|
||||
- Update dependency: gotrue-csharp@3.1.1
|
||||
- Implements `SignInWithIdToken` for Apple/Google signing from LW7. A HUGE thank you
|
||||
to [@wiverson](https://github.com/wiverson)!
|
||||
- Update dependency: realtime-csharp@5.0.5
|
||||
- Re: [#27](https://github.com/supabase-community/realtime-csharp/issues/27) `PostgresChangesOptions` was not
|
||||
setting `listenType` in constructor. Thanks [@Kuffs2205](https://github.com/Kuffs2205)
|
||||
- Update dependency: supabase-storage-csharp@1.2.10
|
||||
- Re: [#7](https://github.com/supabase-community/storage-csharp/issues/7) Implements a `DownloadPublicFile` method.
|
||||
|
||||
## 0.9.0 - 2023-04-12
|
||||
|
||||
- Update dependency: gotrue-csharp@3.1.0
|
||||
|
||||
- [Minor] Implements PKCE auth flow. SignIn using a provider now returns an instance of `ProviderAuthState` rather
|
||||
than a `string`.
|
||||
|
||||
- Update dependency: supabase-storage-csharp@1.2.9
|
||||
- Implements storage features from LW7:
|
||||
- feat: custom file size limit and mime types at bucket
|
||||
level [supabase/storage-js#151](https://github.com/supabase/storage-js/pull/151) file size and mime type
|
||||
limits per bucket
|
||||
- feat: quality option, image
|
||||
transformation [supabase/storage-js#145](https://github.com/supabase/storage-js/pull/152) quality option for
|
||||
image transformations
|
||||
- feat: format option for webp
|
||||
support [supabase/storage-js#142](https://github.com/supabase/storage-js/pull/142) format option for image
|
||||
transformation
|
||||
|
||||
## 0.8.8 - 2023-03-29
|
||||
|
||||
- Update dependency: gotrue-csharp@3.0.6
|
||||
- Supports adding `SignInOptions` (i.e. `RedirectTo`) on `OAuth Provider` SignIn requests.
|
||||
|
||||
## 0.8.7 - 2023-03-23
|
||||
|
||||
- Update dependency: realtime-csharp@5.0.4
|
||||
- Re: [#26](https://github.com/supabase-community/realtime-csharp/pull/26) - Fixes Connect() not returning callback
|
||||
result when the socket isn't null. Thanks [@BlueWaterCrystal](https://github.com/BlueWaterCrystal)!
|
||||
|
||||
## 0.8.6 - 2023-03-23
|
||||
|
||||
- Update dependency: supabase-storage-csharp@1.2.8
|
||||
- [Merge #5](https://github.com/supabase-community/storage-csharp/pull/5) Added search string as an optional search
|
||||
parameter. Thanks [@ElectroKnight22](https://github.com/ElectroKnight22)!
|
||||
|
||||
## 0.8.5 - 2023-03-10
|
||||
|
||||
- Update dependency: realtime-csharp@5.0.3
|
||||
- Re: [#25](https://github.com/supabase-community/realtime-csharp/issues/25) - Support Channel being resubscribed
|
||||
after having been unsubscribed, fixes rejoin timer being erroneously called on channel `Unsubscribe`.
|
||||
Thanks [@Kuffs2205](https://github.com/Kuffs2205)!
|
||||
|
||||
## 0.8.4 - 2023-03-03
|
||||
|
||||
- Update dependency: supabase-storage-csharp@1.2.7
|
||||
- Re: [#4](https://github.com/supabase-community/storage-csharp/issues/4) Implementation for `ClientOptions` which
|
||||
supports specifying Upload, Download, and Request timeouts.
|
||||
- Update dependency: realtime-csharp@5.0.2
|
||||
- Re: [#24](https://github.com/supabase-community/realtime-csharp/issues/24) - Fixes join failing until reconnect
|
||||
happened + adds access token push on channel join. Big thank you to [@Honeyhead](https://github.com/honeyhead) for
|
||||
the help debugging and identifying!
|
||||
|
||||
## 0.8.3 - 2023-02-26
|
||||
|
||||
- Update dependency: supabase-storage-csharp@1.2.5
|
||||
- Provides fix
|
||||
for [supabase-community/supabase-csharp#54](https://github.com/supabase-community/supabase-csharp/issues/54) -
|
||||
Dynamic headers were always being overwritten by initialized token headers, so the storage client would not
|
||||
receive user's access token as expected.
|
||||
- Provides fix for upload progress not reporting
|
||||
in [supabase-community/storage-csharp#3](https://github.com/supabase-community/storage-csharp/issues/3)
|
||||
- Update dependency: gotrue-csharp@3.0.5
|
||||
- Fixes [#44](https://github.com/supabase-community/gotrue-csharp/issues/44) - refresh timer should automatically
|
||||
reattempt (interval of 5s) for HTTP exceptions - gracefully exits on invalid refresh and triggers
|
||||
an `AuthState.Changed` event
|
||||
|
||||
## 0.8.2 - 2023-02-26
|
||||
|
||||
- Update dependency: supabase-storage-csharp@1.2.4
|
||||
- `UploadOrUpdate` now appropriately throws request exceptions
|
||||
|
||||
## 0.8.1 - 2023-02-06
|
||||
|
||||
- Update dependency: realtime-csharp@5.0.1
|
||||
- Re: [#22](https://github.com/supabase-community/realtime-csharp/issues/22) - `SerializerSettings` were not being
|
||||
passed to `PostgresChangesResponse` - Thanks [@Shenrak](https://github.com/Shenrak) for the help debugging!
|
||||
|
||||
## 0.8.0 - 2023-01-31
|
||||
|
||||
- Update dependency: realtime-csharp@5.0.0
|
||||
- Re: [#21](https://github.com/supabase-community/realtime-csharp/pull/21) Provide API for `presence`, `broadcast`
|
||||
and `postgres_changes`
|
||||
- [Major, New] `Channel.PostgresChanges` event will receive the wildcard `*` changes event,
|
||||
not `Channel.OnMessage`.
|
||||
- [Major] `Channel.OnInsert`, `Channel.OnUpdate`, and `Channel.OnDelete` now conform to the server's payload
|
||||
of `Response.Payload.**Data**`
|
||||
- [Major] `Channel.OnInsert`, `Channel.OnUpdate`, and `Channel.OnDelete` now return `PostgresChangesEventArgs`
|
||||
- [Minor] Rename `Channel` to `RealtimeChannel`
|
||||
- Supports better handling of disconnects in `RealtimeSocket` and adds a `Client.OnReconnect` event.
|
||||
- [Minor] Moves `ChannelOptions` to `Channel.ChannelOptions`
|
||||
- [Minor] Moves `ChannelStateChangedEventArgs` to `Channel.ChannelStateChangedEventArgs`
|
||||
- [Minor] Moves `Push` to `Channel.Push`
|
||||
- [Minor] Moves `Channel.ChannelState` to `Constants.ChannelState`
|
||||
- [Minor] Moves `SocketResponse`, `SocketRequest`, `SocketResponsePayload`, `SocketResponseEventArgs`,
|
||||
and `SocketStateChangedEventArgs` to `Socket` namespace.
|
||||
- [New] Adds `RealtimeBroadcast`
|
||||
- [New] Adds `RealtimePresence`
|
||||
- [Improvement] Better handling of disconnection/reconnection
|
||||
- Update dependency: postgrest-csharp@3.1.3
|
||||
- Another fix for [#61](https://github.com/supabase-community/postgrest-csharp/issues/61) which futher typechecks
|
||||
nullable values.
|
||||
|
||||
## 0.7.2 - 2023-01-27
|
||||
|
||||
- Update dependency: gotrue-csharp@3.0.4
|
||||
- Makes `Session.CreatedAt` a publicly settable property, which should fix incorrect dates on retrieved `Session`s.
|
||||
- Update dependency: postgrest-csharp@3.1.2
|
||||
- Fix [#61](https://github.com/supabase-community/postgrest-csharp/issues/61) which did not correctly parse
|
||||
Linq `Where` when encountering a nullable type.
|
||||
- Add missing support for transforming for `== null` and `!= null`
|
||||
|
||||
## 0.7.1 - 2023-01-17
|
||||
|
||||
- Update dependency: postgrest-csharp@3.1.1
|
||||
- Fix issue from supabase-community/supabase-csharp#48 where boolean model properties would not be evaluated in
|
||||
predicate expressions
|
||||
|
||||
## 0.7.0 - 2023-01-16
|
||||
|
||||
- Update dependency: postgrest-csharp@3.1.0
|
||||
- [Minor] Breaking API Change: `PrimaryKey` attribute defaults to `shouldInsert: false` as most uses will have the
|
||||
Database generate the primary key.
|
||||
- Merged [#60](https://github.com/supabase-community/postgrest-csharp/pull/60) which Added linq support
|
||||
for `Select`, `Where`, `OnConflict`, `Columns`, `Order`, `Update`, `Set`, and `Delete`
|
||||
|
||||
## 0.6.2 - 2022-11-22
|
||||
|
||||
- Update dependency: postgrest-csharp@3.0.4
|
||||
- `GetHeaders` is now passed to `ModeledResponse` and `BaseModel` so that the default `Update` and `Delete` methods
|
||||
use the latest credentials
|
||||
- `GetHeaders` is used in `Rpc` calls (re: [#39](https://github.com/supabase-community/supabase-csharp/issues/39))
|
||||
|
||||
## 0.6.1 - 2022-11-12
|
||||
|
||||
- [Hotfix] `GetHeaders` was not passing properly to `SupabaseTable` and `Gotrue.Api`
|
||||
|
||||
## 0.6.0 - 2022-11-12
|
||||
|
||||
[BREAKING CHANGES]
|
||||
|
||||
- `Client` is no longer a singleton, singleton interactions (if desired) are left to the developer to implement.
|
||||
- `Client` supports injection of dependent clients after initialization via property:
|
||||
- `Auth`
|
||||
- `Functions`
|
||||
- `Realtime`
|
||||
- `Postgrest`
|
||||
- `Storage`
|
||||
- `SupabaseModel` contains no logic but remains for backwards compatibility. (Marked `Obsolete`)
|
||||
- `ClientOptions.ShouldInitializeRealtime` was removed (no longer auto initialized)
|
||||
- `ClientOptions` now references an `ISupabaseSessionHandler` which specifies expected functionality for session
|
||||
persistence on Gotrue (replaces `ClientOptions.SessionPersistor`, `ClientOptions.SessionRetriever`,
|
||||
and `ClientOptions.SessionDestroyer`).
|
||||
- `supabase-csharp` and all child libraries now have support `nullity`
|
||||
|
||||
Other Changes:
|
||||
|
||||
- Update dependency: functions-csharp@1.2.1
|
||||
- Update dependency: gotrue-csharp@3.0.2
|
||||
- Update dependency: postgrest-csharp@3.0.2
|
||||
- Update dependency: realtime-csharp@4.0.1
|
||||
- Update dependency: supabase-storage-csharp@1.2.3
|
||||
- Update dependency: supabase-core@0.0.2
|
||||
|
||||
Big thank you to [@veleek](https://github.com/veleek) for his insight into these changes.
|
||||
|
||||
Re: [#35](https://github.com/supabase-community/supabase-csharp/issues/35), [#34](https://github.com/supabase-community/supabase-csharp/issues/34), [#23](https://github.com/supabase-community/supabase-csharp/issues/23), [#36](https://github.com/supabase-community/supabase-csharp/pull/36)
|
||||
|
||||
## 0.5.3 - 2022-10-11
|
||||
|
||||
- Update dependency: postgrest-csharp@2.1.0
|
||||
- [Minor] Breaking API change: Remove `BaseModel.PrimaryKeyValue` and `BaseModel.PrimaryKeyColumn` in favor of
|
||||
a `PrimaryKey` dictionary with support for composite keys.
|
||||
- Re: [#48](https://github.com/supabase-community/postgrest-csharp/issues/48) - Add support for derived models
|
||||
on `ReferenceAttribute`
|
||||
- Re: [#49](https://github.com/supabase-community/postgrest-csharp/issues/49) - Added `Match(T model)`
|
||||
|
||||
## 0.5.2 - 2022-9-13
|
||||
|
||||
- Update dependency: postgrest-csharp@2.0.12
|
||||
- Merged [#47](https://github.com/supabase-community/postgrest-csharp/pull/49) which added cancellation token
|
||||
support to `Table<T>` methods. Thanks [@devpikachu](https://github.com/devpikachu)!
|
||||
|
||||
## 0.5.1 - 2022-8-1
|
||||
|
||||
- Update dependency: postgrest-csharp@2.0.11
|
||||
- Update dependency: supabase-storage-csharp@1.1.1
|
||||
|
||||
## 0.5.0 - 2022-7-17
|
||||
|
||||
- Update dependency: postgrest-csharp@2.0.9
|
||||
- Update dependency: realtime-csharp@3.0.1
|
||||
- Update dependency: supabase-storage-csharp@1.1.0
|
||||
- API Change [Breaking/Minor] Library no longer uses `WebClient` and instead leverages `HttpClient`. Progress events
|
||||
on `Upload` and `Download` are now handled with `EventHandler<float>` instead of `WebClient` EventHandlers.
|
||||
|
||||
## 0.4.4 - 2022-5-24
|
||||
|
||||
- Update dependency: gotrue-csharp@2.4.5
|
||||
- Update dependency: postgrest-csharp@2.0.8
|
||||
|
||||
## 0.4.3 - 2022-5-13
|
||||
|
||||
- Update dependency: gotrue-csharp@2.4.4
|
||||
|
||||
## 0.4.2 - 2022-4-30
|
||||
|
||||
- Update dependency: gotrue-csharp@2.4.3
|
||||
|
||||
## 0.4.1 - 2022-4-23
|
||||
|
||||
- Update dependency: gotrue-csharp@2.4.2
|
||||
|
||||
## 0.4.0 - 2022-4-12
|
||||
|
||||
- Add support for functions-csharp@1.0.1, giving access to invoking Supabase's edge functions.
|
||||
- Update dependency: gotrue-csharp@2.4.1
|
||||
|
||||
## 0.3.5 - 2022-4-11
|
||||
|
||||
- Update dependency: postgres-csharp@2.0.7
|
||||
|
||||
## 0.3.4 - 2022-03-28
|
||||
|
||||
- Update dependency: gotrue-csharp@2.4.0
|
||||
|
||||
## 0.3.3 - 2022-02-27
|
||||
|
||||
- Update dependency: gotrue-csharp@2.3.6
|
||||
- Update dependency: supabase-storage-csharp@1.0.2
|
||||
|
||||
## 0.3.2 - 2022-02-18
|
||||
|
||||
- Update dependency: realtime-csharp@3.0.0
|
||||
- Exchange existing websocket client: [WebSocketSharp](https://github.com/sta/websocket-sharp)
|
||||
for [Marfusios/websocket-client](https://github.com/Marfusios/websocket-client) which adds support for Blazor WASM
|
||||
apps.
|
||||
Ref: [#14](https://github.com/supabase-community/realtime-csharp/pull/14)
|
||||
|
||||
## 0.3.1 - 2022-01-20
|
||||
|
||||
- Update dependency: gotrue-csharp@2.3.5
|
||||
- [#23](https://github.com/supabase-community/gotrue-csharp/pull/23) Added `redirect_url` option for MagicLink sign
|
||||
in (Thanks [@MisterJimson](https://github.com/MisterJimson))
|
||||
- [#21](https://github.com/supabase-community/gotrue-csharp/pull/21) Added SignOut method to Stateless Client (
|
||||
Thanks [@fplaras](https://github.com/fplaras))
|
||||
|
||||
## 0.3.0 - 2021-12-30
|
||||
|
||||
- Update dependency: postgrest-csharp@2.0.6
|
||||
- Add support for `NullValueHandling` to be specified on a `Column` Attribute and for it to be honored on Inserts
|
||||
and Updates. Defaults to: `NullValueHandling.Include`.
|
||||
- Implements [#38](https://github.com/supabase-community/postgrest-csharp/issues/38)
|
||||
- Update dependency: realtime-csharp@2.0.8
|
||||
- Implement Upstream Realtime RLS Error Broadcast Handler
|
||||
- Implements [#12](https://github.com/supabase-community/realtime-csharp/issues/12)
|
||||
- `SocketResponse` now exposes a method: `OldModel`, that hydrates the `OldRecord` property into a model.
|
||||
|
||||
## 0.2.12 - 2021-12-29
|
||||
|
||||
- Update dependency: gotrue-csharp@2.3.3
|
||||
- `SignUp` will return a `Session` with a _populated `User` object_ on an unconfirmed signup.
|
||||
- Fixes [#19](https://github.com/supabase-community/gotrue-csharp/issues/19)
|
||||
- Developers who were using a `null` check on `Session.User` will need to adjust accordingly.
|
||||
- Update dependency: postgrest-csharp@2.0.5
|
||||
- Fix for [#37](https://github.com/supabase-community/postgrest-csharp/issues/37) - Return Type `minimal` would fail
|
||||
to resolve because of incorrect `Accept` headers. Added header and test to verify for future.
|
||||
- Fix for [#36](https://github.com/supabase-community/postgrest-csharp/issues/36) - Inserting/Upserting bulk records
|
||||
would fail while doing an unnecessary generic coercion.
|
||||
|
||||
## 0.2.11 - 2021-12-24
|
||||
|
||||
- Update dependency: gotrue-csharp@2.3.2 (changes CreateUser parameters to conform to `AdminUserAttributes`)
|
||||
- See [#15](https://github.com/supabase-community/supabase-csharp/issues/15)
|
||||
- See [#16](https://github.com/supabase-community/supabase-csharp/issues/16)
|
||||
- Update dependency: realtime-csharp@2.0.7
|
||||
- See [#13](https://github.com/supabase-community/supabase-csharp/issues/13)
|
||||
|
||||
## 0.2.10 - 2021-12-23
|
||||
|
||||
- Update dependency: gotrue-csharp@2.3.0 (adds metadata support for user signup,
|
||||
see [#14](https://github.com/supabase/community/issues/14))
|
||||
|
||||
## 0.2.9 - 2021-12-9
|
||||
|
||||
- Separate Storage client from Supabase repo and into `storage-csharp`, `supabase-csharp` now references new repo.
|
||||
|
||||
## 0.2.8 - 2021-12-4
|
||||
|
||||
- Update gotrue-csharp to 2.2.4
|
||||
- Adds support for `ListUsers` (paginate, sort, filter), `GetUserById`, `CreateUser`, and `UpdateById`
|
||||
|
||||
## 0.2.7 - 2021-12-2
|
||||
|
||||
- Update gotrue-csharp to 2.2.3
|
||||
- Adds support for sending password resets to users.
|
||||
|
||||
## 0.2.6 - 2021-11-29
|
||||
|
||||
- Support for [#12](https://github.com/supabase-community/supabase-csharp/issues/12)
|
||||
- Update realtime-csharp to 2.0.6
|
||||
- Update gotrue-csharp to 2.2.2
|
||||
- Add `StatelessClient` re:[#7](https://github.com/supabase-community/supabase-csharp/issues/7)
|
||||
@@ -36,6 +36,13 @@ custom_edit_url: https://github.com/supabase/supabase/edit/master/web/spec/supab
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="pnpm" label="pnpm">
|
||||
|
||||
```sh Terminal
|
||||
pnpm add @supabase/supabase-js
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
</RefSubLayout.Examples>
|
||||
|
||||
@@ -96,6 +96,10 @@ const levelsData = {
|
||||
icon: 'reference-csharp',
|
||||
name: 'C# Reference v0.0',
|
||||
},
|
||||
reference_csharp_v1: {
|
||||
icon: 'reference-csharp',
|
||||
name: 'C# Reference v1.0',
|
||||
},
|
||||
reference_python_v2: {
|
||||
icon: 'reference-python',
|
||||
name: 'Python Reference v2.0',
|
||||
|
||||
@@ -8,15 +8,23 @@ type SendFeedbackVariables = {
|
||||
message: string
|
||||
isHelpful: boolean
|
||||
pathname?: string
|
||||
team?: string
|
||||
}
|
||||
|
||||
export async function sendFeedback({ message, pathname, title, isHelpful }: SendFeedbackVariables) {
|
||||
export async function sendFeedback({
|
||||
message,
|
||||
pathname,
|
||||
title,
|
||||
isHelpful,
|
||||
team,
|
||||
}: SendFeedbackVariables) {
|
||||
const { data, error } = await post('/platform/feedback/docs', {
|
||||
body: {
|
||||
page: pathname,
|
||||
isHelpful,
|
||||
title,
|
||||
feedback: message,
|
||||
team,
|
||||
},
|
||||
})
|
||||
if (error) throw Error(`Couldn't send feedback`, { cause: error })
|
||||
|
||||
@@ -68,6 +68,7 @@ const nextConfig = {
|
||||
'mdx-mermaid',
|
||||
'dayjs',
|
||||
'shared-data',
|
||||
'api-types',
|
||||
'icons',
|
||||
],
|
||||
async headers() {
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
"dev:secrets:pull": "AWS_PROFILE=supabase-dev node ../../scripts/getSecrets.js -n local/docs",
|
||||
"build": "next build",
|
||||
"build:analyze": "ANALYZE=true next build",
|
||||
"codegen": "openapi-typescript http://localhost:8080/api-json -o types/api.d.ts && prettier --write types/api.d.ts",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"typecheck": "tsc --noEmit",
|
||||
@@ -96,9 +95,9 @@
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"openapi-types": "^12.1.3",
|
||||
"openapi-typescript": "^6.7.4",
|
||||
"tsconfig": "*",
|
||||
"tsx": "^4.6.2",
|
||||
"typescript": "^5.4.3"
|
||||
"typescript": "^5.4.3",
|
||||
"api-types": "*"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import clientLibsCommonSections from '~/spec/common-client-libs-sections.json'
|
||||
import spec from '~/spec/supabase_csharp_v0.yml' assert { type: 'yml' }
|
||||
import spec from '~/spec/supabase_csharp_v1.yml' assert { type: 'yml' }
|
||||
import RefSectionHandler from '~/components/reference/RefSectionHandler'
|
||||
import { flattenSections } from '~/lib/helpers'
|
||||
import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths'
|
||||
@@ -12,7 +12,7 @@ const libraryPath = '/csharp'
|
||||
export default function CSharpReference(props) {
|
||||
return (
|
||||
<RefSectionHandler
|
||||
menuId={MenuId.RefCSharpV0}
|
||||
menuId={MenuId.RefCSharpV1}
|
||||
sections={sections}
|
||||
spec={spec}
|
||||
pageProps={props}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import clientLibsCommonSections from '~/spec/common-client-libs-sections.json'
|
||||
import typeSpec from '~/spec/enrichments/tsdoc_v2/combined.json'
|
||||
import spec from '~/spec/supabase_csharp_v0.yml' assert { type: 'yml' }
|
||||
import spec from '~/spec/supabase_csharp_v1.yml' assert { type: 'yml' }
|
||||
import RefSectionHandler from '~/components/reference/RefSectionHandler'
|
||||
import { flattenSections } from '~/lib/helpers'
|
||||
import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths'
|
||||
@@ -8,6 +8,7 @@ import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps'
|
||||
import { useRouter } from 'next/router'
|
||||
import RefSEO from '~/components/reference/RefSEO'
|
||||
import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
|
||||
import type { TypeSpec } from '~/components/reference/Reference.types'
|
||||
|
||||
const sections = flattenSections(clientLibsCommonSections)
|
||||
const libraryPath = '/csharp'
|
||||
@@ -26,10 +27,10 @@ export default function CSharpReference(props) {
|
||||
<RefSEO title={pageTitle} />
|
||||
|
||||
<RefSectionHandler
|
||||
menuId={MenuId.RefCSharpV0}
|
||||
menuId={MenuId.RefCSharpV1}
|
||||
sections={filteredSection}
|
||||
spec={spec}
|
||||
typeSpec={typeSpec}
|
||||
typeSpec={typeSpec as TypeSpec}
|
||||
pageProps={props}
|
||||
type="client-lib"
|
||||
/>
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,96 @@
|
||||
import { readdir } from 'node:fs/promises'
|
||||
import { extname, join } from 'node:path'
|
||||
import {
|
||||
HOMEPAGE_MENU_ITEMS,
|
||||
gettingstarted,
|
||||
cli,
|
||||
auth,
|
||||
database,
|
||||
api,
|
||||
graphql,
|
||||
functions,
|
||||
realtime,
|
||||
storage,
|
||||
ai,
|
||||
supabase_cli,
|
||||
platform,
|
||||
resources,
|
||||
self_hosting,
|
||||
migrate,
|
||||
} from '../../components/Navigation/NavigationMenu/NavigationMenu.constants'
|
||||
|
||||
const DOCS_ROOT_DIR = join(__dirname, '..', '..')
|
||||
const DEFAULT_DOCS_CONTENT_DIR = join(DOCS_ROOT_DIR, 'content')
|
||||
|
||||
const DOCS_CONTENT_DIR = process.env.DOCS_CONTENT_DIR || DEFAULT_DOCS_CONTENT_DIR
|
||||
|
||||
const IGNORE_LIST = [
|
||||
/**
|
||||
* This is linked from the Edge Functions examples page though not from the
|
||||
* main nav.
|
||||
*/
|
||||
'guides/functions/examples/github-actions',
|
||||
/**
|
||||
* Auth helpers are deprecated and hidden but still available for legacy
|
||||
* users if they need it.
|
||||
*/
|
||||
'guides/auth/auth-helpers',
|
||||
'guides/auth/auth-helpers/nextjs-pages',
|
||||
'guides/auth/auth-helpers/nextjs',
|
||||
'guides/auth/auth-helpers/remix',
|
||||
'guides/auth/auth-helpers/sveltekit',
|
||||
]
|
||||
|
||||
type RefItem = {
|
||||
href?: string
|
||||
url?: string
|
||||
items?: RefItem[]
|
||||
}
|
||||
|
||||
const recGetUrl = (items: readonly RefItem[], acc: string[] = []) =>
|
||||
items.reduce((acc, item) => {
|
||||
if (item.href || item.url) acc.push(item.href || item.url)
|
||||
if (item.items) acc.push(...recGetUrl(item.items, acc))
|
||||
return acc
|
||||
}, acc)
|
||||
|
||||
const main = async () => {
|
||||
try {
|
||||
const savedFiles = (await readdir(DOCS_CONTENT_DIR, { recursive: true }))
|
||||
.filter((file) => extname(file) === '.mdx')
|
||||
.map((file) => file.replace(/\.mdx$/, ''))
|
||||
|
||||
const flattenedHomepageMenuItems = HOMEPAGE_MENU_ITEMS.flat() as RefItem[]
|
||||
const pagesToPublish = [
|
||||
flattenedHomepageMenuItems,
|
||||
gettingstarted.items,
|
||||
cli.items,
|
||||
auth.items,
|
||||
database.items,
|
||||
api.items,
|
||||
graphql.items,
|
||||
functions.items,
|
||||
realtime.items,
|
||||
storage.items,
|
||||
ai.items,
|
||||
supabase_cli.items,
|
||||
platform.items,
|
||||
resources.items,
|
||||
self_hosting.items,
|
||||
migrate.items,
|
||||
]
|
||||
.flatMap((items) => recGetUrl(items))
|
||||
// Remove initial slash
|
||||
.map((path) => path.substring(1))
|
||||
|
||||
const extraPages = savedFiles.filter(
|
||||
(file) => !pagesToPublish.includes(file) && !IGNORE_LIST.includes(file)
|
||||
)
|
||||
console.log(extraPages)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
@@ -1,7 +1,7 @@
|
||||
clispec: '001'
|
||||
info:
|
||||
id: cli
|
||||
version: 1.157.2
|
||||
version: 1.163.2
|
||||
title: Supabase CLI
|
||||
language: sh
|
||||
source: https://github.com/supabase/cli
|
||||
@@ -3303,7 +3303,7 @@ commands:
|
||||
description: Update a preview branch by its ID.
|
||||
tags: []
|
||||
links: []
|
||||
usage: supabase branches update <branch-id> [flags]
|
||||
usage: supabase branches update [branch-id] [flags]
|
||||
subcommands: []
|
||||
flags:
|
||||
- id: git-branch
|
||||
@@ -3323,6 +3323,10 @@ commands:
|
||||
description: enable experimental features
|
||||
required: true
|
||||
default_value: 'false'
|
||||
- id: project-ref
|
||||
name: --project-ref <string>
|
||||
description: Project ref of the Supabase project.
|
||||
default_value: ''
|
||||
- id: supabase-branches-list
|
||||
title: supabase branches list
|
||||
summary: List all preview branches
|
||||
@@ -3337,13 +3341,17 @@ commands:
|
||||
description: enable experimental features
|
||||
required: true
|
||||
default_value: 'false'
|
||||
- id: project-ref
|
||||
name: --project-ref <string>
|
||||
description: Project ref of the Supabase project.
|
||||
default_value: ''
|
||||
- id: supabase-branches-get
|
||||
title: supabase branches get
|
||||
summary: Retrieve details of a preview branch
|
||||
description: Retrieve details of the specified preview branch.
|
||||
tags: []
|
||||
links: []
|
||||
usage: supabase branches get <branch-id>
|
||||
usage: supabase branches get [branch-id]
|
||||
subcommands: []
|
||||
flags:
|
||||
- id: experimental
|
||||
@@ -3351,6 +3359,10 @@ commands:
|
||||
description: enable experimental features
|
||||
required: true
|
||||
default_value: 'false'
|
||||
- id: project-ref
|
||||
name: --project-ref <string>
|
||||
description: Project ref of the Supabase project.
|
||||
default_value: ''
|
||||
- id: supabase-branches-disable
|
||||
title: supabase branches disable
|
||||
summary: Disable preview branching
|
||||
@@ -3365,13 +3377,17 @@ commands:
|
||||
description: enable experimental features
|
||||
required: true
|
||||
default_value: 'false'
|
||||
- id: project-ref
|
||||
name: --project-ref <string>
|
||||
description: Project ref of the Supabase project.
|
||||
default_value: ''
|
||||
- id: supabase-branches-delete
|
||||
title: supabase branches delete
|
||||
summary: Delete a preview branch
|
||||
description: Delete a preview branch by its ID.
|
||||
tags: []
|
||||
links: []
|
||||
usage: supabase branches delete <branch-id>
|
||||
usage: supabase branches delete [branch-id]
|
||||
subcommands: []
|
||||
flags:
|
||||
- id: experimental
|
||||
@@ -3379,6 +3395,10 @@ commands:
|
||||
description: enable experimental features
|
||||
required: true
|
||||
default_value: 'false'
|
||||
- id: project-ref
|
||||
name: --project-ref <string>
|
||||
description: Project ref of the Supabase project.
|
||||
default_value: ''
|
||||
- id: supabase-branches-create
|
||||
title: supabase branches create
|
||||
summary: Create a preview branch
|
||||
@@ -3494,6 +3514,10 @@ commands:
|
||||
description: enable experimental features
|
||||
required: true
|
||||
default_value: 'false'
|
||||
- id: project-ref
|
||||
name: --project-ref <string>
|
||||
description: Project ref of the Supabase project.
|
||||
default_value: ''
|
||||
- id: supabase-bootstrap
|
||||
title: supabase bootstrap
|
||||
summary: Bootstrap a Supabase project from a starter template
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"reference_dart_v2",
|
||||
"reference_python_v2",
|
||||
"reference_csharp_v0",
|
||||
"reference_csharp_v1",
|
||||
"reference_swift_v1",
|
||||
"reference_swift_v2",
|
||||
"reference_kotlin_v1",
|
||||
@@ -48,6 +49,7 @@
|
||||
"reference_dart_v1",
|
||||
"reference_python_v2",
|
||||
"reference_csharp_v0",
|
||||
"reference_csharp_v1",
|
||||
"reference_swift_v1",
|
||||
"reference_swift_v2",
|
||||
"reference_kotlin_v1",
|
||||
@@ -986,7 +988,6 @@
|
||||
"reference_dart_v1",
|
||||
"reference_dart_v2",
|
||||
"reference_python_v2",
|
||||
"reference_csharp_v0",
|
||||
"reference_swift_v1",
|
||||
"reference_swift_v2",
|
||||
"reference_kotlin_v1",
|
||||
|
||||
@@ -22,7 +22,7 @@ functions:
|
||||
Initializing a new client is pretty straightforward. Find your project url and public key from the
|
||||
admin panel and pass it into your client initialization function.
|
||||
|
||||
`supabase-csharp` is heavily dependent on Models deriving from `BaseModel`. To interact with the API, one must have the associated model (see example) specified.
|
||||
`Supabase` is heavily dependent on Models deriving from `BaseModel`. To interact with the API, one must have the associated model (see example) specified.
|
||||
|
||||
Leverage `Table`, `PrimaryKey`, and `Column` attributes to specify names of classes/properties that are different from their C# Versions.
|
||||
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -2954,6 +2954,95 @@ functions:
|
||||
By default, the data is returned in JSON format, but can also be returned as Comma Separated Values.
|
||||
hideCodeBlock: true
|
||||
isSpotlight: true
|
||||
- id: explain
|
||||
title: Using Explain
|
||||
description: |
|
||||
For debugging slow queries, you can get the [Postgres `EXPLAIN` execution plan](https://www.postgresql.org/docs/current/sql-explain.html) of a query
|
||||
using the `explain()` method. This works on any query, even for `rpc()` or writes.
|
||||
|
||||
Explain is not enabled by default as it can reveal sensitive information about your database.
|
||||
It's best to only enable this for testing environments but if you wish to enable it for production you can provide additional protection by using a `pre-request` function.
|
||||
|
||||
Follow the [Performance Debugging Guide](/docs/guides/database/debugging-performance) to enable the functionality on your project.
|
||||
examples:
|
||||
- id: get-execution-plan
|
||||
name: Get the execution plan
|
||||
code: |
|
||||
```swift
|
||||
try await supabase
|
||||
.from("countries")
|
||||
.select()
|
||||
.explain()
|
||||
.execute()
|
||||
.value
|
||||
```
|
||||
data:
|
||||
sql: |
|
||||
```sql
|
||||
create table
|
||||
countries (id int8 primary key, name text);
|
||||
|
||||
insert into
|
||||
countries (id, name)
|
||||
values
|
||||
(1, 'Afghanistan'),
|
||||
(2, 'Albania'),
|
||||
(3, 'Algeria');
|
||||
```
|
||||
response: |
|
||||
```
|
||||
Aggregate (cost=33.34..33.36 rows=1 width=112)
|
||||
-> Limit (cost=0.00..18.33 rows=1000 width=40)
|
||||
-> Seq Scan on countries (cost=0.00..22.00 rows=1200 width=40)
|
||||
```
|
||||
description: |
|
||||
By default, the data is returned in TEXT format, but can also be returned as JSON by using the `format` parameter.
|
||||
hideCodeBlock: true
|
||||
isSpotlight: true
|
||||
|
||||
- id: get-execution-plan-with-analyze-and-verbose
|
||||
name: Get the execution plan with analyze and verbose
|
||||
code: |
|
||||
```swift
|
||||
try await supabase
|
||||
.from("countries")
|
||||
.select()
|
||||
.explain(
|
||||
analyze: true,
|
||||
verbose: true
|
||||
)
|
||||
.execute()
|
||||
.value
|
||||
```
|
||||
data:
|
||||
sql: |
|
||||
```sql
|
||||
create table
|
||||
countries (id int8 primary key, name text);
|
||||
|
||||
insert into
|
||||
countries (id, name)
|
||||
values
|
||||
(1, 'Afghanistan'),
|
||||
(2, 'Albania'),
|
||||
(3, 'Algeria');
|
||||
```
|
||||
response: |
|
||||
```
|
||||
Aggregate (cost=33.34..33.36 rows=1 width=112) (actual time=0.041..0.041 rows=1 loops=1)
|
||||
Output: NULL::bigint, count(ROW(countries.id, countries.name)), COALESCE(json_agg(ROW(countries.id, countries.name)), '[]'::json), NULLIF(current_setting('response.headers'::text, true), ''::text), NULLIF(current_setting('response.status'::text, true), ''::text)
|
||||
-> Limit (cost=0.00..18.33 rows=1000 width=40) (actual time=0.005..0.006 rows=3 loops=1)
|
||||
Output: countries.id, countries.name
|
||||
-> Seq Scan on public.countries (cost=0.00..22.00 rows=1200 width=40) (actual time=0.004..0.005 rows=3 loops=1)
|
||||
Output: countries.id, countries.name
|
||||
Query Identifier: -4730654291623321173
|
||||
Planning Time: 0.407 ms
|
||||
Execution Time: 0.119 ms
|
||||
```
|
||||
description: |
|
||||
By default, the data is returned in TEXT format, but can also be returned as JSON by using the `format` parameter.
|
||||
hideCodeBlock: true
|
||||
isSpotlight: false
|
||||
- id: invoke
|
||||
title: invoke()
|
||||
description: |
|
||||
@@ -2997,6 +3086,18 @@ functions:
|
||||
|
||||
print(type(of: response)) // String?
|
||||
```
|
||||
- id: invocation-with-streamed-response
|
||||
name: Invocation with streamed response
|
||||
description: |
|
||||
- Use this method to return an `AsyncStream<Data>` when the function returns `Content-Type` = `text/event-stream`.
|
||||
isSpotlight: true
|
||||
code: |
|
||||
```swift
|
||||
var response = Data()
|
||||
for try await data try await supabase.functions._invokeWithStreamedResponse("hello") {
|
||||
response.append(data)
|
||||
}
|
||||
```
|
||||
- id: error-handling
|
||||
name: Error handling
|
||||
description: |
|
||||
@@ -3040,6 +3141,37 @@ functions:
|
||||
)
|
||||
)
|
||||
```
|
||||
- id: regional-invocation
|
||||
name: Invoking a Function in the UsEast1 region
|
||||
description: |
|
||||
Here are the available regions:
|
||||
- `FunctionRegion.any`
|
||||
- `FunctionRegion.apNortheast1`
|
||||
- `FunctionRegion.apNortheast2`
|
||||
- `FunctionRegion.apSouth1`
|
||||
- `FunctionRegion.apSoutheast1`
|
||||
- `FunctionRegion.apSoutheast2`
|
||||
- `FunctionRegion.caCentral1`
|
||||
- `FunctionRegion.euCentral1`
|
||||
- `FunctionRegion.euWest1`
|
||||
- `FunctionRegion.euWest2`
|
||||
- `FunctionRegion.euWest3`
|
||||
- `FunctionRegion.saEast1`
|
||||
- `FunctionRegion.usEast1`
|
||||
- `FunctionRegion.usWest1`
|
||||
- `FunctionRegion.usWest2`
|
||||
isSpotlight: true
|
||||
code: |
|
||||
```swift
|
||||
let response = try await supabase.functions
|
||||
.invoke(
|
||||
"hello",
|
||||
options: FunctionInvokeOptions(
|
||||
body: ["foo": "bar"],
|
||||
region: .usEast1
|
||||
)
|
||||
)
|
||||
```
|
||||
- id: calling-with-delete-verb
|
||||
name: Calling with DELETE HTTP verb
|
||||
description: |
|
||||
@@ -3084,6 +3216,7 @@ functions:
|
||||
- By default, listening to database changes is disabled for new projects due to database performance and security concerns. You can turn it on by managing Realtime's [replication](/docs/guides/api#realtime-api-overview).
|
||||
- You can receive the "previous" data for updates and deletes by setting the table's `REPLICA IDENTITY` to `FULL` (e.g., `ALTER TABLE your_table REPLICA IDENTITY FULL;`).
|
||||
- Row level security is not applied to delete statements. When RLS is enabled and replica identity is set to full, only the primary key is sent to clients.
|
||||
- Use AsyncStream or callbacks for listening to changes.
|
||||
examples:
|
||||
- id: listen-to-broadcast
|
||||
name: Listen to broadcast messages
|
||||
@@ -3113,6 +3246,31 @@ functions:
|
||||
}
|
||||
```
|
||||
|
||||
- id: listen-to-broadcast-callback
|
||||
name: Listen to broadcast messages using callback
|
||||
isSpotlight: true
|
||||
code: |
|
||||
```swift
|
||||
let channel = await supabase.channel("room1")
|
||||
|
||||
let subscription = await channel.onBroadcast(event: "cursor-pos") { message in
|
||||
print("Cursor position received", message.payload)
|
||||
}
|
||||
|
||||
await channel.subscribe()
|
||||
|
||||
await channel.broadcast(
|
||||
event: "cursor-pos",
|
||||
message: [
|
||||
"x": .double(.random(in: 0...1)),
|
||||
"y": .double(.random(in: 0...1))
|
||||
]
|
||||
)
|
||||
|
||||
// remove subscription some time later
|
||||
subscription.remove()
|
||||
```
|
||||
|
||||
- id: listen-to-presence-updates
|
||||
name: Listen to presence updates
|
||||
code: |
|
||||
@@ -3139,6 +3297,30 @@ functions:
|
||||
try await channel.track(PresenceState(username: "John"))
|
||||
}
|
||||
|
||||
- id: listen-to-presence-updates-callback
|
||||
name: Listen to presence updates using callback
|
||||
code: |
|
||||
```swift
|
||||
struct PresenceState: Codable {
|
||||
let username: String
|
||||
}
|
||||
|
||||
let channel = await supabase.channel("channelId")
|
||||
|
||||
let subscription = await channel.onPresenceChange() { presence in
|
||||
let joins = try presence.decodeJoins(as: PresenceState.self)
|
||||
let leaves = try presence.decodeLeaves(as: PresenceState.self)
|
||||
}
|
||||
|
||||
await channel.subscribe()
|
||||
|
||||
// Send your own state
|
||||
try await channel.track(PresenceState(username: "John"))
|
||||
|
||||
// remove subscription some time later
|
||||
subscription.remove()
|
||||
```
|
||||
|
||||
- id: listen-to-all-database-changes
|
||||
name: Listen to all database changes
|
||||
code: |
|
||||
@@ -3158,6 +3340,26 @@ functions:
|
||||
}
|
||||
}
|
||||
```
|
||||
- id: listen-to-all-database-changes-callback
|
||||
name: Listen to all database changes using callback
|
||||
code: |
|
||||
```swift
|
||||
let channel = await supabase.channel("channelId")
|
||||
|
||||
let subscription = await channel.onPostgresChange(AnyAction.self, schema: "public") { change in
|
||||
switch change {
|
||||
case .delete(let action): print("Deleted: \(action.oldRecord)")
|
||||
case .insert(let action): print("Inserted: \(action.record)")
|
||||
case .select(let action): print("Selected: \(action.record)")
|
||||
case .update(let action): print("Updated": \(action.oldRecord) with \(action.record)")
|
||||
}
|
||||
}
|
||||
|
||||
await channel.subscribe()
|
||||
|
||||
// remove subscription some time later
|
||||
subscription.remove()
|
||||
```
|
||||
- id: listen-to-a-specific-table
|
||||
name: Listen to a specific table
|
||||
code: |
|
||||
@@ -3181,6 +3383,30 @@ functions:
|
||||
}
|
||||
}
|
||||
```
|
||||
- id: listen-to-a-specific-table-callback
|
||||
name: Listen to a specific table using callback
|
||||
code: |
|
||||
```swift
|
||||
let channel = await supabase.channel("channelId")
|
||||
|
||||
let subscription = await channel.onPostgresChange(
|
||||
AnyAction.self,
|
||||
schema: "public",
|
||||
table: "users"
|
||||
) { change in
|
||||
switch change {
|
||||
case .delete(let action): print("Deleted: \(action.oldRecord)")
|
||||
case .insert(let action): print("Inserted: \(action.record)")
|
||||
case .select(let action): print("Selected: \(action.record)")
|
||||
case .update(let action): print("Updated": \(action.oldRecord) with \(action.record)")
|
||||
}
|
||||
}
|
||||
|
||||
await channel.subscribe()
|
||||
|
||||
// remove subscription some time later
|
||||
subscription.remove()
|
||||
```
|
||||
- id: listen-to-inserts
|
||||
name: Listen to inserts
|
||||
code: |
|
||||
@@ -3199,6 +3425,25 @@ functions:
|
||||
print("Inserted: \(insert.record)")
|
||||
}
|
||||
```
|
||||
- id: listen-to-inserts-callback
|
||||
name: Listen to inserts using callback
|
||||
code: |
|
||||
```swift
|
||||
let channel = await supabase.channel("channelId")
|
||||
|
||||
let subscription = await channel.onPostgresChange(
|
||||
InsertAction.self,
|
||||
schema: "public",
|
||||
table: "users"
|
||||
) { insert in
|
||||
print("Inserted: \(insert.record)")
|
||||
}
|
||||
|
||||
await channel.subscribe()
|
||||
|
||||
// remove subscription some time later
|
||||
subscription.remove()
|
||||
```
|
||||
- id: listen-to-updates
|
||||
name: Listen to updates
|
||||
description: |
|
||||
@@ -3224,6 +3469,32 @@ functions:
|
||||
print("Updated: \(update.oldRecord) with \(update.record)")
|
||||
}
|
||||
```
|
||||
- id: listen-to-updates-callback
|
||||
name: Listen to updates using callback
|
||||
description: |
|
||||
By default, Supabase will send only the updated record. If you want to receive the previous values as well you can
|
||||
enable full replication for the table you are listening too:
|
||||
|
||||
```sql
|
||||
alter table "your_table" replica identity full;
|
||||
```
|
||||
code: |
|
||||
```swift
|
||||
let channel = await supabase.channel("channelId")
|
||||
|
||||
let subscription = await channel.onPostgresChange(
|
||||
UpdateAction.self,
|
||||
schema: "public",
|
||||
table: "users"
|
||||
) { update in
|
||||
print("Updated: \(update.oldRecord) with \(update.record)")
|
||||
}
|
||||
|
||||
await channel.subscribe()
|
||||
|
||||
// remove subscription some time later
|
||||
subscription.remove()
|
||||
```
|
||||
- id: listen-to-deletes
|
||||
name: Listen to deletes
|
||||
description: |
|
||||
@@ -3249,6 +3520,32 @@ functions:
|
||||
print("Deleted: \(deletion.oldRecord)")
|
||||
}
|
||||
```
|
||||
- id: listen-to-deletes-callback
|
||||
name: Listen to deletes using callback
|
||||
description: |
|
||||
By default, Supabase does not send deleted records. If you want to receive the deleted record you can
|
||||
enable full replication for the table you are listening too:
|
||||
|
||||
```sql
|
||||
alter table "your_table" replica identity full;
|
||||
```
|
||||
code: |
|
||||
```swift
|
||||
let channel = await supabase.channel("channelId")
|
||||
|
||||
let subscription = await channel.onPostgresChange(
|
||||
DeleteAction.self,
|
||||
schema: "public",
|
||||
table: "users"
|
||||
) { deletion in
|
||||
print("Deleted: \(deletion.oldRecord)")
|
||||
}
|
||||
|
||||
await channel.subscribe()
|
||||
|
||||
// remove subscription some time later
|
||||
subscription.remove()
|
||||
```
|
||||
- id: listening-to-row-level-changes
|
||||
name: Listen to row level changes
|
||||
code: |
|
||||
@@ -3268,6 +3565,26 @@ functions:
|
||||
print("Deleted: \(deletion.oldRecord)")
|
||||
}
|
||||
```
|
||||
- id: listening-to-row-level-changes-callback
|
||||
name: Listen to row level changes using callback
|
||||
code: |
|
||||
```swift
|
||||
let channel = await supabase.channel("channelId")
|
||||
|
||||
let subscription = await channel.onPostgresChange(
|
||||
DeleteAction.self,
|
||||
schema: "public",
|
||||
table: "users",
|
||||
filter: "id=eq.1"
|
||||
) { deletion in
|
||||
print("Deleted: \(deletion.oldRecord)")
|
||||
}
|
||||
|
||||
await channel.subscribe()
|
||||
|
||||
// remove subscription some time later
|
||||
subscription.remove()
|
||||
```
|
||||
|
||||
- id: remove-channel
|
||||
description: |
|
||||
|
||||
Vendored
+2
-13659
File diff suppressed because it is too large.
Load diff
@@ -35,25 +35,24 @@ const PROVIDER_EMAIL = {
|
||||
type: 'boolean',
|
||||
},
|
||||
MAILER_OTP_EXP: {
|
||||
title: 'Mailer OTP Expiration',
|
||||
title: 'Email OTP Expiration',
|
||||
type: 'number',
|
||||
description: 'Duration before an email otp / link expires.',
|
||||
units: 'seconds',
|
||||
},
|
||||
PASSWORD_MIN_LENGTH: {
|
||||
title: 'Min password length',
|
||||
description: 'Users will not be able to use a password shorter than this.',
|
||||
MAILER_OTP_LENGTH: {
|
||||
title: 'Email OTP Length',
|
||||
type: 'number',
|
||||
description: 'Number of digits in the email OTP',
|
||||
units: 'number',
|
||||
},
|
||||
},
|
||||
validationSchema: object().shape({
|
||||
PASSWORD_MIN_LENGTH: number()
|
||||
.required('A password is required.')
|
||||
.min(6, 'Password length must be at least 6 characters long'),
|
||||
MAILER_OTP_EXP: number()
|
||||
.min(0, 'Must be more than 0')
|
||||
.max(86400, 'Must be no more than 86400')
|
||||
.required('This is required'),
|
||||
MAILER_OTP_LENGTH: number().min(6, 'Must be at least 6').max(10, 'Must be no more than 10'),
|
||||
}),
|
||||
misc: {
|
||||
iconKey: 'email-icon2',
|
||||
|
||||
+74
-27
@@ -1,15 +1,26 @@
|
||||
import { useEffect } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
|
||||
import { useDatabaseRolesQuery } from 'data/database-roles/database-roles-query'
|
||||
import { useTablesQuery } from 'data/tables/tables-query'
|
||||
import { Check, ChevronsUpDown } from 'lucide-react'
|
||||
import {
|
||||
Button,
|
||||
CommandEmpty_Shadcn_,
|
||||
CommandGroup_Shadcn_,
|
||||
CommandInput_Shadcn_,
|
||||
CommandItem_Shadcn_,
|
||||
CommandList_Shadcn_,
|
||||
Command_Shadcn_,
|
||||
FormControl_Shadcn_,
|
||||
FormField_Shadcn_,
|
||||
FormItem_Shadcn_,
|
||||
FormLabel_Shadcn_,
|
||||
FormMessage_Shadcn_,
|
||||
Input_Shadcn_,
|
||||
PopoverContent_Shadcn_,
|
||||
PopoverTrigger_Shadcn_,
|
||||
Popover_Shadcn_,
|
||||
RadioGroupLargeItem_Shadcn_,
|
||||
RadioGroup_Shadcn_,
|
||||
ScrollArea,
|
||||
@@ -24,6 +35,7 @@ import { MultiSelectV2 } from 'ui-patterns/MultiSelect/MultiSelectV2'
|
||||
interface PolicyDetailsV2Props {
|
||||
schema: string
|
||||
searchString?: string
|
||||
selectedTable?: string
|
||||
isEditing: boolean
|
||||
form: any
|
||||
onUpdateCommand: (command: string) => void
|
||||
@@ -32,11 +44,13 @@ interface PolicyDetailsV2Props {
|
||||
export const PolicyDetailsV2 = ({
|
||||
schema,
|
||||
searchString,
|
||||
selectedTable,
|
||||
isEditing,
|
||||
form,
|
||||
onUpdateCommand,
|
||||
}: PolicyDetailsV2Props) => {
|
||||
const { project } = useProjectContext()
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const { data: tables, isSuccess: isSuccessTables } = useTablesQuery({
|
||||
projectRef: project?.ref,
|
||||
@@ -62,7 +76,7 @@ export const PolicyDetailsV2 = ({
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
|
||||
useEffect(() => {
|
||||
if (!isEditing) {
|
||||
if (!isEditing && selectedTable === undefined) {
|
||||
const table = tables?.find(
|
||||
(table) =>
|
||||
table.schema === schema &&
|
||||
@@ -74,7 +88,7 @@ export const PolicyDetailsV2 = ({
|
||||
form.setValue('table', tables[0].name)
|
||||
}
|
||||
}
|
||||
}, [isEditing, form, searchString, tables, isSuccessTables])
|
||||
}, [isEditing, form, searchString, tables, isSuccessTables, selectedTable])
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -110,30 +124,63 @@ export const PolicyDetailsV2 = ({
|
||||
</p>
|
||||
</FormLabel_Shadcn_>
|
||||
<FormControl_Shadcn_>
|
||||
<Select_Shadcn_
|
||||
disabled={isEditing}
|
||||
value={field.value}
|
||||
onValueChange={(value) => form.setValue('table', value)}
|
||||
>
|
||||
<SelectTrigger_Shadcn_ className="text-sm h-10">
|
||||
{schema}.{field.value}
|
||||
</SelectTrigger_Shadcn_>
|
||||
<SelectContent_Shadcn_>
|
||||
<SelectGroup_Shadcn_>
|
||||
<ScrollArea className={(tables ?? []).length > 7 ? 'h-[200px]' : ''}>
|
||||
{(tables ?? []).map((table) => (
|
||||
<SelectItem_Shadcn_
|
||||
key={table.id}
|
||||
value={table.name}
|
||||
className="text-sm"
|
||||
>
|
||||
{table.name}
|
||||
</SelectItem_Shadcn_>
|
||||
))}
|
||||
</ScrollArea>
|
||||
</SelectGroup_Shadcn_>
|
||||
</SelectContent_Shadcn_>
|
||||
</Select_Shadcn_>
|
||||
<Popover_Shadcn_ open={open} onOpenChange={setOpen} modal={false}>
|
||||
<PopoverTrigger_Shadcn_ asChild>
|
||||
<Button
|
||||
type="default"
|
||||
className="w-full [&>span]:w-full h-[38px] text-sm"
|
||||
iconRight={
|
||||
<ChevronsUpDown
|
||||
className="text-foreground-muted"
|
||||
strokeWidth={2}
|
||||
size={14}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<div className="w-full flex gap-1">
|
||||
<span className="text-foreground">
|
||||
{schema}.{field.value}
|
||||
</span>
|
||||
</div>
|
||||
</Button>
|
||||
</PopoverTrigger_Shadcn_>
|
||||
<PopoverContent_Shadcn_
|
||||
className="p-0"
|
||||
side="bottom"
|
||||
align="start"
|
||||
sameWidthAsTrigger
|
||||
>
|
||||
<Command_Shadcn_>
|
||||
<CommandInput_Shadcn_ placeholder="Find a table..." />
|
||||
<CommandList_Shadcn_>
|
||||
<CommandEmpty_Shadcn_>No tables found</CommandEmpty_Shadcn_>
|
||||
<CommandGroup_Shadcn_>
|
||||
<ScrollArea className={(tables ?? []).length > 7 ? 'h-[200px]' : ''}>
|
||||
{(tables ?? []).map((table) => (
|
||||
<CommandItem_Shadcn_
|
||||
key={table.id}
|
||||
className="cursor-pointer flex items-center justify-between space-x-2 w-full"
|
||||
onSelect={() => {
|
||||
form.setValue('table', table.name)
|
||||
setOpen(false)
|
||||
}}
|
||||
onClick={() => {
|
||||
form.setValue('table', table.name)
|
||||
setOpen(false)
|
||||
}}
|
||||
>
|
||||
<span className="flex items-center gap-1.5">
|
||||
{field.value === table.name ? <Check size={13} /> : ''}
|
||||
{table.name}
|
||||
</span>
|
||||
</CommandItem_Shadcn_>
|
||||
))}
|
||||
</ScrollArea>
|
||||
</CommandGroup_Shadcn_>
|
||||
</CommandList_Shadcn_>
|
||||
</Command_Shadcn_>
|
||||
</PopoverContent_Shadcn_>
|
||||
</Popover_Shadcn_>
|
||||
</FormControl_Shadcn_>
|
||||
<FormMessage_Shadcn_ />
|
||||
</FormItem_Shadcn_>
|
||||
|
||||
@@ -70,6 +70,7 @@ interface AIPolicyEditorPanelProps {
|
||||
visible: boolean
|
||||
schema: string
|
||||
searchString?: string
|
||||
selectedTable?: string
|
||||
selectedPolicy?: PostgresPolicy
|
||||
onSelectCancel: () => void
|
||||
}
|
||||
@@ -81,6 +82,7 @@ export const AIPolicyEditorPanel = memo(function ({
|
||||
visible,
|
||||
schema,
|
||||
searchString,
|
||||
selectedTable,
|
||||
selectedPolicy,
|
||||
onSelectCancel,
|
||||
}: AIPolicyEditorPanelProps) {
|
||||
@@ -415,6 +417,8 @@ export const AIPolicyEditorPanel = memo(function ({
|
||||
if (selectedPolicy.check && selectedPolicy.command !== 'INSERT') {
|
||||
setShowCheckBlock(true)
|
||||
}
|
||||
} else if (selectedTable !== undefined) {
|
||||
form.reset({ ...defaultValues, table: selectedTable })
|
||||
}
|
||||
}
|
||||
}, [visible])
|
||||
@@ -535,6 +539,7 @@ export const AIPolicyEditorPanel = memo(function ({
|
||||
<PolicyDetailsV2
|
||||
schema={schema}
|
||||
searchString={searchString}
|
||||
selectedTable={selectedTable}
|
||||
isEditing={selectedPolicy !== undefined}
|
||||
form={form}
|
||||
onUpdateCommand={(command: string) => {
|
||||
|
||||
@@ -21,6 +21,7 @@ interface PoliciesProps {
|
||||
tables: PostgresTable[]
|
||||
hasTables: boolean
|
||||
isLocked: boolean
|
||||
onSelectCreatePolicy: (table: string) => void
|
||||
onSelectEditPolicy: (policy: PostgresPolicy) => void
|
||||
}
|
||||
|
||||
@@ -29,6 +30,7 @@ const Policies = ({
|
||||
tables,
|
||||
hasTables,
|
||||
isLocked,
|
||||
onSelectCreatePolicy,
|
||||
onSelectEditPolicy: onSelectEditPolicyAI,
|
||||
}: PoliciesProps) => {
|
||||
const router = useRouter()
|
||||
@@ -139,12 +141,13 @@ const Policies = ({
|
||||
<div className="flex flex-col gap-y-4 pb-4">
|
||||
{isLocked && <ProtectedSchemaWarning schema={schema} entity="policies" />}
|
||||
{tables.length > 0 ? (
|
||||
tables.map((table: any) => (
|
||||
tables.map((table) => (
|
||||
<section key={table.id}>
|
||||
<PolicyTableRow
|
||||
table={table}
|
||||
isLocked={isLocked}
|
||||
onSelectToggleRLS={onSelectToggleRLS}
|
||||
onSelectCreatePolicy={() => onSelectCreatePolicy(table.name)}
|
||||
onSelectEditPolicy={onSelectEditPolicy}
|
||||
onSelectDeletePolicy={onSelectDeletePolicy}
|
||||
/>
|
||||
|
||||
+17
-2
@@ -2,25 +2,31 @@ import * as Tooltip from '@radix-ui/react-tooltip'
|
||||
import type { PostgresTable } from '@supabase/postgres-meta'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
import { noop } from 'lodash'
|
||||
import { Lock } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
import { useIsRLSAIAssistantEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
|
||||
import { useCheckPermissions } from 'hooks'
|
||||
import { Badge, Button, IconLock } from 'ui'
|
||||
import { Badge, Button } from 'ui'
|
||||
|
||||
interface PolicyTableRowHeaderProps {
|
||||
table: PostgresTable
|
||||
isLocked: boolean
|
||||
onSelectToggleRLS: (table: PostgresTable) => void
|
||||
onSelectCreatePolicy: () => void
|
||||
}
|
||||
|
||||
const PolicyTableRowHeader = ({
|
||||
table,
|
||||
isLocked,
|
||||
onSelectToggleRLS = noop,
|
||||
onSelectCreatePolicy,
|
||||
}: PolicyTableRowHeaderProps) => {
|
||||
const router = useRouter()
|
||||
const { ref } = router.query
|
||||
|
||||
const isAiAssistantEnabled = useIsRLSAIAssistantEnabled()
|
||||
const canToggleRLS = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables')
|
||||
|
||||
return (
|
||||
@@ -33,7 +39,7 @@ const PolicyTableRowHeader = ({
|
||||
{isLocked && (
|
||||
<Badge>
|
||||
<span className="flex gap-2 items-center text-xs uppercase text-foreground-lighter">
|
||||
<IconLock width={12} /> Locked
|
||||
<Lock size={12} /> Locked
|
||||
</span>
|
||||
</Badge>
|
||||
)}
|
||||
@@ -73,6 +79,15 @@ const PolicyTableRowHeader = ({
|
||||
</Tooltip.Portal>
|
||||
)}
|
||||
</Tooltip.Root>
|
||||
{!isAiAssistantEnabled && (
|
||||
<Button
|
||||
type="default"
|
||||
disabled={!canToggleRLS}
|
||||
onClick={() => onSelectCreatePolicy()}
|
||||
>
|
||||
Create policy
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -13,6 +13,7 @@ interface PolicyTableRowProps {
|
||||
table: PostgresTable
|
||||
isLocked: boolean
|
||||
onSelectToggleRLS: (table: PostgresTable) => void
|
||||
onSelectCreatePolicy: () => void
|
||||
onSelectEditPolicy: (policy: PostgresPolicy) => void
|
||||
onSelectDeletePolicy: (policy: PostgresPolicy) => void
|
||||
}
|
||||
@@ -21,6 +22,7 @@ const PolicyTableRow = ({
|
||||
table,
|
||||
isLocked,
|
||||
onSelectToggleRLS = noop,
|
||||
onSelectCreatePolicy,
|
||||
onSelectEditPolicy = noop,
|
||||
onSelectDeletePolicy = noop,
|
||||
}: PolicyTableRowProps) => {
|
||||
@@ -41,6 +43,7 @@ const PolicyTableRow = ({
|
||||
table={table}
|
||||
isLocked={isLocked}
|
||||
onSelectToggleRLS={onSelectToggleRLS}
|
||||
onSelectCreatePolicy={onSelectCreatePolicy}
|
||||
/>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -40,6 +40,8 @@ const STATUS_TO_LABEL: Record<Status, string> = {
|
||||
RUNNING_MIGRATIONS: 'Running migrations',
|
||||
MIGRATIONS_FAILED: 'Migrations failed',
|
||||
MIGRATIONS_PASSED: 'Migrations applied successfully',
|
||||
FUNCTIONS_DEPLOYED: 'Functions deployed',
|
||||
FUNCTIONS_FAILED: 'Functions failed to deploy',
|
||||
}
|
||||
|
||||
const BranchStatusBadge = ({ status }: BranchStatusBadgeProps) => {
|
||||
|
||||
+5
-1
@@ -1,6 +1,6 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import Link from 'next/link'
|
||||
import { useRef } from 'react'
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { DragDropContext, Droppable, DroppableProvided } from 'react-beautiful-dnd'
|
||||
import { useFieldArray, useForm } from 'react-hook-form'
|
||||
import toast from 'react-hot-toast'
|
||||
@@ -51,6 +51,10 @@ const CreateEnumeratedTypeSidePanel = ({
|
||||
},
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
form.reset(initialValues)
|
||||
}, [visible])
|
||||
|
||||
const FormSchema = z.object({
|
||||
name: z
|
||||
.string()
|
||||
|
||||
@@ -17,3 +17,7 @@ export const HIDDEN_EXTENSIONS = [
|
||||
'xml2',
|
||||
'pg_tle',
|
||||
]
|
||||
|
||||
export const SEARCH_TERMS: Record<string, string[]> = {
|
||||
vector: ['pgvector', 'pg_vector'],
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import { useDatabaseExtensionsQuery } from 'data/database-extensions/database-ex
|
||||
import { useCheckPermissions, usePermissionsLoaded } from 'hooks'
|
||||
import ExtensionCard from './ExtensionCard'
|
||||
import ExtensionCardSkeleton from './ExtensionCardSkeleton'
|
||||
import { HIDDEN_EXTENSIONS } from './Extensions.constants'
|
||||
import { HIDDEN_EXTENSIONS, SEARCH_TERMS } from './Extensions.constants'
|
||||
|
||||
const Extensions = () => {
|
||||
const { filter } = useParams()
|
||||
@@ -28,11 +28,13 @@ const Extensions = () => {
|
||||
const extensions =
|
||||
filterString.length === 0
|
||||
? data ?? []
|
||||
: (data ?? []).filter((ext) =>
|
||||
// if the filter string contains pg, ignore it since some of the extensions may be listed without the
|
||||
// pg prefix (pgvector is listed as vector)
|
||||
ext.name.includes(filterString.replace('pg', ''))
|
||||
)
|
||||
: (data ?? []).filter((ext) => {
|
||||
const nameMatchesSearch = ext.name.toLowerCase().includes(filterString.toLowerCase())
|
||||
const searchTermsMatchesSearch = (SEARCH_TERMS[ext.name] || []).some((x) =>
|
||||
x.includes(filterString.toLowerCase())
|
||||
)
|
||||
return nameMatchesSearch || searchTermsMatchesSearch
|
||||
})
|
||||
const extensionsWithoutHidden = extensions.filter((ext) => !HIDDEN_EXTENSIONS.includes(ext.name))
|
||||
const [enabledExtensions, disabledExtensions] = partition(
|
||||
extensionsWithoutHidden,
|
||||
|
||||
@@ -60,7 +60,7 @@ const FormSchema = z.object({
|
||||
schema: z.string().trim().min(1),
|
||||
args: z.array(z.object({ name: z.string().trim().min(1), type: z.string().trim() })),
|
||||
behavior: z.enum(['IMMUTABLE', 'STABLE', 'VOLATILE']),
|
||||
definition: z.string().trim(),
|
||||
definition: z.string().trim().min(1),
|
||||
language: z.string().trim(),
|
||||
return_type: z.string().trim(),
|
||||
security_definer: z.boolean(),
|
||||
@@ -255,7 +255,9 @@ const CreateFunction = ({ func, visible, setVisible }: CreateFunctionProps) => {
|
||||
Definition
|
||||
</FormLabel_Shadcn_>
|
||||
<FormDescription_Shadcn_ className="text-sm text-foreground-light">
|
||||
<p>The language below should be written in `plpgsql`.</p>
|
||||
<p>
|
||||
The language below should be written in <code>plpgsql</code>.
|
||||
</p>
|
||||
{!isEditing && <p>Change the language in the Advanced Settings below.</p>}
|
||||
</FormDescription_Shadcn_>
|
||||
</div>
|
||||
@@ -272,7 +274,7 @@ const CreateFunction = ({ func, visible, setVisible }: CreateFunctionProps) => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<FormMessage_Shadcn_ />
|
||||
<FormMessage_Shadcn_ className="px-content" />
|
||||
</FormItem_Shadcn_>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -46,8 +46,8 @@ const DeleteFunction = ({ func, visible, setVisible }: DeleteFunctionProps) => {
|
||||
confirmString={name}
|
||||
text={
|
||||
<>
|
||||
This will delete the function
|
||||
<span className="text-bold text-foreground">{name}</span> from the schema
|
||||
<span>This will delete the function</span>{' '}
|
||||
<span className="text-bold text-foreground">{name}</span> <span>from the schema</span>{' '}
|
||||
<span className="text-bold text-foreground">{schema}</span>
|
||||
</>
|
||||
}
|
||||
|
||||
@@ -6,10 +6,11 @@ export const SUPABASE_ROLES = [
|
||||
'dashboard_user',
|
||||
'supabase_admin',
|
||||
'supabase_auth_admin',
|
||||
'supabase_functions_admin',
|
||||
'supabase_read_only_user',
|
||||
'supabase_realtime_admin',
|
||||
'supabase_replication_admin',
|
||||
'supabase_storage_admin',
|
||||
'supabase_functions_admin',
|
||||
'pgbouncer',
|
||||
'pgsodium_keyholder',
|
||||
'pgsodium_keyiduser',
|
||||
|
||||
@@ -230,9 +230,11 @@ const TableList = ({
|
||||
<p>{x.name}</p>
|
||||
)}
|
||||
</Table.td>
|
||||
<Table.td className="hidden max-w-sm truncate lg:table-cell break-all whitespace-normal">
|
||||
<Table.td className="hidden lg:table-cell ">
|
||||
{x.comment !== null ? (
|
||||
<p title={x.comment}>{x.comment}</p>
|
||||
<span className="lg:max-w-48 truncate inline-block" title={x.comment}>
|
||||
{x.comment}
|
||||
</span>
|
||||
) : (
|
||||
<p className="text-border-stronger">No description</p>
|
||||
)}
|
||||
|
||||
@@ -13,6 +13,7 @@ import { useOrganizationsQuery } from 'data/organizations/organizations-query'
|
||||
import { usePermissionsQuery } from 'data/permissions/permissions-query'
|
||||
import { ProjectInfo, useProjectsQuery } from 'data/projects/projects-query'
|
||||
import { ResourceWarning, useResourceWarningsQuery } from 'data/usage/resource-warnings-query'
|
||||
import { useIsFeatureEnabled } from 'hooks'
|
||||
import { IS_PLATFORM } from 'lib/constants'
|
||||
import { makeRandomString } from 'lib/helpers'
|
||||
import type { Organization, ResponseError } from 'types'
|
||||
@@ -244,17 +245,20 @@ const OrganizationProjects = ({
|
||||
}
|
||||
|
||||
const NoProjectsState = ({ slug }: { slug: string }) => {
|
||||
const projectCreationEnabled = useIsFeatureEnabled('projects:create')
|
||||
|
||||
return (
|
||||
<div className="col-span-4 space-y-4 rounded-lg border border-dashed p-6 text-center">
|
||||
<div className="space-y-1">
|
||||
<p>No projects</p>
|
||||
<p className="text-sm text-foreground-light">Get started by creating a new project.</p>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
{projectCreationEnabled && (
|
||||
<Button asChild icon={<IconPlus />}>
|
||||
<Link href={`/new/${slug}`}>New Project</Link>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
+6
-3
@@ -5,6 +5,7 @@ import ProjectLinker from 'components/interfaces/Integrations/ProjectLinker'
|
||||
import { Markdown } from 'components/interfaces/Markdown'
|
||||
import { useGitHubAuthorizationQuery } from 'data/integrations/github-authorization-query'
|
||||
import { useGitHubConnectionCreateMutation } from 'data/integrations/github-connection-create-mutation'
|
||||
import { useGitHubConnectionDeleteMutation } from 'data/integrations/github-connection-delete-mutation'
|
||||
import { useGitHubConnectionsQuery } from 'data/integrations/github-connections-query'
|
||||
import { useGitHubRepositoriesQuery } from 'data/integrations/github-repositories-query'
|
||||
import type { IntegrationConnectionsCreateVariables } from 'data/integrations/integrations.types'
|
||||
@@ -14,7 +15,6 @@ import { openInstallGitHubIntegrationWindow } from 'lib/github'
|
||||
import { EMPTY_ARR } from 'lib/void'
|
||||
import { useSidePanelsStateSnapshot } from 'state/side-panels'
|
||||
import { Button, SidePanel } from 'ui'
|
||||
import { useGitHubConnectionDeleteMutation } from 'data/integrations/github-connection-delete-mutation'
|
||||
|
||||
const GITHUB_ICON = (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 98 96" className="w-6">
|
||||
@@ -39,8 +39,11 @@ const SidePanelGitHubRepoLinker = ({ projectRef }: SidePanelGitHubRepoLinkerProp
|
||||
useGitHubAuthorizationQuery()
|
||||
|
||||
// [Alaister]: temp override with <any> until the typegen is fixed
|
||||
const { data: githubReposData, isLoading: isLoadingGitHubRepos } =
|
||||
useGitHubRepositoriesQuery<any[]>()
|
||||
const { data: githubReposData, isLoading: isLoadingGitHubRepos } = useGitHubRepositoriesQuery<
|
||||
any[]
|
||||
>({
|
||||
enabled: Boolean(gitHubAuthorization),
|
||||
})
|
||||
|
||||
/**
|
||||
* Supabase projects available
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
import * as Tooltip from '@radix-ui/react-tooltip'
|
||||
import Image from 'next/legacy/image'
|
||||
import { Dispatch, SetStateAction, useState } from 'react'
|
||||
import toast from 'react-hot-toast'
|
||||
|
||||
import Table from 'components/to-be-cleaned/Table'
|
||||
import { OrganizationMember } from 'data/organizations/organization-members-query'
|
||||
import { usePermissionsQuery } from 'data/permissions/permissions-query'
|
||||
import { useSelectedOrganization } from 'hooks'
|
||||
import { useProfile } from 'lib/profile'
|
||||
import { Role } from 'types'
|
||||
import { Badge, IconAlertCircle, IconCheck, IconLoader, IconUser, IconX, Listbox } from 'ui'
|
||||
import { getUserDisplayName, isInviteExpired } from '../Organization.utils'
|
||||
import MemberActions from './MemberActions'
|
||||
import { useGetRolesManagementPermissions } from './TeamSettings.utils'
|
||||
|
||||
export interface SelectedMember extends OrganizationMember {
|
||||
oldRoleId: number
|
||||
newRoleId: number
|
||||
}
|
||||
|
||||
interface MemberRowProps {
|
||||
member: OrganizationMember
|
||||
roles: Role[]
|
||||
isLoadingRoles?: boolean
|
||||
showMfaEnabledColumn?: boolean
|
||||
setUserRoleChangeModalVisible: Dispatch<SetStateAction<boolean>>
|
||||
setSelectedMember: Dispatch<SetStateAction<SelectedMember | undefined>>
|
||||
}
|
||||
|
||||
const MemberRow = ({
|
||||
member,
|
||||
roles,
|
||||
isLoadingRoles = false,
|
||||
showMfaEnabledColumn = false,
|
||||
setUserRoleChangeModalVisible,
|
||||
setSelectedMember,
|
||||
}: MemberRowProps) => {
|
||||
const { profile } = useProfile()
|
||||
const selectedOrganization = useSelectedOrganization()
|
||||
|
||||
const { data: permissions } = usePermissionsQuery()
|
||||
|
||||
const { rolesAddable, rolesRemovable } = useGetRolesManagementPermissions(
|
||||
selectedOrganization?.id,
|
||||
roles,
|
||||
permissions ?? []
|
||||
)
|
||||
|
||||
const [memberRoleId] = member.role_ids ?? []
|
||||
const role = (roles || []).find((role) => role.id === memberRoleId)
|
||||
const memberIsUser = member.primary_email == profile?.primary_email
|
||||
const isInvitedUser = Boolean(member.invited_id)
|
||||
const canRemoveRole = rolesRemovable.includes(memberRoleId)
|
||||
const disableRoleEdit = !canRemoveRole || memberIsUser || isInvitedUser
|
||||
const isEmailUser = member.username === member.primary_email
|
||||
|
||||
const validateSelectedRoleToChange = (roleId: any) => {
|
||||
if (!role || role.id === roleId) return
|
||||
|
||||
const selectedRole = (roles || []).find((role) => role.id === roleId)
|
||||
const canAddRole = rolesAddable.includes(selectedRole?.id ?? -1)
|
||||
|
||||
if (!canAddRole) {
|
||||
return toast.error(
|
||||
`You do not have permission to update this team member to ${selectedRole!.name}`
|
||||
)
|
||||
}
|
||||
|
||||
setUserRoleChangeModalVisible(true)
|
||||
setSelectedMember({ ...member, oldRoleId: role.id, newRoleId: roleId })
|
||||
}
|
||||
|
||||
const [hasInvalidImg, setHasInvalidImg] = useState(false)
|
||||
|
||||
return (
|
||||
<Table.tr>
|
||||
<Table.td>
|
||||
<div className="flex items-center space-x-4">
|
||||
<div>
|
||||
{isInvitedUser || isEmailUser || hasInvalidImg ? (
|
||||
<div className="w-[40px] h-[40px] bg-surface-100 border border-overlay rounded-full text-foreground-lighter flex items-center justify-center">
|
||||
<IconUser strokeWidth={1.5} />
|
||||
</div>
|
||||
) : (
|
||||
<Image
|
||||
alt={member.username}
|
||||
src={`https://github.com/${member.username}.png?size=80`}
|
||||
width="40"
|
||||
height="40"
|
||||
className="border rounded-full"
|
||||
onError={() => {
|
||||
setHasInvalidImg(true)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-foreground">{getUserDisplayName(member)}</p>
|
||||
{isInvitedUser === undefined && (
|
||||
<p className="text-foreground-light">{member.primary_email}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Table.td>
|
||||
|
||||
<Table.td>
|
||||
{isInvitedUser && member.invited_at && (
|
||||
<Badge variant={isInviteExpired(member.invited_at) ? 'destructive' : 'warning'}>
|
||||
{isInviteExpired(member.invited_at) ? 'Expired' : 'Invited'}
|
||||
</Badge>
|
||||
)}
|
||||
</Table.td>
|
||||
|
||||
{showMfaEnabledColumn && (
|
||||
<Table.td>
|
||||
<div className="flex items-center justify-center">
|
||||
{member.mfa_enabled ? (
|
||||
<IconCheck className="text-brand" strokeWidth={2} />
|
||||
) : (
|
||||
<IconX className="text-foreground-light" strokeWidth={1.5} />
|
||||
)}
|
||||
</div>
|
||||
</Table.td>
|
||||
)}
|
||||
|
||||
<Table.td>
|
||||
{isLoadingRoles ? (
|
||||
<div className="w-[140px]">
|
||||
<IconLoader className="animate-spin" size={16} strokeWidth={1.5} />
|
||||
</div>
|
||||
) : role !== undefined ? (
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
<Tooltip.Trigger className="w-[140px]" asChild>
|
||||
<div>
|
||||
<Listbox
|
||||
className={disableRoleEdit ? 'pointer-events-none' : ''}
|
||||
disabled={disableRoleEdit}
|
||||
value={role.id}
|
||||
onChange={validateSelectedRoleToChange}
|
||||
>
|
||||
{roles.map((r: any) => (
|
||||
<Listbox.Option
|
||||
key={r.id}
|
||||
value={r.id}
|
||||
label={r.name}
|
||||
disabled={disableRoleEdit}
|
||||
className="w-36"
|
||||
>
|
||||
{r.name}
|
||||
</Listbox.Option>
|
||||
))}
|
||||
</Listbox>
|
||||
</div>
|
||||
</Tooltip.Trigger>
|
||||
{isInvitedUser ? (
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-alternative py-1 px-2 leading-none shadow', // background
|
||||
'border border-background', //border
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-foreground">
|
||||
Role can only be changed after the user has accepted the invite
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
) : !memberIsUser && !canRemoveRole ? (
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-alternative py-1 px-2 leading-none shadow', // background
|
||||
'border border-background', //border
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-foreground">
|
||||
You need additional permissions to manage this team member
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</Tooltip.Root>
|
||||
) : (
|
||||
<div className="flex items-center space-x-2">
|
||||
<p className="text-sm text-foreground-light">Invalid role</p>
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
<Tooltip.Trigger>
|
||||
<IconAlertCircle size={16} strokeWidth={1.5} />
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-alternative py-1 px-2 leading-none shadow', // background
|
||||
'border border-background', //border
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-foreground">
|
||||
This user has an invalid role, please reach out to us via support
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
)}
|
||||
</Table.td>
|
||||
<Table.td>{!memberIsUser && <MemberActions member={member} roles={roles} />}</Table.td>
|
||||
</Table.tr>
|
||||
)
|
||||
}
|
||||
|
||||
export default MemberRow
|
||||
@@ -1,6 +1,4 @@
|
||||
import * as Tooltip from '@radix-ui/react-tooltip'
|
||||
import Image from 'next/legacy/image'
|
||||
import { Fragment, useState } from 'react'
|
||||
import { useState } from 'react'
|
||||
import toast from 'react-hot-toast'
|
||||
|
||||
import { useParams } from 'common'
|
||||
@@ -8,35 +6,12 @@ import Table from 'components/to-be-cleaned/Table'
|
||||
import AlertError from 'components/ui/AlertError'
|
||||
import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader'
|
||||
import { useOrganizationMemberUpdateMutation } from 'data/organizations/organization-member-update-mutation'
|
||||
import {
|
||||
OrganizationMember,
|
||||
useOrganizationMembersQuery,
|
||||
} from 'data/organizations/organization-members-query'
|
||||
import { useOrganizationMembersQuery } from 'data/organizations/organization-members-query'
|
||||
import { useOrganizationRolesQuery } from 'data/organizations/organization-roles-query'
|
||||
import { usePermissionsQuery } from 'data/permissions/permissions-query'
|
||||
import { useSelectedOrganization } from 'hooks'
|
||||
import { useProfile } from 'lib/profile'
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
IconAlertCircle,
|
||||
IconCheck,
|
||||
IconLoader,
|
||||
IconUser,
|
||||
IconX,
|
||||
Listbox,
|
||||
Loading,
|
||||
Modal,
|
||||
} from 'ui'
|
||||
import { getUserDisplayName, isInviteExpired } from '../Organization.utils'
|
||||
import MemberActions from './MemberActions'
|
||||
import { Button, IconAlertCircle, Loading, Modal } from 'ui'
|
||||
import { getUserDisplayName } from '../Organization.utils'
|
||||
import MemberRow, { SelectedMember } from './MemberRow'
|
||||
import RolesHelperModal from './RolesHelperModal/RolesHelperModal'
|
||||
import { useGetRolesManagementPermissions } from './TeamSettings.utils'
|
||||
|
||||
interface SelectedMember extends OrganizationMember {
|
||||
oldRoleId: number
|
||||
newRoleId: number
|
||||
}
|
||||
|
||||
export interface MembersViewProps {
|
||||
searchString: string
|
||||
@@ -44,10 +19,7 @@ export interface MembersViewProps {
|
||||
|
||||
const MembersView = ({ searchString }: MembersViewProps) => {
|
||||
const { slug } = useParams()
|
||||
const selectedOrganization = useSelectedOrganization()
|
||||
|
||||
const { profile } = useProfile()
|
||||
const { data: permissions } = usePermissionsQuery()
|
||||
const {
|
||||
data: members,
|
||||
error: membersError,
|
||||
@@ -75,11 +47,6 @@ const MembersView = ({ searchString }: MembersViewProps) => {
|
||||
|
||||
const allMembers = members ?? []
|
||||
const roles = rolesData?.roles ?? []
|
||||
const { rolesAddable, rolesRemovable } = useGetRolesManagementPermissions(
|
||||
selectedOrganization?.id,
|
||||
roles,
|
||||
permissions ?? []
|
||||
)
|
||||
|
||||
// [Joshen] Proactively adding this, can be removed once infra API changes are in
|
||||
const showMfaEnabledColumn = allMembers.some(
|
||||
@@ -91,21 +58,23 @@ const MembersView = ({ searchString }: MembersViewProps) => {
|
||||
const filteredMembers = (
|
||||
!searchString
|
||||
? allMembers
|
||||
: allMembers.filter((x: any) => {
|
||||
if (x.invited_at) {
|
||||
return x.primary_email.includes(searchString)
|
||||
: allMembers.filter((member) => {
|
||||
if (member.invited_at) {
|
||||
return member.primary_email?.includes(searchString)
|
||||
}
|
||||
if (x.id || x.gotrue_id) {
|
||||
return x.username.includes(searchString) || x.primary_email.includes(searchString)
|
||||
if (member.gotrue_id) {
|
||||
return (
|
||||
member.username.includes(searchString) || member.primary_email?.includes(searchString)
|
||||
)
|
||||
}
|
||||
})
|
||||
)
|
||||
.slice()
|
||||
.sort((a: any, b: any) => a.username.localeCompare(b.username))
|
||||
.sort((a, b) => a.username.localeCompare(b.username))
|
||||
|
||||
const getRoleNameById = (id: number | undefined) => {
|
||||
if (!roles) return id
|
||||
return roles.find((x: any) => x.id === id)?.name
|
||||
return roles.find((member) => member.id === id)?.name
|
||||
}
|
||||
|
||||
const handleRoleChange = async () => {
|
||||
@@ -150,188 +119,17 @@ const MembersView = ({ searchString }: MembersViewProps) => {
|
||||
<Table.th key="header-action"></Table.th>,
|
||||
]}
|
||||
body={[
|
||||
...filteredMembers.map((x, i: number) => {
|
||||
const [memberRoleId] = x.role_ids ?? []
|
||||
const role = (roles || []).find((role) => role.id === memberRoleId)
|
||||
const memberIsUser = x.primary_email == profile?.primary_email
|
||||
const memberIsPendingInvite = !!x.invited_id
|
||||
const canRemoveRole = rolesRemovable.includes(memberRoleId)
|
||||
const disableRoleEdit = !canRemoveRole || memberIsUser || memberIsPendingInvite
|
||||
const isEmailUser = x.username === x.primary_email
|
||||
|
||||
const validateSelectedRoleToChange = (roleId: any) => {
|
||||
if (!role || role.id === roleId) return
|
||||
|
||||
const selectedRole = (roles || []).find((role) => role.id === roleId)
|
||||
const canAddRole = rolesAddable.includes(selectedRole?.id ?? -1)
|
||||
|
||||
if (!canAddRole) {
|
||||
return toast.error(
|
||||
`You do not have permission to update this team member to ${
|
||||
selectedRole!.name
|
||||
}`
|
||||
)
|
||||
}
|
||||
|
||||
setUserRoleChangeModalVisible(true)
|
||||
setSelectedMember({ ...x, oldRoleId: role.id, newRoleId: roleId })
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment key={`member-row-${i}`}>
|
||||
<Table.tr>
|
||||
<Table.td>
|
||||
<div className="flex items-center space-x-4">
|
||||
<div>
|
||||
{x.invited_id ? (
|
||||
<span className="flex p-2 border-2 rounded-full border-strong">
|
||||
<IconUser size={20} strokeWidth={2} />
|
||||
</span>
|
||||
) : isEmailUser ? (
|
||||
<div className="w-[40px] h-[40px] bg-surface-100 border border-overlay rounded-full text-foreground-lighter flex items-center justify-center">
|
||||
<IconUser strokeWidth={1.5} />
|
||||
</div>
|
||||
) : (
|
||||
<Image
|
||||
alt={x.username}
|
||||
src={`https://github.com/${x.username}.png?size=80`}
|
||||
width="40"
|
||||
height="40"
|
||||
className="border rounded-full"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-foreground">{getUserDisplayName(x)}</p>
|
||||
{x.invited_id === undefined && (
|
||||
<p className="text-foreground-light">{x.primary_email}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Table.td>
|
||||
|
||||
<Table.td>
|
||||
{x.invited_id && x.invited_at && (
|
||||
<Badge
|
||||
variant={isInviteExpired(x.invited_at) ? 'destructive' : 'warning'}
|
||||
>
|
||||
{isInviteExpired(x.invited_at) ? 'Expired' : 'Invited'}
|
||||
</Badge>
|
||||
)}
|
||||
</Table.td>
|
||||
|
||||
{showMfaEnabledColumn && (
|
||||
<Table.td>
|
||||
<div className="flex items-center justify-center">
|
||||
{x.mfa_enabled ? (
|
||||
<IconCheck className="text-brand" strokeWidth={2} />
|
||||
) : (
|
||||
<IconX className="text-foreground-light" strokeWidth={1.5} />
|
||||
)}
|
||||
</div>
|
||||
</Table.td>
|
||||
)}
|
||||
|
||||
<Table.td>
|
||||
{isLoadingRoles ? (
|
||||
<div className="w-[140px]">
|
||||
<IconLoader className="animate-spin" size={16} strokeWidth={1.5} />
|
||||
</div>
|
||||
) : role !== undefined ? (
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
<Tooltip.Trigger className="w-[140px]" asChild>
|
||||
<div>
|
||||
<Listbox
|
||||
className={disableRoleEdit ? 'pointer-events-none' : ''}
|
||||
disabled={disableRoleEdit}
|
||||
value={role.id}
|
||||
onChange={validateSelectedRoleToChange}
|
||||
>
|
||||
{roles.map((r: any) => (
|
||||
<Listbox.Option
|
||||
key={r.id}
|
||||
value={r.id}
|
||||
label={r.name}
|
||||
disabled={disableRoleEdit}
|
||||
className="w-36"
|
||||
>
|
||||
{r.name}
|
||||
</Listbox.Option>
|
||||
))}
|
||||
</Listbox>
|
||||
</div>
|
||||
</Tooltip.Trigger>
|
||||
{memberIsPendingInvite ? (
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-alternative py-1 px-2 leading-none shadow', // background
|
||||
'border border-background', //border
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-foreground">
|
||||
Role can only be changed after the user has accepted the
|
||||
invite
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
) : !memberIsUser && !canRemoveRole ? (
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-alternative py-1 px-2 leading-none shadow', // background
|
||||
'border border-background', //border
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-foreground">
|
||||
You need additional permissions to manage this team member
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</Tooltip.Root>
|
||||
) : (
|
||||
<div className="flex items-center space-x-2">
|
||||
<p className="text-sm text-foreground-light">Invalid role</p>
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
<Tooltip.Trigger>
|
||||
<IconAlertCircle size={16} strokeWidth={1.5} />
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-alternative py-1 px-2 leading-none shadow', // background
|
||||
'border border-background', //border
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-foreground">
|
||||
This user has an invalid role, please reach out to us via
|
||||
support
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
)}
|
||||
</Table.td>
|
||||
<Table.td>
|
||||
{!memberIsUser && <MemberActions member={x} roles={roles} />}
|
||||
</Table.td>
|
||||
</Table.tr>
|
||||
</Fragment>
|
||||
)
|
||||
}),
|
||||
...filteredMembers.map((member) => (
|
||||
<MemberRow
|
||||
key={member.gotrue_id}
|
||||
member={member}
|
||||
roles={roles}
|
||||
isLoadingRoles={isLoadingRoles}
|
||||
showMfaEnabledColumn={showMfaEnabledColumn}
|
||||
setUserRoleChangeModalVisible={setUserRoleChangeModalVisible}
|
||||
setSelectedMember={setSelectedMember}
|
||||
/>
|
||||
)),
|
||||
...(searchString.length > 0 && filteredMembers.length === 0
|
||||
? [
|
||||
<Table.tr key="no-results" className="bg-panel-secondary-light">
|
||||
|
||||
@@ -50,7 +50,7 @@ const ProjectAPIDocs = () => {
|
||||
: 'SUPABASE_CLIENT_ANON_KEY'
|
||||
const endpoint =
|
||||
customDomainData?.customDomain?.status === 'active'
|
||||
? `https://${customDomainData.customDomain?.hostname}/auth/v1/callback`
|
||||
? `https://${customDomainData.customDomain?.hostname}`
|
||||
: `https://${data?.autoApiService.endpoint ?? ''}`
|
||||
|
||||
return (
|
||||
|
||||
@@ -28,7 +28,7 @@ export const QueryPerformanceFilterBar = ({
|
||||
}) => {
|
||||
const router = useRouter()
|
||||
const { project } = useProjectContext()
|
||||
const [showBottomSection, setShowBottomSection] = useLocalStorageQuery(
|
||||
const [showBottomSection] = useLocalStorageQuery(
|
||||
LOCAL_STORAGE_KEYS.QUERY_PERF_SHOW_BOTTOM_SECTION,
|
||||
true
|
||||
)
|
||||
|
||||
@@ -161,7 +161,7 @@ export const useRealtimeMessages = (
|
||||
}
|
||||
|
||||
// Finally, subscribe to the Channel we just setup
|
||||
newChannel.subscribe(async (status, error) => {
|
||||
newChannel.subscribe(async (status) => {
|
||||
if (status === 'SUBSCRIBED') {
|
||||
// Let LiveView know we connected so we can update the button text
|
||||
// pushMessageTo('#conn_info', 'broadcast_subscribed', { host: host })
|
||||
@@ -174,8 +174,6 @@ export const useRealtimeMessages = (
|
||||
payload: { name: name, t: performance.now() },
|
||||
})
|
||||
}
|
||||
} else if (status === 'CLOSED') {
|
||||
// console.log(`Realtime Channel status: ${status}`)
|
||||
} else if (status === 'CHANNEL_ERROR') {
|
||||
toast.error(
|
||||
'Failed to connect to the channel: This may be due to restrictive RLS policies. Check your role and try again.'
|
||||
@@ -184,9 +182,6 @@ export const useRealtimeMessages = (
|
||||
newChannel.unsubscribe()
|
||||
setChannel(undefined)
|
||||
setRealtimeConfig({ ...config, enabled: false })
|
||||
} else {
|
||||
// console.error(`Realtime Channel error status: ${status}`)
|
||||
// console.error(`Realtime Channel error: ${error}`)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ export const useQueryPerformanceQuery = ({
|
||||
searchQuery.length > 0 ? `statements.query ~ '${searchQuery}'` : '',
|
||||
]
|
||||
.filter((x) => x.length > 0)
|
||||
.join(' OR ')
|
||||
.join(' AND ')
|
||||
|
||||
const orderBySql = orderBy && `ORDER BY ${orderBy.column} ${orderBy.order}`
|
||||
const sql = baseSQL.sql([], whereSql.length > 0 ? `WHERE ${whereSql}` : undefined, orderBySql)
|
||||
|
||||
@@ -164,10 +164,7 @@ export function ChartConfig({ results = { rows: [] }, config, onConfigChange }:
|
||||
id="cumulative"
|
||||
name="cumulative"
|
||||
checked={config.cumulative}
|
||||
onClick={(e) => {
|
||||
console.log(e)
|
||||
onConfigChange({ ...config, cumulative: !config.cumulative })
|
||||
}}
|
||||
onClick={() => onConfigChange({ ...config, cumulative: !config.cumulative })}
|
||||
/>
|
||||
<Label_Shadcn_ className="text-foreground-light p-1.5" htmlFor="cumulative">
|
||||
Cumulative
|
||||
|
||||
@@ -55,23 +55,35 @@ const Results = ({ id, rows }: { id: string; rows: readonly any[] }) => {
|
||||
return <div className="flex h-full items-center justify-center font-mono text-xs">{name}</div>
|
||||
}
|
||||
|
||||
const columns: CalculatedColumn<any>[] = Object.keys(rows?.[0] ?? []).map((key, idx) => ({
|
||||
idx,
|
||||
key,
|
||||
name: key,
|
||||
resizable: true,
|
||||
parent: undefined,
|
||||
level: 0,
|
||||
width: 120,
|
||||
minWidth: 120,
|
||||
maxWidth: undefined,
|
||||
draggable: false,
|
||||
frozen: false,
|
||||
sortable: false,
|
||||
isLastFrozenColumn: false,
|
||||
renderCell: ({ row }: any) => formatter(key, row),
|
||||
renderHeaderCell: () => columnRender(key),
|
||||
}))
|
||||
const EST_CHAR_WIDTH = 8.25
|
||||
const MIN_COLUMN_WIDTH = 100
|
||||
const MAX_COLUMN_WIDTH = 500
|
||||
|
||||
const columns: CalculatedColumn<any>[] = Object.keys(rows?.[0] ?? []).map((key, idx) => {
|
||||
const maxColumnValueLength = Math.max(...rows.map((row) => String(row[key]).length))
|
||||
const columnWidth = Math.max(
|
||||
Math.min(maxColumnValueLength * EST_CHAR_WIDTH, MAX_COLUMN_WIDTH),
|
||||
MIN_COLUMN_WIDTH
|
||||
)
|
||||
|
||||
return {
|
||||
idx,
|
||||
key,
|
||||
name: key,
|
||||
resizable: true,
|
||||
parent: undefined,
|
||||
level: 0,
|
||||
width: columnWidth,
|
||||
minWidth: MIN_COLUMN_WIDTH,
|
||||
maxWidth: undefined,
|
||||
draggable: false,
|
||||
frozen: false,
|
||||
sortable: false,
|
||||
isLastFrozenColumn: false,
|
||||
renderCell: ({ row }: any) => formatter(key, row),
|
||||
renderHeaderCell: () => columnRender(key),
|
||||
}
|
||||
})
|
||||
|
||||
function onSelectedCellChange(position: any) {
|
||||
setCellPosition(position)
|
||||
|
||||
@@ -2,7 +2,6 @@ import { useQueryClient } from '@tanstack/react-query'
|
||||
import { useParams } from 'common'
|
||||
import toast from 'react-hot-toast'
|
||||
|
||||
import { useContentQuery } from 'data/content/content-query'
|
||||
import { useContentUpsertMutation } from 'data/content/content-upsert-mutation'
|
||||
import { contentKeys } from 'data/content/keys'
|
||||
import { useSqlEditorStateSnapshot } from 'state/sql-editor'
|
||||
@@ -38,7 +37,6 @@ const UtilityPanel = ({
|
||||
}: UtilityPanelProps) => {
|
||||
const snap = useSqlEditorStateSnapshot()
|
||||
const { ref } = useParams()
|
||||
const { data } = useContentQuery(ref)
|
||||
const snippet = snap.snippets[id]?.snippet
|
||||
|
||||
const queryKeys = contentKeys.list(ref)
|
||||
|
||||
@@ -1,30 +1,42 @@
|
||||
import * as Tooltip from '@radix-ui/react-tooltip'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
import { PermissionAction, SupportCategories } from '@supabase/shared-types/out/constants'
|
||||
import dayjs from 'dayjs'
|
||||
import { Info } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { useState } from 'react'
|
||||
import toast from 'react-hot-toast'
|
||||
import { number, object } from 'yup'
|
||||
|
||||
import { useParams } from 'common'
|
||||
import { Markdown } from 'components/interfaces/Markdown'
|
||||
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
|
||||
import { FormHeader } from 'components/ui/Forms'
|
||||
import Panel from 'components/ui/Panel'
|
||||
import { useProjectDiskResizeMutation } from 'data/config/project-disk-resize-mutation'
|
||||
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
|
||||
import { useCheckPermissions, useSelectedOrganization } from 'hooks'
|
||||
import { Alert, Button, Form, InputNumber, Modal } from 'ui'
|
||||
import {
|
||||
AlertDescription_Shadcn_,
|
||||
AlertTitle_Shadcn_,
|
||||
Alert_Shadcn_,
|
||||
Button,
|
||||
Form,
|
||||
InputNumber,
|
||||
Modal,
|
||||
} from 'ui'
|
||||
import { WarningIcon } from 'ui-patterns/Icons/StatusIcons'
|
||||
|
||||
export interface DiskSizeConfigurationProps {
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
const DiskSizeConfiguration = ({ disabled = false }: DiskSizeConfigurationProps) => {
|
||||
const { project } = useProjectContext()
|
||||
const organization = useSelectedOrganization()
|
||||
const { ref: projectRef } = useParams()
|
||||
const { project } = useProjectContext()
|
||||
const { lastDatabaseResizeAt } = project ?? {}
|
||||
|
||||
const organization = useSelectedOrganization()
|
||||
|
||||
const timeTillNextAvailableDatabaseResize =
|
||||
lastDatabaseResizeAt === null ? 0 : 6 * 60 - dayjs().diff(lastDatabaseResizeAt, 'minutes')
|
||||
const isAbleToResizeDatabase = timeTillNextAvailableDatabaseResize <= 0
|
||||
@@ -68,9 +80,9 @@ const DiskSizeConfiguration = ({ disabled = false }: DiskSizeConfigurationProps)
|
||||
const diskSizeValidationSchema = object({
|
||||
'new-disk-size': number()
|
||||
.required('Please enter a GB amount you want to resize the disk up to.')
|
||||
.moreThan(Number(currentDiskSize ?? 0), `Must be more than ${currentDiskSize} GB`)
|
||||
.min(Number(currentDiskSize ?? 0), `Must be more than ${currentDiskSize} GB`)
|
||||
// to do, update with max_disk_volume_size_gb
|
||||
.lessThan(Number(maxDiskSize), 'Must be no more than 200 GB'),
|
||||
.max(Number(maxDiskSize), 'Must not be more than 200 GB'),
|
||||
})
|
||||
|
||||
return (
|
||||
@@ -129,51 +141,33 @@ const DiskSizeConfiguration = ({ disabled = false }: DiskSizeConfigurationProps)
|
||||
</div>
|
||||
</Panel.Content>
|
||||
</Panel>
|
||||
<Alert withIcon variant="info" title={'Importing a lot of data?'}>
|
||||
<p className=" max-w-2xl">
|
||||
We auto-scale your disk as you need more storage, but can only do this every 6 hours.
|
||||
If you upload more than 1.5x the current size of your storage, your database will go
|
||||
into read-only mode. If you know how big your database is going to be, you can
|
||||
manually increase the size here.
|
||||
</p>
|
||||
<Alert_Shadcn_>
|
||||
<Info size={16} />
|
||||
<AlertTitle_Shadcn_>Importing a lot of data?</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>
|
||||
<Markdown
|
||||
className="max-w-full"
|
||||
content={`
|
||||
We auto-scale your disk as you need more storage, but can only do this every 6 hours.
|
||||
If you upload more than 1.5x the current size of your storage, your database will go
|
||||
into read-only mode. If you know how big your database is going to be, you can
|
||||
manually increase the size here.
|
||||
|
||||
<p className="mt-4">
|
||||
Read more about{' '}
|
||||
<a
|
||||
className="underline"
|
||||
href="https://supabase.com/docs/guides/platform/database-size#disk-management"
|
||||
>
|
||||
disk management
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
</Alert>
|
||||
Read more about [disk management](https://supabase.com/docs/guides/platform/database-size#disk-management).
|
||||
`}
|
||||
/>
|
||||
</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
</div>
|
||||
) : (
|
||||
<Alert
|
||||
withIcon
|
||||
variant="info"
|
||||
title={
|
||||
projectSubscriptionData?.plan?.id === 'free'
|
||||
<Alert_Shadcn_>
|
||||
<Info size={16} />
|
||||
<AlertTitle_Shadcn_>
|
||||
{projectSubscriptionData?.plan?.id === 'free'
|
||||
? 'Disk size configuration is not available for projects on the Free plan'
|
||||
: 'Disk size configuration is only available when disabling the spend cap.'
|
||||
}
|
||||
actions={
|
||||
<Button asChild type="default">
|
||||
<Link
|
||||
href={`/org/${organization?.slug}/billing?panel=${
|
||||
projectSubscriptionData?.plan?.id === 'free' ? 'subscriptionPlan' : 'costControl'
|
||||
}`}
|
||||
target="_blank"
|
||||
>
|
||||
{projectSubscriptionData?.plan?.id === 'free'
|
||||
? 'Upgrade subscription'
|
||||
: 'Disable spend cap'}
|
||||
</Link>
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<div>
|
||||
: 'Disk size configuration is only available when the spend cap has been disabled'}
|
||||
</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>
|
||||
{projectSubscriptionData?.plan?.id === 'free' ? (
|
||||
<p>
|
||||
If you are intending to use more than 500MB of disk space, then you will need to
|
||||
@@ -185,8 +179,20 @@ const DiskSizeConfiguration = ({ disabled = false }: DiskSizeConfigurationProps)
|
||||
disable your spend cap.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</Alert>
|
||||
<Button asChild type="default" className="mt-3">
|
||||
<Link
|
||||
href={`/org/${organization?.slug}/billing?panel=${
|
||||
projectSubscriptionData?.plan?.id === 'free' ? 'subscriptionPlan' : 'costControl'
|
||||
}`}
|
||||
target="_blank"
|
||||
>
|
||||
{projectSubscriptionData?.plan?.id === 'free'
|
||||
? 'Upgrade subscription'
|
||||
: 'Disable spend cap'}
|
||||
</Link>
|
||||
</Button>
|
||||
</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
)}
|
||||
|
||||
<Modal
|
||||
@@ -205,29 +211,42 @@ const DiskSizeConfiguration = ({ disabled = false }: DiskSizeConfigurationProps)
|
||||
>
|
||||
{() =>
|
||||
currentDiskSize >= maxDiskSize ? (
|
||||
<>
|
||||
<Alert withIcon variant="warning" title="Maximum manual disk size increase reached">
|
||||
You cannot manually expand the disk size any more than {maxDiskSize} GB. If you
|
||||
need more than this, contact us to learn more about the Enterprise plan.
|
||||
</Alert>
|
||||
</>
|
||||
<Alert_Shadcn_ variant="warning" className="rounded-t-none border-0">
|
||||
<WarningIcon />
|
||||
<AlertTitle_Shadcn_>Maximum manual disk size increase reached</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>
|
||||
<p>
|
||||
You cannot manually expand the disk size any more than {maxDiskSize}GB. If you
|
||||
need more than this, contact us via support for help.
|
||||
</p>
|
||||
<Button asChild type="default" className="mt-3">
|
||||
<Link
|
||||
href={`/support/new?ref=${projectRef}&category=${SupportCategories.PERFORMANCE_ISSUES}&subject=Increase%20disk%20size%20beyond%20200GB`}
|
||||
>
|
||||
Contact support
|
||||
</Link>
|
||||
</Button>
|
||||
</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
) : (
|
||||
<>
|
||||
<Modal.Content>
|
||||
<div className="w-full space-y-4 py-6">
|
||||
<Alert
|
||||
withIcon
|
||||
variant={isAbleToResizeDatabase ? 'info' : 'warning'}
|
||||
title="This operation is only possible every 6 hours"
|
||||
>
|
||||
{isAbleToResizeDatabase
|
||||
? `Upon updating your disk size, the next disk size update will only be available from ${dayjs().format(
|
||||
'DD MMM YYYY, HH:mm (ZZ)'
|
||||
)}`
|
||||
: `Your database was last resized at ${dayjs(lastDatabaseResizeAt).format(
|
||||
'DD MMM YYYY, HH:mm (ZZ)'
|
||||
)}. You can resize your database again in approximately ${formattedTimeTillNextAvailableResize}`}
|
||||
</Alert>
|
||||
<Alert_Shadcn_ variant={isAbleToResizeDatabase ? 'default' : 'warning'}>
|
||||
<Info size={16} />
|
||||
<AlertTitle_Shadcn_>
|
||||
This operation is only possible every 6 hours
|
||||
</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>
|
||||
{isAbleToResizeDatabase
|
||||
? `Upon updating your disk size, the next disk size update will only be available from ${dayjs().format(
|
||||
'DD MMM YYYY, HH:mm (ZZ)'
|
||||
)}`
|
||||
: `Your database was last resized at ${dayjs(lastDatabaseResizeAt).format(
|
||||
'DD MMM YYYY, HH:mm (ZZ)'
|
||||
)}. You can resize your database again in approximately ${formattedTimeTillNextAvailableResize}`}
|
||||
</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
<InputNumber
|
||||
required
|
||||
id="new-disk-size"
|
||||
|
||||
@@ -155,7 +155,7 @@ const InfrastructureInfo = () => {
|
||||
<Input
|
||||
readOnly
|
||||
disabled
|
||||
label="GoTrue version"
|
||||
label="Auth version"
|
||||
value={project?.serviceVersions?.gotrue ?? ''}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -20,7 +20,7 @@ const ForgotPasswordForm = () => {
|
||||
const { mutate: resetPassword, isLoading } = useResetPasswordMutation({
|
||||
onSuccess: async () => {
|
||||
toast.success(
|
||||
`If you registered using your email and password, you will receive a password reset email.`
|
||||
`If you registered using your email and password, you will receive a password reset email. The password reset link expires in 10 minutes.`
|
||||
)
|
||||
await router.push('/sign-in')
|
||||
},
|
||||
|
||||
@@ -60,7 +60,7 @@ const SignUpForm = () => {
|
||||
>
|
||||
<Alert className="w-full" withIcon variant="success" title="Check your email to confirm">
|
||||
You've successfully signed up. Please check your email to confirm your account before
|
||||
signing in to the Supabase dashboard
|
||||
signing in to the Supabase dashboard. The confirmation link expires in 10 minutes.
|
||||
</Alert>
|
||||
</div>
|
||||
<Form
|
||||
|
||||
+27
-14
@@ -9,10 +9,6 @@ import {
|
||||
Alert_Shadcn_,
|
||||
Button,
|
||||
Checkbox,
|
||||
IconAlertCircle,
|
||||
IconAlertTriangle,
|
||||
IconExternalLink,
|
||||
IconPlus,
|
||||
Input,
|
||||
SidePanel,
|
||||
Toggle,
|
||||
@@ -31,7 +27,9 @@ import {
|
||||
} from 'data/database/foreign-key-constraints-query'
|
||||
import { useEnumeratedTypesQuery } from 'data/enumerated-types/enumerated-types-query'
|
||||
import { EXCLUDED_SCHEMAS_WITHOUT_EXTENSIONS } from 'lib/constants/schemas'
|
||||
import { ExternalLink, Plus } from 'lucide-react'
|
||||
import type { Dictionary } from 'types'
|
||||
import { WarningIcon } from 'ui-patterns/Icons/StatusIcons'
|
||||
import ActionBar from '../ActionBar'
|
||||
import type { ForeignKey } from '../ForeignKeySelector/ForeignKeySelector.types'
|
||||
import { formatForeignKeys } from '../ForeignKeySelector/ForeignKeySelector.utils'
|
||||
@@ -52,7 +50,6 @@ import {
|
||||
import ColumnForeignKey from './ColumnForeignKey'
|
||||
import ColumnType from './ColumnType'
|
||||
import HeaderTitle from './HeaderTitle'
|
||||
import toast from 'react-hot-toast'
|
||||
|
||||
export interface ColumnEditorProps {
|
||||
column?: PostgresColumn
|
||||
@@ -142,6 +139,22 @@ const ColumnEditor = ({
|
||||
changes.defaultValue = null
|
||||
}
|
||||
|
||||
const changedName = 'name' in changes && changes.name !== columnFields.name
|
||||
|
||||
if (
|
||||
changedName &&
|
||||
fkRelations.find((fk) => fk.columns.find(({ source }) => source === columnFields?.name))
|
||||
) {
|
||||
setFkRelations(
|
||||
fkRelations.map((relation) => ({
|
||||
...relation,
|
||||
columns: relation.columns.map((col) =>
|
||||
col.source === columnFields?.name ? { ...col, source: changes.name! } : col
|
||||
),
|
||||
}))
|
||||
)
|
||||
}
|
||||
|
||||
const updatedColumnFields = { ...columnFields, ...changes } as ColumnField
|
||||
setColumnFields(updatedColumnFields)
|
||||
updateEditorDirty()
|
||||
@@ -225,7 +238,7 @@ const ColumnEditor = ({
|
||||
asChild
|
||||
type="default"
|
||||
size="tiny"
|
||||
icon={<IconPlus size={14} strokeWidth={2} />}
|
||||
icon={<Plus size={14} strokeWidth={2} />}
|
||||
>
|
||||
<Link href={`/project/${ref}/database/types`} target="_blank" rel="noreferrer">
|
||||
Create enum types
|
||||
@@ -235,7 +248,7 @@ const ColumnEditor = ({
|
||||
asChild
|
||||
type="default"
|
||||
size="tiny"
|
||||
icon={<IconExternalLink size={14} strokeWidth={2} />}
|
||||
icon={<ExternalLink size={14} strokeWidth={2} />}
|
||||
>
|
||||
<Link
|
||||
href="https://supabase.com/docs/guides/database/tables#data-types"
|
||||
@@ -361,7 +374,7 @@ const ColumnEditor = ({
|
||||
>
|
||||
<FormSectionContent loading={false} className="lg:!col-span-8">
|
||||
<Alert_Shadcn_>
|
||||
<IconAlertCircle />
|
||||
<WarningIcon />
|
||||
<AlertTitle_Shadcn_>
|
||||
Column encryption has been removed from the GUI
|
||||
</AlertTitle_Shadcn_>
|
||||
@@ -376,7 +389,7 @@ const ColumnEditor = ({
|
||||
</Link>{' '}
|
||||
Transparent Column Encryption (TCE).
|
||||
</p>
|
||||
<Button asChild type="default" icon={<IconExternalLink />} className="mt-2">
|
||||
<Button asChild type="default" icon={<ExternalLink />} className="mt-2">
|
||||
<Link
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
@@ -391,16 +404,16 @@ const ColumnEditor = ({
|
||||
</FormSection>
|
||||
<SidePanel.Separator />
|
||||
|
||||
{/* TODO: need to pull column privileges in here
|
||||
if any columns are using column-level privileges, show this warning */}
|
||||
<FormSection
|
||||
{/* TODO: need to pull column privileges in here if any columns are using column-level privileges, show this warning */}
|
||||
{/* [Joshen] This shouldn't show up for all tables */}
|
||||
{/* <FormSection
|
||||
header={
|
||||
<FormSectionLabel className="lg:!col-span-4">Column privileges</FormSectionLabel>
|
||||
}
|
||||
>
|
||||
<FormSectionContent loading={false} className="lg:!col-span-8">
|
||||
<Alert_Shadcn_ variant="warning">
|
||||
<IconAlertTriangle strokeWidth={2} />
|
||||
<WarningIcon />
|
||||
<AlertTitle_Shadcn_>This table uses column-privileges</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>
|
||||
<p>
|
||||
@@ -417,7 +430,7 @@ const ColumnEditor = ({
|
||||
</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
</FormSectionContent>
|
||||
</FormSection>
|
||||
</FormSection> */}
|
||||
</>
|
||||
)}
|
||||
</SidePanel>
|
||||
|
||||
+4
-1
@@ -111,7 +111,10 @@ const ColumnForeignKey = ({
|
||||
columns:
|
||||
column.isNewColumn && column.name
|
||||
? (table.columns as any[]).concat(column)
|
||||
: (table.columns as any[]),
|
||||
: (table.columns as any[]).map((c) => {
|
||||
if (c.id === column.id) return { ...c, name: column.name }
|
||||
else return c
|
||||
}),
|
||||
}}
|
||||
foreignKey={selectedFk}
|
||||
onClose={() => {
|
||||
|
||||
@@ -351,7 +351,6 @@ const SidePanelEditor = ({
|
||||
})
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.log({ error })
|
||||
toast.error(`Failed to update realtime for ${table.name}: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -166,8 +166,7 @@ export const getAddForeignKeySQL = ({
|
||||
const onUpdateSql = getOnUpdateSql(updateAction)
|
||||
return `
|
||||
ALTER TABLE "${table.schema}"."${table.name}"
|
||||
ADD CONSTRAINT "${table.schema}_${table.name}_${relation.columns.map((x) => x.source).join('_')}_fkey"
|
||||
FOREIGN KEY (${relation.columns.map((column) => `"${column.source}"`).join(',')})
|
||||
ADD FOREIGN KEY (${relation.columns.map((column) => `"${column.source}"`).join(',')})
|
||||
REFERENCES "${relation.schema}"."${relation.table}" (${relation.columns.map((column) => `"${column.target}"`).join(',')})
|
||||
${onUpdateSql}
|
||||
${onDeleteSql}
|
||||
|
||||
+153
-11
@@ -1,13 +1,33 @@
|
||||
import { noop } from 'lodash'
|
||||
import { Checkbox, IconMenu, IconSettings, IconX, Input, Popover } from 'ui'
|
||||
import { Link, Menu, Plus, Settings, X } from 'lucide-react'
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
Checkbox,
|
||||
CommandGroup_Shadcn_,
|
||||
CommandItem_Shadcn_,
|
||||
CommandList_Shadcn_,
|
||||
CommandSeparator_Shadcn_,
|
||||
Command_Shadcn_,
|
||||
Input,
|
||||
Popover,
|
||||
PopoverContent_Shadcn_,
|
||||
PopoverTrigger_Shadcn_,
|
||||
Popover_Shadcn_,
|
||||
cn,
|
||||
} from 'ui'
|
||||
|
||||
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
|
||||
import { useForeignKeyConstraintsQuery } from 'data/database/foreign-key-constraints-query'
|
||||
import type { EnumeratedType } from 'data/enumerated-types/enumerated-types-query'
|
||||
import { EMPTY_ARR, EMPTY_OBJ } from 'lib/void'
|
||||
import { useState } from 'react'
|
||||
import { typeExpressionSuggestions } from '../ColumnEditor/ColumnEditor.constants'
|
||||
import type { Suggestion } from '../ColumnEditor/ColumnEditor.types'
|
||||
import ColumnType from '../ColumnEditor/ColumnType'
|
||||
import InputWithSuggestions from '../ColumnEditor/InputWithSuggestions'
|
||||
import { ForeignKey } from '../ForeignKeySelector/ForeignKeySelector.types'
|
||||
import type { ColumnField } from '../SidePanelEditor.types'
|
||||
import { checkIfRelationChanged } from './ForeignKeysManagement/ForeignKeysManagement.utils'
|
||||
|
||||
/**
|
||||
* [Joshen] For context:
|
||||
@@ -29,6 +49,7 @@ import type { ColumnField } from '../SidePanelEditor.types'
|
||||
|
||||
interface ColumnProps {
|
||||
column: ColumnField
|
||||
relations: ForeignKey[]
|
||||
enumTypes: EnumeratedType[]
|
||||
isNewRecord: boolean
|
||||
hasForeignKeys: boolean
|
||||
@@ -36,18 +57,23 @@ interface ColumnProps {
|
||||
dragHandleProps?: any
|
||||
onUpdateColumn: (changes: Partial<ColumnField>) => void
|
||||
onRemoveColumn: () => void
|
||||
onEditForeignKey: (relation?: ForeignKey) => void
|
||||
}
|
||||
|
||||
const Column = ({
|
||||
column = EMPTY_OBJ as ColumnField,
|
||||
relations = EMPTY_ARR as ForeignKey[],
|
||||
enumTypes = EMPTY_ARR as EnumeratedType[],
|
||||
isNewRecord = false,
|
||||
hasForeignKeys = false,
|
||||
hasImportContent = false,
|
||||
dragHandleProps = EMPTY_OBJ,
|
||||
onUpdateColumn = noop,
|
||||
onRemoveColumn = noop,
|
||||
onUpdateColumn,
|
||||
onRemoveColumn,
|
||||
onEditForeignKey,
|
||||
}: ColumnProps) => {
|
||||
const { project } = useProjectContext()
|
||||
const [open, setOpen] = useState(false)
|
||||
const suggestions: Suggestion[] = typeExpressionSuggestions?.[column.format] ?? []
|
||||
|
||||
const settingsCount = [
|
||||
@@ -57,26 +83,142 @@ const Column = ({
|
||||
column.isArray ? 1 : 0,
|
||||
].reduce((a, b) => a + b, 0)
|
||||
|
||||
const { data } = useForeignKeyConstraintsQuery({
|
||||
projectRef: project?.ref,
|
||||
connectionString: project?.connectionString,
|
||||
schema: column.schema,
|
||||
})
|
||||
|
||||
const getRelationStatus = (fk: ForeignKey) => {
|
||||
const existingRelation = (data ?? []).find((x) => x.id === fk.id)
|
||||
const stateRelation = relations.find((x) => x.id === fk.id)
|
||||
|
||||
if (stateRelation?.toRemove) return 'REMOVE'
|
||||
if (existingRelation === undefined && stateRelation !== undefined) return 'ADD'
|
||||
if (existingRelation !== undefined && stateRelation !== undefined) {
|
||||
const hasUpdated = checkIfRelationChanged(existingRelation, stateRelation)
|
||||
if (hasUpdated) return 'UPDATE'
|
||||
else return undefined
|
||||
}
|
||||
}
|
||||
|
||||
const hasChangesInRelations = relations
|
||||
.map((r) => getRelationStatus(r))
|
||||
.some((x) => x !== undefined)
|
||||
|
||||
return (
|
||||
<div className="flex w-full items-center">
|
||||
<div className={`w-[5%] ${!isNewRecord ? 'hidden' : ''}`}>
|
||||
<div className="cursor-drag" {...dragHandleProps}>
|
||||
<IconMenu strokeWidth={1} size={15} />
|
||||
<Menu strokeWidth={1} size={16} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-[25%]">
|
||||
<div className="flex w-[95%] items-center justify-between">
|
||||
<Input
|
||||
value={column.name}
|
||||
size="small"
|
||||
value={column.name}
|
||||
title={column.name}
|
||||
disabled={hasImportContent}
|
||||
placeholder="column_name"
|
||||
className={`table-editor-columns-input bg-surface-100 lg:gap-0 ${
|
||||
className={cn(
|
||||
'[&>div>div>div>input]:py-1.5 [&>div>div>div>input]:border-r-transparent [&>div>div>div>input]:rounded-r-none',
|
||||
hasImportContent ? 'opacity-50' : ''
|
||||
} rounded-md`}
|
||||
)}
|
||||
onChange={(event: any) => onUpdateColumn({ name: event.target.value })}
|
||||
/>
|
||||
{relations.filter((r) => !r.toRemove).length === 0 ? (
|
||||
<Button
|
||||
type="dashed"
|
||||
className="rounded-l-none h-[30px] py-0 px-2"
|
||||
onClick={() => onEditForeignKey()}
|
||||
>
|
||||
<Link size={12} />
|
||||
</Button>
|
||||
) : (
|
||||
<Popover_Shadcn_ open={open} onOpenChange={setOpen} modal={false}>
|
||||
<PopoverTrigger_Shadcn_ asChild>
|
||||
<Button type="default" className="rounded-l-none h-[30px] py-0 px-2">
|
||||
<Link size={12} />
|
||||
</Button>
|
||||
</PopoverTrigger_Shadcn_>
|
||||
<PopoverContent_Shadcn_
|
||||
className={cn('p-0', hasChangesInRelations ? 'w-96' : 'w-72')}
|
||||
side="bottom"
|
||||
align="end"
|
||||
>
|
||||
<div className="text-xs px-2 pt-2">
|
||||
Involved in {relations.length} foreign key{relations.length > 1 ? 's' : ''}
|
||||
</div>
|
||||
<Command_Shadcn_>
|
||||
<CommandList_Shadcn_>
|
||||
<CommandGroup_Shadcn_>
|
||||
{relations.map((relation, idx) => {
|
||||
const key = String(relation?.id ?? `${column.id}-relation-${idx}`)
|
||||
const status = getRelationStatus(relation)
|
||||
if (status === 'REMOVE') return null
|
||||
|
||||
return (
|
||||
<CommandItem_Shadcn_
|
||||
key={key}
|
||||
value={key}
|
||||
className="cursor-pointer w-full"
|
||||
onSelect={() => onEditForeignKey(relation)}
|
||||
onClick={() => onEditForeignKey(relation)}
|
||||
>
|
||||
{status === undefined ? (
|
||||
<div className="w-full flex items-center justify-between truncate">
|
||||
{relation.name}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-x-2 truncate">
|
||||
<Badge variant={status === 'ADD' ? 'brand' : 'warning'}>
|
||||
{status}
|
||||
</Badge>
|
||||
<p className="truncate">
|
||||
{relation.name || (
|
||||
<>
|
||||
To{' '}
|
||||
{relation.columns
|
||||
.filter((c) => c.source === column.name)
|
||||
.map((c) => {
|
||||
return (
|
||||
<code key={`${c.source}-${c.target}`}>
|
||||
{relation.schema}.{relation.table}.{c.target}
|
||||
</code>
|
||||
)
|
||||
})}
|
||||
{relation.columns.length > 1 && (
|
||||
<>
|
||||
and {relation.columns.length - 1} other column
|
||||
{relation.columns.length > 2 ? 's' : ''}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</CommandItem_Shadcn_>
|
||||
)
|
||||
})}
|
||||
</CommandGroup_Shadcn_>
|
||||
<CommandSeparator_Shadcn_ />
|
||||
<CommandGroup_Shadcn_>
|
||||
<CommandItem_Shadcn_
|
||||
className="cursor-pointer w-full gap-x-2"
|
||||
onSelect={() => onEditForeignKey()}
|
||||
onClick={() => onEditForeignKey()}
|
||||
>
|
||||
<Plus size={14} strokeWidth={1.5} />
|
||||
<p>Add foreign key relation</p>
|
||||
</CommandItem_Shadcn_>
|
||||
</CommandGroup_Shadcn_>
|
||||
</CommandList_Shadcn_>
|
||||
</Command_Shadcn_>
|
||||
</PopoverContent_Shadcn_>
|
||||
</Popover_Shadcn_>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-[25%]">
|
||||
@@ -200,12 +342,12 @@ const Column = ({
|
||||
>
|
||||
<div className="group flex items-center -space-x-1">
|
||||
{settingsCount > 0 && (
|
||||
<div className="rounded-full bg-foreground py-0.5 px-2 text-xs text-background">
|
||||
<div className="rounded-full bg-foreground h-4 w-4 flex items-center justify-center text-xs text-background">
|
||||
{settingsCount}
|
||||
</div>
|
||||
)}
|
||||
<div className="text-foreground-light transition-colors group-hover:text-foreground">
|
||||
<IconSettings size={18} strokeWidth={1} />
|
||||
<Settings size={16} strokeWidth={1} />
|
||||
</div>
|
||||
</div>
|
||||
</Popover>
|
||||
@@ -215,7 +357,7 @@ const Column = ({
|
||||
{!hasImportContent && (
|
||||
<div className="flex w-[5%] justify-end">
|
||||
<button className="cursor-pointer" onClick={() => onRemoveColumn()}>
|
||||
<IconX strokeWidth={1} />
|
||||
<X size={16} strokeWidth={1} />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
+75
-12
@@ -1,5 +1,7 @@
|
||||
import * as Tooltip from '@radix-ui/react-tooltip'
|
||||
import { isEmpty, noop, partition } from 'lodash'
|
||||
import { HelpCircle, Key } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import {
|
||||
DragDropContext,
|
||||
Draggable,
|
||||
@@ -7,18 +9,29 @@ import {
|
||||
Droppable,
|
||||
DroppableProvided,
|
||||
} from 'react-beautiful-dnd'
|
||||
import { Alert, Button, IconEdit, IconExternalLink, IconHelpCircle, IconKey, IconTrash } from 'ui'
|
||||
|
||||
import InformationBox from 'components/ui/InformationBox'
|
||||
import type { EnumeratedType } from 'data/enumerated-types/enumerated-types-query'
|
||||
import {
|
||||
AlertDescription_Shadcn_,
|
||||
AlertTitle_Shadcn_,
|
||||
Alert_Shadcn_,
|
||||
Button,
|
||||
IconEdit,
|
||||
IconExternalLink,
|
||||
IconTrash,
|
||||
} from 'ui'
|
||||
import { WarningIcon } from 'ui-patterns/Icons/StatusIcons'
|
||||
import { generateColumnField } from '../ColumnEditor/ColumnEditor.utils'
|
||||
import { ForeignKeySelector } from '../ForeignKeySelector/ForeignKeySelector'
|
||||
import type { ForeignKey } from '../ForeignKeySelector/ForeignKeySelector.types'
|
||||
import { TEXT_TYPES } from '../SidePanelEditor.constants'
|
||||
import type { ColumnField, ExtendedPostgresRelationship } from '../SidePanelEditor.types'
|
||||
import Column from './Column'
|
||||
import type { ImportContent } from './TableEditor.types'
|
||||
import type { ImportContent, TableField } from './TableEditor.types'
|
||||
|
||||
interface ColumnManagementProps {
|
||||
table: TableField
|
||||
columns?: ColumnField[]
|
||||
relations: ForeignKey[]
|
||||
enumTypes: EnumeratedType[]
|
||||
@@ -27,9 +40,11 @@ interface ColumnManagementProps {
|
||||
onColumnsUpdated: (columns: ColumnField[]) => void
|
||||
onSelectImportData: () => void
|
||||
onClearImportContent: () => void
|
||||
onUpdateFkRelations: (relations: ForeignKey[]) => void
|
||||
}
|
||||
|
||||
const ColumnManagement = ({
|
||||
table,
|
||||
columns = [],
|
||||
relations,
|
||||
enumTypes = [],
|
||||
@@ -38,7 +53,12 @@ const ColumnManagement = ({
|
||||
onColumnsUpdated = noop,
|
||||
onSelectImportData = noop,
|
||||
onClearImportContent = noop,
|
||||
onUpdateFkRelations,
|
||||
}: ColumnManagementProps) => {
|
||||
const [open, setOpen] = useState(false)
|
||||
const [selectedColumn, setSelectedColumn] = useState<ColumnField>()
|
||||
const [selectedFk, setSelectedFk] = useState<ForeignKey>()
|
||||
|
||||
const hasImportContent = !isEmpty(importContent)
|
||||
const [primaryKeyColumns, otherColumns] = partition(
|
||||
columns,
|
||||
@@ -154,20 +174,22 @@ const ColumnManagement = ({
|
||||
)}
|
||||
|
||||
{primaryKeyColumns.length === 0 && (
|
||||
<Alert title="Warning: No primary keys selected" variant="warning" withIcon>
|
||||
Tables require at least one column as a primary key in order to uniquely identify each
|
||||
row. Without a primary key, you will not be able to update or delete rows from the
|
||||
table.
|
||||
</Alert>
|
||||
<Alert_Shadcn_ variant="warning">
|
||||
<WarningIcon />
|
||||
<AlertTitle_Shadcn_>Warning: No primary keys selected</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>
|
||||
Tables should have at least one column as the primary key to identify each row.
|
||||
Without a primary key, you will not be able to update or delete rows from the table.
|
||||
</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
)}
|
||||
|
||||
{primaryKeyColumns.length > 1 && (
|
||||
<InformationBox
|
||||
block
|
||||
icon={<IconKey className="text-white" size="large" />}
|
||||
icon={<Key size={16} />}
|
||||
title="Composite primary key selected"
|
||||
description="The columns that you've selected will be grouped as a primary key, and will serve
|
||||
as the unique identifier for the rows in your table"
|
||||
description="The columns that you've selected will be grouped as a primary key, and will serve as the unique identifier for the rows in your table"
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -181,7 +203,7 @@ const ColumnManagement = ({
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
<Tooltip.Trigger>
|
||||
<h5 className="text-xs text-foreground-lighter">
|
||||
<IconHelpCircle size={15} strokeWidth={1.5} />
|
||||
<HelpCircle size={15} strokeWidth={1.5} />
|
||||
</h5>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Portal>
|
||||
@@ -211,7 +233,7 @@ const ColumnManagement = ({
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
<Tooltip.Trigger>
|
||||
<h5 className="text-xs text-foreground-lighter">
|
||||
<IconHelpCircle size={15} strokeWidth={1.5} />
|
||||
<HelpCircle size={15} strokeWidth={1.5} />
|
||||
</h5>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Portal>
|
||||
@@ -262,6 +284,9 @@ const ColumnManagement = ({
|
||||
>
|
||||
<Column
|
||||
column={column}
|
||||
relations={relations.filter((relation) => {
|
||||
return relation.columns.some((x) => x.source === column.name)
|
||||
})}
|
||||
enumTypes={enumTypes}
|
||||
hasForeignKeys={checkIfHaveForeignKeys(column)}
|
||||
isNewRecord={isNewRecord}
|
||||
@@ -269,6 +294,11 @@ const ColumnManagement = ({
|
||||
dragHandleProps={draggableProvided.dragHandleProps}
|
||||
onUpdateColumn={(changes) => onUpdateColumn(column, changes)}
|
||||
onRemoveColumn={() => onRemoveColumn(column)}
|
||||
onEditForeignKey={(fk) => {
|
||||
setOpen(true)
|
||||
setSelectedColumn(column)
|
||||
if (fk) setSelectedFk(fk)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -294,6 +324,9 @@ const ColumnManagement = ({
|
||||
<div ref={draggableProvided.innerRef} {...draggableProvided.draggableProps}>
|
||||
<Column
|
||||
column={column}
|
||||
relations={relations.filter((relation) => {
|
||||
return relation.columns.some((x) => x.source === column.name)
|
||||
})}
|
||||
enumTypes={enumTypes}
|
||||
isNewRecord={isNewRecord}
|
||||
hasForeignKeys={checkIfHaveForeignKeys(column)}
|
||||
@@ -301,6 +334,11 @@ const ColumnManagement = ({
|
||||
dragHandleProps={draggableProvided.dragHandleProps}
|
||||
onUpdateColumn={(changes) => onUpdateColumn(column, changes)}
|
||||
onRemoveColumn={() => onRemoveColumn(column)}
|
||||
onEditForeignKey={(fk) => {
|
||||
setOpen(true)
|
||||
setSelectedColumn(column)
|
||||
if (fk) setSelectedFk(fk)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -321,6 +359,31 @@ const ColumnManagement = ({
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<ForeignKeySelector
|
||||
visible={open}
|
||||
column={selectedColumn}
|
||||
table={{ id: table.id, name: table.name, columns: table.columns }}
|
||||
foreignKey={selectedFk}
|
||||
onClose={() => {
|
||||
setOpen(false)
|
||||
setSelectedFk(undefined)
|
||||
setSelectedColumn(undefined)
|
||||
}}
|
||||
onSaveRelation={(fk) => {
|
||||
const existingRelationIds = relations.map((x) => x.id)
|
||||
if (fk.id !== undefined && existingRelationIds.includes(fk.id)) {
|
||||
onUpdateFkRelations(
|
||||
relations.map((x) => {
|
||||
if (x.id === fk.id) return fk
|
||||
return x
|
||||
})
|
||||
)
|
||||
} else {
|
||||
onUpdateFkRelations(relations.concat([fk]))
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
+8
-3
@@ -2,7 +2,8 @@ import clsx from 'clsx'
|
||||
import { useParams } from 'common'
|
||||
import Link from 'next/link'
|
||||
import SVG from 'react-inlinesvg'
|
||||
import { Badge, Button, IconArrowRight } from 'ui'
|
||||
import { Badge, Button, cn } from 'ui'
|
||||
import { ArrowRight } from 'lucide-react'
|
||||
|
||||
import { BASE_PATH } from 'lib/constants'
|
||||
import type { ForeignKey } from '../../ForeignKeySelector/ForeignKeySelector.types'
|
||||
@@ -89,8 +90,12 @@ export const ForeignKeyRow = ({
|
||||
<div className="flex flex-col gap-y-1">
|
||||
{foreignKey.columns.map((x, idx) => (
|
||||
<div key={`relation-${idx}}`} className="flex items-center gap-x-2">
|
||||
<code className="text-xs">{x.source}</code>
|
||||
<IconArrowRight />
|
||||
<code
|
||||
className={cn('text-xs', (x?.source ?? '').length === 0 && 'text-foreground-light')}
|
||||
>
|
||||
{x.source || '[column_name]'}
|
||||
</code>
|
||||
<ArrowRight size={16} />
|
||||
<code className="text-xs">
|
||||
{foreignKey.schema}.{foreignKey.table}.{x.target}
|
||||
</code>
|
||||
|
||||
+26
-29
@@ -1,9 +1,8 @@
|
||||
import type { PostgresTable } from '@supabase/postgres-meta'
|
||||
import { isEmpty, isUndefined, noop } from 'lodash'
|
||||
import Link from 'next/link'
|
||||
import { useEffect, useState } from 'react'
|
||||
import toast from 'react-hot-toast'
|
||||
import { Alert, Badge, Button, Checkbox, IconBookOpen, Input, Modal, SidePanel } from 'ui'
|
||||
import { Alert, Badge, Button, Checkbox, Input, SidePanel } from 'ui'
|
||||
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
|
||||
|
||||
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
|
||||
@@ -20,6 +19,7 @@ import {
|
||||
import { useEnumeratedTypesQuery } from 'data/enumerated-types/enumerated-types-query'
|
||||
import { useIsFeatureEnabled } from 'hooks'
|
||||
import { EXCLUDED_SCHEMAS_WITHOUT_EXTENSIONS } from 'lib/constants/schemas'
|
||||
import { ExternalLink } from 'lucide-react'
|
||||
import { useTableEditorStateSnapshot } from 'state/table-editor'
|
||||
import { SpreadsheetImport } from '../'
|
||||
import ActionBar from '../ActionBar'
|
||||
@@ -306,23 +306,20 @@ const TableEditor = ({
|
||||
title="Policies are required to query data"
|
||||
>
|
||||
<p>
|
||||
You need to write an access policy before you can query data from this table. Without
|
||||
a policy, querying this table will result in an <u>empty array</u> of results.
|
||||
</p>
|
||||
{isNewRecord && (
|
||||
<p className="mt-3">You can create policies after you create this table.</p>
|
||||
)}
|
||||
<p className="mt-4">
|
||||
<Button asChild type="default" icon={<IconBookOpen strokeWidth={1.5} />}>
|
||||
<Link
|
||||
href="https://supabase.com/docs/guides/auth/row-level-security"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
RLS Documentation
|
||||
</Link>
|
||||
</Button>
|
||||
You need to create an access policy before you can query data from this table. Without
|
||||
a policy, querying this table will return an{' '}
|
||||
<u className="text-foreground">empty array</u> of results.{' '}
|
||||
{isNewRecord ? 'You can create policies after saving this table.' : ''}
|
||||
</p>
|
||||
<Button asChild type="default" icon={<ExternalLink />} className="mt-4">
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href="https://supabase.com/docs/guides/auth/row-level-security"
|
||||
>
|
||||
RLS Documentation
|
||||
</a>
|
||||
</Button>
|
||||
</Alert>
|
||||
) : (
|
||||
<Alert
|
||||
@@ -335,17 +332,15 @@ const TableEditor = ({
|
||||
{tableFields.name ? `The table ${tableFields.name}` : 'Your table'} will be publicly
|
||||
writable and readable
|
||||
</p>
|
||||
<p className="mt-4">
|
||||
<Button asChild type="default" icon={<IconBookOpen strokeWidth={1.5} />}>
|
||||
<Link
|
||||
href="https://supabase.com/docs/guides/auth/row-level-security"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
RLS Documentation
|
||||
</Link>
|
||||
</Button>
|
||||
</p>
|
||||
<Button asChild type="default" icon={<ExternalLink />} className="mt-4">
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href="https://supabase.com/docs/guides/auth/row-level-security"
|
||||
>
|
||||
RLS Documentation
|
||||
</a>
|
||||
</Button>
|
||||
</Alert>
|
||||
)}
|
||||
{realtimeEnabled && (
|
||||
@@ -363,6 +358,7 @@ const TableEditor = ({
|
||||
<SidePanel.Content className="space-y-10 py-6">
|
||||
{!isDuplicating && (
|
||||
<ColumnManagement
|
||||
table={tableFields}
|
||||
columns={tableFields?.columns}
|
||||
relations={fkRelations}
|
||||
enumTypes={enumTypes}
|
||||
@@ -374,6 +370,7 @@ const TableEditor = ({
|
||||
onUpdateField({ columns: DEFAULT_COLUMNS })
|
||||
setImportContent(undefined)
|
||||
}}
|
||||
onUpdateFkRelations={onUpdateFkRelations}
|
||||
/>
|
||||
)}
|
||||
{isDuplicating && (
|
||||
|
||||
+5
-17
@@ -23,7 +23,6 @@ import { useBranchCreateMutation } from 'data/branches/branch-create-mutation'
|
||||
import { useProjectUpgradeEligibilityQuery } from 'data/config/project-upgrade-eligibility-query'
|
||||
import { useCheckGithubBranchValidity } from 'data/integrations/github-branch-check-query'
|
||||
import { useGitHubConnectionsQuery } from 'data/integrations/github-connections-query'
|
||||
import { useGitHubRepositoriesQuery } from 'data/integrations/github-repositories-query'
|
||||
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
|
||||
import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query'
|
||||
import { useSelectedOrganization } from 'hooks'
|
||||
@@ -43,14 +42,6 @@ const EnableBranchingModal = () => {
|
||||
// and makes the validation run onChange instead. This is a workaround
|
||||
const [isValid, setIsValid] = useState(false)
|
||||
|
||||
const {
|
||||
data: repositories,
|
||||
error: repositoriesError,
|
||||
isLoading: isLoadingRepositories,
|
||||
isSuccess: isSuccessRepositories,
|
||||
isError: isErrorRepositories,
|
||||
} = useGitHubRepositoriesQuery()
|
||||
|
||||
const {
|
||||
data: connections,
|
||||
error: connectionsError,
|
||||
@@ -123,9 +114,9 @@ const EnableBranchingModal = () => {
|
||||
defaultValues: { branchName: '' },
|
||||
})
|
||||
|
||||
const isLoading = isLoadingRepositories
|
||||
const isError = isErrorRepositories || isErrorUpgradeEligibility
|
||||
const isSuccess = isSuccessRepositories && isSuccessUpgradeEligibility
|
||||
const isLoading = isLoadingConnections || isLoadingUpgradeEligibility
|
||||
const isError = isErrorConnections || isErrorUpgradeEligibility
|
||||
const isSuccess = isSuccessConnections && isSuccessUpgradeEligibility
|
||||
|
||||
const canSubmit = form.getValues('branchName').length > 0 && !isChecking && isValid
|
||||
const onSubmit = (data: z.infer<typeof FormSchema>) => {
|
||||
@@ -187,11 +178,8 @@ const EnableBranchingModal = () => {
|
||||
<>
|
||||
<Modal.Separator />
|
||||
<Modal.Content className="px-7 py-6">
|
||||
{isErrorRepositories ? (
|
||||
<AlertError
|
||||
error={repositoriesError}
|
||||
subject="Failed to retrieve repositories"
|
||||
/>
|
||||
{isErrorConnections ? (
|
||||
<AlertError error={connectionsError} subject="Failed to retrieve connections" />
|
||||
) : isErrorUpgradeEligibility ? (
|
||||
<AlertError
|
||||
error={upgradeEligibilityError}
|
||||
|
||||
@@ -111,6 +111,7 @@ const NavigationBar = () => {
|
||||
<Link
|
||||
href={IS_PLATFORM ? '/projects' : `/project/${projectRef}`}
|
||||
className="mx-2 flex items-center h-[40px]"
|
||||
onClick={onCloseNavigationIconLink}
|
||||
>
|
||||
<img
|
||||
alt="Supabase"
|
||||
|
||||
@@ -8,24 +8,16 @@ import toast from 'react-hot-toast'
|
||||
import { useParams } from 'common'
|
||||
import { untitledSnippetTitle } from 'components/interfaces/SQLEditor/SQLEditor.constants'
|
||||
import { createSqlSnippetSkeleton } from 'components/interfaces/SQLEditor/SQLEditor.utils'
|
||||
import AlertError from 'components/ui/AlertError'
|
||||
import { useContentDeleteMutation } from 'data/content/content-delete-mutation'
|
||||
import { SqlSnippet, useSqlSnippetsQuery } from 'data/content/sql-snippets-query'
|
||||
import { useCheckPermissions, useSelectedProject } from 'hooks'
|
||||
import { uuidv4 } from 'lib/helpers'
|
||||
import { useProfile } from 'lib/profile'
|
||||
import { useSnippets, useSqlEditorStateSnapshot } from 'state/sql-editor'
|
||||
import {
|
||||
AlertDescription_Shadcn_,
|
||||
AlertTitle_Shadcn_,
|
||||
Alert_Shadcn_,
|
||||
Button,
|
||||
Modal,
|
||||
TooltipContent_Shadcn_,
|
||||
TooltipTrigger_Shadcn_,
|
||||
Tooltip_Shadcn_,
|
||||
} from 'ui'
|
||||
import { ResponseError } from 'types'
|
||||
import { Button, TooltipContent_Shadcn_, TooltipTrigger_Shadcn_, Tooltip_Shadcn_ } from 'ui'
|
||||
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
|
||||
import { WarningIcon } from 'ui-patterns/Icons/StatusIcons'
|
||||
import {
|
||||
InnerSideBarEmptyPanel,
|
||||
InnerSideBarFilterSearchInput,
|
||||
@@ -51,7 +43,7 @@ const SideBarContent = () => {
|
||||
const [selectedQueries, setSelectedQueries] = useState<string[]>([])
|
||||
|
||||
const snap = useSqlEditorStateSnapshot()
|
||||
const { isLoading, isSuccess } = useSqlSnippetsQuery(ref, {
|
||||
const { isLoading, isSuccess, isError, error } = useSqlSnippetsQuery(ref, {
|
||||
refetchOnWindowFocus: false,
|
||||
staleTime: 300, // 5 minutes
|
||||
onSuccess(data) {
|
||||
@@ -205,37 +197,43 @@ const SideBarContent = () => {
|
||||
return (
|
||||
<>
|
||||
<div className="mt-6">
|
||||
{isLoading ? (
|
||||
<InnerSideBarShimmeringLoaders />
|
||||
) : isSuccess ? (
|
||||
<div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<Button
|
||||
type="default"
|
||||
className="justify-start mx-4"
|
||||
onClick={() => handleNewQuery()}
|
||||
icon={<Plus className="text-foreground-muted" strokeWidth={1} size={14} />}
|
||||
>
|
||||
New query
|
||||
</Button>
|
||||
<div className="flex flex-col gap-4">
|
||||
<Button
|
||||
type="default"
|
||||
className="justify-start mx-4"
|
||||
onClick={() => handleNewQuery()}
|
||||
icon={<Plus className="text-foreground-muted" strokeWidth={1} size={14} />}
|
||||
>
|
||||
New query
|
||||
</Button>
|
||||
|
||||
<div className="px-2">
|
||||
<InnerSideMenuItem
|
||||
title="Templates"
|
||||
isActive={router.asPath === `/project/${ref}/sql/templates`}
|
||||
href={`/project/${ref}/sql/templates`}
|
||||
>
|
||||
Templates
|
||||
</InnerSideMenuItem>
|
||||
<InnerSideMenuItem
|
||||
title="Quickstarts"
|
||||
isActive={router.asPath === `/project/${ref}/sql/quickstarts`}
|
||||
href={`/project/${ref}/sql/quickstarts`}
|
||||
>
|
||||
Quickstarts
|
||||
</InnerSideMenuItem>
|
||||
</div>
|
||||
<div className="px-2">
|
||||
<InnerSideMenuItem
|
||||
title="Templates"
|
||||
isActive={router.asPath === `/project/${ref}/sql/templates`}
|
||||
href={`/project/${ref}/sql/templates`}
|
||||
>
|
||||
Templates
|
||||
</InnerSideMenuItem>
|
||||
<InnerSideMenuItem
|
||||
title="Quickstarts"
|
||||
isActive={router.asPath === `/project/${ref}/sql/quickstarts`}
|
||||
href={`/project/${ref}/sql/quickstarts`}
|
||||
>
|
||||
Quickstarts
|
||||
</InnerSideMenuItem>
|
||||
</div>
|
||||
|
||||
{isLoading && <InnerSideBarShimmeringLoaders />}
|
||||
|
||||
{isError && (
|
||||
<div className="px-4">
|
||||
<AlertError error={error as ResponseError} subject="Failed to load SQL snippets" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isSuccess && (
|
||||
<>
|
||||
{snippets.length > 0 && (
|
||||
<InnerSideBarFilters className="mx-2">
|
||||
<InnerSideBarFilterSearchInput
|
||||
@@ -343,9 +341,9 @@ const SideBarContent = () => {
|
||||
</InnerSideMenuCollapsibleContent>
|
||||
</InnerSideMenuCollapsible>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ConfirmationModal
|
||||
|
||||
@@ -13,14 +13,8 @@ import { EmptyBucketModal } from 'components/to-be-cleaned/Storage/EmptyBucketMo
|
||||
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
|
||||
import { useBucketsQuery } from 'data/storage/buckets-query'
|
||||
import { useCheckPermissions, useSelectedProject } from 'hooks'
|
||||
import {
|
||||
AlertDescription_Shadcn_,
|
||||
AlertTitle_Shadcn_,
|
||||
Alert_Shadcn_,
|
||||
Button,
|
||||
IconEdit,
|
||||
Menu,
|
||||
} from 'ui'
|
||||
import { ArrowUpRight, Edit } from 'lucide-react'
|
||||
import { AlertDescription_Shadcn_, AlertTitle_Shadcn_, Alert_Shadcn_, Button, Menu } from 'ui'
|
||||
import BucketRow from './BucketRow'
|
||||
|
||||
const StorageMenu = () => {
|
||||
@@ -56,8 +50,8 @@ const StorageMenu = () => {
|
||||
block
|
||||
type="default"
|
||||
icon={
|
||||
<div className="text-foreground-lighter">
|
||||
<IconEdit size={14} />
|
||||
<div className="text-foreground-light">
|
||||
<Edit size={14} />
|
||||
</div>
|
||||
}
|
||||
disabled={!canCreateBuckets}
|
||||
@@ -152,6 +146,14 @@ const StorageMenu = () => {
|
||||
<p className="truncate">Policies</p>
|
||||
</Menu.Item>
|
||||
</Link>
|
||||
<Link href={`/project/${ref}/settings/storage`}>
|
||||
<Menu.Item rounded>
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="truncate">Settings</p>
|
||||
<ArrowUpRight strokeWidth={1} className="h-4 w-4" />
|
||||
</div>
|
||||
</Menu.Item>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</Menu>
|
||||
|
||||
@@ -7,7 +7,7 @@ import toast from 'react-hot-toast'
|
||||
import { useProjectSettingsQuery } from 'data/config/project-settings-query'
|
||||
import { useCustomDomainsQuery } from 'data/custom-domains/custom-domains-query'
|
||||
import type { Bucket } from 'data/storage/buckets-query'
|
||||
import { DEFAULT_PROJECT_API_SERVICE_ID } from 'lib/constants'
|
||||
import { DEFAULT_PROJECT_API_SERVICE_ID, IS_PLATFORM } from 'lib/constants'
|
||||
import { copyToClipboard } from 'lib/helpers'
|
||||
import { useStorageStore } from 'localStores/storageExplorer/StorageExplorerStore'
|
||||
import { STORAGE_ROW_TYPES, STORAGE_VIEWS } from '../Storage.constants'
|
||||
@@ -18,6 +18,7 @@ import FileExplorerHeader from './FileExplorerHeader'
|
||||
import FileExplorerHeaderSelection from './FileExplorerHeaderSelection'
|
||||
import MoveItemsModal from './MoveItemsModal'
|
||||
import PreviewPane from './PreviewPane'
|
||||
import { useProjectStorageConfigQuery } from 'data/config/project-storage-config-query'
|
||||
|
||||
interface StorageExplorerProps {
|
||||
bucket: Bucket
|
||||
@@ -58,6 +59,11 @@ const StorageExplorer = ({ bucket }: StorageExplorerProps) => {
|
||||
const { ref } = useParams()
|
||||
const { data: customDomainData } = useCustomDomainsQuery({ projectRef: ref })
|
||||
const { data: projectSettings } = useProjectSettingsQuery({ projectRef: ref })
|
||||
|
||||
// [Joshen] This is to ensure that StorageExplorerStore can get the storage file size limit
|
||||
// Will be better once we deprecate the mobx store entirely, which we will get there
|
||||
useProjectStorageConfigQuery({ projectRef: ref }, { enabled: IS_PLATFORM })
|
||||
|
||||
const apiService = (projectSettings?.services ?? []).find(
|
||||
(x) => x.app.id == DEFAULT_PROJECT_API_SERVICE_ID
|
||||
)
|
||||
|
||||
@@ -30,7 +30,7 @@ const StorageSettings = () => {
|
||||
isError,
|
||||
} = useProjectStorageConfigQuery({ projectRef }, { enabled: IS_PLATFORM })
|
||||
const { fileSizeLimit, isFreeTier } = config || {}
|
||||
const { value, unit } = convertFromBytes(fileSizeLimit)
|
||||
const { value, unit } = convertFromBytes(fileSizeLimit ?? 0)
|
||||
|
||||
const [selectedUnit, setSelectedUnit] = useState(unit)
|
||||
let initialValues = { fileSizeLimit: value, unformattedFileSizeLimit: fileSizeLimit }
|
||||
|
||||
Vendored
+2
-12947
File diff suppressed because it is too large.
Load diff
@@ -1,32 +1,29 @@
|
||||
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
|
||||
import { get } from 'lib/common/fetch'
|
||||
import { get, handleError } from 'data/fetchers'
|
||||
import { API_URL } from 'lib/constants'
|
||||
import { configKeys } from './keys'
|
||||
import type { ResponseError } from 'types'
|
||||
import { components } from 'data/api'
|
||||
|
||||
export type ProjectStorageConfigVariables = {
|
||||
projectRef?: string
|
||||
}
|
||||
|
||||
// TODO: Add proper type
|
||||
export type ProjectStorageConfigResponse = any
|
||||
export type ProjectStorageConfigResponse = components['schemas']['StorageConfigResponse']
|
||||
|
||||
export async function getProjectStorageConfig(
|
||||
{ projectRef }: ProjectStorageConfigVariables,
|
||||
signal?: AbortSignal
|
||||
) {
|
||||
if (!projectRef) {
|
||||
throw new Error('projectRef is required')
|
||||
}
|
||||
if (!projectRef) throw new Error('projectRef is required')
|
||||
|
||||
const response = await get(`${API_URL}/projects/${projectRef}/config/storage`, {
|
||||
const { data, error } = await get('/platform/projects/{ref}/config/storage', {
|
||||
params: { path: { ref: projectRef } },
|
||||
signal,
|
||||
})
|
||||
if (response.error) {
|
||||
throw response.error
|
||||
}
|
||||
|
||||
return response as ProjectStorageConfigResponse
|
||||
if (error) throw handleError(error)
|
||||
return data
|
||||
}
|
||||
|
||||
export type ProjectStorageConfigData = Awaited<ReturnType<typeof getProjectStorageConfig>>
|
||||
|
||||
Loaded 100 of 127 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user