Files
supabase/apps/studio/lib/integration-utils.ts

135 lines
4.6 KiB
TypeScript

import { getCreateMigrationsTableSQL, getInsertMigrationSQL } from '@supabase/pg-meta'
import { isResponseOk } from './api/apiWrapper'
import { fetchHandler } from '@/data/fetchers'
import type { Integration } from '@/data/integrations/integrations.types'
import { ResponseError, type SupaResponse } from '@/types'
async function fetchGitHub<T = any>(url: string, responseJson = true): Promise<SupaResponse<T>> {
const response = await fetchHandler(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 createMigrationsTableSql = getCreateMigrationsTableSQL()
const migrationsTableSql = `
${createMigrationsTableSql}
${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 getInsertMigrationSQL({ name: file.name, version, statements })
})
.join('')}
`
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}`
}