74 lines · 3.0 KB
import { all, placeholders } from "../db";
import { repoDb } from "../scoped-db";
import { pullBaseRepoIds } from "../pull-scope";

/** Most bytes one account's fork pull requests may hold bridged into other repos at once. */
export const MAX_OPEN_BRIDGED_BYTES = 1024 * 1024 * 1024;

export type BridgeCharge = { headOwnerId: string; baseRepoId: string; number: number };

/**
 * The bytes still pinned by bridges of pull requests whose head repo `ownerId` owns. Those pull requests live
 * in the parents of the owner's forks, so this asks each parent about the owner's forks of it.
 */
export async function openBridgedBytes(ownerId: string): Promise<number> {
  const forks = await all<{ id: string; fork_of_id: string }>(
    "SELECT id, fork_of_id FROM repos WHERE owner_id = ? AND fork_of_id IS NOT NULL",
    ownerId,
  );
  const byParent = new Map<string, string[]>();
  for (const fork of forks) byParent.set(fork.fork_of_id, [...(byParent.get(fork.fork_of_id) ?? []), fork.id]);
  const totals = await Promise.all(
    [...byParent].map(async ([parentId, forkIds]) => {
      const row = await repoDb(parentId).first<{ total: number | null }>(
        `SELECT SUM(bridged_bytes) AS total FROM pull_requests
         WHERE head_repo_id IN (${placeholders(forkIds.length)}) AND bridged_bytes > 0`,
        ...forkIds,
      );
      return row?.total ?? 0;
    }),
  );
  return totals.reduce((sum, total) => sum + total, 0);
}

/** Whether the head owner may start another relay; bridges reusing objects the target already has stay free. */
export async function bridgeQuotaAvailable(charge: BridgeCharge): Promise<boolean> {
  return (await openBridgedBytes(charge.headOwnerId)) < MAX_OPEN_BRIDGED_BYTES;
}

export async function recordBridgedBytes(charge: BridgeCharge, bytes: number): Promise<void> {
  if (bytes <= 0) return;