import { waitUntil } from "cloudflare:workers";
import { all, batch, stmt } from "./db";
import { loadUserById, toViewer } from "./auth";
import { pullHeadAccessible } from "./git/bridge-policy";
import { dropHiddenRefsForRepo, dropPullRefsForIssue, requestHiddenRefCleanup } from "./git/pull-bridge";
import { CAPS } from "./limits";
import { addIssueEvent } from "./pulls";
import type { PullRow } from "./pulls";
import { hasPermission, loadRepoById, repoPermission } from "./repos";
import type { RepoRow, Viewer } from "~/lib/types";
type GuardedPull = Pick<PullRow, "issue_id" | "repo_id" | "head_repo_id" | "author_id" | "state">;
/** Closes a pull request because its author can no longer read one of its repositories. */
async function closeForRevokedAccess(pull: GuardedPull): Promise<void> {
const now = Date.now();
await batch([
stmt(
"UPDATE issues SET state = 'closed', closed_at = ?, closed_by = NULL, updated_at = ? WHERE id = ? AND state = 'open'",
now,
now,
pull.issue_id,
),
stmt("UPDATE repos SET open_pulls_count = MAX(open_pulls_count - 1, 0) WHERE id = ?", pull.repo_id),
]);
await addIssueEvent(pull.issue_id, null, "closed", { reason: "access_revoked" });
waitUntil(dropPullRefsForIssue(pull.issue_id));
}
/**
* Whether a fork pull request may still synchronize, bridge or run CI: the author must currently read
* both repositories and the head must be a live fork of the base. When that stopped being true the pull
* request is closed and its hidden refs dropped, so revoked readers stop receiving (and feeding) history.
*/
export async function ensurePullHeadAccessible(pull: GuardedPull): Promise<boolean> {
if (pull.repo_id === pull.head_repo_id) return true;
const [baseRepo, headRepo] = await Promise.all([loadRepoById(pull.repo_id), loadRepoById(pull.head_repo_id)]);
if (!baseRepo || !headRepo) return false;
if (await pullHeadAccessible(pull, baseRepo, headRepo)) return true;
if (pull.state === "open") await closeForRevokedAccess(pull);