integration-utils.ts 4.6 KB

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