Merge pull request #12731 from supabase/master

Prod deploy dashboard
This commit is contained in:
Joshen Lim authored and GitHub committed 2023-03-01 12:26:12 +08:00
commit b6953ba9e1
24 files changed
+180 -178

No files matched your search

@@ -38,6 +38,12 @@ Primary keys are **guaranteed not to change**. Columns, indices, constraints or
You may delete users directly or via the management console at Authentication > Users. Note that deleting a user from the `auth.users` table does not automatically sign out a user. As Supabase makes use of JSON Web Tokens (JWT), a user's JWT will remain "valid" until it has expired. Should you wish to immediately revoke access for a user, do considering making use of a Row Level Security policy as described below.
<Admonition type="caution">
You cannot delete a user if they are the owner of any objects in Supabase Storage.
You will encounter an error when you try to delete an Auth user that owns any Storage objects. If this happens, try deleting all the objects for that user, or reassign ownership to another user.
</Admonition>
## Public access
Since Row Level Security is enabled, this table is accessible via the API but no data will be returned unless we set up some Policies.
+24 -31
View File
@@ -17,6 +17,7 @@ This guide explains how to quickly connect the Postgres database provided by Sup
## Step 1: Get the connection string from Supabase project settings
Go to the settings page from the sidebar and navigate to the **Database** tab. You’ll find the database’s connection string with a placeholder for the password you provided when you created the project.
![Getting the connection string](/docs/img/guides/integrations/prisma/zntcsh3ic91gf1gy8j73.png)
## Step 2: Testing the connection
@@ -59,27 +60,17 @@ This starter also comes with the following packages installed:
### Configuring the project
By default, Prisma migrations will try to drop the `postgres` database, which can lead to conflicts with Supabase databases. For this scenario, use [Prisma Shadow Databases](https://www.prisma.io/docs/concepts/components/prisma-migrate/shadow-database#cloud-hosted-shadow-databases-must-be-created-manually).
Create a shadow database in your PostgreSQL server within the same Supabase project using the `psql` CLI and the `DATABASE_URL` from the previous steps (or use the local database).
Create a `.env` file at the root of your project:
```bash
psql postgresql://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:5432
touch .env
```
After you connect to your project's PostgreSQL instance, create another database (e.g., `postgres_shadow`):
```bash
postgres=> CREATE DATABASE postgres_shadow;
postgres=> exit
```
In the `.env` file, update `DATABASE_URL` and `SHADOW_DATABASE_URL` to the connection string from **step 1**. The `.env` file should look like:
In the `.env` file, add a `DATABASE_URL` variable and add the connection string from **step 1**. The `.env` file should look like:
```env
# .env
DATABASE_URL="postgres://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:5432/postgres"
SHADOW_DATABASE_URL="postgres://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:5432/postgres_shadow"
```
This is what your `schema.prisma` file should look like:
@@ -88,7 +79,6 @@ This is what your `schema.prisma` file should look like:
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
shadowDatabaseUrl = env("SHADOW_DATABASE_URL")
}
generator client {
@@ -141,7 +131,7 @@ That’s it! You have now successfully connected a Prisma project to a PostgreSQ
If you’re working in a serverless environment (for example Node.js functions hosted on AWS Lambda, Vercel or Netlify Functions), you need to set up [connection pooling](https://www.prisma.io/docs/guides/performance-and-optimization/connection-management#serverless-environments-faas) using a tool like [PgBouncer](https://www.pgbouncer.org/). That’s because every function invocation may result in a [new connection to the database](https://www.prisma.io/docs/guides/performance-and-optimization/connection-management#the-serverless-challenge).
Supabase [supports connection management using PgBouncer](/docs/guides/database/connecting-to-postgres#connection-pool).
Supabase [supports connection management using PgBouncer](/docs/guides/database/connecting-to-postgres#connection-pool) which prevents a traffic spike from overwhelming your database.
Go to the **Database** page from the sidebar in the Supabase dashboard and navigate to **Connection pool** settings:
@@ -153,18 +143,17 @@ Update your `.env` file with the following changes:
1. Rename the `DATABASE_URL` environment variable to `DIRECT_URL`
1. Create a `DATABASE_URL` environment variable and paste in the new connection string from the dashboard as its value
It is recommended to minimize the number of concurrent connections by setting the `connection_limit` to `1`. You can set connection limit by appending `?connection_limit=1` to your connection string
Append the `?pgbouncer=true` flag to the `DATABASE_URL` variable.
Your `.env` file should resemble the following:
```env
# .env
# PostgreSQL connection string used for migrations
DIRECT_URL="postgres://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:6543/postgres"
DIRECT_URL="postgres://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:5432/postgres"
# PostgreSQL connection string with pgBouncer config — used by Prisma Client
DATABASE_URL="postgres://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:6543/postgres?connection_limit=1"
DATABASE_URL="postgres://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:6543/postgres?pgbouncer=true"
SHADOW_DATABASE_URL="postgres://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:5432/postgres_shadow"
```
Update your Prisma schema by setting the `directUrl` in the datasource block:
@@ -173,8 +162,7 @@ Update your Prisma schema by setting the `directUrl` in the datasource block:
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
directUrl = env("DIRECT_URL")
shadowDatabaseUrl = env("SHADOW_DATABASE_URL")
directURL = env("DIRECT_URL")
}
```
@@ -184,11 +172,11 @@ If you want to learn more about Prisma, check out the [docs](https://www.prisma.
## Troubleshooting
### 1. Missing grants
### Missing grants
If you run `prisma migrate dev --name init` multiple times, it sometimes asks if you want to recreate the whole schema. If you chose yes, it will delete the public schema and recreate it. The default grants are missing after this.
If your database schema is out of sync from your migration history, `prisma migrate dev` will detect a migration history conflict or a [schema drift](https://www.prisma.io/docs/guides/database/developing-with-prisma-migrate/troubleshooting-development#schema-drift). When `prisma migrate dev` detects the drift, it might ask to to reset your database schema. If you choose yes, it will delete the `public` schema along with the default grants defined in your database.
If you run into this problem, create a draft migration using `prisma migrate dev --create-only`, and add the following helper SQL:
If you run into this problem, create a draft migration using `prisma migrate dev --create-only`, and add the following helper SQL:
```sql
grant usage on schema public to postgres, anon, authenticated, service_role;
@@ -204,16 +192,15 @@ alter default privileges in schema public grant all on sequences to postgres, an
Run `prisma migrate dev` to apply the draft migration to the database.
### 2. Using Prisma with multiple PostgreSQL schemas
### Using Supabase Auth with Prisma
If you're using multiple database schemas, enable the `multiSchema` Preview feature flag in the `generator` block of your Prisma schema:
If you would like to use Supabase Auth and Prisma in your application, you will have to enable the `multiSchema` Preview feature flag in the `generator` block of your Prisma schema:
```go
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
directURL = env("DIRECT_URL")
shadowDatabaseUrl = env("SHADOW_DATABASE_URL")
}
generator client {
@@ -229,7 +216,6 @@ datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
directURL = env("DIRECT_URL")
shadowDatabaseUrl = env("SHADOW_DATABASE_URL")
schemas = ["public", "auth"]
}
@@ -252,7 +238,16 @@ model User {
To learn more about using Prisma with multiple database schemas, refer to [this page in the Prisma docs](https://www.prisma.io/docs/guides/database/multi-schema#learn-more-about-the-multischema-preview-feature).
### 3. Enabling PosgreSQL extensions
### Using PostgreSQL Row Level Security with Prisma
If you would like to use Row Level Security (RLS) with Prisma, check out the [Prisma Client Extension - Row Level Security example](https://github.com/prisma/prisma-client-extensions/tree/main/row-level-security) that provides the primitives you could use to build and extend Prisma Client in PostgreSQL.
Also check out [useSupabaseRowLevelSecurity](https://github.com/dthyresson/prisma-extension-supabase-rls) Prisma Client extension that supports [Supabase RLS](/docs/guides/auth/row-level-security#authrole) and policies written to use [Supabase auth](/docs/guides/auth/overview).
The example and extension use [Prisma Client extensions](https://www.prisma.io/docs/concepts/components/prisma-client/client-extensions) Preview feature.
### Enabling PosgreSQL extensions
If you would like to use a PostgreSQL extension with Prisma, enable the `postgresqlExtensions` Preview feature flag in the `generator` block of your Prisma schema:
@@ -261,7 +256,6 @@ datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
directURL = env("DIRECT_URL")
shadowDatabaseUrl = env("SHADOW_DATABASE_URL")
}
generator client {
@@ -277,7 +271,6 @@ datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
directURL = env("DIRECT_URL")
shadowDatabaseUrl = env("SHADOW_DATABASE_URL")
extensions = [hstore(schema: "myHstoreSchema"), pg_trgm, postgis(version: "2.1")]
}
+1
View File
@@ -17,6 +17,7 @@ Dave Wilson
Div Arora
Egor Romanov
Fabrizio Fenoglio
Francesco Sansalvadore
Haydn Maley
Hieu Pham
Inian P
+2 -2
View File
@@ -87,8 +87,8 @@ An Information request about Supabase or feature request.
| Severity Level | Standard | Priority | Priority Plus |
| -------------- | ------------------------------------- | ------------------------------------- | -------------------------------------- |
| 1. Urgent | 1 business hour<br />24/7 × 365 | 1 business hour<br />24/7 × 365 | 1 business hour<br />24/7 × 365 |
| 2. High | 4 business hours<br />Monday - Friday | 2 business hours<br />Monday - Friday | 2 business hours<br />24/7 × 365 |
| 1. Urgent | 1 hour<br />24/7 × 365 | 1 hour<br />24/7 × 365 | 1 hour<br />24/7 × 365 |
| 2. High | 4 business hours<br />Monday - Friday | 2 business hours<br />Monday - Friday | 2 hours<br />24/7 × 365 |
| 3. Normal | 1 business day<br />Monday - Friday | 1 business day<br />Monday - Friday | 12 business hours<br />Monday - Friday |
| 4. Low | 2 business days<br />Monday - Friday | 2 business days<br />Monday - Friday | 1 business day<br />Monday - Friday |
@@ -1,4 +1,4 @@
import { Badge, IconArchive, IconCode, IconDatabase, IconKey } from 'ui'
import { IconArchive, IconCode, IconDatabase, IconKey, IconZap } from 'ui'
export const CANCELLATION_REASONS = [
'Pricing',
@@ -154,4 +154,36 @@ export const USAGE_BASED_PRODUCTS = [
},
],
},
{
title: 'Realtime',
icon: <IconZap className="dark:text-scale-100" size={16} strokeWidth={2} />,
features: [
{
key: 'realtime_message_count',
attribute: 'total_realtime_message_count',
title: 'Realtime Messages',
units: 'absolute',
costPerUnit: 0.0000025,
tooltip: (
<span>
Billing is based on the total amount of messages throughout your billing period.
</span>
),
},
{
key: 'realtime_peak_connection',
attribute: 'total_realtime_peak_connection',
title: 'Realtime Concurrent Peak Connections',
units: 'absolute',
costPerUnit: 0.01,
tooltip: (
<span>
Billing is based on the maximum amount of concurrent peak connections throughout your
billing period.
</span>
),
},
],
},
]
@@ -47,7 +47,7 @@ const EdgeFunctionDetails: FC<Props> = () => {
const apiService = settings?.autoApiService
const anonKey = apiService?.service_api_keys.find((x) => x.name === 'anon key')
? apiService.defaultApiKey
: undefined
: '[YOUR ANON KEY]'
const endpoint = apiService?.app_config.endpoint ?? ''
const endpointSections = endpoint.split('.')
@@ -23,7 +23,7 @@ const TerminalInstructions: FC<Props> = ({ closable = false, removeBorder = fals
const apiService = settings?.autoApiService
const anonKey = apiService?.service_api_keys.find((x) => x.name === 'anon key')
? apiService.defaultApiKey
: undefined
: '[YOUR ANON KEY]'
const endpoint = settings?.autoApiService.app_config.endpoint ?? ''
const endpointSections = endpoint.split('.')
@@ -44,10 +44,10 @@ import styles from './graphiql.module.css'
export type GraphiQLProps = {
fetcher: Fetcher
theme?: 'dark' | 'light'
apiKey?: string
accessToken?: string
}
const GraphiQL = ({ fetcher, theme = 'dark', apiKey }: GraphiQLProps) => {
const GraphiQL = ({ fetcher, theme = 'dark', accessToken }: GraphiQLProps) => {
// Ensure props are correct
if (typeof fetcher !== 'function') {
throw new TypeError(
@@ -56,7 +56,14 @@ const GraphiQL = ({ fetcher, theme = 'dark', apiKey }: GraphiQLProps) => {
}
return (
<GraphiQLProvider fetcher={fetcher} defaultHeaders={JSON.stringify({ apiKey }, null, 2)}>
<GraphiQLProvider
fetcher={fetcher}
defaultHeaders={
accessToken !== undefined
? JSON.stringify({ Authorization: `Bearer ${accessToken}` })
: undefined
}
>
<GraphiQLInterface theme={theme} />
</GraphiQLProvider>
)
@@ -122,17 +122,12 @@ const TableGridEditor: FC<Props> = ({
const anonKey = apiService?.service_api_keys.find((x) => x.name === 'anon key')
? apiService.defaultApiKey
: undefined
const swaggerUrl = settings?.autoApiService?.restUrl
const {
data: jsonSchema,
error: jsonSchemaError,
refetch,
} = useProjectJsonSchemaQuery({
projectRef,
swaggerUrl,
apiKey: anonKey,
})
} = useProjectJsonSchemaQuery({ projectRef })
if (jsonSchemaError) console.log('jsonSchemaError', jsonSchemaError)
@@ -37,9 +37,7 @@ const ConnectingState: FC<Props> = ({ project }) => {
}, [project])
const testProjectConnection = async () => {
const result = await pingPostgrest(project.restUrl!, project.ref, {
kpsVersion: project.kpsVersion,
})
const result = await pingPostgrest(project.ref, { kpsVersion: project.kpsVersion })
if (result) {
clearInterval(checkProjectConnectionIntervalRef.current)
app.onProjectPostgrestStatusUpdated(project.id, 'ONLINE')
@@ -118,7 +118,7 @@ const SignInLayout = ({
<div className="flex flex-1">
<main className="flex flex-col items-center flex-1 flex-shrink-0 px-5 pt-16 pb-8 border-r shadow-lg bg-scale-200 border-scale-500">
<div className="flex-1 flex flex-col justify-center w-[384px]">
<div className="flex-1 flex flex-col justify-center w-[330px] sm:w-[384px]">
<div className="mb-10">
<h1 className="mt-8 mb-2 text-2xl lg:text-3xl">{heading}</h1>
<h2 className="text-sm text-scale-1100">{subheading}</h2>
@@ -5,9 +5,13 @@ import CodeSnippet from '../CodeSnippet'
export default function Authentication({ autoApiService, selectedLang, showApiKey }) {
// [Joshen] ShowApiKey should really be a boolean, its confusing
const defaultApiKey =
showApiKey !== 'SUPABASE_KEY' ? autoApiService.defaultApiKey : 'SUPABASE_CLIENT_API_KEY'
showApiKey !== 'SUPABASE_KEY'
? autoApiService?.defaultApiKey ?? 'SUPABASE_CLIENT_API_KEY'
: 'SUPABASE_CLIENT_API_KEY'
const serviceApiKey =
showApiKey !== 'SUPABASE_KEY' ? autoApiService.serviceApiKey : 'SUPABASE_SERVICE_KEY'
showApiKey !== 'SUPABASE_KEY'
? autoApiService?.serviceApiKey ?? 'SUPABASE_SERVICE_KEY'
: 'SUPABASE_SERVICE_KEY'
return (
<>
+3
View File
@@ -0,0 +1,3 @@
export const authKeys = {
accessToken: () => ['access-token'] as const,
}
@@ -0,0 +1,35 @@
import { useCallback } from 'react'
import { useQuery, useQueryClient, UseQueryOptions } from '@tanstack/react-query'
import { authKeys } from './keys'
import { auth } from 'lib/gotrue'
export async function getSessionAccessToken() {
// ignore if server-side
if (typeof window === 'undefined') return ''
const {
data: { session },
} = await auth.getSession()
return session?.access_token
}
export type SessionAccessTokenData = Awaited<ReturnType<typeof getSessionAccessToken>>
export type SessionAccessTokenError = unknown
export const useSessionAccessTokenQuery = <TData = SessionAccessTokenData>({
enabled = true,
...options
}: UseQueryOptions<SessionAccessTokenData, SessionAccessTokenError, TData> = {}) =>
useQuery<SessionAccessTokenData, SessionAccessTokenError, TData>(
authKeys.accessToken(),
() => getSessionAccessToken(),
options
)
export const useSessionAccessTokenPrefetch = () => {
const client = useQueryClient()
return useCallback(
() => client.prefetchQuery(authKeys.accessToken(), () => getSessionAccessToken()),
[]
)
}
+1 -5
View File
@@ -1,7 +1,3 @@
export const docsKeys = {
jsonSchema: (
projectRef: string | undefined,
swaggerUrl: string | undefined,
apiKey: string | undefined
) => ['projects', projectRef, 'docs', { swaggerUrl, apiKey }] as const,
jsonSchema: (projectRef: string | undefined) => ['projects', projectRef, 'docs'] as const,
}
+14 -33
View File
@@ -1,41 +1,26 @@
import { useQuery, useQueryClient, UseQueryOptions } from '@tanstack/react-query'
import { get } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import { useCallback } from 'react'
import { docsKeys } from './keys'
export type ProjectJsonSchemaVariables = {
projectRef?: string
swaggerUrl?: string
apiKey?: string
}
export type ProjectJsonSchemaResponse = any
export async function getProjectJsonSchema(
{ projectRef, swaggerUrl, apiKey }: ProjectJsonSchemaVariables,
{ projectRef }: ProjectJsonSchemaVariables,
signal?: AbortSignal
) {
if (!projectRef) {
throw new Error('projectRef is required')
}
if (!swaggerUrl) {
throw new Error('id is required')
}
let headers: {
[key: string]: string | undefined
} = { apikey: apiKey }
if ((apiKey?.length ?? 0) > 40) headers['Authorization'] = `Bearer ${apiKey}`
const response = await get(swaggerUrl, {
signal,
headers,
credentials: 'omit',
})
if (response.error) {
throw response.error
}
const url = `${API_URL}/projects/${projectRef}/api/rest`
const response = await get(url, { signal })
if (response.error) throw response.error
return response as ProjectJsonSchemaResponse
}
@@ -43,33 +28,29 @@ export type ProjectJsonSchemaData = Awaited<ReturnType<typeof getProjectJsonSche
export type ProjectJsonSchemaError = unknown
export const useProjectJsonSchemaQuery = <TData = ProjectJsonSchemaData>(
{ projectRef, swaggerUrl, apiKey }: ProjectJsonSchemaVariables,
{ projectRef }: ProjectJsonSchemaVariables,
{
enabled = true,
...options
}: UseQueryOptions<ProjectJsonSchemaData, ProjectJsonSchemaError, TData> = {}
) =>
useQuery<ProjectJsonSchemaData, ProjectJsonSchemaError, TData>(
docsKeys.jsonSchema(projectRef, swaggerUrl, apiKey),
({ signal }) => getProjectJsonSchema({ projectRef, swaggerUrl, apiKey }, signal),
docsKeys.jsonSchema(projectRef),
({ signal }) => getProjectJsonSchema({ projectRef }, signal),
{
enabled: enabled && typeof projectRef !== 'undefined' && typeof swaggerUrl !== 'undefined',
enabled: enabled && typeof projectRef !== 'undefined',
...options,
}
)
export const useProjectJsonSchemaPrefetch = ({
projectRef,
swaggerUrl,
apiKey,
}: ProjectJsonSchemaVariables) => {
export const useProjectJsonSchemaPrefetch = ({ projectRef }: ProjectJsonSchemaVariables) => {
const client = useQueryClient()
return useCallback(() => {
if (projectRef && swaggerUrl) {
client.prefetchQuery(docsKeys.jsonSchema(projectRef, swaggerUrl, apiKey), ({ signal }) =>
getProjectJsonSchema({ projectRef, swaggerUrl, apiKey }, signal)
if (projectRef) {
client.prefetchQuery(docsKeys.jsonSchema(projectRef), ({ signal }) =>
getProjectJsonSchema({ projectRef }, signal)
)
}
}, [projectRef, swaggerUrl])
}, [projectRef])
}
+10 -28
View File
@@ -2,6 +2,8 @@ import semver from 'semver'
import { headWithTimeout, getWithTimeout } from './common/fetch'
import { API_URL } from './constants'
const DEFAULT_TIMEOUT_MILLISECONDS = 2000
/**
* Ping Postgrest for health check. Default timeout in 2s.
*
@@ -15,7 +17,6 @@ import { API_URL } from './constants'
* @return true if ping is successful else false
*/
async function pingPostgrest(
restUrl: string,
projectRef: string,
options?: {
kpsVersion?: string
@@ -23,8 +24,6 @@ async function pingPostgrest(
}
) {
if (projectRef === undefined) return false
const apiKey = await getApiKey(projectRef, options?.timeout)
if (apiKey === undefined) return false
const { kpsVersion, timeout } = options ?? {}
const healthCheckApiEnable = semver.gte(
@@ -33,35 +32,21 @@ async function pingPostgrest(
semver.coerce('kps-v3.8.6')
)
if (healthCheckApiEnable) {
const healthCheckUrl = `${restUrl.replace('/rest/', '/rest-admin/')}live`
return pingHealthCheckApi(healthCheckUrl, apiKey, timeout)
return pingHealthCheckApi(projectRef, timeout)
} else {
return pingOpenApi(projectRef, timeout)
}
return pingOpenApi(restUrl, apiKey, timeout)
}
export default pingPostgrest
const getApiKey = async (
projectRef: string,
timeout = DEFAULT_TIMEOUT_MILLISECONDS
): Promise<string | undefined> => {
const response = await getWithTimeout(`${API_URL}/props/project/${projectRef}/api`, { timeout })
if (response.error || response.autoApiService.service_api_keys.length === 0) return undefined
return response.autoApiService.defaultApiKey
}
const DEFAULT_TIMEOUT_MILLISECONDS = 2000
/**
* Send a HEAD request to postgrest OpenAPI.
*
* @return true if there's no error else false
*/
async function pingOpenApi(url: string, apikey: string, timeout?: number) {
const headers = { apikey, Authorization: `Bearer ${apikey}` }
const { error } = await headWithTimeout(url, [], {
headers,
credentials: 'omit',
async function pingOpenApi(ref: string, timeout?: number) {
const { error } = await headWithTimeout(`${API_URL}/projects/${ref}/api/rest`, [], {
timeout: timeout ?? DEFAULT_TIMEOUT_MILLISECONDS,
})
return error === undefined
@@ -72,11 +57,8 @@ async function pingOpenApi(url: string, apikey: string, timeout?: number) {
*
* @return true if there's no error else false
*/
async function pingHealthCheckApi(url: string, apikey: string, timeout?: number) {
const headers = { apikey }
const { error } = await getWithTimeout(url, {
headers,
credentials: 'omit',
async function pingHealthCheckApi(ref: string, timeout?: number) {
const { error } = await getWithTimeout(`${API_URL}/projects/${ref}/live`, {
timeout: timeout ?? DEFAULT_TIMEOUT_MILLISECONDS,
})
return error === undefined
+10 -35
View File
@@ -1,15 +1,15 @@
import { useEffect, useMemo } from 'react'
import { observer } from 'mobx-react-lite'
import { createGraphiQLFetcher } from '@graphiql/toolkit'
import { NextPageWithLayout } from 'types'
import { useParams, useStore } from 'hooks'
import { API_URL } from 'lib/constants'
import ExtensionCard from 'components/interfaces/Database/Extensions/ExtensionCard'
import GraphiQL from 'components/interfaces/GraphQL/GraphiQL'
import { DocsLayout } from 'components/layouts'
import Connecting from 'components/ui/Loading/Loading'
import { useParams, useStore } from 'hooks'
import { useProjectApiQuery } from 'data/config/project-api-query'
import { useEffect, useMemo } from 'react'
import { NextPageWithLayout } from 'types'
import { IconAlertCircle } from 'ui'
import { useSessionAccessTokenQuery } from 'data/auth/session-access-token-query'
const GraphiQLPage: NextPageWithLayout = () => {
const { ref: projectRef } = useParams()
@@ -17,6 +17,7 @@ const GraphiQLPage: NextPageWithLayout = () => {
const isExtensionsLoading = meta.extensions.isLoading
const pgGraphqlExtension = meta.extensions.byId('pg_graphql')
const { data: accessToken } = useSessionAccessTokenQuery()
useEffect(() => {
if (ui.selectedProject?.ref) {
@@ -26,42 +27,16 @@ const GraphiQLPage: NextPageWithLayout = () => {
}
}, [ui.selectedProject?.ref])
const {
data: settings,
isError: isProjectSettingsError,
isLoading: isProjectSettingsLoading,
} = useProjectApiQuery({
projectRef,
})
const apiService = settings?.autoApiService
const anonKey = apiService?.service_api_keys.find((x) => x.name === 'anon key')
? apiService.defaultApiKey
: undefined
const endpoint = settings?.autoApiService.app_config.endpoint ?? ''
const graphqlUrl = `https://${endpoint}/graphql/v1`
const graphqlUrl = `${API_URL}/projects/${projectRef}/api/graphql`
const fetcher = useMemo(() => {
return createGraphiQLFetcher({
url: graphqlUrl,
fetch,
})
return createGraphiQLFetcher({ url: graphqlUrl, fetch })
}, [graphqlUrl])
if (isProjectSettingsLoading || (isExtensionsLoading && !pgGraphqlExtension)) {
if (!accessToken || (isExtensionsLoading && !pgGraphqlExtension)) {
return <Connecting />
}
if (isProjectSettingsError) {
return (
<div className="py-8 flex items-center justify-center space-x-2">
<IconAlertCircle size={16} strokeWidth={1.5} />
<p className="text-sm text-scale-1100">Failed to retrieve API keys</p>
</div>
)
}
if (pgGraphqlExtension?.installed_version === null) {
return (
<div className="flex-1 flex flex-col justify-center items-center px-4">
@@ -80,7 +55,7 @@ const GraphiQLPage: NextPageWithLayout = () => {
)
}
return <GraphiQL fetcher={fetcher} theme={ui.theme} apiKey={anonKey} />
return <GraphiQL fetcher={fetcher} theme={ui.theme} accessToken={accessToken} />
}
GraphiQLPage.getLayout = (page) => <DocsLayout title="GraphiQL">{page}</DocsLayout>
+1 -6
View File
@@ -77,17 +77,12 @@ const DocView: FC<any> = observer(({}) => {
const anonKey = apiService?.service_api_keys.find((x) => x.name === 'anon key')
? apiService.defaultApiKey
: undefined
const swaggerUrl = data?.autoApiService?.restUrl
const {
data: jsonSchema,
error: jsonSchemaError,
refetch,
} = useProjectJsonSchemaQuery({
projectRef,
swaggerUrl,
apiKey: anonKey,
})
} = useProjectJsonSchemaQuery({ projectRef })
useEffect(() => {
PageState.setJsonSchema(jsonSchema)
@@ -62,7 +62,7 @@ const QueryPerformanceReport: NextPageWithLayout = () => {
It relies on the \`pg_stat_statements\` table. Read more about [examining query performance]((https://supabase.com/docs/guides/platform/performance#examining-query-performance)).
Consider resetting the analysis after optimising any queries.
Consider resetting the analysis after optimizing any queries.
`
+1 -3
View File
@@ -68,9 +68,7 @@ export default class ProjectStore extends PostgresMetaInterface<Project> {
async pingPostgrest(project: Project): Promise<'ONLINE' | 'OFFLINE' | undefined> {
if (project.status === PROJECT_STATUS.ACTIVE_HEALTHY && project.restUrl) {
const success = await pingPostgrest(project.restUrl, project.ref, {
kpsVersion: project.kpsVersion,
})
const success = await pingPostgrest(project.ref, { kpsVersion: project.kpsVersion })
return success ? 'ONLINE' : 'OFFLINE'
}
return undefined
@@ -18,9 +18,11 @@ export const CONFIRMATION: FormSchema = {
descriptionOptional: 'HTML body of your email',
type: 'code',
description: `
- \`{{ .ConfirmationURL }}\` : URL to confirm the message
- \`{{ .ConfirmationURL }}\` : URL to confirm the e-mail address for the new account
- \`{{ .Token }}\` : The 6-digit numeric email OTP
- \`{{ .TokenHash }}\` : The hashed token used in the URL
- \`{{ .SiteURL }}\` : The URL of the site
- \`{{ .Email }}\` : The users email address
`,
},
},
@@ -49,9 +51,11 @@ export const INVITE: FormSchema = {
descriptionOptional: 'HTML body of your email',
type: 'code',
description: `
- \`{{ .ConfirmationURL }}\` : URL to confirm the message
- \`{{ .ConfirmationURL }}\` : URL to accept the invitation to create an account
- \`{{ .Token }}\` : The 6-digit numeric email OTP
- \`{{ .TokenHash }}\` : The hashed token used in the URL
- \`{{ .SiteURL }}\` : The URL of the site
- \`{{ .Email }}\` : The users email address
`,
},
},
@@ -80,9 +84,11 @@ export const MAGIC_LINK: FormSchema = {
descriptionOptional: 'HTML body of your email',
type: 'code',
description: `
- \`{{ .ConfirmationURL }}\` : URL to confirm the message
- \`{{ .ConfirmationURL }}\` : URL for a one-time login to the user's account
- \`{{ .Token }}\` : The 6-digit numeric email OTP
- \`{{ .TokenHash }}\` : The hashed token used in the URL
- \`{{ .SiteURL }}\` : The URL of the site
- \`{{ .Email }}\` : The users email address
`,
},
},
@@ -114,6 +120,9 @@ export const EMAIL_CHANGE: FormSchema = {
- \`{{ .ConfirmationURL }}\` : URL to confirm the email change
- \`{{ .Token }}\` : The 6-digit numeric email OTP
- \`{{ .TokenHash }}\` : The hashed token used in the URL
- \`{{ .SiteURL }}\` : The URL of the site
- \`{{ .Email }}\` : The original users email address
- \`{{ .NewEmail }\` : The users new email address
`,
},
},
@@ -145,6 +154,8 @@ export const RECOVERY: FormSchema = {
- \`{{ .ConfirmationURL }}\` : URL to confirm the password reset
- \`{{ .Token }}\` : The 6-digit numeric email OTP
- \`{{ .TokenHash }}\` : The hashed token used in the URL
- \`{{ .SiteURL }}\` : The URL of the site
- \`{{ .Email }}\` : The users email address
`,
},
},
+2 -2
View File
@@ -183,7 +183,7 @@ export default class MetaStore implements IMetaStore {
this.openApi = new OpenApiStore(
this.rootStore,
`${API_URL}/props/project/${this.projectRef}/api`
`${API_URL}/projects/${this.projectRef}/api/rest`
)
this.tables = new TableStore(this.rootStore, `${this.baseUrl}/tables`, this.headers)
this.columns = new ColumnStore(this.rootStore, `${this.baseUrl}/columns`, this.headers)
@@ -895,7 +895,7 @@ export default class MetaStore implements IMetaStore {
this.headers['x-connection-encrypted'] = connectionString
}
this.openApi.setUrl(`${API_URL}/props/project/${this.projectRef}/api`)
this.openApi.setUrl(`${API_URL}/projects/${this.projectRef}/api/rest`)
this.openApi.setHeaders(this.headers)
this.tables.setUrl(`${this.baseUrl}/tables`)
+1 -11
View File
@@ -52,17 +52,7 @@ export default class OpenApiStore implements IOpenApiStore {
}
async fetchData() {
const headers = { 'Content-Type': 'application/json', ...this.headers }
const projectConfig = await get(this.url, { headers })
if (projectConfig.error) throw projectConfig.error
const apiKey = projectConfig.autoApiService?.defaultApiKey
const restApiUrl = projectConfig.autoApiService?.restUrl
const response = await get<OpenAPIV2.Document>(`${restApiUrl}?apikey=${apiKey}`, {
headers: { ...headers, apiKey: apiKey, Authorization: `Bearer ${apiKey}` },
credentials: 'omit',
})
const response = await get<OpenAPIV2.Document>(this.url)
if (response.error) throw response.error
const tables = response.definitions