mirror of
https://github.com/supabase/supabase.git
synced 2026-05-28 00:16:46 +08:00
* fix: response error codes * upgrade docs * remove request url modification middleware * move api routes for self-hosted to platform folder * remove some lib/common/fetch usage * docs: use middleware for openapi-fetch (#30600) Get rid of the unauthedAllowedPost function (I don't think there's any harm in letting any requests that require authentication to just 403, they should be disabled at the React Query level and if not they will fail gracefully enough...) * fix local count query * add default values for clone mutation * fix ts and codegen * add missing lodash dep to playwright tests * Fix the playwright tests to match the new folder structure for selfhosted variant. * remove unused import * Remove unused state * remove unused sql debug mutation * remove unused export * fix notifications query * fix jwt updating status * fix typescript * save sql snippet after renaming * update codegen & fix ts error * override array querySerializer --------- Co-authored-by: Charis <26616127+charislam@users.noreply.github.com> Co-authored-by: Joshen Lim <joshenlimek@gmail.com> Co-authored-by: Ivan Vasilov <vasilov.ivan@gmail.com>
138 lines
4.8 KiB
TypeScript
138 lines
4.8 KiB
TypeScript
import type { Integration } from 'data/integrations/integrations.types'
|
|
import { ResponseError, type SupaResponse } from 'types'
|
|
import { isResponseOk } from './common/fetch'
|
|
|
|
async function fetchGitHub<T = any>(url: string, responseJson = true): Promise<SupaResponse<T>> {
|
|
const response = await fetch(url)
|
|
if (!response.ok) {
|
|
return {
|
|
error: new ResponseError(response.statusText, response.status),
|
|
}
|
|
}
|
|
try {
|
|
return (responseJson ? await response.json() : await response.text()) as T
|
|
} catch (error: any) {
|
|
return {
|
|
error: new ResponseError(error.message, 500),
|
|
}
|
|
}
|
|
}
|
|
|
|
export type File = {
|
|
name: string
|
|
download_url: string
|
|
}
|
|
|
|
/**
|
|
* Returns the initial migration SQL from a GitHub repo.
|
|
* @param externalId An external GitHub URL for example: https://github.com/vercel/next.js/tree/canary/examples/with-supabase
|
|
*/
|
|
export async function getInitialMigrationSQLFromGitHubRepo(
|
|
externalId?: string
|
|
): Promise<string | null> {
|
|
if (!externalId) return null
|
|
|
|
const [, , , owner, repo, , branch, ...pathSegments] = externalId?.split('/') ?? []
|
|
const path = pathSegments.join('/')
|
|
|
|
const baseGitHubUrl = `https://api.github.com/repos/${owner}/${repo}/contents/${path}`
|
|
const supabaseFolderUrl = `${baseGitHubUrl}/supabase?ref=${branch}`
|
|
const supabaseMigrationsPath = `supabase/migrations` // TODO: read this from the `supabase/config.toml` file
|
|
const migrationsFolderUrl = `${baseGitHubUrl}/${supabaseMigrationsPath}${
|
|
branch ? `?ref=${branch}` : ``
|
|
}`
|
|
|
|
const [supabaseFilesResponse, migrationFilesResponse] = await Promise.all([
|
|
fetchGitHub<File[]>(supabaseFolderUrl),
|
|
fetchGitHub<File[]>(migrationsFolderUrl),
|
|
])
|
|
|
|
if (!isResponseOk(supabaseFilesResponse)) {
|
|
console.warn(`Failed to fetch supabase files from GitHub: ${supabaseFilesResponse.error}`)
|
|
return null
|
|
}
|
|
if (!isResponseOk(migrationFilesResponse)) {
|
|
console.warn(`Failed to fetch migration files from GitHub: ${migrationFilesResponse.error}`)
|
|
return null
|
|
}
|
|
|
|
const seedFileUrl = supabaseFilesResponse.find((file) => file.name === 'seed.sql')?.download_url
|
|
const sortedFiles = migrationFilesResponse.sort((a, b) => {
|
|
// sort by name ascending
|
|
if (a.name < b.name) return -1
|
|
if (a.name > b.name) return 1
|
|
return 0
|
|
})
|
|
const migrationFileDownloadUrlPromises = sortedFiles.map((file) =>
|
|
fetchGitHub<string>(file.download_url, false)
|
|
)
|
|
|
|
const [seedFileResponse, ...migrationFileResponses] = await Promise.all([
|
|
seedFileUrl ? fetchGitHub<string>(seedFileUrl, false) : Promise.resolve<string>(''),
|
|
...migrationFileDownloadUrlPromises,
|
|
])
|
|
|
|
const migrations = migrationFileResponses.filter((response) => isResponseOk(response)).join(';')
|
|
const seed = isResponseOk(seedFileResponse) ? seedFileResponse : ''
|
|
|
|
const migrationsTableSql = /* SQL */ `
|
|
create schema if not exists supabase_migrations;
|
|
create table if not exists supabase_migrations.schema_migrations (
|
|
version text not null primary key,
|
|
statements text[],
|
|
name text
|
|
);
|
|
${sortedFiles.map((file, i) => {
|
|
const migration = migrationFileResponses[i]
|
|
if (!isResponseOk(migration)) return ''
|
|
|
|
const version = file.name.split('_')[0]
|
|
const statements = JSON.stringify(
|
|
migration
|
|
.split(';')
|
|
.map((statement) => statement.trim())
|
|
.filter(Boolean)
|
|
)
|
|
|
|
return /* SQL */ `
|
|
insert into supabase_migrations.schema_migrations (version, statements, name)
|
|
select '${version}', array_agg(jsonb_statements)::text[], '${file.name}'
|
|
from jsonb_array_elements_text($statements$${statements}$statements$::jsonb) as jsonb_statements;
|
|
`
|
|
})}
|
|
`
|
|
|
|
return `${migrations};${migrationsTableSql};${seed}`
|
|
}
|
|
|
|
type VercelIntegration = Extract<Integration, { integration: { name: 'Vercel' } }>
|
|
type GitHubIntegration = Extract<Integration, { integration: { name: 'GitHub' } }>
|
|
|
|
export function getIntegrationConfigurationUrl(integration: Integration) {
|
|
if (integration.integration.name === 'Vercel') {
|
|
return getVercelConfigurationUrl(integration as VercelIntegration)
|
|
}
|
|
|
|
if (integration.integration.name === 'GitHub') {
|
|
return getGitHubConfigurationUrl(integration as GitHubIntegration)
|
|
}
|
|
|
|
return ''
|
|
}
|
|
|
|
function getVercelConfigurationUrl(integration: VercelIntegration) {
|
|
return `https://vercel.com/dashboard/${
|
|
integration.metadata?.account.type === 'Team'
|
|
? `${integration.metadata?.account.team_slug}/`
|
|
: ''
|
|
}integrations/${integration.metadata?.configuration_id}`
|
|
}
|
|
|
|
function getGitHubConfigurationUrl(integration: GitHubIntegration) {
|
|
return `https://github.com/${
|
|
integration.metadata?.account.type === 'Organization'
|
|
? `organizations/${integration.metadata?.account.name}/`
|
|
: ''
|
|
}settings/installations/${integration.metadata?.installation_id}`
|
|
}
|