import { first, run } from "./db"; import { notFoundError } from "./errors"; import { assertBelowCap, CAPS } from "./limits"; import { scheduleRevocationSweep, sweepRevokedRepoAccess } from "./pull-guard"; import { loadRepoById } from "./repos"; import { purgeFollowerIfRevoked } from "./access-revocation"; import type { RepoRow } from "~/lib/types"; export async function ownerAccount(name: string): Promise<{ id: string; name: string; kind: string }> { const account = await first<{ id: string; name: string; kind: string }>( "SELECT id, name, kind FROM accounts WHERE name = ?", name, ); if (!account) throw notFoundError("Owner not found"); return account; } export async function findCollaboratorUser(username: string): Promise<{ id: string }> { const user = await first<{ id: string }>( "SELECT a.id FROM accounts a JOIN users u ON u.account_id = a.id WHERE a.name = ?", username, ); if (!user) throw notFoundError("User not found"); return user; } /** Grants, changes or (with "none") removes a user's collaborator permission; callers check admin access. */ export async function setCollaborator( repo: Pick, username: string, permission: "read" | "write" | "admin" | "none", ): Promise { const user = await findCollaboratorUser(username); const existing = await first("SELECT 1 AS ok FROM collaborators WHERE repo_id = ? AND user_id = ?", repo.id, user.id); if (!existing && permission !== "none") { const count = await first<{ count: number }>( "SELECT COUNT(*) AS count FROM collaborators WHERE repo_id = ?", repo.id, ); assertBelowCap(count?.count ?? 0, CAPS.collaboratorsPerRepo, "collaborators"); } if (permission === "none") { await run("DELETE FROM collaborators WHERE repo_id = ? AND user_id = ?", repo.id, user.id); // GIT-A1 / GIT-A8: a removed reader's fork pull requests stop relaying and their private forks detach. scheduleRevocationSweep(async () => { const fresh = await loadRepoById(repo.id); if (fresh) await sweepRevokedRepoAccess(fresh, user.id); }); await purgeFollowerIfRevoked(repo.id, user.id); } else { await run( `INSERT INTO collaborators (repo_id, user_id, permission, created_at) VALUES (?, ?, ?, ?) ON CONFLICT (repo_id, user_id) DO UPDATE SET permission = excluded.permission`, repo.id, user.id, permission, Date.now(), ); } }