integration-utils.ts134 lines · main
| 1 | import { getCreateMigrationsTableSQL, getInsertMigrationSQL } from '@supabase/pg-meta' |
| 2 | |
| 3 | import { isResponseOk } from './api/apiWrapper' |
| 4 | import { fetchHandler } from '@/data/fetchers' |
| 5 | import type { Integration } from '@/data/integrations/integrations.types' |
| 6 | import { ResponseError, type SupaResponse } from '@/types' |
| 7 | |
| 8 | async function fetchGitHub<T = any>(url: string, responseJson = true): Promise<SupaResponse<T>> { |
| 9 | const response = await fetchHandler(url) |
| 10 | if (!response.ok) { |
| 11 | return { |
| 12 | error: new ResponseError(response.statusText, response.status), |
| 13 | } |
| 14 | } |
| 15 | try { |
| 16 | return (responseJson ? await response.json() : await response.text()) as T |
| 17 | } catch (error: any) { |
| 18 | return { |
| 19 | error: new ResponseError(error.message, 500), |
| 20 | } |
| 21 | } |
| 22 | } |
| 23 | |
| 24 | export type File = { |
| 25 | name: string |
| 26 | download_url: string |
| 27 | } |
| 28 | |
| 29 | /** |
| 30 | * Returns the initial migration SQL from a GitHub repo. |
| 31 | * @param externalId An external GitHub URL for example: https://github.com/vercel/next.js/tree/canary/examples/with-briven |
| 32 | */ |
| 33 | export async function getInitialMigrationSQLFromGitHubRepo( |
| 34 | externalId?: string |
| 35 | ): Promise<string | null> { |
| 36 | if (!externalId) return null |
| 37 | |
| 38 | const [, , , owner, repo, , branch, ...pathSegments] = externalId?.split('/') ?? [] |
| 39 | const path = pathSegments.join('/') |
| 40 | |
| 41 | const baseGitHubUrl = `https://api.github.com/repos/${owner}/${repo}/contents/${path}` |
| 42 | const brivenFolderUrl = `${baseGitHubUrl}/briven?ref=${branch}` |
| 43 | const brivenMigrationsPath = `briven/migrations` // TODO: read this from the `briven/config.toml` file |
| 44 | const migrationsFolderUrl = `${baseGitHubUrl}/${brivenMigrationsPath}${ |
| 45 | branch ? `?ref=${branch}` : `` |
| 46 | }` |
| 47 | |
| 48 | const [brivenFilesResponse, migrationFilesResponse] = await Promise.all([ |
| 49 | fetchGitHub<File[]>(brivenFolderUrl), |
| 50 | fetchGitHub<File[]>(migrationsFolderUrl), |
| 51 | ]) |
| 52 | |
| 53 | if (!isResponseOk(brivenFilesResponse)) { |
| 54 | console.warn(`Failed to fetch briven files from GitHub: ${brivenFilesResponse.error}`) |
| 55 | return null |
| 56 | } |
| 57 | if (!isResponseOk(migrationFilesResponse)) { |
| 58 | console.warn(`Failed to fetch migration files from GitHub: ${migrationFilesResponse.error}`) |
| 59 | return null |
| 60 | } |
| 61 | |
| 62 | const seedFileUrl = brivenFilesResponse.find((file) => file.name === 'seed.sql')?.download_url |
| 63 | const sortedFiles = migrationFilesResponse.sort((a, b) => { |
| 64 | // sort by name ascending |
| 65 | if (a.name < b.name) return -1 |
| 66 | if (a.name > b.name) return 1 |
| 67 | return 0 |
| 68 | }) |
| 69 | const migrationFileDownloadUrlPromises = sortedFiles.map((file) => |
| 70 | fetchGitHub<string>(file.download_url, false) |
| 71 | ) |
| 72 | |
| 73 | const [seedFileResponse, ...migrationFileResponses] = await Promise.all([ |
| 74 | seedFileUrl ? fetchGitHub<string>(seedFileUrl, false) : Promise.resolve<string>(''), |
| 75 | ...migrationFileDownloadUrlPromises, |
| 76 | ]) |
| 77 | |
| 78 | const migrations = migrationFileResponses.filter((response) => isResponseOk(response)).join(';') |
| 79 | const seed = isResponseOk(seedFileResponse) ? seedFileResponse : '' |
| 80 | |
| 81 | const createMigrationsTableSql = getCreateMigrationsTableSQL() |
| 82 | |
| 83 | const migrationsTableSql = ` |
| 84 | ${createMigrationsTableSql} |
| 85 | ${sortedFiles |
| 86 | .map((file, i) => { |
| 87 | const migration = migrationFileResponses[i] |
| 88 | if (!isResponseOk(migration)) return '' |
| 89 | |
| 90 | const version = file.name.split('_')[0] |
| 91 | const statements = JSON.stringify( |
| 92 | migration |
| 93 | .split(';') |
| 94 | .map((statement) => statement.trim()) |
| 95 | .filter(Boolean) |
| 96 | ) |
| 97 | return getInsertMigrationSQL({ name: file.name, version, statements }) |
| 98 | }) |
| 99 | .join('')} |
| 100 | ` |
| 101 | |
| 102 | return `${migrations};${migrationsTableSql};${seed}` |
| 103 | } |
| 104 | |
| 105 | type VercelIntegration = Extract<Integration, { integration: { name: 'Vercel' } }> |
| 106 | type GitHubIntegration = Extract<Integration, { integration: { name: 'GitHub' } }> |
| 107 | |
| 108 | export function getIntegrationConfigurationUrl(integration: Integration) { |
| 109 | if (integration.integration.name === 'Vercel') { |
| 110 | return getVercelConfigurationUrl(integration as VercelIntegration) |
| 111 | } |
| 112 | |
| 113 | if (integration.integration.name === 'GitHub') { |
| 114 | return getGitHubConfigurationUrl(integration as GitHubIntegration) |
| 115 | } |
| 116 | |
| 117 | return '' |
| 118 | } |
| 119 | |
| 120 | function getVercelConfigurationUrl(integration: VercelIntegration) { |
| 121 | return `https://vercel.com/dashboard/${ |
| 122 | integration.metadata?.account.type === 'Team' |
| 123 | ? `${integration.metadata?.account.team_slug}/` |
| 124 | : '' |
| 125 | }integrations/${integration.metadata?.configuration_id}` |
| 126 | } |
| 127 | |
| 128 | function getGitHubConfigurationUrl(integration: GitHubIntegration) { |
| 129 | return `https://github.com/${ |
| 130 | integration.metadata?.account.type === 'Organization' |
| 131 | ? `organizations/${integration.metadata?.account.name}/` |
| 132 | : '' |
| 133 | }settings/installations/${integration.metadata?.installation_id}` |
| 134 | } |