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(url: string, responseJson = true): Promise> { 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 { 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(supabaseFolderUrl), fetchGitHub(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(file.download_url, false) ) const [seedFileResponse, ...migrationFileResponses] = await Promise.all([ seedFileUrl ? fetchGitHub(seedFileUrl, false) : Promise.resolve(''), ...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 type GitHubIntegration = Extract 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}` }