98 lines · 4.1 KB
import { all, chunks, placeholders } from "./db";
import { accountDb, repoDb, stmt } from "./scoped-db";
import { forgetLocator } from "./scoped-db/locators";
import { forEachScope } from "./account-scope";
import { backfillDone, backfillRepos } from "./backfill";

/**
 * Deploy tokens live in their repository's RepoState, but are revoked by who made them: the personal token that
 * minted them, or their creator on a password reset. The creator's AccountState keeps `deploy_token_index` so
 * those revocations visit only the repositories that can hold such a token, never every repository. The index
 * may hold stale rows (a token revoked another way); visiting one is a harmless no-op.
 */
export const DEPLOY_TOKEN_INDEX = "deploy-token-index";

export async function recordDeployToken(
  creatorId: string,
  token: { id: string; repoId: string; parentTokenId: string | null },
): Promise<void> {
  await accountDb(creatorId).run(
    "INSERT OR REPLACE INTO deploy_token_index (token_id, repo_id, parent_token_id, created_at) VALUES (?, ?, ?, ?)",
    token.id,
    token.repoId,
    token.parentTokenId,
    Date.now(),
  );
}

/** Repositories to visit: from the index once it covers every older token, else (until then) all of them. */
async function candidateRepos(userId: string, where: string, params: string[]): Promise<string[]> {
  if (!(await backfillDone(DEPLOY_TOKEN_INDEX))) {
    return (await all<{ id: string }>("SELECT id FROM repos")).map((row) => row.id);
  }
  const rows = await accountDb(userId).all<{ repo_id: string }>(
    `SELECT DISTINCT repo_id FROM deploy_token_index WHERE ${where}`,
    ...params,
  );
  return rows.map((row) => row.repo_id);
}

async function revokeIn(repoIds: string[], where: string, params: string[]): Promise<void> {