42 lines · 2.1 KB
import { accountDb } from "./scoped-db";
import { forgetLocator, forgetScope } from "./scoped-db/locators";
import { dropAccountState } from "./account-scope";

/**
 * Removes the repository-scoped runners and pending runner registrations that `ownerId` holds for `repoId`. Run on
 * deletion and on transfer: a runner registered under the old owner must not keep claiming the repository's jobs
 * (and receiving the new owner's secrets) where the new owner can neither see nor revoke it.
 */
export async function dropRepoRunners(ownerId: string, repoId: string): Promise<void> {
  const owner = accountDb(ownerId);
  const registrations = await owner.all<{ token_hash: string }>(
    "DELETE FROM runner_registration_tokens WHERE repo_id = ? RETURNING token_hash",
    repoId,
  );
  const runners = await owner.all<{ id: string; token_hash: string; previous_token_hash: string | null }>(
    "DELETE FROM runners WHERE repo_id = ? RETURNING id, token_hash, previous_token_hash",
    repoId,
  );
  await Promise.all([
    ...registrations.map((row) => forgetLocator("runner_registration", row.token_hash)),
    ...runners.flatMap((runner) => [
      forgetLocator("runner", runner.id),
      forgetLocator("runner_token", runner.token_hash),
      ...(runner.previous_token_hash ? [forgetLocator("runner_token", runner.previous_token_hash)] : []),
    ]),
  ]);
}

/**
 * Account-side leftovers of a deleted repository: the owner's runners and runner registrations for it, the
 * repository's AI key (kept in the AccountState named by the repository id), and every locator pointing into it.
 * Other users' notifications and digest items about it are not chased through every account: reads filter rows
 * whose repository is gone and prune them (notification-store `filterReadable`), and token grants naming a
 * deleted repository id match nothing since ids are never reused.
 */
export async function purgeDeletedRepoFromAccounts(repo: { id: string; owner_id: string }): Promise<void> {
  await dropRepoRunners(repo.owner_id, repo.id);
  await dropAccountState(repo.id);
  await forgetScope(repo.id);