37 lines · 1.5 KB
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<ResolvedScope> {
  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` };
}