import { first } from "../db"; import { forbidden, notFoundError } from "../errors"; import { assertNotArchived, canAdministerAccount, requireRepoAccess } from "../repos"; import type { Viewer } from "~/lib/types"; export type ScopeRef = { owner: string; repo: string | null }; export type ResolvedScope = { scopeType: "repo" | "org"; scopeId: string; label: string; settingsPath: string }; /** * Repo integrations need repo admin; org integrations need org ownership. `write` additionally refuses archived * repositories; reads and removals pass `"read"`. */ export async function requireIntegrationScope( ref: ScopeRef, viewer: Viewer | null, mode: "read" | "write" = "write", ): Promise { if (!viewer) throw forbidden("Sign in to manage integrations"); if (ref.repo) { const { repo } = await requireRepoAccess(ref.owner, ref.repo, viewer, "admin"); if (mode === "write") assertNotArchived(repo); return { scopeType: "repo", scopeId: repo.id, label: `${repo.owner_name}/${repo.name}`, settingsPath: `/${repo.owner_name}/${repo.name}/settings/integrations`, }; } const org = await first<{ id: string; name: string }>( "SELECT id, name FROM accounts WHERE name = ? AND kind = 'org'", ref.owner, ); if (!org) throw notFoundError("Organization not found"); if (!(await canAdministerAccount(org.id, viewer))) throw forbidden(); return { scopeType: "org", scopeId: org.id, label: org.name, settingsPath: `/orgs/${org.name}/integrations` }; }