From 15c9a6ced3a458bdd9b3a74c4719eccf5e7d678f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Tue, 14 Oct 2025 18:27:34 +0800 Subject: [PATCH 1/5] perf: pass org slug / project ref to resource warnings endpoint (#39471) Based on https://github.com/supabase/infrastructure/pull/26483 - pass in project ref / org slug to ensure we filter down and not query across all orgs unnecessarily --- .../Database/Replication/DestinationPanel.tsx | 22 +- .../Database/Replication/Destinations.tsx | 2 +- .../DiskManagement/DiskManagementForm.tsx | 3 +- .../Home/ProjectList/ProjectList.tsx | 2 +- .../Database/DatabaseReadOnlyAlert.tsx | 5 +- .../Infrastructure/InfrastructureActivity.tsx | 3 +- .../ResourceExhaustionWarningBanner.tsx | 3 +- .../integrations/github-branch-check-query.ts | 6 +- .../integrations/github-branches-query.ts | 4 +- .../invoices/invoice-payment-link-mutation.ts | 4 +- apps/studio/data/invoices/invoice-query.ts | 11 +- apps/studio/data/usage/keys.ts | 3 +- .../data/usage/resource-warnings-query.ts | 38 +- packages/api-types/types/api.d.ts | 167 ++- packages/api-types/types/platform.d.ts | 1077 +++++++++++------ 15 files changed, 944 insertions(+), 406 deletions(-) diff --git a/apps/studio/components/interfaces/Database/Replication/DestinationPanel.tsx b/apps/studio/components/interfaces/Database/Replication/DestinationPanel.tsx index 812ad66d7b83d..102a14e09fe13 100644 --- a/apps/studio/components/interfaces/Database/Replication/DestinationPanel.tsx +++ b/apps/studio/components/interfaces/Database/Replication/DestinationPanel.tsx @@ -123,20 +123,24 @@ export const DestinationPanel = ({ pipelineId: existingDestination?.pipelineId, }) - const defaultValues = useMemo( - () => ({ + const defaultValues = useMemo(() => { + const bigQueryConfig = + destinationData && 'big_query' in destinationData.config + ? destinationData?.config.big_query + : null + + return { type: TypeEnum.enum.BigQuery, name: destinationData?.name ?? '', - projectId: destinationData?.config?.big_query?.project_id ?? '', - datasetId: destinationData?.config?.big_query?.dataset_id ?? '', + projectId: bigQueryConfig?.project_id ?? '', + datasetId: bigQueryConfig?.dataset_id ?? '', // For now, the password will always be set as empty for security reasons. - serviceAccountKey: destinationData?.config?.big_query?.service_account_key ?? '', + serviceAccountKey: bigQueryConfig?.service_account_key ?? '', publicationName: pipelineData?.config.publication_name ?? '', maxFillMs: pipelineData?.config?.batch?.max_fill_ms, - maxStalenessMins: destinationData?.config?.big_query?.max_staleness_mins, - }), - [destinationData, pipelineData] - ) + maxStalenessMins: bigQueryConfig?.max_staleness_mins, + } + }, [destinationData, pipelineData]) const form = useForm>({ mode: 'onBlur', diff --git a/apps/studio/components/interfaces/Database/Replication/Destinations.tsx b/apps/studio/components/interfaces/Database/Replication/Destinations.tsx index 88a6a25458872..7cef5dd364b4a 100644 --- a/apps/studio/components/interfaces/Database/Replication/Destinations.tsx +++ b/apps/studio/components/interfaces/Database/Replication/Destinations.tsx @@ -159,7 +159,7 @@ export const Destinations = () => { sourceId={sourceId} destinationId={destination.id} destinationName={destination.name} - type={destination.config.big_query ? 'BigQuery' : 'Other'} + type={'big_query' in destination.config ? 'BigQuery' : 'Other'} pipeline={pipeline} error={pipelinesError} isLoading={isPipelinesLoading} diff --git a/apps/studio/components/interfaces/DiskManagement/DiskManagementForm.tsx b/apps/studio/components/interfaces/DiskManagement/DiskManagementForm.tsx index f02ce81f0ada1..34b228bc60ef2 100644 --- a/apps/studio/components/interfaces/DiskManagement/DiskManagementForm.tsx +++ b/apps/studio/components/interfaces/DiskManagement/DiskManagementForm.tsx @@ -71,7 +71,8 @@ export function DiskManagementForm() { const { data: org } = useSelectedOrganizationQuery() const { setProjectStatus } = useSetProjectStatus() - const { data: resourceWarnings } = useResourceWarningsQuery() + const { data: resourceWarnings } = useResourceWarningsQuery({ ref: projectRef }) + // [Joshen Cleanup] JFYI this client side filtering can be cleaned up once BE changes are live which will only return the warnings based on the provided ref const projectResourceWarnings = (resourceWarnings ?? [])?.find( (warning) => warning.project === project?.ref ) diff --git a/apps/studio/components/interfaces/Home/ProjectList/ProjectList.tsx b/apps/studio/components/interfaces/Home/ProjectList/ProjectList.tsx index 5bfb9b0214d73..98b05396dc607 100644 --- a/apps/studio/components/interfaces/Home/ProjectList/ProjectList.tsx +++ b/apps/studio/components/interfaces/Home/ProjectList/ProjectList.tsx @@ -86,7 +86,7 @@ export const ProjectList = ({ organization: organization_, rewriteHref }: Projec isError: isErrorPermissions, error: permissionsError, } = usePermissionsQuery() - const { data: resourceWarnings } = useResourceWarningsQuery() + const { data: resourceWarnings } = useResourceWarningsQuery({ slug }) // Move all hooks to the top to comply with Rules of Hooks const { data: integrations } = useOrgIntegrationsQuery({ orgSlug: organization?.slug }) diff --git a/apps/studio/components/interfaces/Settings/Database/DatabaseReadOnlyAlert.tsx b/apps/studio/components/interfaces/Settings/Database/DatabaseReadOnlyAlert.tsx index 79beef58d7c54..2c0ad328f65a1 100644 --- a/apps/studio/components/interfaces/Settings/Database/DatabaseReadOnlyAlert.tsx +++ b/apps/studio/components/interfaces/Settings/Database/DatabaseReadOnlyAlert.tsx @@ -14,8 +14,9 @@ export const DatabaseReadOnlyAlert = () => { const { data: organization } = useSelectedOrganizationQuery() const [showConfirmationModal, setShowConfirmationModal] = useState(false) - const { data: resourceWarnings } = useResourceWarningsQuery() - + const { data: resourceWarnings } = useResourceWarningsQuery({ ref: projectRef }) + // [Joshen Cleanup] JFYI this can be cleaned up once BE changes are live which will only return the warnings based on the provided ref + // No longer need to filter by ref on the client side const isReadOnlyMode = (resourceWarnings ?? [])?.find((warning) => warning.project === projectRef) ?.is_readonly_mode_enabled ?? false diff --git a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureActivity.tsx b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureActivity.tsx index 582c7b2f8230f..04840ec30dfb3 100644 --- a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureActivity.tsx +++ b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureActivity.tsx @@ -59,7 +59,8 @@ export const InfrastructureActivity = () => { }) const isFreePlan = organization?.plan?.id === 'free' - const { data: resourceWarnings } = useResourceWarningsQuery() + const { data: resourceWarnings } = useResourceWarningsQuery({ ref: projectRef }) + // [Joshen Cleanup] JFYI this client side filtering can be cleaned up once BE changes are live which will only return the warnings based on the provided ref const projectResourceWarnings = resourceWarnings?.find((x) => x.project === projectRef) const { data: addons } = useProjectAddonsQuery({ projectRef }) diff --git a/apps/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.tsx b/apps/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.tsx index 60c1ce45da7a3..bb8f56feff9fa 100644 --- a/apps/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.tsx +++ b/apps/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.tsx @@ -11,7 +11,8 @@ import { getWarningContent } from './ResourceExhaustionWarningBanner.utils' export const ResourceExhaustionWarningBanner = () => { const { ref } = useParams() const router = useRouter() - const { data: resourceWarnings } = useResourceWarningsQuery() + const { data: resourceWarnings } = useResourceWarningsQuery({ ref: ref }) + // [Joshen Cleanup] JFYI this client side filtering can be cleaned up once BE changes are live which will only return the warnings based on the provided ref const projectResourceWarnings = (resourceWarnings ?? [])?.find( (warning) => warning.project === ref ) diff --git a/apps/studio/data/integrations/github-branch-check-query.ts b/apps/studio/data/integrations/github-branch-check-query.ts index 05f21499c6744..b7f98fbe41602 100644 --- a/apps/studio/data/integrations/github-branch-check-query.ts +++ b/apps/studio/data/integrations/github-branch-check-query.ts @@ -14,12 +14,12 @@ export async function checkGithubBranchValidity( signal?: AbortSignal ) { const { data, error } = await get( - '/platform/integrations/github/repositories/{repositoryId}/branches/{branchName}', + '/platform/integrations/github/repositories/{repository_id}/branches/{branch_name}', { params: { path: { - repositoryId, - branchName, + repository_id: repositoryId, + branch_name: branchName, }, }, signal, diff --git a/apps/studio/data/integrations/github-branches-query.ts b/apps/studio/data/integrations/github-branches-query.ts index 342238466c54e..361039e1397e8 100644 --- a/apps/studio/data/integrations/github-branches-query.ts +++ b/apps/studio/data/integrations/github-branches-query.ts @@ -13,8 +13,8 @@ export async function getGitHubBranches( ) { if (!connectionId) throw new Error('connectionId is required') - const { data, error } = await get(`/platform/integrations/github/branches/{connectionId}`, { - params: { path: { connectionId } }, + const { data, error } = await get(`/platform/integrations/github/branches/{connection_id}`, { + params: { path: { connection_id: connectionId } }, signal, }) diff --git a/apps/studio/data/invoices/invoice-payment-link-mutation.ts b/apps/studio/data/invoices/invoice-payment-link-mutation.ts index 35fcdbdd93947..d4ea25ca3e5a1 100644 --- a/apps/studio/data/invoices/invoice-payment-link-mutation.ts +++ b/apps/studio/data/invoices/invoice-payment-link-mutation.ts @@ -18,12 +18,12 @@ export async function updateInvoicePaymentLink({ if (!invoiceId) throw new Error('Invoice ID is required') const { data, error } = await get( - '/platform/organizations/{slug}/billing/invoices/{invoiceId}/payment-link', + '/platform/organizations/{slug}/billing/invoices/{invoice_id}/payment-link', { params: { path: { slug, - invoiceId, + invoice_id: invoiceId, }, }, } diff --git a/apps/studio/data/invoices/invoice-query.ts b/apps/studio/data/invoices/invoice-query.ts index 770957f5bedc7..d3e09dcf0f087 100644 --- a/apps/studio/data/invoices/invoice-query.ts +++ b/apps/studio/data/invoices/invoice-query.ts @@ -12,10 +12,13 @@ export async function getInvoice({ invoiceId, slug }: InvoiceVariables, signal?: if (!invoiceId) throw new Error('Invoice ID is required') if (!slug) throw new Error('Slug is required') - const { data, error } = await get(`/platform/organizations/{slug}/billing/invoices/{invoiceId}`, { - params: { path: { invoiceId, slug } }, - signal, - }) + const { data, error } = await get( + `/platform/organizations/{slug}/billing/invoices/{invoice_id}`, + { + params: { path: { invoice_id: invoiceId, slug } }, + signal, + } + ) if (error) handleError(error) return data diff --git a/apps/studio/data/usage/keys.ts b/apps/studio/data/usage/keys.ts index 066dca1bc6fc6..bddb545c550e8 100644 --- a/apps/studio/data/usage/keys.ts +++ b/apps/studio/data/usage/keys.ts @@ -2,5 +2,6 @@ export const usageKeys = { usage: (projectRef: string | undefined) => ['projects', projectRef, 'usage'] as const, orgUsage: (orgSlug: string | undefined, projectRef?: string, start?: string, end?: string) => ['organizations', orgSlug, 'usage', projectRef, start, end] as const, - resourceWarnings: () => ['projects', 'resource-warnings'] as const, + resourceWarnings: (slug?: string, projectRef?: string) => + ['projects', 'resource-warnings', { slug, projectRef }] as const, } diff --git a/apps/studio/data/usage/resource-warnings-query.ts b/apps/studio/data/usage/resource-warnings-query.ts index 9248c2dc9d5a0..cf69871c80b0f 100644 --- a/apps/studio/data/usage/resource-warnings-query.ts +++ b/apps/studio/data/usage/resource-warnings-query.ts @@ -6,8 +6,24 @@ import { get, handleError } from 'data/fetchers' import type { ResponseError } from 'types' import { usageKeys } from './keys' -export async function getResourceWarnings(signal?: AbortSignal) { - const { data, error } = await get(`/platform/projects-resource-warnings`, { signal }) +export type ResourceWarningsVariables = { + ref?: string + slug?: string +} + +export async function getResourceWarnings( + variables?: ResourceWarningsVariables, + signal?: AbortSignal +) { + const { data, error } = await get(`/platform/projects-resource-warnings`, { + params: { + query: { + ref: variables?.ref, + slug: variables?.slug, + }, + }, + signal, + }) if (error) handleError(error) return data @@ -17,15 +33,19 @@ export type ResourceWarning = components['schemas']['ProjectResourceWarningsResp export type ResourceWarningsData = Awaited> export type ResourceWarningsError = ResponseError -export const useResourceWarningsQuery = ({ - enabled = true, - ...options -}: UseQueryOptions = {}) => +export const useResourceWarningsQuery = ( + variables: ResourceWarningsVariables, + { + enabled = true, + ...options + }: UseQueryOptions = {} +) => useQuery( - usageKeys.resourceWarnings(), - ({ signal }) => getResourceWarnings(signal), + usageKeys.resourceWarnings(variables.slug, variables.ref), + ({ signal }) => getResourceWarnings(variables, signal), { - enabled: IS_PLATFORM && enabled, + enabled: + IS_PLATFORM && enabled && (variables.ref !== undefined || variables.slug !== undefined), staleTime: 1000 * 60 * 60, // default 60 minutes ...options, } diff --git a/packages/api-types/types/api.d.ts b/packages/api-types/types/api.d.ts index 6bfe7cfbaa493..e43f935e651da 100644 --- a/packages/api-types/types/api.d.ts +++ b/packages/api-types/types/api.d.ts @@ -1729,6 +1729,23 @@ export interface paths { patch?: never trace?: never } + '/v1/projects/available-regions': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + /** [Beta] Gets the list of available regions that can be used for a new project */ + get: operations['v1-get-available-regions'] + put?: never + post?: never + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } '/v1/snippets': { parameters: { query?: never @@ -1951,6 +1968,9 @@ export interface components { external_zoom_email_optional: boolean | null external_zoom_enabled: boolean | null external_zoom_secret: string | null + hook_after_user_created_enabled: boolean | null + hook_after_user_created_secrets: string | null + hook_after_user_created_uri: string | null hook_before_user_created_enabled: boolean | null hook_before_user_created_secrets: string | null hook_before_user_created_uri: string | null @@ -2139,6 +2159,8 @@ export interface components { */ latest_check_run_id?: number name: string + /** Format: uri */ + notify_url?: string parent_project_ref: string persistent: boolean /** Format: int32 */ @@ -2232,6 +2254,11 @@ export interface components { | '48xlarge_high_memory' git_branch?: string is_default?: boolean + /** + * Format: uri + * @description HTTP endpoint to receive branch status updates. + */ + notify_url?: string persistent?: boolean /** * @description Postgres engine version. If not provided, the latest version will be used. @@ -2900,10 +2927,11 @@ export interface components { redirect_uri?: string refresh_token?: string /** + * Format: uri * @description Resource indicator for MCP (Model Context Protocol) clients - * @enum {string} */ - resource?: 'https://api.supabase.green/mcp' | 'https://mcp.supabase.green/mcp' + resource?: string + scope?: string } OAuthTokenResponse: { access_token: string @@ -2932,8 +2960,6 @@ export interface components { }[] /** @enum {string} */ source_subscription_plan: 'free' | 'pro' | 'team' | 'enterprise' - target_organization_eligible: boolean | null - target_organization_has_free_project_slots: boolean | null /** @enum {string|null} */ target_subscription_plan: 'free' | 'pro' | 'team' | 'enterprise' | null valid: boolean @@ -3028,6 +3054,46 @@ export interface components { override_active_until: string override_enabled: boolean } + RegionsInfo: { + all: { + smartGroup: { + /** @enum {string} */ + code: 'americas' | 'emea' | 'apac' + name: string + /** @enum {string} */ + type: 'smartGroup' + }[] + specific: { + code: string + name: string + /** @enum {string} */ + provider: 'AWS' | 'FLY' | 'AWS_K8S' | 'AWS_NIMBUS' + /** @enum {string} */ + status?: 'capacity' | 'other' + /** @enum {string} */ + type: 'specific' + }[] + } + recommendations: { + smartGroup: { + /** @enum {string} */ + code: 'americas' | 'emea' | 'apac' + name: string + /** @enum {string} */ + type: 'smartGroup' + } + specific: { + code: string + name: string + /** @enum {string} */ + provider: 'AWS' | 'FLY' | 'AWS_K8S' | 'AWS_NIMBUS' + /** @enum {string} */ + status?: 'capacity' | 'other' + /** @enum {string} */ + type: 'specific' + }[] + } + } RemoveNetworkBanRequest: { identifier?: string /** @description List of IP addresses to unban. */ @@ -3332,6 +3398,9 @@ export interface components { external_zoom_email_optional?: boolean | null external_zoom_enabled?: boolean | null external_zoom_secret?: string | null + hook_after_user_created_enabled?: boolean | null + hook_after_user_created_secrets?: string | null + hook_after_user_created_uri?: string | null hook_before_user_created_enabled?: boolean | null hook_before_user_created_secrets?: string | null hook_before_user_created_uri?: string | null @@ -3469,6 +3538,11 @@ export interface components { UpdateBranchBody: { branch_name?: string git_branch?: string + /** + * Format: uri + * @description HTTP endpoint to receive branch status updates. + */ + notify_url?: string persistent?: boolean request_review?: boolean /** @@ -3751,10 +3825,17 @@ export interface components { */ plan?: 'free' | 'pro' /** - * @description Region you want your server to reside in + * @deprecated + * @description Postgres engine version. If not provided, the latest version will be used. * @enum {string} */ - region: + postgres_engine?: '15' | '17' | '17-oriole' + /** + * @deprecated + * @description Region you want your server to reside in. Use region_selection instead. + * @enum {string} + */ + region?: | 'us-east-1' | 'us-east-2' | 'us-west-1' @@ -3773,6 +3854,54 @@ export interface components { | 'ca-central-1' | 'ap-south-1' | 'sa-east-1' + /** + * @description Region selection. Only one of region or region_selection can be specified. + * @example { type: 'smartGroup', code: 'americas' } + */ + region_selection?: + | { + /** + * @description Specific region code. The codes supported are not a stable API, and should be retrieved from the /available-regions endpoint. + * @enum {string} + */ + code: + | 'us-east-1' + | 'us-east-2' + | 'us-west-1' + | 'us-west-2' + | 'ap-east-1' + | 'ap-southeast-1' + | 'ap-northeast-1' + | 'ap-northeast-2' + | 'ap-southeast-2' + | 'eu-west-1' + | 'eu-west-2' + | 'eu-west-3' + | 'eu-north-1' + | 'eu-central-1' + | 'eu-central-2' + | 'ca-central-1' + | 'ap-south-1' + | 'sa-east-1' + /** @enum {string} */ + type: 'specific' + } + | { + /** + * @description The Smart Region Group's code. The codes supported are not a stable API, and should be retrieved from the /available-regions endpoint. + * @example apac + * @enum {string} + */ + code: 'americas' | 'emea' | 'apac' + /** @enum {string} */ + type: 'smartGroup' + } + /** + * @deprecated + * @description Release channel. If not provided, GA will be used. + * @enum {string} + */ + release_channel?: 'internal' | 'alpha' | 'beta' | 'ga' | 'withdrawn' | 'preview' /** * Format: uri * @description Template URL used to create the project from the CLI. @@ -4322,7 +4451,7 @@ export interface operations { organization_slug?: string redirect_uri: string /** @description Resource indicator for MCP (Model Context Protocol) clients */ - resource?: 'https://api.supabase.green/mcp' | 'https://mcp.supabase.green/mcp' + resource?: string response_mode?: string response_type: 'code' | 'token' | 'id_token token' scope?: string @@ -10675,6 +10804,30 @@ export interface operations { } } } + 'v1-get-available-regions': { + parameters: { + query: { + /** @description Continent code to determine regional recommendations: NA (North America), SA (South America), EU (Europe), AF (Africa), AS (Asia), OC (Oceania), AN (Antarctica) */ + continent?: 'NA' | 'SA' | 'EU' | 'AF' | 'AS' | 'OC' | 'AN' + /** @description Slug of your organization */ + organization_slug: string + } + header?: never + path?: never + cookie?: never + } + requestBody?: never + responses: { + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['RegionsInfo'] + } + } + } + } 'v1-list-all-snippets': { parameters: { query?: { diff --git a/packages/api-types/types/platform.d.ts b/packages/api-types/types/platform.d.ts index 924e77e68b36c..b2a53293795be 100644 --- a/packages/api-types/types/platform.d.ts +++ b/packages/api-types/types/platform.d.ts @@ -552,7 +552,7 @@ export interface paths { patch?: never trace?: never } - '/platform/integrations/github/branches/{connectionId}': { + '/platform/integrations/github/branches/{connection_id}': { parameters: { query?: never header?: never @@ -569,7 +569,7 @@ export interface paths { patch?: never trace?: never } - '/platform/integrations/github/branches/{connectionId}/{branchName}': { + '/platform/integrations/github/branches/{connection_id}/{branch_name}': { parameters: { query?: never header?: never @@ -639,7 +639,7 @@ export interface paths { patch?: never trace?: never } - '/platform/integrations/github/repositories/{repositoryId}/branches': { + '/platform/integrations/github/repositories/{repository_id}/branches': { parameters: { query?: never header?: never @@ -656,7 +656,7 @@ export interface paths { patch?: never trace?: never } - '/platform/integrations/github/repositories/{repositoryId}/branches/{branchName}': { + '/platform/integrations/github/repositories/{repository_id}/branches/{branch_name}': { parameters: { query?: never header?: never @@ -801,7 +801,7 @@ export interface paths { patch?: never trace?: never } - '/platform/integrations/vercel/connections/project/{project_ref}': { + '/platform/integrations/vercel/connections/project/{ref}': { parameters: { query?: never header?: never @@ -1028,7 +1028,7 @@ export interface paths { patch?: never trace?: never } - '/platform/organizations/{slug}/billing/invoices/{invoiceId}': { + '/platform/organizations/{slug}/billing/invoices/{invoice_id}': { parameters: { query?: never header?: never @@ -1045,7 +1045,7 @@ export interface paths { patch?: never trace?: never } - '/platform/organizations/{slug}/billing/invoices/{invoiceId}/payment-link': { + '/platform/organizations/{slug}/billing/invoices/{invoice_id}/payment-link': { parameters: { query?: never header?: never @@ -1200,40 +1200,6 @@ export interface paths { patch?: never trace?: never } - '/platform/organizations/{slug}/daily-stats': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - /** Gets daily organization stats */ - get: operations['OrgDailyStatsController_getDailyStats'] - put?: never - post?: never - delete?: never - options?: never - head?: never - patch?: never - trace?: never - } - '/platform/organizations/{slug}/daily-stats/compute': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - /** Gets daily organization stats for compute */ - get: operations['OrgDailyStatsController_getDailyStatsCompute'] - put?: never - post?: never - delete?: never - options?: never - head?: never - patch?: never - trace?: never - } '/platform/organizations/{slug}/documents/dpa': { parameters: { query?: never @@ -2168,6 +2134,42 @@ export interface paths { patch?: never trace?: never } + '/platform/profile/scoped-access-tokens': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + /** Gets the user's scoped access tokens */ + get: operations['ScopedAccessTokensController_getAccessTokens'] + put?: never + /** Creates a new scoped access token */ + post: operations['ScopedAccessTokensController_createAccessToken'] + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } + '/platform/profile/scoped-access-tokens/{id}': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + /** Gets the scoped access token with the given ID */ + get: operations['ScopedAccessTokensController_getAccessToken'] + put?: never + post?: never + /** Deletes the scoped access token with the given ID */ + delete: operations['ScopedAccessTokensController_deleteAccessToken'] + options?: never + head?: never + patch?: never + trace?: never + } '/platform/projects': { parameters: { query?: never @@ -4730,6 +4732,9 @@ export interface components { url: string username?: string | null } + | { + dsn: string + } description?: string name: string /** @enum {string} */ @@ -5168,32 +5173,74 @@ export interface components { } CreateReplicationDestinationBody: { /** @description Destination configuration */ - config: { - big_query: { - /** - * @description BigQuery dataset id - * @example analytics - */ - dataset_id: string - /** - * @description Maximum number of concurrent write streams - * @example 8 - */ - max_concurrent_streams?: number - /** - * @description Maximum data staleness in minutes - * @example 5 - */ - max_staleness_mins?: number - /** - * @description BigQuery project id - * @example my-gcp-project - */ - project_id: string - /** @description BigQuery service account key */ - service_account_key: string - } - } + config: + | { + big_query: { + /** + * @description BigQuery dataset id + * @example analytics + */ + dataset_id: string + /** + * @description Maximum number of concurrent write streams + * @example 8 + */ + max_concurrent_streams?: number + /** + * @description Maximum data staleness in minutes + * @example 5 + */ + max_staleness_mins?: number + /** + * @description BigQuery project id + * @example my-gcp-project + */ + project_id: string + /** @description BigQuery service account key */ + service_account_key: string + } + } + | { + iceberg: { + supabase: { + /** + * @description Catalog token + * @example A jwt secret + */ + catalog_token: string + /** + * @description Namespace + * @example my-namespace + */ + namespace: string + /** + * @description Project ref + * @example abcdefghijklmnopqrst + */ + project_ref: string + /** + * @description S3 access key ID + * @example 53383b1d0cdb16a3afa63152656aa3cc + */ + s3_access_key_id: string + /** + * @description S3 region + * @example ap-southeast-1 + */ + s3_region: string + /** + * @description S3 secret access key + * @example 25a0c5e69d847088a3e6ffb901adf4d19bbf74a400dec2ee49f46401039b3258 + */ + s3_secret_access_key: string + /** + * @description Warehouse name + * @example my-warehouse + */ + warehouse_name: string + } + } + } /** * @description Destination name * @example bq-analytics @@ -5202,32 +5249,74 @@ export interface components { } CreateReplicationDestinationPipelineBody: { /** @description Destination configuration */ - destination_config: { - big_query: { - /** - * @description BigQuery dataset id - * @example analytics - */ - dataset_id: string - /** - * @description Maximum number of concurrent write streams - * @example 8 - */ - max_concurrent_streams?: number - /** - * @description Maximum data staleness in minutes - * @example 5 - */ - max_staleness_mins?: number - /** - * @description BigQuery project id - * @example my-gcp-project - */ - project_id: string - /** @description BigQuery service account key */ - service_account_key: string - } - } + destination_config: + | { + big_query: { + /** + * @description BigQuery dataset id + * @example analytics + */ + dataset_id: string + /** + * @description Maximum number of concurrent write streams + * @example 8 + */ + max_concurrent_streams?: number + /** + * @description Maximum data staleness in minutes + * @example 5 + */ + max_staleness_mins?: number + /** + * @description BigQuery project id + * @example my-gcp-project + */ + project_id: string + /** @description BigQuery service account key */ + service_account_key: string + } + } + | { + iceberg: { + supabase: { + /** + * @description Catalog token + * @example A jwt secret + */ + catalog_token: string + /** + * @description Namespace + * @example my-namespace + */ + namespace: string + /** + * @description Project ref + * @example abcdefghijklmnopqrst + */ + project_ref: string + /** + * @description S3 access key ID + * @example 53383b1d0cdb16a3afa63152656aa3cc + */ + s3_access_key_id: string + /** + * @description S3 region + * @example ap-southeast-1 + */ + s3_region: string + /** + * @description S3 secret access key + * @example 25a0c5e69d847088a3e6ffb901adf4d19bbf74a400dec2ee49f46401039b3258 + */ + s3_secret_access_key: string + /** + * @description Warehouse name + * @example my-warehouse + */ + warehouse_name: string + } + } + } /** * @description Destination name * @example bq-analytics @@ -5317,6 +5406,89 @@ export interface components { name: string owner: string } + CreateScopedAccessTokenBody: { + /** Format: date-time */ + expires_at?: string + name: string + organization_slugs?: string[] + permissions: ( + | 'organizations_read' + | 'organizations_write' + | 'projects_read' + | 'available_regions_read' + | 'snippets_read' + | 'organization_admin_read' + | 'organization_admin_write' + | 'members_read' + | 'members_write' + | 'project_admin_read' + | 'project_admin_write' + | 'advisors_read' + | 'api_gateway_keys_read' + | 'api_gateway_keys_write' + | 'auth_config_read' + | 'auth_config_write' + | 'auth_signing_keys_read' + | 'auth_signing_keys_write' + | 'backups_read' + | 'backups_write' + | 'branching_development_read' + | 'branching_development_write' + | 'branching_production_read' + | 'branching_production_write' + | 'custom_domain_read' + | 'custom_domain_write' + | 'data_api_config_read' + | 'data_api_config_write' + | 'database_read' + | 'database_write' + | 'database_config_read' + | 'database_config_write' + | 'database_network_bans_read' + | 'database_network_bans_write' + | 'database_network_restrictions_read' + | 'database_network_restrictions_write' + | 'database_migrations_read' + | 'database_migrations_write' + | 'database_pooling_config_read' + | 'database_pooling_config_write' + | 'database_readonly_config_read' + | 'database_readonly_config_write' + | 'database_ssl_config_read' + | 'database_ssl_config_write' + | 'database_webhooks_config_read' + | 'database_webhooks_config_write' + | 'edge_functions_read' + | 'edge_functions_write' + | 'edge_functions_secrets_read' + | 'edge_functions_secrets_write' + | 'infra_add-ons_read' + | 'infra_add-ons_write' + | 'infra_read_replicas_read' + | 'infra_read_replicas_write' + | 'project_snippets_read' + | 'project_snippets_write' + | 'storage_read' + | 'storage_write' + | 'storage_config_read' + | 'storage_config_write' + | 'telemetry_logs_read' + | 'telemetry_usage_read' + )[] + project_refs?: string[] + } + CreateScopedAccessTokenResponse: { + created_at: string + expires_at: string | null + id: string + last_used_at: string | null + name: string + organization_slugs?: string[] + permissions: string[] + project_refs?: string[] + token: string + token_alias: string + } CreateSourceResponse: { /** @description Source ID */ id: number @@ -6095,6 +6267,27 @@ export interface components { } path: string } + GetScopedAccessTokenResponse: { + created_at: string + expires_at: string | null + id: string + last_used_at: string | null + name: string + organization_slugs?: string[] + permissions: string[] + project_refs?: string[] + token_alias: string + } + GetScopedAccessTokensResponse: { + tokens: { + created_at: string + expires_at: string | null + id: string + last_used_at: string | null + name: string + token_alias: string + }[] + } GetSignedUrlBody: { expiresIn: number options?: { @@ -6471,6 +6664,9 @@ export interface components { EXTERNAL_ZOOM_EMAIL_OPTIONAL: boolean EXTERNAL_ZOOM_ENABLED: boolean EXTERNAL_ZOOM_SECRET: string + HOOK_AFTER_USER_CREATED_ENABLED: boolean + HOOK_AFTER_USER_CREATED_SECRETS: string + HOOK_AFTER_USER_CREATED_URI: string HOOK_BEFORE_USER_CREATED_ENABLED: boolean HOOK_BEFORE_USER_CREATED_SECRETS: string HOOK_BEFORE_USER_CREATED_URI: string @@ -6541,6 +6737,9 @@ export interface components { MFA_WEB_AUTHN_VERIFY_ENABLED: boolean NIMBUS_OAUTH_CLIENT_ID: string | null NIMBUS_OAUTH_CLIENT_SECRET: string | null + OAUTH_SERVER_ALLOW_DYNAMIC_REGISTRATION: boolean + OAUTH_SERVER_AUTHORIZATION_PATH: string | null + OAUTH_SERVER_ENABLED: boolean PASSWORD_HIBP_ENABLED: boolean PASSWORD_MIN_LENGTH: number PASSWORD_REQUIRED_CHARACTERS: string @@ -6683,6 +6882,9 @@ export interface components { url: string username?: string | null } + | { + dsn: string + } description?: string id: number metadata: { @@ -7318,6 +7520,7 @@ export interface components { } PauseStatusResponse: { can_restore: boolean + last_paused_on: string | null latest_downloadable_backup_id: number | null max_days_till_restore_disabled: number remaining_days_till_restore_disabled: number | null @@ -7671,11 +7874,8 @@ export interface components { limit: number name: string }[] - source_project_eligible: boolean /** @enum {string} */ source_subscription_plan: 'free' | 'pro' | 'team' | 'enterprise' - target_organization_eligible: boolean | null - target_organization_has_free_project_slots: boolean | null /** @enum {string|null} */ target_subscription_plan: 'free' | 'pro' | 'team' | 'enterprise' | null valid: boolean @@ -8091,32 +8291,74 @@ export interface components { } ReplicationDestinationResponse: { /** @description Destination configuration */ - config: { - big_query: { - /** - * @description BigQuery dataset id - * @example analytics - */ - dataset_id: string - /** - * @description Maximum number of concurrent write streams - * @example 8 - */ - max_concurrent_streams?: number - /** - * @description Maximum data staleness in minutes - * @example 5 - */ - max_staleness_mins?: number - /** - * @description BigQuery project id - * @example my-gcp-project - */ - project_id: string - /** @description BigQuery service account key */ - service_account_key: string - } - } + config: + | { + big_query: { + /** + * @description BigQuery dataset id + * @example analytics + */ + dataset_id: string + /** + * @description Maximum number of concurrent write streams + * @example 8 + */ + max_concurrent_streams?: number + /** + * @description Maximum data staleness in minutes + * @example 5 + */ + max_staleness_mins?: number + /** + * @description BigQuery project id + * @example my-gcp-project + */ + project_id: string + /** @description BigQuery service account key */ + service_account_key: string + } + } + | { + iceberg: { + supabase: { + /** + * @description Catalog token + * @example A jwt secret + */ + catalog_token: string + /** + * @description Namespace + * @example my-namespace + */ + namespace: string + /** + * @description Project ref + * @example abcdefghijklmnopqrst + */ + project_ref: string + /** + * @description S3 access key ID + * @example 53383b1d0cdb16a3afa63152656aa3cc + */ + s3_access_key_id: string + /** + * @description S3 region + * @example ap-southeast-1 + */ + s3_region: string + /** + * @description S3 secret access key + * @example 25a0c5e69d847088a3e6ffb901adf4d19bbf74a400dec2ee49f46401039b3258 + */ + s3_secret_access_key: string + /** + * @description Warehouse name + * @example my-warehouse + */ + warehouse_name: string + } + } + } /** * @description Destination id * @example 2001 @@ -8137,32 +8379,74 @@ export interface components { /** @description List of destinations */ destinations: { /** @description Destination configuration */ - config: { - big_query: { - /** - * @description BigQuery dataset id - * @example analytics - */ - dataset_id: string - /** - * @description Maximum number of concurrent write streams - * @example 8 - */ - max_concurrent_streams?: number - /** - * @description Maximum data staleness in minutes - * @example 5 - */ - max_staleness_mins?: number - /** - * @description BigQuery project id - * @example my-gcp-project - */ - project_id: string - /** @description BigQuery service account key */ - service_account_key: string - } - } + config: + | { + big_query: { + /** + * @description BigQuery dataset id + * @example analytics + */ + dataset_id: string + /** + * @description Maximum number of concurrent write streams + * @example 8 + */ + max_concurrent_streams?: number + /** + * @description Maximum data staleness in minutes + * @example 5 + */ + max_staleness_mins?: number + /** + * @description BigQuery project id + * @example my-gcp-project + */ + project_id: string + /** @description BigQuery service account key */ + service_account_key: string + } + } + | { + iceberg: { + supabase: { + /** + * @description Catalog token + * @example A jwt secret + */ + catalog_token: string + /** + * @description Namespace + * @example my-namespace + */ + namespace: string + /** + * @description Project ref + * @example abcdefghijklmnopqrst + */ + project_ref: string + /** + * @description S3 access key ID + * @example 53383b1d0cdb16a3afa63152656aa3cc + */ + s3_access_key_id: string + /** + * @description S3 region + * @example ap-southeast-1 + */ + s3_region: string + /** + * @description S3 secret access key + * @example 25a0c5e69d847088a3e6ffb901adf4d19bbf74a400dec2ee49f46401039b3258 + */ + s3_secret_access_key: string + /** + * @description Warehouse name + * @example my-warehouse + */ + warehouse_name: string + } + } + } /** * @description Destination id * @example 2001 @@ -9192,10 +9476,22 @@ export interface components { url: string username?: string | null } + | { + dsn: string + } description?: string name?: string /** @enum {string} */ - type: 'postgres' | 'bigquery' | 'webhook' | 'datadog' | 'elastic' | 'loki' + type: + | 'postgres' + | 'bigquery' + | 'clickhouse' + | 'webhook' + | 'datadog' + | 'elastic' + | 'loki' + | 'sentry' + | 's3' } UpdateCollectionBody: { name: string @@ -9357,6 +9653,9 @@ export interface components { EXTERNAL_ZOOM_EMAIL_OPTIONAL?: boolean | null EXTERNAL_ZOOM_ENABLED?: boolean | null EXTERNAL_ZOOM_SECRET?: string | null + HOOK_AFTER_USER_CREATED_ENABLED?: boolean | null + HOOK_AFTER_USER_CREATED_SECRETS?: string | null + HOOK_AFTER_USER_CREATED_URI?: string | null HOOK_BEFORE_USER_CREATED_ENABLED?: boolean | null HOOK_BEFORE_USER_CREATED_SECRETS?: string | null HOOK_BEFORE_USER_CREATED_URI?: string | null @@ -9427,6 +9726,9 @@ export interface components { MFA_WEB_AUTHN_VERIFY_ENABLED?: boolean | null NIMBUS_OAUTH_CLIENT_ID?: string | null NIMBUS_OAUTH_CLIENT_SECRET?: string | null + OAUTH_SERVER_ALLOW_DYNAMIC_REGISTRATION?: boolean | null + OAUTH_SERVER_AUTHORIZATION_PATH?: string | null + OAUTH_SERVER_ENABLED?: boolean | null PASSWORD_HIBP_ENABLED?: boolean | null PASSWORD_MIN_LENGTH?: number | null /** @enum {string|null} */ @@ -9494,6 +9796,9 @@ export interface components { URI_ALLOW_LIST?: string | null } UpdateGoTrueConfigHooksBody: { + HOOK_AFTER_USER_CREATED_ENABLED?: boolean | null + HOOK_AFTER_USER_CREATED_SECRETS?: string | null + HOOK_AFTER_USER_CREATED_URI?: string | null HOOK_BEFORE_USER_CREATED_ENABLED?: boolean | null HOOK_BEFORE_USER_CREATED_SECRETS?: string | null HOOK_BEFORE_USER_CREATED_URI?: string | null @@ -9689,32 +9994,74 @@ export interface components { } UpdateReplicationDestinationBody: { /** @description Destination configuration */ - config: { - big_query: { - /** - * @description BigQuery dataset id - * @example analytics - */ - dataset_id: string - /** - * @description Maximum number of concurrent write streams - * @example 8 - */ - max_concurrent_streams?: number - /** - * @description Maximum data staleness in minutes - * @example 5 - */ - max_staleness_mins?: number - /** - * @description BigQuery project id - * @example my-gcp-project - */ - project_id: string - /** @description BigQuery service account key */ - service_account_key: string - } - } + config: + | { + big_query: { + /** + * @description BigQuery dataset id + * @example analytics + */ + dataset_id: string + /** + * @description Maximum number of concurrent write streams + * @example 8 + */ + max_concurrent_streams?: number + /** + * @description Maximum data staleness in minutes + * @example 5 + */ + max_staleness_mins?: number + /** + * @description BigQuery project id + * @example my-gcp-project + */ + project_id: string + /** @description BigQuery service account key */ + service_account_key: string + } + } + | { + iceberg: { + supabase: { + /** + * @description Catalog token + * @example A jwt secret + */ + catalog_token: string + /** + * @description Namespace + * @example my-namespace + */ + namespace: string + /** + * @description Project ref + * @example abcdefghijklmnopqrst + */ + project_ref: string + /** + * @description S3 access key ID + * @example 53383b1d0cdb16a3afa63152656aa3cc + */ + s3_access_key_id: string + /** + * @description S3 region + * @example ap-southeast-1 + */ + s3_region: string + /** + * @description S3 secret access key + * @example 25a0c5e69d847088a3e6ffb901adf4d19bbf74a400dec2ee49f46401039b3258 + */ + s3_secret_access_key: string + /** + * @description Warehouse name + * @example my-warehouse + */ + warehouse_name: string + } + } + } /** * @description Destination name * @example bq-analytics @@ -9723,32 +10070,74 @@ export interface components { } UpdateReplicationDestinationPipelineBody: { /** @description Destination configuration */ - destination_config: { - big_query: { - /** - * @description BigQuery dataset id - * @example analytics - */ - dataset_id: string - /** - * @description Maximum number of concurrent write streams - * @example 8 - */ - max_concurrent_streams?: number - /** - * @description Maximum data staleness in minutes - * @example 5 - */ - max_staleness_mins?: number - /** - * @description BigQuery project id - * @example my-gcp-project - */ - project_id: string - /** @description BigQuery service account key */ - service_account_key: string - } - } + destination_config: + | { + big_query: { + /** + * @description BigQuery dataset id + * @example analytics + */ + dataset_id: string + /** + * @description Maximum number of concurrent write streams + * @example 8 + */ + max_concurrent_streams?: number + /** + * @description Maximum data staleness in minutes + * @example 5 + */ + max_staleness_mins?: number + /** + * @description BigQuery project id + * @example my-gcp-project + */ + project_id: string + /** @description BigQuery service account key */ + service_account_key: string + } + } + | { + iceberg: { + supabase: { + /** + * @description Catalog token + * @example A jwt secret + */ + catalog_token: string + /** + * @description Namespace + * @example my-namespace + */ + namespace: string + /** + * @description Project ref + * @example abcdefghijklmnopqrst + */ + project_ref: string + /** + * @description S3 access key ID + * @example 53383b1d0cdb16a3afa63152656aa3cc + */ + s3_access_key_id: string + /** + * @description S3 region + * @example ap-southeast-1 + */ + s3_region: string + /** + * @description S3 secret access key + * @example 25a0c5e69d847088a3e6ffb901adf4d19bbf74a400dec2ee49f46401039b3258 + */ + s3_secret_access_key: string + /** + * @description Warehouse name + * @example my-warehouse + */ + warehouse_name: string + } + } + } /** * @description Destination name * @example bq-analytics @@ -11775,7 +12164,7 @@ export interface operations { } header?: never path: { - connectionId: number + connection_id: number } cookie?: never } @@ -11803,8 +12192,8 @@ export interface operations { query?: never header?: never path: { - branchName: string - connectionId: number + branch_name: string + connection_id: number } cookie?: never } @@ -11972,7 +12361,7 @@ export interface operations { query?: never header?: never path: { - repositoryId: number + repository_id: number } cookie?: never } @@ -12000,8 +12389,8 @@ export interface operations { query?: never header?: never path: { - branchName: string - repositoryId: number + branch_name: string + repository_id: number } cookie?: never } @@ -12327,7 +12716,7 @@ export interface operations { query?: never header?: never path: { - project_ref: string + ref: string } cookie?: never } @@ -13036,7 +13425,7 @@ export interface operations { query?: never header?: never path: { - invoiceId: string + invoice_id: string /** @description Organization slug */ slug: string } @@ -13087,7 +13476,7 @@ export interface operations { query?: never header?: never path: { - invoiceId: string + invoice_id: string /** @description Organization slug */ slug: string } @@ -13645,157 +14034,6 @@ export interface operations { } } } - OrgDailyStatsController_getDailyStats: { - parameters: { - query: { - endDate: string - interval?: string - metric: - | 'EGRESS' - | 'CACHED_EGRESS' - | 'DATABASE_SIZE' - | 'STORAGE_SIZE' - | 'MONTHLY_ACTIVE_USERS' - | 'MONTHLY_ACTIVE_SSO_USERS' - | 'FUNCTION_INVOCATIONS' - | 'FUNCTION_CPU_MILLISECONDS' - | 'STORAGE_IMAGES_TRANSFORMED' - | 'REALTIME_MESSAGE_COUNT' - | 'REALTIME_PEAK_CONNECTIONS' - | 'DISK_SIZE_GB_HOURS_GP3' - | 'DISK_SIZE_GB_HOURS_IO2' - | 'AUTH_MFA_PHONE' - | 'AUTH_MFA_WEB_AUTHN' - | 'LOG_DRAIN_EVENTS' - | 'MONTHLY_ACTIVE_THIRD_PARTY_USERS' - | 'DISK_THROUGHPUT_GP3' - | 'DISK_IOPS_GP3' - | 'DISK_IOPS_IO2' - | 'COMPUTE_HOURS_BRANCH' - | 'COMPUTE_HOURS_XS' - | 'COMPUTE_HOURS_SM' - | 'COMPUTE_HOURS_MD' - | 'COMPUTE_HOURS_L' - | 'COMPUTE_HOURS_XL' - | 'COMPUTE_HOURS_2XL' - | 'COMPUTE_HOURS_4XL' - | 'COMPUTE_HOURS_8XL' - | 'COMPUTE_HOURS_12XL' - | 'COMPUTE_HOURS_16XL' - | 'COMPUTE_HOURS_24XL' - | 'COMPUTE_HOURS_24XL_OPTIMIZED_CPU' - | 'COMPUTE_HOURS_24XL_OPTIMIZED_MEMORY' - | 'COMPUTE_HOURS_24XL_HIGH_MEMORY' - | 'COMPUTE_HOURS_48XL' - | 'COMPUTE_HOURS_48XL_OPTIMIZED_CPU' - | 'COMPUTE_HOURS_48XL_OPTIMIZED_MEMORY' - | 'COMPUTE_HOURS_48XL_HIGH_MEMORY' - | 'CUSTOM_DOMAIN' - | 'PITR_7' - | 'PITR_14' - | 'PITR_28' - | 'IPV4' - | 'LOG_DRAIN' - projectRef?: string - startDate: string - } - header?: never - path: { - /** @description Organization slug */ - slug: string - } - cookie?: never - } - requestBody?: never - responses: { - 200: { - headers: { - [name: string]: unknown - } - content?: never - } - /** @description Unauthorized */ - 401: { - headers: { - [name: string]: unknown - } - content?: never - } - /** @description Forbidden action */ - 403: { - headers: { - [name: string]: unknown - } - content?: never - } - /** @description Rate limit exceeded */ - 429: { - headers: { - [name: string]: unknown - } - content?: never - } - /** @description Failed to get daily organization stats */ - 500: { - headers: { - [name: string]: unknown - } - content?: never - } - } - } - OrgDailyStatsController_getDailyStatsCompute: { - parameters: { - query: { - endDate: string - projectRef?: string - startDate: string - } - header?: never - path: { - /** @description Organization slug */ - slug: string - } - cookie?: never - } - requestBody?: never - responses: { - 200: { - headers: { - [name: string]: unknown - } - content?: never - } - /** @description Unauthorized */ - 401: { - headers: { - [name: string]: unknown - } - content?: never - } - /** @description Forbidden action */ - 403: { - headers: { - [name: string]: unknown - } - content?: never - } - /** @description Rate limit exceeded */ - 429: { - headers: { - [name: string]: unknown - } - content?: never - } - /** @description Failed to get daily organization stats for compute */ - 500: { - headers: { - [name: string]: unknown - } - content?: never - } - } - } OrgDocumentsController_createDpaDocument: { parameters: { query?: never @@ -18480,6 +18718,116 @@ export interface operations { } } } + ScopedAccessTokensController_getAccessTokens: { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + requestBody?: never + responses: { + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['GetScopedAccessTokensResponse'] + } + } + /** @description Failed to get user's scoped access tokens */ + 500: { + headers: { + [name: string]: unknown + } + content?: never + } + } + } + ScopedAccessTokensController_createAccessToken: { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + requestBody: { + content: { + 'application/json': components['schemas']['CreateScopedAccessTokenBody'] + } + } + responses: { + 201: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['CreateScopedAccessTokenResponse'] + } + } + /** @description Failed to create scoped access token */ + 500: { + headers: { + [name: string]: unknown + } + content?: never + } + } + } + ScopedAccessTokensController_getAccessToken: { + parameters: { + query?: never + header?: never + path: { + id: string + } + cookie?: never + } + requestBody?: never + responses: { + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['GetScopedAccessTokenResponse'] + } + } + /** @description Failed to get scoped access token */ + 500: { + headers: { + [name: string]: unknown + } + content?: never + } + } + } + ScopedAccessTokensController_deleteAccessToken: { + parameters: { + query?: never + header?: never + path: { + id: string + } + cookie?: never + } + requestBody?: never + responses: { + 200: { + headers: { + [name: string]: unknown + } + content?: never + } + /** @description Failed to delete scoped access token */ + 500: { + headers: { + [name: string]: unknown + } + content?: never + } + } + } ProjectsController_getProjects: { parameters: { query?: never @@ -18524,7 +18872,12 @@ export interface operations { } ProjectsResourceWarningsController_getProjectsResourceWarnings: { parameters: { - query?: never + query?: { + /** @description Project ref */ + ref?: string + /** @description Organization slug */ + slug?: string + } header?: never path?: never cookie?: never From 2d986e5b7f78a4c6e43e4bedfe3c8c67164b4c86 Mon Sep 17 00:00:00 2001 From: Alaister Young Date: Tue, 14 Oct 2025 22:48:12 +0800 Subject: [PATCH 2/5] chore: hideable TOS link on sign in/up page (#39530) --- .../layouts/SignInLayout/SignInLayout.tsx | 13 ++++++++++--- .../common/enabled-features/enabled-features.json | 1 + .../enabled-features/enabled-features.schema.json | 5 +++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/apps/studio/components/layouts/SignInLayout/SignInLayout.tsx b/apps/studio/components/layouts/SignInLayout/SignInLayout.tsx index 0ef51bb710a7e..0785b981c5c45 100644 --- a/apps/studio/components/layouts/SignInLayout/SignInLayout.tsx +++ b/apps/studio/components/layouts/SignInLayout/SignInLayout.tsx @@ -30,8 +30,15 @@ const SignInLayout = ({ const { resolvedTheme } = useTheme() const ongoingIncident = useFlag('ongoingIncident') - const { dashboardAuthShowTestimonial: showTestimonial, brandingLargeLogo: largeLogo } = - useIsFeatureEnabled(['dashboard_auth:show_testimonial', 'branding:large_logo']) + const { + dashboardAuthShowTestimonial: showTestimonial, + brandingLargeLogo: largeLogo, + dashboardAuthShowTos: showTos, + } = useIsFeatureEnabled([ + 'dashboard_auth:show_testimonial', + 'branding:large_logo', + 'dashboard_auth:show_tos', + ]) // This useEffect redirects the user to MFA if they're already halfway signed in useEffect(() => { @@ -126,7 +133,7 @@ const SignInLayout = ({ {children} - {showDisclaimer && ( + {showDisclaimer && showTos && (

By continuing, you agree to Supabase's{' '} diff --git a/packages/common/enabled-features/enabled-features.json b/packages/common/enabled-features/enabled-features.json index e5204a6f50f8e..5e51a564817a8 100644 --- a/packages/common/enabled-features/enabled-features.json +++ b/packages/common/enabled-features/enabled-features.json @@ -34,6 +34,7 @@ "dashboard_auth:sign_in_with_sso": true, "dashboard_auth:sign_in_with_email": true, "dashboard_auth:show_testimonial": true, + "dashboard_auth:show_tos": true, "database:replication": true, "database:roles": true, diff --git a/packages/common/enabled-features/enabled-features.schema.json b/packages/common/enabled-features/enabled-features.schema.json index 8fe3d88dbcc7d..37cfcfdf32595 100644 --- a/packages/common/enabled-features/enabled-features.schema.json +++ b/packages/common/enabled-features/enabled-features.schema.json @@ -120,6 +120,10 @@ "type": "boolean", "description": "Enable the testimonial on the sign in/up page" }, + "dashboard_auth:show_tos": { + "type": "boolean", + "description": "Enable the terms of service link on the sign in/up page" + }, "database:replication": { "type": "boolean", @@ -430,6 +434,7 @@ "dashboard_auth:sign_in_with_github", "dashboard_auth:sign_in_with_sso", "dashboard_auth:sign_in_with_email", + "dashboard_auth:show_tos", "database:replication", "database:roles", "database:restore_to_new_project", From c6a09b3fcf1dfa2f53bfdb9267435c6516f051d4 Mon Sep 17 00:00:00 2001 From: Alaister Young Date: Tue, 14 Oct 2025 22:49:51 +0800 Subject: [PATCH 3/5] chore: hideable legal org documents tab (#39531) --- .../OrganizationSettingsLayout.tsx | 19 ++++++++++++++----- apps/studio/pages/org/[slug]/documents.tsx | 12 +++++++++++- .../enabled-features/enabled-features.json | 1 + .../enabled-features.schema.json | 6 ++++++ 4 files changed, 32 insertions(+), 6 deletions(-) diff --git a/apps/studio/components/layouts/ProjectLayout/OrganizationSettingsLayout.tsx b/apps/studio/components/layouts/ProjectLayout/OrganizationSettingsLayout.tsx index 836ad3584ac9c..b9f974dc65446 100644 --- a/apps/studio/components/layouts/ProjectLayout/OrganizationSettingsLayout.tsx +++ b/apps/studio/components/layouts/ProjectLayout/OrganizationSettingsLayout.tsx @@ -16,7 +16,12 @@ function OrganizationSettingsLayout({ children }: PropsWithChildren) { const { organizationShowSsoSettings: showSsoSettings, organizationShowSecuritySettings: showSecuritySettings, - } = useIsFeatureEnabled(['organization:show_sso_settings', 'organization:show_security_settings']) + organizationShowLegalDocuments: showLegalDocuments, + } = useIsFeatureEnabled([ + 'organization:show_sso_settings', + 'organization:show_security_settings', + 'organization:show_legal_documents', + ]) const navMenuItems = [ { @@ -48,10 +53,14 @@ function OrganizationSettingsLayout({ children }: PropsWithChildren) { label: 'Audit Logs', href: `/org/${slug}/audit`, }, - { - label: 'Legal Documents', - href: `/org/${slug}/documents`, - }, + ...(showLegalDocuments + ? [ + { + label: 'Legal Documents', + href: `/org/${slug}/documents`, + }, + ] + : []), ] return ( diff --git a/apps/studio/pages/org/[slug]/documents.tsx b/apps/studio/pages/org/[slug]/documents.tsx index 829b83ba8d7dd..1d2fb1e44d519 100644 --- a/apps/studio/pages/org/[slug]/documents.tsx +++ b/apps/studio/pages/org/[slug]/documents.tsx @@ -1,11 +1,21 @@ +import { useParams } from 'common' import { Documents } from 'components/interfaces/Organization' -import AppLayout from 'components/layouts/AppLayout/AppLayout' import DefaultLayout from 'components/layouts/DefaultLayout' import OrganizationLayout from 'components/layouts/OrganizationLayout' import OrganizationSettingsLayout from 'components/layouts/ProjectLayout/OrganizationSettingsLayout' +import { UnknownInterface } from 'components/ui/UnknownInterface' +import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import type { NextPageWithLayout } from 'types' const OrgDocuments: NextPageWithLayout = () => { + const { slug } = useParams() + + const showLegalDocuments = useIsFeatureEnabled('organization:show_legal_documents') + + if (!showLegalDocuments) { + return + } + return } diff --git a/packages/common/enabled-features/enabled-features.json b/packages/common/enabled-features/enabled-features.json index 5e51a564817a8..6420164ff2311 100644 --- a/packages/common/enabled-features/enabled-features.json +++ b/packages/common/enabled-features/enabled-features.json @@ -79,6 +79,7 @@ "organization:show_sso_settings": true, "organization:show_security_settings": true, + "organization:show_legal_documents": true, "profile:show_email": true, "profile:show_information": true, diff --git a/packages/common/enabled-features/enabled-features.schema.json b/packages/common/enabled-features/enabled-features.schema.json index 37cfcfdf32595..c742c5a2848e3 100644 --- a/packages/common/enabled-features/enabled-features.schema.json +++ b/packages/common/enabled-features/enabled-features.schema.json @@ -276,6 +276,10 @@ "type": "boolean", "description": "Show the security settings tab in the organization settings page" }, + "organization:show_legal_documents": { + "type": "boolean", + "description": "Show the legal documents tab in the organization settings page" + }, "profile:show_email": { "type": "boolean", @@ -472,6 +476,8 @@ "logs:metadata", "logs:show_metadata_ip_template", "organization:show_sso_settings", + "organization:show_security_settings", + "organization:show_legal_documents", "project_creation:show_advanced_config", "project_homepage:show_instance_size", "project_homepage:show_examples", From 92a79d5156d1191c40f1cf906294d422af75deff Mon Sep 17 00:00:00 2001 From: Charis <26616127+charislam@users.noreply.github.com> Date: Tue, 14 Oct 2025 10:52:05 -0400 Subject: [PATCH 4/5] fix(support form): project selector, test timeout (#39529) * fix(support form): fetch projects on mount The OrganizationProjectSelector was updated to only fetch when opened to save on API requests. This causes a problem for the Support Form, which expects the projects to be fetched immediately so it can fall back to the first project if no project was paused in the URL. Added a fetchOnMount prop to OrganizationProjectSelector, which defaults to false, so we can override this behaviour for the Support Form. * fix(tests): increase timeout on support form tests --- .../interfaces/Support/ProjectAndPlanInfo.tsx | 3 ++- .../Support/__tests__/SupportFormPage.test.tsx | 18 +++++++++--------- .../ui/OrganizationProjectSelector.tsx | 4 +++- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/apps/studio/components/interfaces/Support/ProjectAndPlanInfo.tsx b/apps/studio/components/interfaces/Support/ProjectAndPlanInfo.tsx index 0d85a5015c55b..dcb593f5bc46b 100644 --- a/apps/studio/components/interfaces/Support/ProjectAndPlanInfo.tsx +++ b/apps/studio/components/interfaces/Support/ProjectAndPlanInfo.tsx @@ -11,9 +11,9 @@ import { OrganizationProjectSelector } from 'components/ui/OrganizationProjectSe import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import { Button, + cn, CommandGroup_Shadcn_, CommandItem_Shadcn_, - cn, FormControl_Shadcn_, FormField_Shadcn_, } from 'ui' @@ -76,6 +76,7 @@ function ProjectSelector({ form, orgSlug, projectRef }: ProjectSelectorProps) { { expect(submitSpy).toHaveBeenCalledTimes(1) }) expect(submitSpy.mock.calls[0]?.[0]?.dashboardSentryIssueId).toBe(sentryIssueId) - }) + }, 10_000) test('includes initial error message from URL in submission payload', async () => { const initialError = 'failed to fetch user data' @@ -551,7 +551,7 @@ describe('SupportFormPage', () => { const payload = submitSpy.mock.calls[0]?.[0] expect(payload?.message).toMatch(initialError) - }) + }, 10_000) test('submits support request with problem category, library, and affected services', async () => { const submitSpy = vi.fn() @@ -640,7 +640,7 @@ describe('SupportFormPage', () => { await waitFor(() => { expect(screen.getByRole('heading', { name: /success/i })).toBeInTheDocument() }) - }) + }, 10_000) test('submits urgent login issues ticket for a different organization', async () => { const submitSpy = vi.fn() @@ -729,7 +729,7 @@ describe('SupportFormPage', () => { await waitFor(() => { expect(screen.getByRole('heading', { name: /success/i })).toBeInTheDocument() }) - }) + }, 10_000) test('submits database unresponsive ticket with initial error', async () => { const submitSpy = vi.fn() @@ -829,7 +829,7 @@ describe('SupportFormPage', () => { await waitFor(() => { expect(screen.getByRole('heading', { name: /success/i })).toBeInTheDocument() }) - }) + }, 10_000) test('when organization changes, project selector updates to match', async () => { renderSupportFormPage() @@ -968,7 +968,7 @@ describe('SupportFormPage', () => { expect(screen.getByRole('heading', { name: /success/i })).toBeInTheDocument() }) } - }) + }, 10_000) test('shows toast on submission error and allows form re-editing and resubmission', async () => { const submitSpy = vi.fn() @@ -1046,7 +1046,7 @@ describe('SupportFormPage', () => { await waitFor(() => { expect(screen.getByRole('heading', { name: /success/i })).toBeInTheDocument() }) - }) + }, 10_000) test('submits support request with attachments and includes attachment URLs in message', async () => { const submitSpy = vi.fn() @@ -1199,7 +1199,7 @@ describe('SupportFormPage', () => { url.revokeObjectURL = originalRevokeObjectURL vi.mocked(createSupportStorageClient).mockReset() } - }) + }, 10_000) test('can submit form with no organizations and no projects', async () => { const submitSpy = vi.fn() @@ -1266,5 +1266,5 @@ describe('SupportFormPage', () => { await waitFor(() => { expect(screen.getByRole('heading', { name: /success/i })).toBeInTheDocument() }) - }) + }, 10_000) }) diff --git a/apps/studio/components/ui/OrganizationProjectSelector.tsx b/apps/studio/components/ui/OrganizationProjectSelector.tsx index 678f11bfdc428..70a9a26e2a2f9 100644 --- a/apps/studio/components/ui/OrganizationProjectSelector.tsx +++ b/apps/studio/components/ui/OrganizationProjectSelector.tsx @@ -41,6 +41,7 @@ interface OrganizationProjectSelectorSelectorProps { renderActions?: (setOpen: (value: boolean) => void) => ReactNode onSelect?: (project: OrgProject) => void onInitialLoad?: (projects: OrgProject[]) => void + fetchOnMount?: boolean } export const OrganizationProjectSelector = ({ @@ -56,6 +57,7 @@ export const OrganizationProjectSelector = ({ renderActions, onSelect, onInitialLoad, + fetchOnMount = false, }: OrganizationProjectSelectorSelectorProps) => { const { data: organization } = useSelectedOrganizationQuery() const slug = _slug ?? organization?.slug @@ -86,7 +88,7 @@ export const OrganizationProjectSelector = ({ fetchNextPage, } = useOrgProjectsInfiniteQuery( { slug, search: search.length === 0 ? search : debouncedSearch }, - { enabled: open, keepPreviousData: true } + { enabled: fetchOnMount || open, keepPreviousData: true } ) const projects = useMemo(() => data?.pages.flatMap((page) => page.projects), [data?.pages]) || [] From e1703d4429e7eb738236f7481822fed60fd6161b Mon Sep 17 00:00:00 2001 From: Sean Oliver <882952+seanoliver@users.noreply.github.com> Date: Tue, 14 Oct 2025 09:12:36 -0700 Subject: [PATCH 5/5] feat: add basic table template infrastructure (1 of 3) (#38933) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Use useLocalStorage hook for quickstart dismiss state * Standardize varchar to text and remove local storage keys * Remove gen_random_uuid() defaults from templates * feat(studio): flatten table quickstart selector; show for any new table\n\n- Flattened category→template flow into single-step with category chips\n- Removed first-table-only guard so panel appears for all new tables when flag is on * add comments to clarify diff btwn false and undefined in feature flag response type * refactor: improve performance and type safety in table quickstart - Memoize categories and displayed templates to prevent unnecessary re-renders - Use generic type parameter instead of type assertion for feature flag - Simplify dismiss handler with inline function - Add JSDoc comment explaining feature flag return states * Mark variant param as intentionally unused for future PR * chore: clean up unused import in TableEditor Remove unused useTablesQuery import after switching to localStorage-based dismissal. * add 7 day project creation gate and fixed uuid default * Improve table template selector UX with success feedback and clearer copy - Add success toast when template is applied - Update heading from "Quickstart: choose a template" to "Start faster with a table template" - Improve subheading to explain time-saving benefit - Use monospace font for table names to match code convention - Update template rationales to be more descriptive and specific - Increase font sizes for better readability - Remove redundant category display - Reorder imports alphabetically * Apply prettier formatting to TableTemplateSelector --- .../TableEditor/TableEditor.tsx | 49 ++- .../TableQuickstart/TableTemplateSelector.tsx | 121 +++++ .../TableEditor/TableQuickstart/constants.ts | 12 + .../TableEditor/TableQuickstart/templates.ts | 416 ++++++++++++++++++ .../TableEditor/TableQuickstart/types.ts | 82 ++++ .../TableEditor/TableQuickstart/utils.ts | 47 ++ packages/common/constants/local-storage.ts | 4 + 7 files changed, 730 insertions(+), 1 deletion(-) create mode 100644 apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableQuickstart/TableTemplateSelector.tsx create mode 100644 apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableQuickstart/constants.ts create mode 100644 apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableQuickstart/templates.ts create mode 100644 apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableQuickstart/types.ts create mode 100644 apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableQuickstart/utils.ts diff --git a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx index 780093e1517c9..10690bd227b12 100644 --- a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx +++ b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx @@ -1,6 +1,7 @@ import type { PostgresTable } from '@supabase/postgres-meta' +import dayjs from 'dayjs' import { isEmpty, isUndefined, noop } from 'lodash' -import { useEffect, useState } from 'react' +import { useEffect, useMemo, useState } from 'react' import { toast } from 'sonner' import { DocsButton } from 'components/ui/DocsButton' @@ -24,6 +25,7 @@ import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { useUrlState } from 'hooks/ui/useUrlState' import { useProtectedSchemas } from 'hooks/useProtectedSchemas' import { DOCS_URL } from 'lib/constants' +import { usePHFlag } from 'hooks/ui/useFlag' import { useTableEditorStateSnapshot } from 'state/table-editor' import { Badge, Checkbox, Input, SidePanel } from 'ui' import { Admonition } from 'ui-patterns' @@ -45,6 +47,12 @@ import { generateTableFieldFromPostgresTable, validateFields, } from './TableEditor.utils' +import { TableTemplateSelector } from './TableQuickstart/TableTemplateSelector' +import { QuickstartVariant } from './TableQuickstart/types' +import { LOCAL_STORAGE_KEYS } from 'common' +import { useLocalStorage } from 'hooks/misc/useLocalStorage' + +const NEW_PROJECT_THRESHOLD_DAYS = 7 export interface TableEditorProps { table?: PostgresTable @@ -90,6 +98,31 @@ export const TableEditor = ({ const { realtimeAll: realtimeEnabled } = useIsFeatureEnabled(['realtime:all']) const { mutate: sendEvent } = useSendEventMutation() + /** + * Returns: + * - `QuickstartVariant`: user variation (if bucketed) + * - `false`: user not yet bucketed or targeted + * - `undefined`: posthog still loading + */ + const tableQuickstartVariant = usePHFlag('tableQuickstart') + + const [quickstartDismissed, setQuickstartDismissed] = useLocalStorage( + LOCAL_STORAGE_KEYS.TABLE_QUICKSTART_DISMISSED, + false + ) + + const isRecentProject = useMemo(() => { + if (!project?.inserted_at) return false + return dayjs().diff(dayjs(project.inserted_at), 'day') < NEW_PROJECT_THRESHOLD_DAYS + }, [project?.inserted_at]) + + const shouldShowTemplateQuickstart = + isNewRecord && + !isDuplicating && + tableQuickstartVariant === QuickstartVariant.TEMPLATES && + !quickstartDismissed && + isRecentProject + const { docsRowLevelSecurityGuidePath } = useCustomContent(['docs:row_level_security_guide_path']) const [params, setParams] = useUrlState() @@ -280,6 +313,20 @@ export const TableEditor = ({ } > + {shouldShowTemplateQuickstart && ( + { + const updates: Partial = {} + if (template.name) updates.name = template.name + if (template.comment) updates.comment = template.comment + if (template.columns) updates.columns = template.columns + onUpdateField(updates) + }} + onDismiss={() => setQuickstartDismissed(true)} + disabled={false} + /> + )} // [Sean] this will be used in PR #38934 + onSelectTemplate: (tableField: Partial) => void + onDismiss?: () => void + disabled?: boolean +} + +const SUCCESS_MESSAGE_DURATION_MS = 3000 + +export const TableTemplateSelector = ({ + variant: _variant, + onSelectTemplate, + onDismiss, + disabled, +}: TableTemplateSelectorProps) => { + const [activeCategory, setActiveCategory] = useState(null) // null => All + const [selectedTemplate, setSelectedTemplate] = useState(null) + + const handleSelectTemplate = useCallback( + (template: TableSuggestion) => { + const tableField = convertTableSuggestionToTableField(template) + onSelectTemplate(tableField) + setSelectedTemplate(template) + toast.success( + `${template.tableName} template applied. You can add or modify the fields below.`, + { + duration: SUCCESS_MESSAGE_DURATION_MS, + } + ) + }, + [onSelectTemplate] + ) + + const categories = useMemo(() => Object.keys(tableTemplates), []) + + useEffect(() => { + if (activeCategory === null && categories.length > 0) { + setActiveCategory(categories[0]) + } + }, [categories, activeCategory]) + + const displayed = useMemo( + () => (activeCategory ? tableTemplates[activeCategory] || [] : []), + [activeCategory] + ) + + return ( +

+
+
+

Start faster with a table template

+

+ Save time by starting from a ready-made table schema. +

+
+ {onDismiss && ( + + )} +
+ +
+ {categories.map((category) => ( + + ))} +
+ +
+ {displayed.map((t) => ( + + ))} +
+
+ ) +} diff --git a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableQuickstart/constants.ts b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableQuickstart/constants.ts new file mode 100644 index 0000000000000..506a4e7d0cbbe --- /dev/null +++ b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableQuickstart/constants.ts @@ -0,0 +1,12 @@ +export const LIMITS = { + MAX_PROMPT_LENGTH: 500, + MAX_TABLES_TO_GENERATE: 3, + MIN_TABLES_TO_GENERATE: 2, +} as const + +export const AI_QUICK_IDEAS = [ + 'Recipe sharing app', + 'Event ticketing system', + 'Fitness tracker', + 'Learning management platform', +] as const diff --git a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableQuickstart/templates.ts b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableQuickstart/templates.ts new file mode 100644 index 0000000000000..0566358236c61 --- /dev/null +++ b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableQuickstart/templates.ts @@ -0,0 +1,416 @@ +import { TableSource, TableSuggestion } from './types' + +export const tableTemplates: Record = { + 'Social Network': [ + { + tableName: 'profiles', + fields: [ + { + name: 'id', + type: 'uuid', + nullable: false, + isPrimary: true, + default: 'gen_random_uuid()', + }, + { + name: 'user_id', + type: 'uuid', + nullable: false, + unique: true, + isForeign: true, + references: 'auth.users(id)', + }, + { name: 'username', type: 'text', nullable: true, unique: true }, + { name: 'display_name', type: 'text', nullable: true }, + { name: 'avatar_url', type: 'text', nullable: true }, + { name: 'bio', type: 'text', nullable: true }, + { name: 'location', type: 'text', nullable: true }, + { name: 'website', type: 'text', nullable: true }, + { name: 'created_at', type: 'timestamptz', nullable: false, default: 'now()' }, + { name: 'updated_at', type: 'timestamptz', nullable: false, default: 'now()' }, + ], + rationale: 'Stores user profile details such as name, avatar, and bio', + source: TableSource.TEMPLATE, + }, + { + tableName: 'posts', + fields: [ + { + name: 'id', + type: 'uuid', + nullable: false, + isPrimary: true, + default: 'gen_random_uuid()', + }, + { + name: 'author_id', + type: 'uuid', + nullable: false, + isForeign: true, + references: 'profiles(id)', + }, + { name: 'content', type: 'text', nullable: false }, + { name: 'image_url', type: 'text', nullable: true }, + { name: 'likes_count', type: 'int4', nullable: false, default: '0' }, + { name: 'comments_count', type: 'int4', nullable: false, default: '0' }, + { name: 'created_at', type: 'timestamptz', nullable: false, default: 'now()' }, + { name: 'updated_at', type: 'timestamptz', nullable: false, default: 'now()' }, + ], + rationale: 'Stores user posts, images, and engagement counts', + source: TableSource.TEMPLATE, + }, + { + tableName: 'follows', + fields: [ + { + name: 'id', + type: 'uuid', + nullable: false, + isPrimary: true, + default: 'gen_random_uuid()', + }, + { + name: 'follower_id', + type: 'uuid', + nullable: false, + isForeign: true, + references: 'profiles(id)', + }, + { + name: 'following_id', + type: 'uuid', + nullable: false, + isForeign: true, + references: 'profiles(id)', + }, + { name: 'created_at', type: 'timestamptz', nullable: false, default: 'now()' }, + ], + rationale: 'Tracks relationships between followers and followed users', + source: TableSource.TEMPLATE, + }, + ], + 'E-commerce': [ + { + tableName: 'products', + fields: [ + { + name: 'id', + type: 'uuid', + nullable: false, + isPrimary: true, + default: 'gen_random_uuid()', + }, + { name: 'name', type: 'text', nullable: false }, + { name: 'slug', type: 'text', nullable: false, unique: true }, + { name: 'description', type: 'text', nullable: true }, + { name: 'price', type: 'numeric', nullable: false }, + { name: 'compare_at_price', type: 'numeric', nullable: true }, + { name: 'cost', type: 'numeric', nullable: true }, + { name: 'sku', type: 'text', nullable: true, unique: true }, + { name: 'barcode', type: 'text', nullable: true }, + { name: 'stock_quantity', type: 'int4', nullable: false, default: '0' }, + { name: 'weight', type: 'numeric', nullable: true }, + { name: 'published', type: 'bool', nullable: false, default: 'false' }, + { name: 'created_at', type: 'timestamptz', nullable: false, default: 'now()' }, + { name: 'updated_at', type: 'timestamptz', nullable: false, default: 'now()' }, + ], + rationale: 'Stores product details, pricing, and stock levels', + source: TableSource.TEMPLATE, + }, + { + tableName: 'orders', + fields: [ + { + name: 'id', + type: 'uuid', + nullable: false, + isPrimary: true, + default: 'gen_random_uuid()', + }, + { name: 'order_number', type: 'text', nullable: false, unique: true }, + { + name: 'customer_id', + type: 'uuid', + nullable: false, + isForeign: true, + references: 'profiles(id)', + }, + { name: 'status', type: 'text', nullable: false, default: "'pending'" }, + { name: 'subtotal', type: 'numeric', nullable: false }, + { name: 'tax', type: 'numeric', nullable: false, default: '0' }, + { name: 'shipping', type: 'numeric', nullable: false, default: '0' }, + { name: 'total', type: 'numeric', nullable: false }, + { name: 'notes', type: 'text', nullable: true }, + { name: 'created_at', type: 'timestamptz', nullable: false, default: 'now()' }, + { name: 'updated_at', type: 'timestamptz', nullable: false, default: 'now()' }, + ], + rationale: 'Manages customer orders, payment totals, and statuses', + source: TableSource.TEMPLATE, + }, + { + tableName: 'cart_items', + fields: [ + { + name: 'id', + type: 'uuid', + nullable: false, + isPrimary: true, + default: 'gen_random_uuid()', + }, + { + name: 'user_id', + type: 'uuid', + nullable: false, + isForeign: true, + references: 'profiles(id)', + }, + { + name: 'product_id', + type: 'uuid', + nullable: false, + isForeign: true, + references: 'products(id)', + }, + { name: 'quantity', type: 'int4', nullable: false, default: '1' }, + { name: 'added_at', type: 'timestamptz', nullable: false, default: 'now()' }, + ], + rationale: 'Tracks products added to customer shopping carts', + source: TableSource.TEMPLATE, + }, + ], + Blog: [ + { + tableName: 'articles', + fields: [ + { + name: 'id', + type: 'uuid', + nullable: false, + isPrimary: true, + default: 'gen_random_uuid()', + }, + { name: 'title', type: 'text', nullable: false }, + { name: 'slug', type: 'text', nullable: false, unique: true }, + { name: 'content', type: 'text', nullable: true }, + { name: 'excerpt', type: 'text', nullable: true }, + { name: 'cover_image', type: 'text', nullable: true }, + { + name: 'author_id', + type: 'uuid', + nullable: false, + isForeign: true, + references: 'profiles(id)', + }, + { name: 'status', type: 'text', nullable: false, default: "'draft'" }, + { name: 'published_at', type: 'timestamptz', nullable: true }, + { name: 'created_at', type: 'timestamptz', nullable: false, default: 'now()' }, + { name: 'updated_at', type: 'timestamptz', nullable: false, default: 'now()' }, + ], + rationale: 'Stores blog posts with author, status, and publish dates', + source: TableSource.TEMPLATE, + }, + { + tableName: 'categories', + fields: [ + { + name: 'id', + type: 'uuid', + nullable: false, + isPrimary: true, + default: 'gen_random_uuid()', + }, + { name: 'name', type: 'text', nullable: false }, + { name: 'slug', type: 'text', nullable: false, unique: true }, + { name: 'description', type: 'text', nullable: true }, + { name: 'color', type: 'text', nullable: true }, + { name: 'created_at', type: 'timestamptz', nullable: false, default: 'now()' }, + ], + rationale: 'Organizes articles into categories or tags for filtering', + source: TableSource.TEMPLATE, + }, + { + tableName: 'comments', + fields: [ + { + name: 'id', + type: 'uuid', + nullable: false, + isPrimary: true, + default: 'gen_random_uuid()', + }, + { + name: 'article_id', + type: 'uuid', + nullable: false, + isForeign: true, + references: 'articles(id)', + }, + { name: 'author_name', type: 'text', nullable: false }, + { name: 'author_email', type: 'text', nullable: false }, + { name: 'content', type: 'text', nullable: false }, + { name: 'approved', type: 'bool', nullable: false, default: 'false' }, + { name: 'created_at', type: 'timestamptz', nullable: false, default: 'now()' }, + ], + rationale: 'Stores reader comments and approval status for moderation', + source: TableSource.TEMPLATE, + }, + ], + 'To-Do List': [ + { + tableName: 'tasks', + fields: [ + { + name: 'id', + type: 'uuid', + nullable: false, + isPrimary: true, + default: 'gen_random_uuid()', + }, + { name: 'title', type: 'text', nullable: false }, + { name: 'description', type: 'text', nullable: true }, + { name: 'completed', type: 'bool', nullable: false, default: 'false' }, + { name: 'priority', type: 'text', nullable: true, default: "'medium'" }, + { name: 'due_date', type: 'date', nullable: true }, + { + name: 'user_id', + type: 'uuid', + nullable: false, + isForeign: true, + references: 'auth.users(id)', + }, + { name: 'list_id', type: 'uuid', nullable: true, isForeign: true, references: 'lists(id)' }, + { name: 'created_at', type: 'timestamptz', nullable: false, default: 'now()' }, + { name: 'updated_at', type: 'timestamptz', nullable: false, default: 'now()' }, + ], + rationale: 'Stores user tasks with priorities, due dates, and status', + source: TableSource.TEMPLATE, + }, + { + tableName: 'lists', + fields: [ + { + name: 'id', + type: 'uuid', + nullable: false, + isPrimary: true, + default: 'gen_random_uuid()', + }, + { name: 'name', type: 'text', nullable: false }, + { name: 'description', type: 'text', nullable: true }, + { name: 'color', type: 'text', nullable: true }, + { name: 'icon', type: 'text', nullable: true }, + { + name: 'user_id', + type: 'uuid', + nullable: false, + isForeign: true, + references: 'auth.users(id)', + }, + { name: 'created_at', type: 'timestamptz', nullable: false, default: 'now()' }, + { name: 'updated_at', type: 'timestamptz', nullable: false, default: 'now()' }, + ], + rationale: 'Groups tasks into named lists for better organization', + source: TableSource.TEMPLATE, + }, + { + tableName: 'subtasks', + fields: [ + { + name: 'id', + type: 'uuid', + nullable: false, + isPrimary: true, + default: 'gen_random_uuid()', + }, + { + name: 'task_id', + type: 'uuid', + nullable: false, + isForeign: true, + references: 'tasks(id)', + }, + { name: 'title', type: 'text', nullable: false }, + { name: 'completed', type: 'bool', nullable: false, default: 'false' }, + { name: 'position', type: 'int4', nullable: false, default: '0' }, + { name: 'created_at', type: 'timestamptz', nullable: false, default: 'now()' }, + ], + rationale: 'Tracks smaller subtasks linked to a main task', + source: TableSource.TEMPLATE, + }, + ], + Analytics: [ + { + tableName: 'events', + fields: [ + { + name: 'id', + type: 'uuid', + nullable: false, + isPrimary: true, + default: 'gen_random_uuid()', + }, + { + name: 'user_id', + type: 'uuid', + nullable: true, + isForeign: true, + references: 'profiles(id)', + }, + { name: 'session_id', type: 'text', nullable: true }, + { name: 'event_type', type: 'text', nullable: false }, + { name: 'properties', type: 'jsonb', nullable: true }, + { name: 'user_agent', type: 'text', nullable: true }, + { name: 'ip_address', type: 'text', nullable: true }, + { name: 'created_at', type: 'timestamptz', nullable: false, default: 'now()' }, + ], + rationale: 'Captures user events and properties for analytics tracking', + source: TableSource.TEMPLATE, + }, + { + tableName: 'page_views', + fields: [ + { + name: 'id', + type: 'uuid', + nullable: false, + isPrimary: true, + default: 'gen_random_uuid()', + }, + { + name: 'user_id', + type: 'uuid', + nullable: true, + isForeign: true, + references: 'profiles(id)', + }, + { name: 'session_id', type: 'text', nullable: true }, + { name: 'path', type: 'text', nullable: false }, + { name: 'referrer', type: 'text', nullable: true }, + { name: 'duration', type: 'int4', nullable: true }, + { name: 'created_at', type: 'timestamptz', nullable: false, default: 'now()' }, + ], + rationale: 'Tracks user page views, referrers, and session info', + source: TableSource.TEMPLATE, + }, + { + tableName: 'metrics', + fields: [ + { + name: 'id', + type: 'uuid', + nullable: false, + isPrimary: true, + default: 'gen_random_uuid()', + }, + { name: 'metric_name', type: 'text', nullable: false }, + { name: 'value', type: 'numeric', nullable: false }, + { name: 'tags', type: 'jsonb', nullable: true }, + { name: 'timestamp', type: 'timestamptz', nullable: false, default: 'now()' }, + { name: 'aggregation_period', type: 'text', nullable: true }, + { name: 'created_at', type: 'timestamptz', nullable: false, default: 'now()' }, + ], + rationale: 'Stores aggregated metrics and KPIs with timestamps', + source: TableSource.TEMPLATE, + }, + ], +} diff --git a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableQuickstart/types.ts b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableQuickstart/types.ts new file mode 100644 index 0000000000000..aeceaa3acdb36 --- /dev/null +++ b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableQuickstart/types.ts @@ -0,0 +1,82 @@ +export type PostgresType = + | 'text' + | 'varchar' + | 'uuid' + | 'int2' + | 'int4' + | 'int8' + | 'float4' + | 'float8' + | 'numeric' + | 'bool' + | 'json' + | 'jsonb' + | 'date' + | 'time' + | 'timestamp' + | 'timestamptz' + | 'timetz' + | 'bytea' + +export type TableField = { + name: string + type: PostgresType + nullable?: boolean + unique?: boolean + default?: string // Must be string for table editor compatibility + description?: string + isPrimary?: boolean + isForeign?: boolean + references?: string +} + +export type TableRelationship = { + from: string + to: string + type: 'one-to-one' | 'one-to-many' | 'many-to-many' | 'many-to-one' +} + +export enum TableSource { + AI = 'ai', + TEMPLATE = 'template', +} + +export enum QuickstartVariant { + CONTROL = 'control', + AI = 'ai', + TEMPLATES = 'templates', +} + +export enum ViewMode { + INITIAL = 'initial', + AI_INPUT = 'ai-input', + AI_RESULTS = 'ai-results', + CATEGORY_SELECTED = 'category-selected', +} + +export type TableSuggestion = { + tableName: string + fields: TableField[] + rationale?: string + source: TableSource + relationships?: TableRelationship[] +} + +export type AIGeneratedSchema = { + tables: Array<{ + name: string + description: string + columns: Array<{ + name: string + type: string + isPrimary?: boolean + isForeign?: boolean + references?: string + isNullable?: boolean + defaultValue?: string + isUnique?: boolean + }> + relationships?: TableRelationship[] + }> + summary: string +} diff --git a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableQuickstart/utils.ts b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableQuickstart/utils.ts new file mode 100644 index 0000000000000..727d5b7ac5c48 --- /dev/null +++ b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableQuickstart/utils.ts @@ -0,0 +1,47 @@ +import type { TableSuggestion, TableField } from './types' +import type { ColumnField } from '../../SidePanelEditor.types' +import type { TableField as EditorTableField } from '../TableEditor.types' + +export const DEFAULT_SCHEMA = 'public' as const + +export function isPrimaryKeyField(field: TableField): boolean { + return field.isPrimary === true || field.name === 'id' +} + +export function isIdentityField(field: TableField): boolean { + return field.name === 'id' && field.type.toLowerCase().includes('int') && !field.default +} + +export function convertTableSuggestionToTableField( + table: TableSuggestion +): Partial { + const columns: ColumnField[] = table.fields.map((field, index) => { + const isPrimaryKey = isPrimaryKeyField(field) + const isIdentity = isIdentityField(field) + const defaultValue = field.default ? String(field.default) : null + + return { + id: `column-${index}`, + name: field.name, + format: field.type, + defaultValue, + isNullable: field.nullable !== false, + isUnique: field.unique ?? false, + isIdentity, + isPrimaryKey, + comment: field.description || '', + isNewColumn: true, + table: table.tableName, + schema: DEFAULT_SCHEMA, + check: null, + isArray: false, + isEncrypted: false, + } + }) + + return { + name: table.tableName, + comment: table.rationale || '', + columns, + } +} diff --git a/packages/common/constants/local-storage.ts b/packages/common/constants/local-storage.ts index 0ac015cb730d5..c6a1308420816 100644 --- a/packages/common/constants/local-storage.ts +++ b/packages/common/constants/local-storage.ts @@ -90,6 +90,9 @@ export const LOCAL_STORAGE_KEYS = { * WWW */ BLOG_VIEW: 'supabase-blog-view', + + // Used to track if user has dismissed table editor quickstart prompt + TABLE_QUICKSTART_DISMISSED: 'table-quickstart-dismissed', } as const export type LocalStorageKey = (typeof LOCAL_STORAGE_KEYS)[keyof typeof LOCAL_STORAGE_KEYS] @@ -111,6 +114,7 @@ const LOCAL_STORAGE_KEYS_ALLOWLIST = [ LOCAL_STORAGE_KEYS.AI_ASSISTANT_MCP_OPT_IN, LOCAL_STORAGE_KEYS.UI_PREVIEW_BRANCHING_2_0, LOCAL_STORAGE_KEYS.LINTER_SHOW_FOOTER, + LOCAL_STORAGE_KEYS.TABLE_QUICKSTART_DISMISSED, ] export function clearLocalStorage() {