diff --git a/apps/studio/components/interfaces/Auth/Users/UsersV2.tsx b/apps/studio/components/interfaces/Auth/Users/UsersV2.tsx index 63ee824ffb819..0593a02a59b30 100644 --- a/apps/studio/components/interfaces/Auth/Users/UsersV2.tsx +++ b/apps/studio/components/interfaces/Auth/Users/UsersV2.tsx @@ -587,7 +587,10 @@ export const UsersV2 = () => { visible={!!selectedUserToDelete} selectedUser={selectedUserToDelete} onClose={() => setSelectedUserToDelete(undefined)} - onDeleteSuccess={() => setSelectedUserToDelete(undefined)} + onDeleteSuccess={() => { + if (selectedUserToDelete?.id === selectedUser) setSelectedUser(undefined) + setSelectedUserToDelete(undefined) + }} /> ) diff --git a/apps/studio/components/interfaces/Database/Hooks/EditHookPanel.tsx b/apps/studio/components/interfaces/Database/Hooks/EditHookPanel.tsx index 73f739cd8f8ce..f809e700ad6f8 100644 --- a/apps/studio/components/interfaces/Database/Hooks/EditHookPanel.tsx +++ b/apps/studio/components/interfaces/Database/Hooks/EditHookPanel.tsx @@ -1,25 +1,18 @@ -import type { PostgresTable, PostgresTrigger } from '@supabase/postgres-meta' -import Image from 'next/legacy/image' -import { MutableRefObject, useEffect, useMemo, useRef, useState } from 'react' +import { PGTriggerCreate } from '@supabase/pg-meta/src/pg-meta-triggers' +import type { PostgresTrigger } from '@supabase/postgres-meta' +import { useEffect, useMemo, useRef, useState } from 'react' import { toast } from 'sonner' import { useParams } from 'common' import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' -import { FormSection, FormSectionContent, FormSectionLabel } from 'components/ui/Forms/FormSection' import { useDatabaseTriggerCreateMutation } from 'data/database-triggers/database-trigger-create-mutation' import { useDatabaseTriggerUpdateMutation } from 'data/database-triggers/database-trigger-update-transaction-mutation' -import { - EdgeFunctionsResponse, - useEdgeFunctionsQuery, -} from 'data/edge-functions/edge-functions-query' import { getTableEditor } from 'data/table-editor/table-editor-query' import { useTablesQuery } from 'data/tables/tables-query' import { isValidHttpUrl, uuidv4 } from 'lib/helpers' -import { Button, Checkbox, Form, Input, Listbox, Radio, SidePanel } from 'ui' +import { Button, Form, SidePanel } from 'ui' import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' -import HTTPRequestFields from './HTTPRequestFields' -import { AVAILABLE_WEBHOOK_TYPES, HOOK_EVENTS } from './Hooks.constants' -import { PGTriggerCreate } from '@supabase/pg-meta/src/pg-meta-triggers' +import { FormContents } from './FormContents' export interface EditHookPanelProps { visible: boolean @@ -29,7 +22,19 @@ export interface EditHookPanelProps { export type HTTPArgument = { id: string; name: string; value: string } -const EditHookPanel = ({ visible, selectedHook, onClose }: EditHookPanelProps) => { +export const isEdgeFunction = ({ + ref, + restUrlTld, + url, +}: { + ref?: string + restUrlTld?: string + url: string +}) => + url.includes(`https://${ref}.functions.supabase.${restUrlTld}/`) || + url.includes(`https://${ref}.supabase.${restUrlTld}/functions/`) + +export const EditHookPanel = ({ visible, selectedHook, onClose }: EditHookPanelProps) => { const { ref } = useParams() const submitRef = useRef(null) const [isEdited, setIsEdited] = useState(false) @@ -49,7 +54,6 @@ const EditHookPanel = ({ visible, selectedHook, onClose }: EditHookPanelProps) = projectRef: project?.ref, connectionString: project?.connectionString, }) - const { data: functions } = useEdgeFunctionsQuery({ projectRef: ref }) const [isSubmitting, setIsSubmitting] = useState(false) const { mutate: createDatabaseTrigger } = useDatabaseTriggerCreateMutation({ onSuccess: (res) => { @@ -81,16 +85,12 @@ const EditHookPanel = ({ visible, selectedHook, onClose }: EditHookPanelProps) = const restUrl = project?.restUrl const restUrlTld = restUrl ? new URL(restUrl).hostname.split('.').pop() : 'co' - const isEdgeFunction = (url: string) => - url.includes(`https://${ref}.functions.supabase.${restUrlTld}/`) || - url.includes(`https://${ref}.supabase.${restUrlTld}/functions/`) - const initialValues = { name: selectedHook?.name ?? '', table_id: selectedHook?.table_id ?? '', http_url: selectedHook?.function_args?.[0] ?? '', http_method: selectedHook?.function_args?.[1] ?? 'POST', - function_type: isEdgeFunction(selectedHook?.function_args?.[0] ?? '') + function_type: isEdgeFunction({ ref, restUrlTld, url: selectedHook?.function_args?.[0] ?? '' }) ? 'supabase_function' : 'http_request', timeout_ms: Number(selectedHook?.function_args?.[4] ?? 5000), @@ -104,7 +104,7 @@ const EditHookPanel = ({ visible, selectedHook, onClose }: EditHookPanelProps) = if (selectedHook !== undefined) { setEvents(selectedHook.events) - const [url, method, headers, parameters] = selectedHook.function_args + const [_, __, headers, parameters] = selectedHook.function_args let parsedParameters: Record = {} @@ -314,10 +314,6 @@ const EditHookPanel = ({ visible, selectedHook, onClose }: EditHookPanelProps) = values={values} resetForm={resetForm} errors={errors} - projectRef={ref} - restUrlTld={restUrlTld} - functions={functions} - isEdgeFunction={isEdgeFunction} tables={tables} events={events} eventsError={eventsError} @@ -352,211 +348,3 @@ const EditHookPanel = ({ visible, selectedHook, onClose }: EditHookPanelProps) = ) } - -export default EditHookPanel - -interface FormContentsProps { - values: any - resetForm: any - errors: any - projectRef?: string - restUrlTld?: string - selectedHook?: PostgresTrigger - functions: EdgeFunctionsResponse[] | undefined - isEdgeFunction: (url: string) => boolean - tables: PostgresTable[] - events: string[] - eventsError?: string - onUpdateSelectedEvents: (event: string) => void - httpHeaders: HTTPArgument[] - httpParameters: HTTPArgument[] - setHttpHeaders: (arr: HTTPArgument[]) => void - setHttpParameters: (arr: HTTPArgument[]) => void - submitRef: MutableRefObject -} - -const FormContents = ({ - values, - resetForm, - errors, - projectRef, - restUrlTld, - selectedHook, - functions, - isEdgeFunction, - tables, - events, - eventsError, - onUpdateSelectedEvents, - httpHeaders, - httpParameters, - setHttpHeaders, - setHttpParameters, - submitRef, -}: FormContentsProps) => { - useEffect(() => { - if (values.function_type === 'http_request') { - if (selectedHook !== undefined) { - const [url, method] = selectedHook.function_args - const updatedValues = { ...values, http_url: url, http_method: method } - resetForm({ values: updatedValues, initialValues: updatedValues }) - } else { - const updatedValues = { ...values, http_url: '' } - resetForm({ values: updatedValues, initialValues: updatedValues }) - } - } else if (values.function_type === 'supabase_function') { - const fnSlug = (functions ?? [])[0]?.slug - const defaultFunctionUrl = `https://${projectRef}.supabase.${restUrlTld}/functions/v1/${fnSlug}` - const updatedValues = { - ...values, - http_url: isEdgeFunction(values.http_url) ? values.http_url : defaultFunctionUrl, - } - resetForm({ values: updatedValues, initialValues: updatedValues }) - } - }, [values.function_type]) - - return ( -
- General}> - - - - - - - Select which table and events will trigger your webhook -

- } - > - Conditions to fire webhook - - } - > - - - - --- - - {tables.map((table) => ( - -
-

{table.schema}

-

{table.name}

-
-
- ))} -
- - {HOOK_EVENTS.map((event) => ( - onUpdateSelectedEvents(event.value)} - /> - ))} - -
-
- - Webhook configuration - } - > - - - {AVAILABLE_WEBHOOK_TYPES.map((webhook) => ( - - -
-
-

{webhook.label}

-
-

{webhook.description}

-
-
- } - /> - ))} - - - - - - { - if (header) setHttpHeaders(httpHeaders.concat(header)) - else setHttpHeaders(httpHeaders.concat({ id: uuidv4(), name: '', value: '' })) - }} - onUpdateHeader={(idx, property, value) => - setHttpHeaders( - httpHeaders.map((header, i) => { - if (idx === i) return { ...header, [property]: value } - else return header - }) - ) - } - onRemoveHeader={(idx) => setHttpHeaders(httpHeaders.filter((_, i) => idx !== i))} - onAddParameter={() => - setHttpParameters(httpParameters.concat({ id: uuidv4(), name: '', value: '' })) - } - onUpdateParameter={(idx, property, value) => - setHttpParameters( - httpParameters.map((param, i) => { - if (idx === i) return { ...param, [property]: value } - else return param - }) - ) - } - onRemoveParameter={(idx) => setHttpParameters(httpParameters.filter((_, i) => idx !== i))} - /> - - - - - - ) -} - export const ObjectsToBeDroppedWarning = ({ objectsToBeDropped, }: { diff --git a/apps/studio/data/auth/auth-config-query.ts b/apps/studio/data/auth/auth-config-query.ts index 3a697a7e054cd..337b30f4bac04 100644 --- a/apps/studio/data/auth/auth-config-query.ts +++ b/apps/studio/data/auth/auth-config-query.ts @@ -1,6 +1,7 @@ import { useQuery, useQueryClient, UseQueryOptions } from '@tanstack/react-query' import type { components } from 'data/api' import { get, handleError } from 'data/fetchers' +import { IS_PLATFORM } from 'lib/constants' import { useCallback } from 'react' import type { ResponseError } from 'types' import { authKeys } from './keys' @@ -40,7 +41,7 @@ export const useAuthConfigQuery = ( authKeys.authConfig(projectRef), ({ signal }) => getProjectAuthConfig({ projectRef }, signal), { - enabled: enabled && typeof projectRef !== 'undefined', + enabled: enabled && IS_PLATFORM && typeof projectRef !== 'undefined', ...options, } ) diff --git a/apps/studio/data/database-extensions/database-extensions-query.ts b/apps/studio/data/database-extensions/database-extensions-query.ts index e9eb3ae42a521..76133e1dc174d 100644 --- a/apps/studio/data/database-extensions/database-extensions-query.ts +++ b/apps/studio/data/database-extensions/database-extensions-query.ts @@ -5,6 +5,7 @@ import { databaseExtensionsKeys } from './keys' import { components } from 'api-types' import { useSelectedProject } from 'hooks/misc/useSelectedProject' import { PROJECT_STATUS } from 'lib/constants' +import { DEFAULT_PLATFORM_APPLICATION_NAME } from '@supabase/pg-meta/src/constants' export type DatabaseExtension = components['schemas']['PostgresExtension'] @@ -27,6 +28,7 @@ export async function getDatabaseExtensions( params: { header: { 'x-connection-encrypted': connectionString!, + 'x-pg-application-name': DEFAULT_PLATFORM_APPLICATION_NAME, }, path: { ref: projectRef, diff --git a/apps/studio/data/database-policies/database-policies-query.ts b/apps/studio/data/database-policies/database-policies-query.ts index a2602491f4607..291a263526e6e 100644 --- a/apps/studio/data/database-policies/database-policies-query.ts +++ b/apps/studio/data/database-policies/database-policies-query.ts @@ -5,6 +5,7 @@ import { useSelectedProject } from 'hooks/misc/useSelectedProject' import { PROJECT_STATUS } from 'lib/constants' import type { ResponseError } from 'types' import { databasePoliciesKeys } from './keys' +import { DEFAULT_PLATFORM_APPLICATION_NAME } from '@supabase/pg-meta/src/constants' export type DatabasePoliciesVariables = { projectRef?: string @@ -24,7 +25,10 @@ export async function getDatabasePolicies( const { data, error } = await get('/platform/pg-meta/{ref}/policies', { params: { - header: { 'x-connection-encrypted': connectionString! }, + header: { + 'x-connection-encrypted': connectionString!, + 'x-pg-application-name': DEFAULT_PLATFORM_APPLICATION_NAME, + }, path: { ref: projectRef }, query: { included_schemas: schema || '', diff --git a/apps/studio/data/database-publications/database-publications-query.ts b/apps/studio/data/database-publications/database-publications-query.ts index f8e20b269f7b9..616efce369289 100644 --- a/apps/studio/data/database-publications/database-publications-query.ts +++ b/apps/studio/data/database-publications/database-publications-query.ts @@ -2,6 +2,7 @@ import { UseQueryOptions, useQuery } from '@tanstack/react-query' import { get, handleError } from 'data/fetchers' import type { ResponseError } from 'types' import { databasePublicationsKeys } from './keys' +import { DEFAULT_PLATFORM_APPLICATION_NAME } from '@supabase/pg-meta/src/constants' export type DatabasePublicationsVariables = { projectRef?: string @@ -21,6 +22,7 @@ export async function getDatabasePublications( params: { header: { 'x-connection-encrypted': connectionString!, + 'x-pg-application-name': DEFAULT_PLATFORM_APPLICATION_NAME, }, path: { ref: projectRef, diff --git a/apps/studio/data/database-triggers/database-triggers-query.ts b/apps/studio/data/database-triggers/database-triggers-query.ts index 997d077c46911..faa78e23fc211 100644 --- a/apps/studio/data/database-triggers/database-triggers-query.ts +++ b/apps/studio/data/database-triggers/database-triggers-query.ts @@ -2,6 +2,7 @@ import { useQuery, UseQueryOptions } from '@tanstack/react-query' import { get, handleError } from 'data/fetchers' import type { ResponseError } from 'types' import { databaseTriggerKeys } from './keys' +import { DEFAULT_PLATFORM_APPLICATION_NAME } from '@supabase/pg-meta/src/constants' export type DatabaseTriggersVariables = { projectRef?: string @@ -19,7 +20,10 @@ export async function getDatabaseTriggers( const { data, error } = await get('/platform/pg-meta/{ref}/triggers', { params: { - header: { 'x-connection-encrypted': connectionString! }, + header: { + 'x-connection-encrypted': connectionString!, + 'x-pg-application-name': DEFAULT_PLATFORM_APPLICATION_NAME, + }, path: { ref: projectRef }, query: undefined as any, }, diff --git a/apps/studio/data/enumerated-types/enumerated-types-query.ts b/apps/studio/data/enumerated-types/enumerated-types-query.ts index e7b541f0f7772..ed61ae2291df8 100644 --- a/apps/studio/data/enumerated-types/enumerated-types-query.ts +++ b/apps/studio/data/enumerated-types/enumerated-types-query.ts @@ -4,6 +4,7 @@ import type { components } from 'data/api' import { get, handleError } from 'data/fetchers' import type { ResponseError } from 'types' import { enumeratedTypesKeys } from './keys' +import { DEFAULT_PLATFORM_APPLICATION_NAME } from '@supabase/pg-meta/src/constants' export type EnumeratedTypesVariables = { projectRef?: string @@ -23,7 +24,10 @@ export async function getEnumeratedTypes( const { data, error } = await get('/platform/pg-meta/{ref}/types', { params: { - header: { 'x-connection-encrypted': connectionString! }, + header: { + 'x-connection-encrypted': connectionString!, + 'x-pg-application-name': DEFAULT_PLATFORM_APPLICATION_NAME, + }, path: { ref: projectRef }, }, headers: Object.fromEntries(headers), diff --git a/apps/studio/data/fetchers.ts b/apps/studio/data/fetchers.ts index b2c3d4aaa6f34..99e010f7c8682 100644 --- a/apps/studio/data/fetchers.ts +++ b/apps/studio/data/fetchers.ts @@ -6,8 +6,8 @@ import { API_URL } from 'lib/constants' import { getAccessToken } from 'lib/gotrue' import { uuidv4 } from 'lib/helpers' import { ResponseError } from 'types' -// generated from openapi-typescript -import type { paths } from './api' +import type { paths } from './api' // generated from openapi-typescript +import { DEFAULT_PLATFORM_APPLICATION_NAME } from '@supabase/pg-meta/src/constants' const DEFAULT_HEADERS = { Accept: 'application/json' } @@ -73,6 +73,9 @@ function pgMetaGuard(request: Request) { retryAfterHeader ? parseInt(retryAfterHeader) : undefined ) } + if (!request.headers.get('x-pg-application-name')) { + request.headers.set('x-pg-application-name', DEFAULT_PLATFORM_APPLICATION_NAME) + } } return request } @@ -173,7 +176,7 @@ export const handleError = ( throw new ResponseError(undefined) } -// [Joshen] The methods below are brought over from lib/common/fetchers because we still need them +// [Joshen] The methods below are brought over from lib/common/fetch because we still need them // primarily for our own endpoints in the dashboard repo. So consolidating all the fetch methods into here. async function handleFetchResponse(response: Response): Promise { @@ -193,6 +196,82 @@ async function handleFetchResponse(response: Response): Promise( + response: Response, + headers: string[] +): Promise { + try { + const res = {} as any + headers.forEach((header: string) => { + res[header] = response.headers.get(header) + }) + return res + } catch (e) { + return handleError(response) as T | ResponseError + } +} + +async function handleFetchError(response: unknown): Promise { + let resJson: any = {} + + if (response instanceof Error) { + resJson = response + } + + if (response instanceof Response) { + resJson = await response.json() + } + + const status = response instanceof Response ? response.status : undefined + + const message = + resJson.message ?? + resJson.msg ?? + resJson.error ?? + `An error has occurred: ${status ?? 'Unknown error'}` + const retryAfter = + response instanceof Response && response.headers.get('Retry-After') + ? parseInt(response.headers.get('Retry-After')!) + : undefined + + let error = new ResponseError(message, status, undefined, retryAfter) + + // @ts-expect-error - [Alaister] many of our local api routes check `if (response.error)`. + // This is a fix to keep those checks working without breaking changes. + // In future we should check for `if (response instanceof ResponseError)` instead. + error.error = error + + return error +} + +/** + * To be used only for dashboard API endpoints. Use `fetch` directly if calling a non dashboard API endpoint + */ +export async function fetchGet( + url: string, + options?: { [prop: string]: any } +): Promise { + try { + const { headers: otherHeaders, abortSignal, ...otherOptions } = options ?? {} + const headers = await constructHeaders({ + 'Content-Type': 'application/json', + ...DEFAULT_HEADERS, + ...otherHeaders, + }) + const response = await fetch(url, { + headers, + method: 'GET', + referrerPolicy: 'no-referrer-when-downgrade', + ...otherOptions, + signal: abortSignal, + }) + if (!response.ok) return handleFetchError(response) + return handleFetchResponse(response) + } catch (error) { + return handleFetchError(error) + } +} + /** * To be used only for dashboard API endpoints. Use `fetch` directly if calling a non dashboard API endpoint * @@ -211,16 +290,49 @@ export async function fetchPost( ...otherHeaders, }) const response = await fetch(url, { + headers, method: 'POST', body: JSON.stringify(data), referrerPolicy: 'no-referrer-when-downgrade', - headers, ...otherOptions, signal: abortSignal, }) - if (!response.ok) return handleError(response) + if (!response.ok) return handleFetchError(response) return handleFetchResponse(response) } catch (error) { - return handleError(error) + return handleFetchError(error) + } +} + +/** + * To be used only for dashboard API endpoints. Use `fetch` directly if calling a non dashboard API endpoint + */ +export async function fetchHeadWithTimeout( + url: string, + headersToRetrieve: string[], + options?: { [prop: string]: any } +): Promise { + try { + const timeout = options?.timeout ?? 60000 + + const { headers: otherHeaders, abortSignal, ...otherOptions } = options ?? {} + const headers = await constructHeaders({ + 'Content-Type': 'application/json', + ...DEFAULT_HEADERS, + ...otherHeaders, + }) + + const response = await fetch(url, { + method: 'HEAD', + referrerPolicy: 'no-referrer-when-downgrade', + headers, + ...otherOptions, + signal: AbortSignal.timeout(timeout), + }) + + if (!response.ok) return handleFetchError(response) + return handleFetchHeadResponse(response, headersToRetrieve) + } catch (error) { + return handleFetchError(error) } } diff --git a/apps/studio/data/foreign-tables/foreign-tables-query.ts b/apps/studio/data/foreign-tables/foreign-tables-query.ts index 3c2f315712236..bffbb857aaafd 100644 --- a/apps/studio/data/foreign-tables/foreign-tables-query.ts +++ b/apps/studio/data/foreign-tables/foreign-tables-query.ts @@ -4,6 +4,7 @@ import { PostgresView } from '@supabase/postgres-meta' import { get, handleError } from 'data/fetchers' import type { ResponseError } from 'types' import { foreignTableKeys } from './keys' +import { DEFAULT_PLATFORM_APPLICATION_NAME } from '@supabase/pg-meta/src/constants' export type ForeignTablesVariables = { projectRef?: string @@ -22,7 +23,10 @@ export async function getForeignTables( const { data, error } = await get('/platform/pg-meta/{ref}/foreign-tables', { params: { - header: { 'x-connection-encrypted': connectionString! }, + header: { + 'x-connection-encrypted': connectionString!, + 'x-pg-application-name': DEFAULT_PLATFORM_APPLICATION_NAME, + }, path: { ref: projectRef }, query: { included_schemas: schema || '', diff --git a/apps/studio/data/materialized-views/materialized-views-query.ts b/apps/studio/data/materialized-views/materialized-views-query.ts index a8a7eab23dd0c..052a3dc337777 100644 --- a/apps/studio/data/materialized-views/materialized-views-query.ts +++ b/apps/studio/data/materialized-views/materialized-views-query.ts @@ -4,6 +4,7 @@ import { PostgresMaterializedView } from '@supabase/postgres-meta' import { get, handleError } from 'data/fetchers' import type { ResponseError } from 'types' import { materializedViewKeys } from './keys' +import { DEFAULT_PLATFORM_APPLICATION_NAME } from '@supabase/pg-meta/src/constants' export type MaterializedViewsVariables = { projectRef?: string @@ -22,7 +23,10 @@ export async function getMaterializedViews( const { data, error } = await get('/platform/pg-meta/{ref}/materialized-views', { params: { - header: { 'x-connection-encrypted': connectionString! }, + header: { + 'x-connection-encrypted': connectionString!, + 'x-pg-application-name': DEFAULT_PLATFORM_APPLICATION_NAME, + }, path: { ref: projectRef }, query: { included_schemas: schema || '', diff --git a/apps/studio/data/privileges/column-privileges-query.ts b/apps/studio/data/privileges/column-privileges-query.ts index 01bda4ce1f36d..249a7c028ffb6 100644 --- a/apps/studio/data/privileges/column-privileges-query.ts +++ b/apps/studio/data/privileges/column-privileges-query.ts @@ -4,6 +4,7 @@ import type { components } from 'data/api' import { get, handleError } from 'data/fetchers' import type { ResponseError } from 'types' import { privilegeKeys } from './keys' +import { DEFAULT_PLATFORM_APPLICATION_NAME } from '@supabase/pg-meta/src/constants' export type ColumnPrivilegesVariables = { projectRef?: string @@ -25,7 +26,10 @@ export async function getColumnPrivileges( params: { path: { ref: projectRef }, // this is needed to satisfy the typescript, but it doesn't pass the actual header - header: { 'x-connection-encrypted': connectionString! }, + header: { + 'x-connection-encrypted': connectionString!, + 'x-pg-application-name': DEFAULT_PLATFORM_APPLICATION_NAME, + }, }, signal, headers, diff --git a/apps/studio/data/sql/execute-sql-query.ts b/apps/studio/data/sql/execute-sql-query.ts index a7fe145fc9e9b..65fe32a565b51 100644 --- a/apps/studio/data/sql/execute-sql-query.ts +++ b/apps/studio/data/sql/execute-sql-query.ts @@ -9,6 +9,7 @@ import { } from 'lib/role-impersonation' import type { ResponseError } from 'types' import { sqlKeys } from './keys' +import { DEFAULT_PLATFORM_APPLICATION_NAME } from '@supabase/pg-meta/src/constants' export type ExecuteSqlVariables = { projectRef?: string @@ -73,7 +74,12 @@ export async function executeSql( const result = await post('/platform/pg-meta/{ref}/query', { signal, params: { - header: { 'x-connection-encrypted': connectionString ?? '' }, + header: { + 'x-connection-encrypted': connectionString ?? '', + 'x-pg-application-name': isStatementTimeoutDisabled + ? 'supabase/dashboard-query-editor' + : DEFAULT_PLATFORM_APPLICATION_NAME, + }, path: { ref: projectRef }, // @ts-expect-error: This is just a client side thing to identify queries better query: { diff --git a/apps/studio/data/tables/tables-query.ts b/apps/studio/data/tables/tables-query.ts index 02ce5a5c625e0..b7da827a5bb16 100644 --- a/apps/studio/data/tables/tables-query.ts +++ b/apps/studio/data/tables/tables-query.ts @@ -6,6 +6,7 @@ import { useCallback } from 'react' import { get, handleError } from 'data/fetchers' import type { ResponseError } from 'types' import { tableKeys } from './keys' +import { DEFAULT_PLATFORM_APPLICATION_NAME } from '@supabase/pg-meta/src/constants' export type TablesVariables = { projectRef?: string @@ -45,7 +46,10 @@ export async function getTables( const { data, error } = await get('/platform/pg-meta/{ref}/tables', { params: { - header: { 'x-connection-encrypted': connectionString! }, + header: { + 'x-connection-encrypted': connectionString!, + 'x-pg-application-name': DEFAULT_PLATFORM_APPLICATION_NAME, + }, path: { ref: projectRef }, query: queryParams as any, }, diff --git a/apps/studio/data/views/views-query.ts b/apps/studio/data/views/views-query.ts index 4903c28323f0a..884dda527e916 100644 --- a/apps/studio/data/views/views-query.ts +++ b/apps/studio/data/views/views-query.ts @@ -4,6 +4,7 @@ import { PostgresView } from '@supabase/postgres-meta' import { get, handleError } from 'data/fetchers' import type { ResponseError } from 'types' import { viewKeys } from './keys' +import { DEFAULT_PLATFORM_APPLICATION_NAME } from '@supabase/pg-meta/src/constants' export type ViewsVariables = { projectRef?: string @@ -22,7 +23,10 @@ export async function getViews( const { data, error } = await get('/platform/pg-meta/{ref}/views', { params: { - header: { 'x-connection-encrypted': connectionString! }, + header: { + 'x-connection-encrypted': connectionString!, + 'x-pg-application-name': DEFAULT_PLATFORM_APPLICATION_NAME, + }, path: { ref: projectRef }, query: { included_schemas: schema || '', diff --git a/apps/studio/lib/api/apiWrapper.ts b/apps/studio/lib/api/apiWrapper.ts index 89369151fb511..daa5cd6d141f5 100644 --- a/apps/studio/lib/api/apiWrapper.ts +++ b/apps/studio/lib/api/apiWrapper.ts @@ -1,8 +1,25 @@ -import { isResponseOk } from 'lib/common/fetch' import type { NextApiHandler, NextApiRequest, NextApiResponse } from 'next' + +import { ResponseError, ResponseFailure } from 'types' import { IS_PLATFORM } from '../constants' import { apiAuthenticate } from './apiAuthenticate' +export function isResponseOk(response: T | ResponseFailure | undefined): response is T { + if (response === undefined || response === null) { + return false + } + + if (response instanceof ResponseError) { + return false + } + + if (typeof response === 'object' && 'error' in response && Boolean(response.error)) { + return false + } + + return true +} + // Purpose of this apiWrapper is to function like a global catchall for ANY errors // It's a safety net as the API service should never drop, nor fail diff --git a/apps/studio/lib/common/fetch/base.ts b/apps/studio/lib/common/fetch/base.ts deleted file mode 100644 index 3f000da9971fe..0000000000000 --- a/apps/studio/lib/common/fetch/base.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { getAccessToken } from 'lib/gotrue' -import { isUndefined } from 'lodash' -import type { SupaResponse } from 'types/base' - -/* @deprecated Use handleError from data/fetchers instead */ -export function handleError(e: any, requestId: string): SupaResponse { - const message = e?.message ? `An error has occurred: ${e.message}` : 'An error has occurred' - const error = { code: 500, message, requestId } - return { error } as unknown as SupaResponse -} - -export async function handleResponse( - response: Response, - requestId: string -): Promise> { - const contentType = response.headers.get('Content-Type') - if (contentType === 'application/octet-stream') return response as any - try { - const resTxt = await response.text() - try { - // try to parse response text as json - return JSON.parse(resTxt) - } catch (err) { - // return as text plain - return resTxt as any - } - } catch (e) { - return handleError(response, requestId) as SupaResponse - } -} - -export async function handleHeadResponse( - response: Response, - requestId: string, - headers: string[] -): Promise> { - try { - const res = {} as any - headers.forEach((header: string) => { - res[header] = response.headers.get(header) - }) - return res - } catch (e) { - return handleError(response, requestId) as SupaResponse - } -} - -export async function handleResponseError( - response: Response, - requestId: string -): Promise> { - let resJson: { [prop: string]: any } - - const resTxt = await response.text() - try { - resJson = JSON.parse(resTxt) - } catch (_) { - resJson = {} - } - - if (resJson.error && typeof resJson.error === 'string') { - if (resJson.error_description) { - const error = { - code: response.status, - message: resJson.error, - description: resJson.error_description, - requestId, - } - return { error } as unknown as SupaResponse - } else { - const error = { code: response.status, message: resJson.error, requestId } - return { error } as unknown as SupaResponse - } - } else if (resJson.message) { - const error = { code: response.status, message: resJson.message, requestId } - return { error } as unknown as SupaResponse - } else if (resJson.msg) { - const error = { code: response.status, message: resJson.msg, requestId } - return { error } as unknown as SupaResponse - } else if (resJson.error && resJson.error.message) { - return { error: { code: response.status, ...resJson.error } } as unknown as SupaResponse - } else { - const message = resTxt ?? `An error has occurred: ${response.status}` - const error = { code: response.status, message, requestId } - return { error } as unknown as SupaResponse - } -} - -export async function constructHeaders(requestId: string, optionHeaders?: { [prop: string]: any }) { - let headers: { [prop: string]: any } = { - 'Content-Type': 'application/json', - Accept: 'application/json', - 'X-Request-Id': requestId, - ...optionHeaders, - } - - const hasAuthHeader = !isUndefined(optionHeaders) && 'Authorization' in optionHeaders - if (!hasAuthHeader) { - const accessToken = await getAccessToken() - if (accessToken) headers.Authorization = `Bearer ${accessToken}` - } - - return headers -} - -export function isResponseOk(response: SupaResponse | undefined): response is T { - return ( - response !== undefined && - response !== null && - !(typeof response === 'object' && 'error' in response && Boolean(response.error)) - ) -} diff --git a/apps/studio/lib/common/fetch/delete.ts b/apps/studio/lib/common/fetch/delete.ts deleted file mode 100644 index b8da5a117dce5..0000000000000 --- a/apps/studio/lib/common/fetch/delete.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { handleError, handleResponse, handleResponseError, constructHeaders } from './base' -import { uuidv4 } from '../../helpers' -import type { SupaResponse } from 'types/base' - -/** - * @deprecated please use del method from data/fetchers instead - */ -export async function delete_( - url: string, - data?: { [prop: string]: any }, - options?: { [prop: string]: any } -): Promise> { - const requestId = uuidv4() - try { - const { headers: optionHeaders, ...otherOptions } = options ?? {} - const headers = await constructHeaders(requestId, optionHeaders) - const response = await fetch(url, { - method: 'DELETE', - body: JSON.stringify(data), - referrerPolicy: 'no-referrer-when-downgrade', - headers, - ...otherOptions, - }) - - if (!response.ok) return handleResponseError(response, requestId) - return handleResponse(response, requestId) - } catch (error) { - return handleError(error, requestId) - } -} diff --git a/apps/studio/lib/common/fetch/get.ts b/apps/studio/lib/common/fetch/get.ts deleted file mode 100644 index b1f912934c975..0000000000000 --- a/apps/studio/lib/common/fetch/get.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { SupaResponse } from 'types/base' -import { uuidv4 } from '../../helpers' -import { constructHeaders, handleError, handleResponse, handleResponseError } from './base' - -/** - * @deprecated Please use get method from data/fetchers instead, unless making requests to an external URL - */ -export async function get( - url: string, - options?: { [prop: string]: any } -): Promise> { - const requestId = uuidv4() - try { - const { headers: optionHeaders, ...otherOptions } = options ?? {} - const headers = await constructHeaders(requestId, optionHeaders) - const response = await fetch(url, { - method: 'GET', - referrerPolicy: 'no-referrer-when-downgrade', - headers, - ...otherOptions, - }) - if (!response.ok) return handleResponseError(response, requestId) - return handleResponse(response, requestId) - } catch (error) { - return handleError(error, requestId) - } -} diff --git a/apps/studio/lib/common/fetch/head.ts b/apps/studio/lib/common/fetch/head.ts deleted file mode 100644 index 97d3e514c6254..0000000000000 --- a/apps/studio/lib/common/fetch/head.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { handleError, handleHeadResponse, handleResponseError, constructHeaders } from './base' -import { uuidv4 } from '../../helpers' -import type { SupaResponse } from 'types/base' - -/** - * @deprecated please use head method from data/fetchers instead - */ -export async function head( - url: string, - headersToRetrieve: string[], - options?: { [prop: string]: any } -): Promise> { - const requestId = uuidv4() - try { - const { headers: optionHeaders, ...otherOptions } = options ?? {} - const headers = await constructHeaders(requestId, optionHeaders) - const response = await fetch(url, { - method: 'HEAD', - referrerPolicy: 'no-referrer-when-downgrade', - headers, - ...otherOptions, - }) - if (!response.ok) return handleResponseError(response, requestId) - return handleHeadResponse(response, requestId, headersToRetrieve) - } catch (error) { - return handleError(error, requestId) - } -} - -export async function headWithTimeout( - url: string, - headersToRetrieve: string[], - options?: { [prop: string]: any } -): Promise> { - const requestId = uuidv4() - try { - const timeout = options?.timeout ?? 60000 - const controller = new AbortController() - const id = setTimeout(() => controller.abort(), timeout) - const { headers: optionHeaders, ...otherOptions } = options ?? {} - const headers = await constructHeaders(requestId, optionHeaders) - const response = await fetch(url, { - method: 'HEAD', - referrerPolicy: 'no-referrer-when-downgrade', - headers, - ...otherOptions, - signal: controller.signal, - }) - clearTimeout(id) - - if (!response.ok) return handleResponseError(response, requestId) - return handleHeadResponse(response, requestId, headersToRetrieve) - } catch (error) { - return handleError(error, requestId) - } -} diff --git a/apps/studio/lib/common/fetch/index.ts b/apps/studio/lib/common/fetch/index.ts deleted file mode 100644 index f476c3c02f49f..0000000000000 --- a/apps/studio/lib/common/fetch/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -// Even though this is a barrel file, the exports are all very similar - -export { - constructHeaders, - handleError, - handleHeadResponse, - handleResponse, - handleResponseError, - isResponseOk, -} from './base' -export { delete_ } from './delete' -export { get } from './get' -export { head, headWithTimeout } from './head' -export { patch } from './patch' -export { post } from './post' -export { put } from './put' diff --git a/apps/studio/lib/common/fetch/patch.ts b/apps/studio/lib/common/fetch/patch.ts deleted file mode 100644 index f8b3bb47d4a80..0000000000000 --- a/apps/studio/lib/common/fetch/patch.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { constructHeaders, handleError, handleResponse, handleResponseError } from './base' -import { uuidv4 } from '../../helpers' -import type { SupaResponse } from 'types/base' - -/** - * @deprecated please use patch method from data/fetchers instead - */ -export async function patch( - url: string, - data: { [prop: string]: any }, - options?: { [prop: string]: any } -): Promise> { - const requestId = uuidv4() - try { - const { headers: optionHeaders, ...otherOptions } = options ?? {} - const headers = await constructHeaders(requestId, optionHeaders) - const response = await fetch(url, { - method: 'PATCH', - body: JSON.stringify(data), - referrerPolicy: 'no-referrer-when-downgrade', - headers, - ...otherOptions, - }) - - if (!response.ok) return handleResponseError(response, requestId) - return handleResponse(response, requestId) - } catch (error) { - return handleError(error, requestId) - } -} diff --git a/apps/studio/lib/common/fetch/post.ts b/apps/studio/lib/common/fetch/post.ts deleted file mode 100644 index 9eea5771411ba..0000000000000 --- a/apps/studio/lib/common/fetch/post.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { uuidv4 } from 'lib/helpers' -import type { SupaResponse } from 'types/base' -import { constructHeaders, handleError, handleResponse, handleResponseError } from './base' - -/** - * @deprecated Please use post method from data/fetchers instead - * - * Exception for bucket-object-download-mutation as openapi-fetch doesn't support octet-stream responses - */ -export async function post( - url: string, - data: { [prop: string]: any }, - options?: { [prop: string]: any } -): Promise> { - const requestId = uuidv4() - try { - const { headers: optionHeaders, abortSignal, ...otherOptions } = options ?? {} - const headers = await constructHeaders(requestId, optionHeaders) - const response = await fetch(url, { - method: 'POST', - body: JSON.stringify(data), - referrerPolicy: 'no-referrer-when-downgrade', - headers, - ...otherOptions, - signal: abortSignal, - }) - if (!response.ok) return handleResponseError(response, requestId) - return handleResponse(response, requestId) - } catch (error) { - return handleError(error, requestId) - } -} diff --git a/apps/studio/lib/common/fetch/put.ts b/apps/studio/lib/common/fetch/put.ts deleted file mode 100644 index a2439e23d8764..0000000000000 --- a/apps/studio/lib/common/fetch/put.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { constructHeaders, handleError, handleResponse, handleResponseError } from './base' -import { uuidv4 } from '../../helpers' -import type { SupaResponse } from 'types/base' - -/** - * @deprecated please use post method from data/fetchers instead - */ -export async function put( - url: string, - data: { [prop: string]: any }, - options?: { [prop: string]: any } -): Promise> { - const requestId = uuidv4() - try { - const { headers: optionHeaders, ...otherOptions } = options ?? {} - const headers = await constructHeaders(requestId, optionHeaders) - const response = await fetch(url, { - method: 'PUT', - body: JSON.stringify(data), - credentials: 'include', - referrerPolicy: 'no-referrer-when-downgrade', - headers, - ...otherOptions, - }) - - if (!response.ok) return handleResponseError(response, requestId) - return handleResponse(response, requestId) - } catch (error) { - return handleError(error, requestId) - } -} diff --git a/apps/studio/lib/integration-utils.ts b/apps/studio/lib/integration-utils.ts index 44f176fb3bd81..2f845488e73e3 100644 --- a/apps/studio/lib/integration-utils.ts +++ b/apps/studio/lib/integration-utils.ts @@ -1,7 +1,7 @@ import { fetchHandler } from 'data/fetchers' import type { Integration } from 'data/integrations/integrations.types' import { ResponseError, type SupaResponse } from 'types' -import { isResponseOk } from './common/fetch' +import { isResponseOk } from './api/apiWrapper' async function fetchGitHub(url: string, responseJson = true): Promise> { const response = await fetchHandler(url) diff --git a/apps/studio/lib/pingPostgrest.test.ts b/apps/studio/lib/pingPostgrest.test.ts index 79a2e216ade31..fce1127c5c9e5 100644 --- a/apps/studio/lib/pingPostgrest.test.ts +++ b/apps/studio/lib/pingPostgrest.test.ts @@ -1,6 +1,6 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest' +import * as fetchModule from 'data/fetchers' +import { beforeEach, describe, expect, it, vi } from 'vitest' import pingPostgrest from './pingPostgrest' -import * as fetchModule from './common/fetch' vi.mock('./constants', () => ({ API_URL: 'https://api.example.com' })) @@ -9,14 +9,14 @@ describe('pingPostgrest', () => { vi.restoreAllMocks() }) - it('returns true if headWithTimeout returns no error', async () => { - vi.spyOn(fetchModule, 'headWithTimeout').mockResolvedValue({ error: undefined }) + it('returns true if fetchHeadWithTimeout returns no error', async () => { + vi.spyOn(fetchModule, 'fetchHeadWithTimeout').mockResolvedValue({ error: undefined }) const result = await pingPostgrest('my-project') expect(result).toBe(true) }) - it('returns false if headWithTimeout returns an error', async () => { - vi.spyOn(fetchModule, 'headWithTimeout').mockResolvedValue({ error: { message: 'fail' } }) + it('returns false if fetchHeadWithTimeout returns an error', async () => { + vi.spyOn(fetchModule, 'fetchHeadWithTimeout').mockResolvedValue({ error: { message: 'fail' } }) const result = await pingPostgrest('my-project') expect(result).toBe(false) }) @@ -26,8 +26,10 @@ describe('pingPostgrest', () => { expect(result).toBe(false) }) - it('passes timeout option to headWithTimeout', async () => { - const spy = vi.spyOn(fetchModule, 'headWithTimeout').mockResolvedValue({ error: undefined }) + it('passes timeout option to fetchHeadWithTimeout', async () => { + const spy = vi + .spyOn(fetchModule, 'fetchHeadWithTimeout') + .mockResolvedValue({ error: undefined }) await pingPostgrest('my-project', { timeout: 1234 }) expect(spy).toHaveBeenCalledWith( 'https://api.example.com/projects/my-project/api/rest', diff --git a/apps/studio/lib/pingPostgrest.ts b/apps/studio/lib/pingPostgrest.ts index 2dbad44f10927..208be243a182a 100644 --- a/apps/studio/lib/pingPostgrest.ts +++ b/apps/studio/lib/pingPostgrest.ts @@ -1,4 +1,4 @@ -import { headWithTimeout } from './common/fetch' +import { fetchHeadWithTimeout } from 'data/fetchers' import { API_URL } from './constants' const DEFAULT_TIMEOUT_MILLISECONDS = 2000 @@ -33,7 +33,7 @@ export default pingPostgrest * @return true if there's no error else false */ async function pingOpenApi(ref: string, timeout?: number) { - const { error } = await headWithTimeout(`${API_URL}/projects/${ref}/api/rest`, [], { + const { error } = await fetchHeadWithTimeout(`${API_URL}/projects/${ref}/api/rest`, [], { timeout: timeout ?? DEFAULT_TIMEOUT_MILLISECONDS, }) return error === undefined diff --git a/apps/studio/lib/self-hosted.ts b/apps/studio/lib/self-hosted.ts index b8ab1b779b782..8ce7676a8e178 100644 --- a/apps/studio/lib/self-hosted.ts +++ b/apps/studio/lib/self-hosted.ts @@ -1,11 +1,11 @@ +import { fetchPost } from 'data/fetchers' import { constructHeaders } from 'lib/api/apiHelpers' -import { post } from 'lib/common/fetch' import { PG_META_URL } from 'lib/constants' import type { ResponseError } from 'types' export async function queryPgMetaSelfHosted(sql: string, headersInit?: { [prop: string]: any }) { const headers = constructHeaders(headersInit ?? {}) - const response = await post(`${PG_META_URL}/query`, { query: sql }, { headers }) + const response = await fetchPost(`${PG_META_URL}/query`, { query: sql }, { headers }) if (response.error) { return { error: response.error as ResponseError } diff --git a/apps/studio/pages/api/platform/auth/[ref]/config.ts b/apps/studio/pages/api/platform/auth/[ref]/config.ts deleted file mode 100644 index 796a37165fc06..0000000000000 --- a/apps/studio/pages/api/platform/auth/[ref]/config.ts +++ /dev/null @@ -1,179 +0,0 @@ -import { NextApiRequest, NextApiResponse } from 'next' -import apiWrapper from 'lib/api/apiWrapper' -import { get } from 'lib/common/fetch' -import { constructHeaders } from 'lib/api/apiHelpers' - -export default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler) - -async function handler(req: NextApiRequest, res: NextApiResponse) { - const { method } = req - - switch (method) { - case 'GET': - return handleGetAll(req, res) - case 'POST': - return handlePost(req, res) - default: - res.setHeader('Allow', ['GET', 'POST']) - res.status(405).json({ data: null, error: { message: `Method ${method} Not Allowed` } }) - } -} - -const handleGetAll = async (req: NextApiRequest, res: NextApiResponse) => { - const headers = constructHeaders({}) - const url = `${process.env.SUPABASE_URL}/auth/v1/settings` - const { external, disable_signup, mailer_autoconfirm, phone_autoconfirm } = await get(url, { - headers, - }) - // Platform only API - return res.status(200).json({ - app_version: '', - config_override_id: '', - project_id: '', - jwt_secret_encrypted: '', - isFreeTier: true, - SITE_URL: '', - OPERATOR_TOKEN: null, - DISABLE_SIGNUP: disable_signup ?? false, - RATE_LIMIT_HEADER: null, - JWT_EXP: 3600, - JWT_AUD: '', - JWT_DEFAULT_GROUP_NAME: '', - URI_ALLOW_LIST: '', - MAILER_AUTOCONFIRM: mailer_autoconfirm ?? false, - MAILER_OTP_EXP: 3600, - MAILER_OTP_LENGTH: 6, - MAILER_URLPATHS_INVITE: '', - MAILER_URLPATHS_CONFIRMATION: '', - MAILER_URLPATHS_RECOVERY: '', - MAILER_URLPATHS_EMAIL_CHANGE: '', - SECURITY_UPDATE_PASSWORD_REQUIRE_REAUTHENTICATION: false, - SMTP_ADMIN_EMAIL: '', - SMTP_HOST: null, - SMTP_PORT: null, - SMTP_USER: null, - SMTP_PASS: null, - SMTP_MAX_FREQUENCY: 0, - EXTERNAL_ANONYMOUS_USERS_ENABLED: external?.anonymous_users ?? true, - EXTERNAL_EMAIL_ENABLED: external?.email ?? true, - EXTERNAL_PHONE_ENABLED: external?.phone ?? true, - EXTERNAL_APPLE_ENABLED: external?.apple ?? false, - EXTERNAL_APPLE_CLIENT_ID: null, - EXTERNAL_APPLE_SECRET: null, - EXTERNAL_AZURE_ENABLED: external?.azure ?? false, - EXTERNAL_AZURE_CLIENT_ID: null, - EXTERNAL_AZURE_SECRET: null, - EXTERNAL_AZURE_URL: null, - EXTERNAL_BITBUCKET_ENABLED: external?.bitbucket ?? false, - EXTERNAL_BITBUCKET_CLIENT_ID: null, - EXTERNAL_BITBUCKET_SECRET: null, - EXTERNAL_DISCORD_ENABLED: external?.discord ?? false, - EXTERNAL_DISCORD_CLIENT_ID: null, - EXTERNAL_DISCORD_SECRET: null, - EXTERNAL_FACEBOOK_ENABLED: external?.facebook ?? false, - EXTERNAL_FACEBOOK_CLIENT_ID: null, - EXTERNAL_FACEBOOK_SECRET: null, - EXTERNAL_FIGMA_ENABLED: external?.figma ?? false, - EXTERNAL_FIGMA_CLIENT_ID: null, - EXTERNAL_FIGMA_SECRET: null, - EXTERNAL_GITHUB_ENABLED: external?.github ?? false, - EXTERNAL_GITHUB_CLIENT_ID: null, - EXTERNAL_GITHUB_SECRET: null, - EXTERNAL_GITLAB_ENABLED: external?.gitlab ?? false, - EXTERNAL_GITLAB_CLIENT_ID: null, - EXTERNAL_GITLAB_SECRET: null, - EXTERNAL_GITLAB_REDIRECT_URI: null, - EXTERNAL_GOOGLE_ENABLED: external?.google ?? false, - EXTERNAL_GOOGLE_CLIENT_ID: null, - EXTERNAL_GOOGLE_SECRET: null, - EXTERNAL_KAKAO_ENABLED: external?.kakao ?? false, - EXTERNAL_KAKAO_CLIENT_ID: null, - EXTERNAL_KAKAO_SECRET: null, - EXTERNAL_KEYCLOAK_ENABLED: external?.keycloak ?? false, - EXTERNAL_KEYCLOAK_CLIENT_ID: null, - EXTERNAL_KEYCLOAK_SECRET: null, - EXTERNAL_KEYCLOAK_URL: null, - EXTERNAL_NOTION_ENABLED: external?.notion ?? false, - EXTERNAL_NOTION_CLIENT_ID: null, - EXTERNAL_NOTION_SECRET: null, - EXTERNAL_SPOTIFY_ENABLED: external?.spotify ?? false, - EXTERNAL_SPOTIFY_CLIENT_ID: null, - EXTERNAL_SPOTIFY_SECRET: null, - EXTERNAL_SLACK_OIDC_ENABLED: external?.slack ?? false, - EXTERNAL_SLACK_OIDC_CLIENT_ID: null, - EXTERNAL_SLACK_OIDC_SECRET: null, - EXTERNAL_TWITTER_ENABLED: external?.twitter ?? false, - EXTERNAL_TWITTER_CLIENT_ID: null, - EXTERNAL_TWITTER_SECRET: null, - EXTERNAL_TWITCH_ENABLED: external?.twitch ?? false, - EXTERNAL_TWITCH_CLIENT_ID: null, - EXTERNAL_TWITCH_SECRET: null, - EXTERNAL_WORKOS_ENABLED: external?.workos ?? false, - EXTERNAL_WORKOS_CLIENT_ID: null, - EXTERNAL_WORKOS_SECRET: null, - EXTERNAL_WORKOS_URL: null, - EXTERNAL_ZOOM_ENABLED: external?.zoom ?? false, - EXTERNAL_ZOOM_CLIENT_ID: null, - EXTERNAL_ZOOM_SECRET: null, - MAILER_SUBJECTS_INVITE: 'You have been invited', - MAILER_SUBJECTS_CONFIRMATION: 'Confirm Your Signup', - MAILER_SUBJECTS_RECOVERY: 'Reset Your Password', - MAILER_SUBJECTS_EMAIL_CHANGE: 'Confirm Email Change', - MAILER_SUBJECTS_MAGIC_LINK: 'Your Magic Link', - MAILER_SUBJECTS_REAUTHENTICATION: 'Confirm Reauthentication', - MAILER_TEMPLATES_INVITE: null, - MAILER_TEMPLATES_INVITE_CONTENT: - '

You have been invited

\n\n

You have been invited to create a user on {{ .SiteURL }}. Follow this link to accept the invite:

\n

Accept the invite

', - MAILER_TEMPLATES_CONFIRMATION: null, - MAILER_TEMPLATES_CONFIRMATION_CONTENT: - '

Confirm your email

\n\n

Follow this link to confirm your email:

\n\n

Confirm your email address

', - MAILER_TEMPLATES_RECOVERY: null, - MAILER_TEMPLATES_RECOVERY_CONTENT: - '

Reset Password

\n\n

Follow this link to reset the password for your user:

\n

Reset Password

', - MAILER_TEMPLATES_EMAIL_CHANGE: null, - MAILER_TEMPLATES_EMAIL_CHANGE_CONTENT: - '

Confirm Change of Email

\n\n

Follow this link to confirm the update of your email from {{ .Email }} to {{ .NewEmail }}:

\n

Change Email

', - MAILER_TEMPLATES_MAGIC_LINK: null, - MAILER_TEMPLATES_MAGIC_LINK_CONTENT: - '

Magic Link

\n\n

Follow this link to login:

\n

Log In

', - PASSWORD_MIN_LENGTH: 6, - SMTP_SENDER_NAME: null, - SMS_AUTOCONFIRM: phone_autoconfirm ?? false, - SMS_MAX_FREQUENCY: 0, - SMS_OTP_EXP: 60, - SMS_OTP_LENGTH: 6, - SMS_PROVIDER: 'twilio', - SMS_TWILIO_ACCOUNT_SID: null, - SMS_TWILIO_AUTH_TOKEN: null, - SMS_TWILIO_CONTENT_SID: null, - SMS_TWILIO_MESSAGE_SERVICE_SID: null, - SMS_TWILIO_VERIFY_ACCOUNT_SID: null, - SMS_TWILIO_VERIFY_AUTH_TOKEN: null, - SMS_TWILIO_VERIFY_MESSAGE_SERVICE_SID: null, - SMS_TEMPLATE: 'Your code is {{ .Code }}', - SECURITY_CAPTCHA_ENABLED: false, - SECURITY_CAPTCHA_PROVIDER: 'hcaptcha', - SECURITY_CAPTCHA_SECRET: null, - SECURITY_MANUAL_LINKING_ENABLED: false, - SECURITY_REFRESH_TOKEN_REUSE_INTERVAL: '10', - RATE_LIMIT_EMAIL_SENT: 0, - RATE_LIMIT_SMS_SENT: 0, - RATE_LIMIT_ANONYMOUS_USERS: 0, - MAILER_SECURE_EMAIL_CHANGE_ENABLED: true, - SMS_MESSAGEBIRD_ACCESS_KEY: null, - SMS_MESSAGEBIRD_ORIGINATOR: null, - SMS_VONAGE_API_KEY: null, - SMS_VONAGE_API_SECRET: null, - SMS_VONAGE_FROM: null, - SMS_TEXTLOCAL_API_KEY: null, - SMS_TEXTLOCAL_SENDER: null, - MAILER_TEMPLATES_REAUTHENTICATION: null, - MAILER_TEMPLATES_REAUTHENTICATION_CONTENT: - '

Confirm reauthentication

Enter the code: {{ .Token }}

', - }) -} - -const handlePost = async (req: NextApiRequest, res: NextApiResponse) => { - // Platform only API - return res.status(200).json({}) -} diff --git a/apps/studio/pages/api/platform/auth/[ref]/invite.ts b/apps/studio/pages/api/platform/auth/[ref]/invite.ts index 5e626df5f59fb..662df08ded098 100644 --- a/apps/studio/pages/api/platform/auth/[ref]/invite.ts +++ b/apps/studio/pages/api/platform/auth/[ref]/invite.ts @@ -1,7 +1,11 @@ +import { createClient } from '@supabase/supabase-js' import { NextApiRequest, NextApiResponse } from 'next' -import apiWrapper from 'lib/api/apiWrapper' + +import { fetchPost } from 'data/fetchers' import { constructHeaders } from 'lib/api/apiHelpers' -import { post } from 'lib/common/fetch' +import apiWrapper from 'lib/api/apiWrapper' + +const supabase = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_KEY!) export default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler) @@ -25,6 +29,12 @@ const handlePost = async (req: NextApiRequest, res: NextApiResponse) => { }) const url = `${process.env.SUPABASE_URL}/auth/v1/invite` const payload = { email: req.body.email } - const response = await post(url, payload, { headers }) - return res.status(200).json(response) + + const response = await fetchPost(url, payload, { headers }) + if (response.error) { + const { code, message } = response.error + return res.status(code).json({ message }) + } else { + return res.status(200).json(response) + } } diff --git a/apps/studio/pages/api/platform/auth/[ref]/magiclink.ts b/apps/studio/pages/api/platform/auth/[ref]/magiclink.ts index 50c25e9199e22..2063d47447e61 100644 --- a/apps/studio/pages/api/platform/auth/[ref]/magiclink.ts +++ b/apps/studio/pages/api/platform/auth/[ref]/magiclink.ts @@ -1,10 +1,8 @@ import { NextApiRequest, NextApiResponse } from 'next' -import SqlString from 'sqlstring' -import apiWrapper from 'lib/api/apiWrapper' +import { fetchPost } from 'data/fetchers' import { constructHeaders } from 'lib/api/apiHelpers' -import { post } from 'lib/common/fetch' -import { tryParseInt } from 'lib/helpers' +import apiWrapper from 'lib/api/apiWrapper' export default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler) @@ -28,6 +26,12 @@ const handlePost = async (req: NextApiRequest, res: NextApiResponse) => { }) const url = `${process.env.SUPABASE_URL}/auth/v1/magiclink` const payload = { email: req.body.email } - const response = await post(url, payload, { headers }) - return res.status(200).json(response) + + const response = await fetchPost(url, payload, { headers }) + if (response.error) { + const { code, message } = response.error + return res.status(code).json({ message }) + } else { + return res.status(200).json(response) + } } diff --git a/apps/studio/pages/api/platform/auth/[ref]/otp.ts b/apps/studio/pages/api/platform/auth/[ref]/otp.ts index e9e8892a38806..ca70cd0298be0 100644 --- a/apps/studio/pages/api/platform/auth/[ref]/otp.ts +++ b/apps/studio/pages/api/platform/auth/[ref]/otp.ts @@ -1,10 +1,8 @@ import { NextApiRequest, NextApiResponse } from 'next' -import SqlString from 'sqlstring' -import apiWrapper from 'lib/api/apiWrapper' +import { fetchPost } from 'data/fetchers' import { constructHeaders } from 'lib/api/apiHelpers' -import { post } from 'lib/common/fetch' -import { tryParseInt } from 'lib/helpers' +import apiWrapper from 'lib/api/apiWrapper' export default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler) @@ -28,6 +26,12 @@ const handlePost = async (req: NextApiRequest, res: NextApiResponse) => { }) const url = `${process.env.SUPABASE_URL}/auth/v1/otp` const payload = { phone: req.body.phone } - const response = await post(url, payload, { headers }) - return res.status(200).json(response) + + const response = await fetchPost(url, payload, { headers }) + if (response.error) { + const { code, message } = response.error + return res.status(code).json({ message }) + } else { + return res.status(200).json(response) + } } diff --git a/apps/studio/pages/api/platform/auth/[ref]/recover.ts b/apps/studio/pages/api/platform/auth/[ref]/recover.ts index 6a05ff857080f..50e4c4afa21fe 100644 --- a/apps/studio/pages/api/platform/auth/[ref]/recover.ts +++ b/apps/studio/pages/api/platform/auth/[ref]/recover.ts @@ -1,10 +1,8 @@ import { NextApiRequest, NextApiResponse } from 'next' -import SqlString from 'sqlstring' -import apiWrapper from 'lib/api/apiWrapper' +import { fetchPost } from 'data/fetchers' import { constructHeaders } from 'lib/api/apiHelpers' -import { post } from 'lib/common/fetch' -import { tryParseInt } from 'lib/helpers' +import apiWrapper from 'lib/api/apiWrapper' export default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler) @@ -28,6 +26,12 @@ const handlePost = async (req: NextApiRequest, res: NextApiResponse) => { }) const url = `${process.env.SUPABASE_URL}/auth/v1/recover` const payload = { email: req.body.email } - const response = await post(url, payload, { headers }) - return res.status(200).json(response) + + const response = await fetchPost(url, payload, { headers }) + if (response.error) { + const { code, message } = response.error + return res.status(code).json({ message }) + } else { + return res.status(200).json(response) + } } diff --git a/apps/studio/pages/api/platform/auth/[ref]/users.ts b/apps/studio/pages/api/platform/auth/[ref]/users.ts deleted file mode 100644 index 0fbcf299d363e..0000000000000 --- a/apps/studio/pages/api/platform/auth/[ref]/users.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { NextApiRequest, NextApiResponse } from 'next' -import SqlString from 'sqlstring' -import { createClient } from '@supabase/supabase-js' - -import { post } from 'lib/common/fetch' -import { tryParseInt } from 'lib/helpers' -import { PG_META_URL } from 'lib/constants' -import apiWrapper from 'lib/api/apiWrapper' -import { constructHeaders } from 'lib/api/apiHelpers' - -const supabase = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_KEY!) - -export default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler) - -async function handler(req: NextApiRequest, res: NextApiResponse) { - const { method } = req - - switch (method) { - case 'GET': - return handleGetAll(req, res) - case 'POST': - return handlePost(req, res) - case 'DELETE': - return handleDelete(req, res) - default: - res.setHeader('Allow', ['GET', 'POST', 'DELETE']) - res.status(405).json({ data: null, error: { message: `Method ${method} Not Allowed` } }) - } -} - -const handleGetAll = async (req: NextApiRequest, res: NextApiResponse) => { - const headers = constructHeaders(req.headers) - const { keywords, limit, offset, verified } = req.query - const limitInt = tryParseInt(limit as string) || 10 - const offsetInt = tryParseInt(offset as string) || 0 - const hasValidKeywords = keywords && keywords != '' - const hasVerifiedValue = verified && verified != '' - - let queryCount = '' - let queryUsers = '' - - if (hasValidKeywords && !hasVerifiedValue) { - queryCount = SqlString.format( - 'SELECT count(*) from auth.users WHERE (email ilike ? OR id::text ilike ?);', - [`%${keywords}%`, `%${keywords}%`] - ) - queryUsers = SqlString.format( - 'SELECT * from auth.users WHERE (email ilike ? OR id::text ilike ?) ORDER BY created_at DESC LIMIT ? OFFSET ?;', - [`%${keywords}%`, `%${keywords}%`, limitInt, offsetInt] - ) - } - - if (!hasValidKeywords && hasVerifiedValue) { - if (verified === 'verified') { - queryCount = SqlString.format( - 'SELECT count(*) from auth.users WHERE (email_confirmed_at IS NOT NULL or phone_confirmed_at IS NOT NULL);' - ) - queryUsers = SqlString.format( - 'SELECT * from auth.users WHERE (email_confirmed_at IS NOT NULL or phone_confirmed_at IS NOT NULL) ORDER BY created_at DESC LIMIT ? OFFSET ?;', - [limitInt, offsetInt] - ) - } - if (verified === 'unverified') { - queryCount = SqlString.format( - 'SELECT count(*) from auth.users WHERE (email_confirmed_at IS NULL AND phone_confirmed_at IS NULL);' - ) - queryUsers = SqlString.format( - 'SELECT * from auth.users WHERE (email_confirmed_at IS NULL AND phone_confirmed_at IS NULL) ORDER BY created_at DESC LIMIT ? OFFSET ?;', - [limitInt, offsetInt] - ) - } - } - - if (hasValidKeywords && hasVerifiedValue) { - if (verified === 'verified') { - queryCount = SqlString.format( - 'SELECT count(*) from auth.users WHERE (email_confirmed_at IS NOT NULL or phone_confirmed_at IS NOT NULL) AND (email ilike ? OR id::text ilike ?);', - [`%${keywords}%`, `%${keywords}%`] - ) - queryUsers = SqlString.format( - 'SELECT * from auth.users WHERE (email_confirmed_at IS NOT NULL or phone_confirmed_at IS NOT NULL) AND (email ilike ? OR id::text ilike ?) ORDER BY created_at DESC LIMIT ? OFFSET ?;', - [`%${keywords}%`, `%${keywords}%`, limitInt, offsetInt] - ) - } - if (verified === 'unverified') { - queryCount = SqlString.format( - 'SELECT count(*) from auth.users WHERE (email_confirmed_at IS NULL AND phone_confirmed_at IS NULL) AND (email ilike ? OR id::text ilike ?);', - [`%${keywords}%`, `%${keywords}%`] - ) - queryUsers = SqlString.format( - 'SELECT * from auth.users WHERE (email_confirmed_at IS NULL AND phone_confirmed_at IS NULL) AND (email ilike ? OR id::text ilike ?) ORDER BY created_at DESC LIMIT ? OFFSET ?;', - [`%${keywords}%`, `%${keywords}%`, limitInt, offsetInt] - ) - } - } - - if (!hasValidKeywords && !hasVerifiedValue) { - queryCount = 'SELECT count(*) from auth.users;' - queryUsers = SqlString.format( - 'SELECT * from auth.users ORDER BY created_at DESC LIMIT ? OFFSET ?;', - [limitInt, offsetInt] - ) - } - - const [getTotal, getUsers] = await Promise.all([ - post(`${PG_META_URL}/query`, { query: queryCount }, { headers }), - post(`${PG_META_URL}/query`, { query: queryUsers }, { headers }), - ]) - - let total = 0 - if (getTotal && (getTotal as any[]).length > 0) { - total = (getTotal[0] as any).count - } - - return res.status(200).json({ total, users: getUsers }) -} - -const handlePost = async (req: NextApiRequest, res: NextApiResponse) => { - const { email, password, email_confirm } = req.body - const { data, error } = await supabase.auth.admin.createUser({ email, password, email_confirm }) - - if (error) return res.status(400).json({ error: { message: error.message } }) - return res.status(200).json(data.user) -} - -const handleDelete = async (req: NextApiRequest, res: NextApiResponse) => { - const { id } = req.body - const { data, error } = await supabase.auth.admin.deleteUser(id) - - if (error) return res.status(400).json({ error: { message: error.message } }) - return res.status(200).json(data.user) -} diff --git a/apps/studio/pages/api/platform/auth/[ref]/users/index.ts b/apps/studio/pages/api/platform/auth/[ref]/users/index.ts new file mode 100644 index 0000000000000..a4c2239d381b5 --- /dev/null +++ b/apps/studio/pages/api/platform/auth/[ref]/users/index.ts @@ -0,0 +1,27 @@ +import { createClient } from '@supabase/supabase-js' +import { NextApiRequest, NextApiResponse } from 'next' + +import apiWrapper from 'lib/api/apiWrapper' + +const supabase = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_KEY!) + +export default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler) + +async function handler(req: NextApiRequest, res: NextApiResponse) { + const { method } = req + + switch (method) { + case 'POST': + return handlePost(req, res) + default: + res.setHeader('Allow', ['POST']) + res.status(405).json({ data: null, error: { message: `Method ${method} Not Allowed` } }) + } +} + +const handlePost = async (req: NextApiRequest, res: NextApiResponse) => { + const { data, error } = await supabase.auth.admin.createUser(req.body) + + if (error) return res.status(400).json({ error: { message: error.message } }) + return res.status(200).json(data.user) +} diff --git a/apps/studio/pages/api/platform/pg-meta/[ref]/column-privileges.ts b/apps/studio/pages/api/platform/pg-meta/[ref]/column-privileges.ts index 15306ddcc94ca..e8b288d02107c 100644 --- a/apps/studio/pages/api/platform/pg-meta/[ref]/column-privileges.ts +++ b/apps/studio/pages/api/platform/pg-meta/[ref]/column-privileges.ts @@ -1,9 +1,8 @@ import { NextApiRequest, NextApiResponse } from 'next' +import { fetchGet } from 'data/fetchers' import { constructHeaders } from 'lib/api/apiHelpers' import apiWrapper from 'lib/api/apiWrapper' -import { delete_, get, post } from 'lib/common/fetch' -import { PG_META_URL } from 'lib/constants' import { getPgMetaRedirectUrl } from './tables' export default (req: NextApiRequest, res: NextApiResponse) => @@ -14,10 +13,6 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { switch (method) { case 'GET': return handleGetAll(req, res) - case 'POST': - return handlePost(req, res) - case 'DELETE': - return handleDelete(req, res) default: res.setHeader('Allow', ['GET']) res.status(405).json({ error: { message: `Method ${method} Not Allowed` } }) @@ -26,39 +21,12 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { const handleGetAll = async (req: NextApiRequest, res: NextApiResponse) => { const headers = constructHeaders(req.headers) - let response = await get(getPgMetaRedirectUrl(req, 'column-privileges'), { - headers, - }) - if (response.error) { - return res.status(400).json({ error: response.error }) - } - return res.status(200).json(response) -} - -const handlePost = async (req: NextApiRequest, res: NextApiResponse) => { - const headers = constructHeaders(req.headers) - const payload = req.body - const response = await post(`${PG_META_URL}/column-privileges`, payload, { - headers, - }) + const response = await fetchGet(getPgMetaRedirectUrl(req, 'column-privileges'), { headers }) if (response.error) { - console.error('Role POST:', response.error) - return res.status(400).json({ error: response.error }) + const { code, message } = response.error + return res.status(code).json({ message }) + } else { + return res.status(200).json(response) } - - return res.status(200).json(response) -} - -const handleDelete = async (req: NextApiRequest, res: NextApiResponse) => { - const headers = constructHeaders(req.headers) - const payload = req.body - const response = await delete_(`${PG_META_URL}/column-privileges`, payload, { headers }) - - if (response.error) { - console.error('Role DELETE:', response.error) - return res.status(400).json({ error: response.error }) - } - - return res.status(200).json(response) } diff --git a/apps/studio/pages/api/platform/pg-meta/[ref]/columns.ts b/apps/studio/pages/api/platform/pg-meta/[ref]/columns.ts deleted file mode 100644 index 7b131a24d0d55..0000000000000 --- a/apps/studio/pages/api/platform/pg-meta/[ref]/columns.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { NextApiRequest, NextApiResponse } from 'next' -import apiWrapper from 'lib/api/apiWrapper' -import { constructHeaders, toSnakeCase } from 'lib/api/apiHelpers' -import { PG_META_URL } from 'lib/constants' -import { delete_, get, patch, post } from 'lib/common/fetch' -import { getPgMetaRedirectUrl } from './tables' - -export default (req: NextApiRequest, res: NextApiResponse) => - apiWrapper(req, res, handler, { withAuth: true }) - -async function handler(req: NextApiRequest, res: NextApiResponse) { - const { method } = req - - switch (method) { - case 'GET': - return handleGetAll(req, res) - case 'POST': - return handlePost(req, res) - case 'PATCH': - return handlePatch(req, res) - case 'DELETE': - return handleDelete(req, res) - default: - res.setHeader('Allow', ['GET', 'POST', 'PATCH', 'DELETE']) - res.status(405).json({ error: { message: `Method ${method} Not Allowed` } }) - } -} - -const handleGetAll = async (req: NextApiRequest, res: NextApiResponse) => { - const headers = constructHeaders(req.headers) - let response = await get(getPgMetaRedirectUrl(req, 'columns'), { - headers, - }) - if (response.error) { - return res.status(400).json({ error: response.error }) - } - return res.status(200).json(response) -} - -const handlePost = async (req: NextApiRequest, res: NextApiResponse) => { - const headers = constructHeaders(req.headers) - const payload = toSnakeCase(req.body) - const response = await post(`${PG_META_URL}/columns`, payload, { - headers, - }) - - if (response.error) { - console.error('Column POST:', response.error) - return res.status(400).json({ error: response.error }) - } - - return res.status(200).json(response) -} - -const handlePatch = async (req: NextApiRequest, res: NextApiResponse) => { - const headers = constructHeaders(req.headers) - const payload = toSnakeCase(req.body) - const response = await patch(`${PG_META_URL}/columns/${req.query.id}`, payload, { - headers, - }) - - if (response.error) { - console.error('Column PATCH:', response) - return res.status(400).json({ error: response.error }) - } - - return res.status(200).json(response) -} - -const handleDelete = async (req: NextApiRequest, res: NextApiResponse) => { - const headers = constructHeaders(req.headers) - const response = await delete_(`${PG_META_URL}/columns/${req.query.id}`, {}, { headers }) - - if (response.error) { - console.error('Column DELETE:', response.error) - return res.status(400).json({ error: response.error }) - } - - return res.status(200).json(response) -} diff --git a/apps/studio/pages/api/platform/pg-meta/[ref]/extensions.ts b/apps/studio/pages/api/platform/pg-meta/[ref]/extensions.ts index bc060035c3873..51862a2e97678 100644 --- a/apps/studio/pages/api/platform/pg-meta/[ref]/extensions.ts +++ b/apps/studio/pages/api/platform/pg-meta/[ref]/extensions.ts @@ -1,9 +1,9 @@ import { NextApiRequest, NextApiResponse } from 'next' +import { fetchGet } from 'data/fetchers' +import { constructHeaders } from 'lib/api/apiHelpers' import apiWrapper from 'lib/api/apiWrapper' import { PG_META_URL } from 'lib/constants' -import { get, post, delete_ } from 'lib/common/fetch' -import { constructHeaders, toSnakeCase } from 'lib/api/apiHelpers' export default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler, { withAuth: true }) @@ -14,10 +14,6 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { switch (method) { case 'GET': return handleGetAll(req, res) - case 'POST': - return handlePost(req, res) - case 'DELETE': - return handleDelete(req, res) default: res.setHeader('Allow', ['GET']) res.status(405).json({ error: { message: `Method ${method} Not Allowed` } }) @@ -26,38 +22,12 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { const handleGetAll = async (req: NextApiRequest, res: NextApiResponse) => { const headers = constructHeaders(req.headers) - let response = await get(`${PG_META_URL}/extensions`, { - headers, - }) - if (response.error) { - return res.status(400).json(response.error) - } - return res.status(200).json(response) -} - -const handlePost = async (req: NextApiRequest, res: NextApiResponse) => { - const headers = constructHeaders(req.headers) - const payload = toSnakeCase(req.body) - const response = await post(`${PG_META_URL}/extensions`, payload, { - headers, - }) + const response = await fetchGet(`${PG_META_URL}/extensions`, { headers }) if (response.error) { - console.error('Extensions POST:', response.error) - return res.status(400).json(response.error) + const { code, message } = response.error + return res.status(code).json({ message }) + } else { + return res.status(200).json(response) } - - return res.status(200).json(response) -} - -const handleDelete = async (req: NextApiRequest, res: NextApiResponse) => { - const headers = constructHeaders(req.headers) - const response = await delete_(`${PG_META_URL}/extensions/${req.query.id}`, {}, { headers }) - - if (response.error) { - console.error('Extensions DELETE:', response.error) - return res.status(400).json(response.error) - } - - return res.status(200).json(response) } diff --git a/apps/studio/pages/api/platform/pg-meta/[ref]/foreign-tables.ts b/apps/studio/pages/api/platform/pg-meta/[ref]/foreign-tables.ts index e2459fb0dbaf4..a919ca0950953 100644 --- a/apps/studio/pages/api/platform/pg-meta/[ref]/foreign-tables.ts +++ b/apps/studio/pages/api/platform/pg-meta/[ref]/foreign-tables.ts @@ -1,9 +1,8 @@ import { NextApiRequest, NextApiResponse } from 'next' -import apiWrapper from 'lib/api/apiWrapper' -import { get } from 'lib/common/fetch' +import { fetchGet } from 'data/fetchers' import { constructHeaders } from 'lib/api/apiHelpers' -import { PG_META_URL } from 'lib/constants' +import apiWrapper from 'lib/api/apiWrapper' import { getPgMetaRedirectUrl } from './tables' export default (req: NextApiRequest, res: NextApiResponse) => @@ -14,7 +13,6 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { switch (method) { case 'GET': - if (req.query.id) return handleGetOne(req, res) return handleGetAll(req, res) default: res.setHeader('Allow', ['GET']) @@ -24,23 +22,12 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { const handleGetAll = async (req: NextApiRequest, res: NextApiResponse) => { const headers = constructHeaders(req.headers) - let response = await get(getPgMetaRedirectUrl(req, 'foreign-tables'), { - headers, - }) - if (response.error) { - return res.status(400).json({ error: response.error }) - } + const response = await fetchGet(getPgMetaRedirectUrl(req, 'foreign-tables'), { headers }) - return res.status(200).json(response) -} - -const handleGetOne = async (req: NextApiRequest, res: NextApiResponse) => { - const headers = constructHeaders(req.headers) - let response = await get(`${PG_META_URL}/foreign-tables/${req.query.id}`, { - headers, - }) if (response.error) { - return res.status(400).json({ error: response.error }) + const { code, message } = response.error + return res.status(code).json({ message }) + } else { + return res.status(200).json(response) } - return res.status(200).json(response) } diff --git a/apps/studio/pages/api/platform/pg-meta/[ref]/materialized-views.ts b/apps/studio/pages/api/platform/pg-meta/[ref]/materialized-views.ts index d0ecfac3512d9..72a35c9622a26 100644 --- a/apps/studio/pages/api/platform/pg-meta/[ref]/materialized-views.ts +++ b/apps/studio/pages/api/platform/pg-meta/[ref]/materialized-views.ts @@ -1,9 +1,8 @@ import { NextApiRequest, NextApiResponse } from 'next' -import apiWrapper from 'lib/api/apiWrapper' -import { get } from 'lib/common/fetch' +import { fetchGet } from 'data/fetchers' import { constructHeaders } from 'lib/api/apiHelpers' -import { PG_META_URL } from 'lib/constants' +import apiWrapper from 'lib/api/apiWrapper' import { getPgMetaRedirectUrl } from './tables' export default (req: NextApiRequest, res: NextApiResponse) => @@ -14,7 +13,6 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { switch (method) { case 'GET': - if (req.query.id) return handleGetOne(req, res) return handleGetAll(req, res) default: res.setHeader('Allow', ['GET']) @@ -24,23 +22,12 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { const handleGetAll = async (req: NextApiRequest, res: NextApiResponse) => { const headers = constructHeaders(req.headers) - let response = await get(getPgMetaRedirectUrl(req, 'materialized-views'), { - headers, - }) - if (response.error) { - return res.status(400).json({ error: response.error }) - } + const response = await fetchGet(getPgMetaRedirectUrl(req, 'materialized-views'), { headers }) - return res.status(200).json(response) -} - -const handleGetOne = async (req: NextApiRequest, res: NextApiResponse) => { - const headers = constructHeaders(req.headers) - let response = await get(`${PG_META_URL}/materialized-views/${req.query.id}`, { - headers, - }) if (response.error) { - return res.status(400).json({ error: response.error }) + const { code, message } = response.error + return res.status(code).json({ message }) + } else { + return res.status(200).json(response) } - return res.status(200).json(response) } diff --git a/apps/studio/pages/api/platform/pg-meta/[ref]/policies.ts b/apps/studio/pages/api/platform/pg-meta/[ref]/policies.ts index 479c41efd5e2c..775b7cf36e2d9 100644 --- a/apps/studio/pages/api/platform/pg-meta/[ref]/policies.ts +++ b/apps/studio/pages/api/platform/pg-meta/[ref]/policies.ts @@ -1,8 +1,9 @@ import { NextApiRequest, NextApiResponse } from 'next' + +import { fetchGet } from 'data/fetchers' +import { constructHeaders } from 'lib/api/apiHelpers' import apiWrapper from 'lib/api/apiWrapper' -import { constructHeaders, toSnakeCase } from 'lib/api/apiHelpers' import { PG_META_URL } from 'lib/constants' -import { delete_, get, patch, post } from 'lib/common/fetch' export default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler, { withAuth: true }) @@ -13,67 +14,20 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { switch (method) { case 'GET': return handleGetAll(req, res) - case 'POST': - return handlePost(req, res) - case 'PATCH': - return handlePatch(req, res) - case 'DELETE': - return handleDelete(req, res) default: - res.setHeader('Allow', ['GET', 'POST', 'PATCH', 'DELETE']) + res.setHeader('Allow', ['GET']) res.status(405).json({ error: { message: `Method ${method} Not Allowed` } }) } } const handleGetAll = async (req: NextApiRequest, res: NextApiResponse) => { const headers = constructHeaders(req.headers) - let response = await get(`${PG_META_URL}/policies`, { - headers, - }) - if (response.error) { - return res.status(400).json({ error: response.error }) - } - return res.status(200).json(response) -} - -const handlePost = async (req: NextApiRequest, res: NextApiResponse) => { - const headers = constructHeaders(req.headers) - const payload = toSnakeCase(req.body) - const response = await post(`${PG_META_URL}/policies`, payload, { - headers, - }) + const response = await fetchGet(`${PG_META_URL}/policies`, { headers }) if (response.error) { - console.error('Policies POST:', response.error) - return res.status(400).json({ error: response.error }) + const { code, message } = response.error + return res.status(code).json({ message }) + } else { + return res.status(200).json(response) } - - return res.status(200).json(response) -} - -const handlePatch = async (req: NextApiRequest, res: NextApiResponse) => { - const headers = constructHeaders(req.headers) - const payload = toSnakeCase(req.body) - const response = await patch(`${PG_META_URL}/policies/${req.query.id}`, payload, { - headers, - }) - - if (response.error) { - console.error('Policies PATCH:', response) - return res.status(400).json({ error: response.error }) - } - - return res.status(200).json(response) -} - -const handleDelete = async (req: NextApiRequest, res: NextApiResponse) => { - const headers = constructHeaders(req.headers) - const response = await delete_(`${PG_META_URL}/policies/${req.query.id}`, {}, { headers }) - - if (response.error) { - console.error('Policies DELETE:', response.error) - return res.status(400).json({ error: response.error }) - } - - return res.status(200).json(response) } diff --git a/apps/studio/pages/api/platform/pg-meta/[ref]/publications.ts b/apps/studio/pages/api/platform/pg-meta/[ref]/publications.ts index 7f3a2bd1ac06e..fdb191442f76c 100644 --- a/apps/studio/pages/api/platform/pg-meta/[ref]/publications.ts +++ b/apps/studio/pages/api/platform/pg-meta/[ref]/publications.ts @@ -1,8 +1,9 @@ import { NextApiRequest, NextApiResponse } from 'next' + +import { fetchGet } from 'data/fetchers' +import { constructHeaders } from 'lib/api/apiHelpers' import apiWrapper from 'lib/api/apiWrapper' -import { constructHeaders, toSnakeCase } from 'lib/api/apiHelpers' import { PG_META_URL } from 'lib/constants' -import { delete_, get, patch, post } from 'lib/common/fetch' export default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler, { withAuth: true }) @@ -13,67 +14,20 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { switch (method) { case 'GET': return handleGetAll(req, res) - case 'POST': - return handlePost(req, res) - case 'PATCH': - return handlePatch(req, res) - case 'DELETE': - return handleDelete(req, res) default: - res.setHeader('Allow', ['GET', 'POST', 'PATCH', 'DELETE']) + res.setHeader('Allow', ['GET']) res.status(405).json({ error: { message: `Method ${method} Not Allowed` } }) } } const handleGetAll = async (req: NextApiRequest, res: NextApiResponse) => { const headers = constructHeaders(req.headers) - let response = await get(`${PG_META_URL}/publications`, { - headers, - }) - if (response.error) { - return res.status(400).json({ error: response.error }) - } - return res.status(200).json(response) -} - -const handlePost = async (req: NextApiRequest, res: NextApiResponse) => { - const headers = constructHeaders(req.headers) - const payload = toSnakeCase(req.body) - const response = await post(`${PG_META_URL}/publications`, payload, { - headers, - }) + const response = await fetchGet(`${PG_META_URL}/publications`, { headers }) if (response.error) { - console.error('Publication POST:', response.error) - return res.status(400).json({ error: response.error }) + const { code, message } = response.error + return res.status(code).json({ message }) + } else { + return res.status(200).json(response) } - - return res.status(200).json(response) -} - -const handlePatch = async (req: NextApiRequest, res: NextApiResponse) => { - const headers = constructHeaders(req.headers) - const payload = toSnakeCase(req.body) - const response = await patch(`${PG_META_URL}/publications/${req.query.id}`, payload, { - headers, - }) - - if (response.error) { - console.error('Publication PATCH:', response) - return res.status(400).json({ error: response.error }) - } - - return res.status(200).json(response) -} - -const handleDelete = async (req: NextApiRequest, res: NextApiResponse) => { - const headers = constructHeaders(req.headers) - const response = await delete_(`${PG_META_URL}/publications/${req.query.id}`, {}, { headers }) - - if (response.error) { - console.error('Publication DELETE:', response.error) - return res.status(400).json({ error: response.error }) - } - - return res.status(200).json(response) } diff --git a/apps/studio/pages/api/platform/pg-meta/[ref]/query/format.ts b/apps/studio/pages/api/platform/pg-meta/[ref]/query/format.ts deleted file mode 100644 index 92a3cfe6ff874..0000000000000 --- a/apps/studio/pages/api/platform/pg-meta/[ref]/query/format.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { NextApiRequest, NextApiResponse } from 'next' -import apiWrapper from 'lib/api/apiWrapper' -import { constructHeaders } from 'lib/api/apiHelpers' -import { PG_META_URL } from 'lib/constants' -import { post } from 'lib/common/fetch' - -export default (req: NextApiRequest, res: NextApiResponse) => - apiWrapper(req, res, handler, { withAuth: true }) - -async function handler(req: NextApiRequest, res: NextApiResponse) { - const { method } = req - - switch (method) { - case 'POST': - return handlePost(req, res) - default: - res.setHeader('Allow', ['POST']) - res.status(405).json({ error: { message: `Method ${method} Not Allowed` } }) - } -} - -const handlePost = async (req: NextApiRequest, res: NextApiResponse) => { - const { query } = req.body - const headers = constructHeaders(req.headers) - const response = await post(`${PG_META_URL}/query/format`, { query }, { headers }) - - if (response.error) { - return res.status(400).json({ error: response.error }) - } else { - return res.status(200).json(response) - } -} diff --git a/apps/studio/pages/api/platform/pg-meta/[ref]/query/index.ts b/apps/studio/pages/api/platform/pg-meta/[ref]/query/index.ts index fc76638b99f0a..3a88a32927d03 100644 --- a/apps/studio/pages/api/platform/pg-meta/[ref]/query/index.ts +++ b/apps/studio/pages/api/platform/pg-meta/[ref]/query/index.ts @@ -1,6 +1,6 @@ +import { fetchPost } from 'data/fetchers' import { constructHeaders } from 'lib/api/apiHelpers' import apiWrapper from 'lib/api/apiWrapper' -import { post } from 'lib/common/fetch' import { PG_META_URL } from 'lib/constants' import { NextApiRequest, NextApiResponse } from 'next' @@ -22,10 +22,11 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { const handlePost = async (req: NextApiRequest, res: NextApiResponse) => { const { query } = req.body const headers = constructHeaders(req.headers) - const response = await post(`${PG_META_URL}/query`, { query }, { headers }) + const response = await fetchPost(`${PG_META_URL}/query`, { query }, { headers }) if (response.error) { - return res.status(400).json(response.error) + const { code, message } = response.error + return res.status(code).json({ message, formattedError: message }) } else { return res.status(200).json(response) } diff --git a/apps/studio/pages/api/platform/pg-meta/[ref]/query/validate.ts b/apps/studio/pages/api/platform/pg-meta/[ref]/query/validate.ts deleted file mode 100644 index 4279f479ce59b..0000000000000 --- a/apps/studio/pages/api/platform/pg-meta/[ref]/query/validate.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { NextApiRequest, NextApiResponse } from 'next' -import apiWrapper from 'lib/api/apiWrapper' -import { constructHeaders } from 'lib/api/apiHelpers' -import { PG_META_URL } from 'lib/constants' -import { post } from 'lib/common/fetch' - -export default (req: NextApiRequest, res: NextApiResponse) => - apiWrapper(req, res, handler, { withAuth: true }) - -async function handler(req: NextApiRequest, res: NextApiResponse) { - const { method } = req - - switch (method) { - case 'POST': - return handlePost(req, res) - default: - res.setHeader('Allow', ['POST']) - res.status(405).json({ error: { message: `Method ${method} Not Allowed` } }) - } -} - -const handlePost = async (req: NextApiRequest, res: NextApiResponse) => { - const { query } = req.body - const headers = constructHeaders(req.headers) - const response = await post(`${PG_META_URL}/query/parse`, { query }, { headers }) - - if (response.error) { - return res.status(400).json({ valid: false, error: response.error }) - } else { - return res.status(200).json({ valid: true }) - } -} diff --git a/apps/studio/pages/api/platform/pg-meta/[ref]/tables.ts b/apps/studio/pages/api/platform/pg-meta/[ref]/tables.ts index d4db8c40067aa..2c9ecbd1bdded 100644 --- a/apps/studio/pages/api/platform/pg-meta/[ref]/tables.ts +++ b/apps/studio/pages/api/platform/pg-meta/[ref]/tables.ts @@ -1,8 +1,9 @@ import { NextApiRequest, NextApiResponse } from 'next' -import { PG_META_URL } from 'lib/constants' -import apiWrapper from 'lib/api/apiWrapper' + +import { fetchGet } from 'data/fetchers' import { constructHeaders } from 'lib/api/apiHelpers' -import { delete_, get, patch, post } from 'lib/common/fetch' +import apiWrapper from 'lib/api/apiWrapper' +import { PG_META_URL } from 'lib/constants' export default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler, { withAuth: true }) @@ -12,16 +13,9 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { switch (method) { case 'GET': - if (req.query.id) return handleGetOne(req, res) return handleGetAll(req, res) - case 'POST': - return handlePost(req, res) - case 'PATCH': - return handlePatch(req, res) - case 'DELETE': - return handleDelete(req, res) default: - res.setHeader('Allow', ['GET', 'POST', 'PATCH', 'DELETE']) + res.setHeader('Allow', ['GET']) res.status(405).json({ error: { message: `Method ${method} Not Allowed` } }) } } @@ -53,61 +47,12 @@ export function getPgMetaRedirectUrl(req: NextApiRequest, endpoint: string) { const handleGetAll = async (req: NextApiRequest, res: NextApiResponse) => { const headers = constructHeaders(req.headers) + const response = await fetchGet(getPgMetaRedirectUrl(req, 'tables'), { headers }) - const response = await get(getPgMetaRedirectUrl(req, 'tables'), { headers }) if (response.error) { - return res.status(400).json({ error: response.error }) + const { code, message } = response.error + return res.status(code).json({ message }) + } else { + return res.status(200).json(response) } - - return res.status(200).json(response) -} - -const handleGetOne = async (req: NextApiRequest, res: NextApiResponse) => { - const headers = constructHeaders(req.headers) - let response = await get(`${PG_META_URL}/tables/${req.query.id}`, { - headers, - }) - if (response.error) { - return res.status(400).json({ error: response.error }) - } - return res.status(200).json(response) -} - -const handlePost = async (req: NextApiRequest, res: NextApiResponse) => { - const headers = constructHeaders(req.headers) - const payload = req.body - const response = await post(`${PG_META_URL}/tables`, payload, { - headers, - }) - if (response.error) { - console.error('Table POST:', response.error) - return res.status(400).json({ error: response.error }) - } - - return res.status(200).json(response) -} - -const handlePatch = async (req: NextApiRequest, res: NextApiResponse) => { - const headers = constructHeaders(req.headers) - const payload = req.body - const response = await patch(`${PG_META_URL}/tables/${req.query.id}`, payload, { - headers, - }) - if (response.error) { - console.error('Table PATCH:', response.error) - return res.status(400).json({ error: response.error }) - } - - return res.status(200).json(response) -} - -const handleDelete = async (req: NextApiRequest, res: NextApiResponse) => { - const headers = constructHeaders(req.headers) - const response = await delete_(`${PG_META_URL}/tables/${req.query.id}`, {}, { headers }) - if (response.error) { - console.error('Table DELETE:', response.error) - return res.status(400).json({ error: response.error }) - } - - return res.status(200).json(response) } diff --git a/apps/studio/pages/api/platform/pg-meta/[ref]/triggers.ts b/apps/studio/pages/api/platform/pg-meta/[ref]/triggers.ts index ba71fcfb2bceb..fe4c1308a278e 100644 --- a/apps/studio/pages/api/platform/pg-meta/[ref]/triggers.ts +++ b/apps/studio/pages/api/platform/pg-meta/[ref]/triggers.ts @@ -1,7 +1,8 @@ import { NextApiRequest, NextApiResponse } from 'next' + +import { fetchGet } from 'data/fetchers' +import { constructHeaders } from 'lib/api/apiHelpers' import apiWrapper from 'lib/api/apiWrapper' -import { constructHeaders, toSnakeCase } from 'lib/api/apiHelpers' -import { delete_, get, patch, post } from 'lib/common/fetch' import { PG_META_URL } from 'lib/constants' export default (req: NextApiRequest, res: NextApiResponse) => @@ -13,67 +14,20 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { switch (method) { case 'GET': return handleGetAll(req, res) - case 'POST': - return handlePost(req, res) - case 'PATCH': - return handlePatch(req, res) - case 'DELETE': - return handleDelete(req, res) default: - res.setHeader('Allow', ['GET', 'POST', 'PATCH', 'DELETE']) + res.setHeader('Allow', ['GET']) res.status(405).json({ error: { message: `Method ${method} Not Allowed` } }) } } const handleGetAll = async (req: NextApiRequest, res: NextApiResponse) => { const headers = constructHeaders(req.headers) - let response = await get(`${PG_META_URL}/triggers`, { - headers, - }) - if (response.error) { - return res.status(400).json({ error: response.error }) - } - return res.status(200).json(response) -} - -const handlePost = async (req: NextApiRequest, res: NextApiResponse) => { - const headers = constructHeaders(req.headers) - const payload = toSnakeCase(req.body) - const response = await post(`${PG_META_URL}/triggers`, payload, { - headers, - }) + const response = await fetchGet(`${PG_META_URL}/triggers`, { headers }) if (response.error) { - console.error('Trigger POST:', response.error) - return res.status(400).json({ error: response.error }) + const { code, message } = response.error + return res.status(code).json({ message }) + } else { + return res.status(200).json(response) } - - return res.status(200).json(response) -} - -const handlePatch = async (req: NextApiRequest, res: NextApiResponse) => { - const headers = constructHeaders(req.headers) - const payload = toSnakeCase(req.body) - const response = await patch(`${PG_META_URL}/triggers/${req.query.id}`, payload, { - headers, - }) - - if (response.error) { - console.error('Trigger PATCH:', response) - return res.status(400).json({ error: response.error }) - } - - return res.status(200).json(response) -} - -const handleDelete = async (req: NextApiRequest, res: NextApiResponse) => { - const headers = constructHeaders(req.headers) - const response = await delete_(`${PG_META_URL}/triggers/${req.query.id}`, {}, { headers }) - - if (response.error) { - console.error('Trigger DELETE:', response.error) - return res.status(400).json({ error: response.error }) - } - - return res.status(200).json(response) } diff --git a/apps/studio/pages/api/platform/pg-meta/[ref]/types.ts b/apps/studio/pages/api/platform/pg-meta/[ref]/types.ts index 8adc18b77fc04..8293590f629c5 100644 --- a/apps/studio/pages/api/platform/pg-meta/[ref]/types.ts +++ b/apps/studio/pages/api/platform/pg-meta/[ref]/types.ts @@ -1,8 +1,9 @@ +import { NextApiRequest, NextApiResponse } from 'next' + +import { fetchGet } from 'data/fetchers' import { constructHeaders } from 'lib/api/apiHelpers' import apiWrapper from 'lib/api/apiWrapper' -import { get } from 'lib/common/fetch' import { PG_META_URL } from 'lib/constants' -import { NextApiRequest, NextApiResponse } from 'next' export default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler, { withAuth: true }) @@ -21,11 +22,12 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { const handleGetAll = async (req: NextApiRequest, res: NextApiResponse) => { const headers = constructHeaders(req.headers) - const response = await get(`${PG_META_URL}/types`, { headers }) + const response = await fetchGet(`${PG_META_URL}/types`, { headers }) if (response.error) { - return res.status(400).json({ error: response.error }) + const { code, message } = response.error + return res.status(code).json({ message }) + } else { + return res.status(200).json(response) } - - return res.status(200).json(response) } diff --git a/apps/studio/pages/api/platform/pg-meta/[ref]/views.ts b/apps/studio/pages/api/platform/pg-meta/[ref]/views.ts index 636dd93af9cb9..755ff6cf42c23 100644 --- a/apps/studio/pages/api/platform/pg-meta/[ref]/views.ts +++ b/apps/studio/pages/api/platform/pg-meta/[ref]/views.ts @@ -1,9 +1,8 @@ import { NextApiRequest, NextApiResponse } from 'next' -import apiWrapper from 'lib/api/apiWrapper' -import { get } from 'lib/common/fetch' +import { fetchGet } from 'data/fetchers' import { constructHeaders } from 'lib/api/apiHelpers' -import { PG_META_URL } from 'lib/constants' +import apiWrapper from 'lib/api/apiWrapper' import { getPgMetaRedirectUrl } from './tables' export default (req: NextApiRequest, res: NextApiResponse) => @@ -14,7 +13,6 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { switch (method) { case 'GET': - if (req.query.id) return handleGetOne(req, res) return handleGetAll(req, res) default: res.setHeader('Allow', ['GET']) @@ -24,23 +22,12 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { const handleGetAll = async (req: NextApiRequest, res: NextApiResponse) => { const headers = constructHeaders(req.headers) - let response = await get(getPgMetaRedirectUrl(req, 'views'), { - headers, - }) - if (response.error) { - return res.status(400).json({ error: response.error }) - } + const response = await fetchGet(getPgMetaRedirectUrl(req, 'views'), { headers }) - return res.status(200).json(response) -} - -const handleGetOne = async (req: NextApiRequest, res: NextApiResponse) => { - const headers = constructHeaders(req.headers) - let response = await get(`${PG_META_URL}/views/${req.query.id}`, { - headers, - }) if (response.error) { - return res.status(400).json({ error: response.error }) + const { code, message } = response.error + return res.status(code).json({ message }) + } else { + return res.status(200).json(response) } - return res.status(200).json(response) } diff --git a/apps/studio/pages/api/platform/projects/[ref]/run-lints.ts b/apps/studio/pages/api/platform/projects/[ref]/run-lints.ts index fff0ccbc5e80b..e58f7b326f781 100644 --- a/apps/studio/pages/api/platform/projects/[ref]/run-lints.ts +++ b/apps/studio/pages/api/platform/projects/[ref]/run-lints.ts @@ -1,9 +1,9 @@ import { NextApiRequest, NextApiResponse } from 'next' import { paths } from 'api-types' +import { fetchPost } from 'data/fetchers' import { constructHeaders } from 'lib/api/apiHelpers' import apiWrapper from 'lib/api/apiWrapper' -import { post } from 'lib/common/fetch' import { PG_META_URL } from 'lib/constants' export default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler) @@ -25,7 +25,11 @@ type ResponseData = const handleGet = async (req: NextApiRequest, res: NextApiResponse) => { const headers = constructHeaders(req.headers) - const response = await post(`${PG_META_URL}/query`, { query: enrichQuery(LINT_SQL) }, { headers }) + const response = await fetchPost( + `${PG_META_URL}/query`, + { query: enrichQuery(LINT_SQL) }, + { headers } + ) if (response.error) { return res.status(400).json(response.error) } else { diff --git a/apps/studio/pages/api/v1/projects/[ref]/types/typescript.ts b/apps/studio/pages/api/v1/projects/[ref]/types/typescript.ts index fa62b486eb3cb..904ea6598be82 100644 --- a/apps/studio/pages/api/v1/projects/[ref]/types/typescript.ts +++ b/apps/studio/pages/api/v1/projects/[ref]/types/typescript.ts @@ -1,8 +1,9 @@ import { NextApiRequest, NextApiResponse } from 'next' -import { PG_META_URL } from 'lib/constants' -import apiWrapper from 'lib/api/apiWrapper' + +import { fetchGet } from 'data/fetchers' import { constructHeaders } from 'lib/api/apiHelpers' -import { get } from 'lib/common/fetch' +import apiWrapper from 'lib/api/apiWrapper' +import { PG_META_URL } from 'lib/constants' export default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler, { withAuth: true }) @@ -40,14 +41,15 @@ const handleGetAll = async (req: NextApiRequest, res: NextApiResponse) => { const headers = constructHeaders(req.headers) - const response = await get( + const response = await fetchGet( `${PG_META_URL}/generators/typescript?included_schema=${includedSchema}&excluded_schemas=${excludedSchema}`, { headers } ) if (response.error) { - return res.status(400).json({ error: response.error }) + const { code, message } = response.error + return res.status(code).json({ message }) + } else { + return res.status(200).json(response) } - - return res.status(200).json({ types: response }) } diff --git a/apps/studio/vitest.config.ts b/apps/studio/vitest.config.ts index f2a848d55817a..9ed6851ec8e57 100644 --- a/apps/studio/vitest.config.ts +++ b/apps/studio/vitest.config.ts @@ -34,12 +34,7 @@ export default defineConfig({ reporters: [['default']], coverage: { reporter: ['lcov'], - exclude: [ - '**/*.test.ts', - '**/*.test.tsx', - // 👇 Excluded because it will be deprecated. - 'lib/common/fetch/**', - ], + exclude: ['**/*.test.ts', '**/*.test.tsx'], include: ['lib/**/*.ts'], }, }, diff --git a/packages/api-types/types/platform.d.ts b/packages/api-types/types/platform.d.ts index 090d53fbbd8e1..00a3b169b1aba 100644 --- a/packages/api-types/types/platform.d.ts +++ b/packages/api-types/types/platform.d.ts @@ -7835,7 +7835,6 @@ export interface components { organization_id: number region: string status: string - subscription_id: string } UpcomingInvoice: { amount_projected?: number @@ -11308,6 +11307,12 @@ export interface operations { 'application/json': components['schemas']['CustomerResponse'] } } + 403: { + headers: { + [name: string]: unknown + } + content?: never + } /** @description Failed to retrieve the Billing customer */ 500: { headers: { @@ -11422,6 +11427,12 @@ export interface operations { } content?: never } + 403: { + headers: { + [name: string]: unknown + } + content?: never + } /** @description Failed to get daily organization stats */ 500: { headers: { @@ -11453,6 +11464,12 @@ export interface operations { } content?: never } + 403: { + headers: { + [name: string]: unknown + } + content?: never + } /** @description Failed to get daily organization stats for compute */ 500: { headers: { @@ -11893,6 +11910,12 @@ export interface operations { 'application/json': components['schemas']['MemberWithFreeProjectLimit'][] } } + 403: { + headers: { + [name: string]: unknown + } + content?: never + } /** @description Failed to retrieve organization members who have reached their free project limit */ 500: { headers: { @@ -12541,6 +12564,12 @@ export interface operations { 'application/json': components['schemas']['OrgUsageResponse'] } } + 403: { + headers: { + [name: string]: unknown + } + content?: never + } /** @description Failed to get usage stats */ 500: { headers: { @@ -12606,6 +12635,7 @@ export interface operations { query?: never header: { 'x-connection-encrypted': string + 'x-pg-application-name': string } path: { /** @description Project ref */ @@ -12643,6 +12673,7 @@ export interface operations { query?: never header: { 'x-connection-encrypted': string + 'x-pg-application-name': string } path: { /** @description Project ref */ @@ -12684,6 +12715,7 @@ export interface operations { query?: never header: { 'x-connection-encrypted': string + 'x-pg-application-name': string } path: { /** @description Project ref */ @@ -12728,6 +12760,7 @@ export interface operations { } header: { 'x-connection-encrypted': string + 'x-pg-application-name': string } path: { /** @description Project ref */ @@ -12765,6 +12798,7 @@ export interface operations { query?: never header: { 'x-connection-encrypted': string + 'x-pg-application-name': string } path: { /** @description Project ref */ @@ -12810,6 +12844,7 @@ export interface operations { } header: { 'x-connection-encrypted': string + 'x-pg-application-name': string } path: { /** @description Project ref */ @@ -12849,6 +12884,7 @@ export interface operations { } header: { 'x-connection-encrypted': string + 'x-pg-application-name': string } path: { /** @description Project ref */ @@ -12890,6 +12926,7 @@ export interface operations { query?: never header: { 'x-connection-encrypted': string + 'x-pg-application-name': string } path: { /** @description Project ref */ @@ -12927,6 +12964,7 @@ export interface operations { query?: never header: { 'x-connection-encrypted': string + 'x-pg-application-name': string } path: { /** @description Project ref */ @@ -12970,6 +13008,7 @@ export interface operations { } header: { 'x-connection-encrypted': string + 'x-pg-application-name': string } path: { /** @description Project ref */ @@ -13012,6 +13051,7 @@ export interface operations { } header: { 'x-connection-encrypted': string + 'x-pg-application-name': string } path: { /** @description Project ref */ @@ -13052,6 +13092,7 @@ export interface operations { } header: { 'x-connection-encrypted': string + 'x-pg-application-name': string } path: { /** @description Project ref */ @@ -13089,6 +13130,7 @@ export interface operations { query?: never header: { 'x-connection-encrypted': string + 'x-pg-application-name': string } path: { /** @description Project ref */ @@ -13132,6 +13174,7 @@ export interface operations { } header: { 'x-connection-encrypted': string + 'x-pg-application-name': string } path: { /** @description Project ref */ @@ -13171,6 +13214,7 @@ export interface operations { } header: { 'x-connection-encrypted': string + 'x-pg-application-name': string } path: { /** @description Project ref */ @@ -13220,6 +13264,7 @@ export interface operations { } header: { 'x-connection-encrypted': string + 'x-pg-application-name': string } path: { /** @description Project ref */ @@ -13260,6 +13305,7 @@ export interface operations { } header: { 'x-connection-encrypted': string + 'x-pg-application-name': string } path: { /** @description Project ref */ @@ -13297,6 +13343,7 @@ export interface operations { query?: never header: { 'x-connection-encrypted': string + 'x-pg-application-name': string } path: { /** @description Project ref */ @@ -13340,6 +13387,7 @@ export interface operations { } header: { 'x-connection-encrypted': string + 'x-pg-application-name': string } path: { /** @description Project ref */ @@ -13379,6 +13427,7 @@ export interface operations { } header: { 'x-connection-encrypted': string + 'x-pg-application-name': string } path: { /** @description Project ref */ @@ -13420,6 +13469,7 @@ export interface operations { query?: never header: { 'x-connection-encrypted': string + 'x-pg-application-name': string } path: { /** @description Project ref */ @@ -13457,6 +13507,7 @@ export interface operations { query?: never header: { 'x-connection-encrypted': string + 'x-pg-application-name': string } path: { /** @description Project ref */ @@ -13500,6 +13551,7 @@ export interface operations { } header: { 'x-connection-encrypted': string + 'x-pg-application-name': string } path: { /** @description Project ref */ @@ -13539,6 +13591,7 @@ export interface operations { } header: { 'x-connection-encrypted': string + 'x-pg-application-name': string } path: { /** @description Project ref */ @@ -13580,6 +13633,7 @@ export interface operations { query?: never header: { 'x-connection-encrypted': string + 'x-pg-application-name': string } path: { /** @description Project ref */ @@ -13683,6 +13737,7 @@ export interface operations { query?: never header: { 'x-connection-encrypted': string + 'x-pg-application-name': string } path: { /** @description Project ref */ @@ -13720,6 +13775,7 @@ export interface operations { query?: never header: { 'x-connection-encrypted': string + 'x-pg-application-name': string } path: { /** @description Project ref */ @@ -13763,6 +13819,7 @@ export interface operations { } header: { 'x-connection-encrypted': string + 'x-pg-application-name': string } path: { /** @description Project ref */ @@ -13802,6 +13859,7 @@ export interface operations { } header: { 'x-connection-encrypted': string + 'x-pg-application-name': string } path: { /** @description Project ref */ @@ -13851,6 +13909,7 @@ export interface operations { } header: { 'x-connection-encrypted': string + 'x-pg-application-name': string } path: { /** @description Project ref */ @@ -13888,6 +13947,7 @@ export interface operations { query?: never header: { 'x-connection-encrypted': string + 'x-pg-application-name': string } path: { /** @description Project ref */ @@ -13933,6 +13993,7 @@ export interface operations { } header: { 'x-connection-encrypted': string + 'x-pg-application-name': string } path: { /** @description Project ref */ @@ -13972,6 +14033,7 @@ export interface operations { } header: { 'x-connection-encrypted': string + 'x-pg-application-name': string } path: { /** @description Project ref */ @@ -14016,6 +14078,7 @@ export interface operations { } header: { 'x-connection-encrypted': string + 'x-pg-application-name': string } path: { /** @description Project ref */ @@ -14053,6 +14116,7 @@ export interface operations { query?: never header: { 'x-connection-encrypted': string + 'x-pg-application-name': string } path: { /** @description Project ref */ @@ -14096,6 +14160,7 @@ export interface operations { } header: { 'x-connection-encrypted': string + 'x-pg-application-name': string } path: { /** @description Project ref */ @@ -14135,6 +14200,7 @@ export interface operations { } header: { 'x-connection-encrypted': string + 'x-pg-application-name': string } path: { /** @description Project ref */ @@ -14179,6 +14245,7 @@ export interface operations { } header: { 'x-connection-encrypted': string + 'x-pg-application-name': string } path: { /** @description Project ref */ @@ -14224,6 +14291,7 @@ export interface operations { } header: { 'x-connection-encrypted': string + 'x-pg-application-name': string } path: { /** @description Project ref */ @@ -16858,6 +16926,12 @@ export interface operations { } content?: never } + 403: { + headers: { + [name: string]: unknown + } + content?: never + } /** @description Failed to get daily project stats */ 500: { headers: { diff --git a/packages/common/consent-state.ts b/packages/common/consent-state.ts index 8575a76c8839c..23e1109dfbda7 100644 --- a/packages/common/consent-state.ts +++ b/packages/common/consent-state.ts @@ -61,10 +61,13 @@ export const consentState = proxy({ async function initUserCentrics() { if (process.env.NODE_ENV === 'test' || !IS_PLATFORM) return - // [Alaister] For local development, we accept all consent by default. - // If you need to test usercentrics locally, comment out the + // [Alaister] For local development and staging, we accept all consent by default. + // If you need to test usercentrics in these environments, comment out this // NEXT_PUBLIC_ENVIRONMENT check and add an ngrok domain to usercentrics - if (process.env.NEXT_PUBLIC_ENVIRONMENT === 'local') { + if ( + process.env.NEXT_PUBLIC_ENVIRONMENT === 'local' || + process.env.NEXT_PUBLIC_ENVIRONMENT === 'staging' + ) { consentState.hasConsented = true return } diff --git a/packages/pg-meta/src/constants.ts b/packages/pg-meta/src/constants.ts index c28ea770cc0f3..bb9513080a9e1 100644 --- a/packages/pg-meta/src/constants.ts +++ b/packages/pg-meta/src/constants.ts @@ -1 +1,2 @@ export const DEFAULT_SYSTEM_SCHEMAS = ['information_schema', 'pg_catalog', 'pg_toast'] +export const DEFAULT_PLATFORM_APPLICATION_NAME = 'supabase/dashboard'