integration-utils.ts134 lines · main
1import { getCreateMigrationsTableSQL, getInsertMigrationSQL } from '@supabase/pg-meta'
2
3import { isResponseOk } from './api/apiWrapper'
4import { fetchHandler } from '@/data/fetchers'
5import type { Integration } from '@/data/integrations/integrations.types'
6import { ResponseError, type SupaResponse } from '@/types'
7
8async 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
24export 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 */
33export 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
105type VercelIntegration = Extract<Integration, { integration: { name: 'Vercel' } }>
106type GitHubIntegration = Extract<Integration, { integration: { name: 'GitHub' } }>
107
108export 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
120function 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
128function 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}