295 lines · 11.6 KB
import { env } from "cloudflare:workers";
import { MAX_IN_LIST, all, batch, chunks, first, placeholders, run, stmt } from "../db";
import { accountDb, repoDb, stmt as scoped } from "../scoped-db";
import { HttpError } from "../errors";
import { randomId } from "../crypto";
import { hasPermission, loadRepoById, repoPermission } from "../repos";
import type { RepoRow, Viewer } from "~/lib/types";

/** Forks read LFS objects from their parents; bound the chain so lookups stay O(1) in D1 calls. */
const MAX_FORK_DEPTH = 3;
/** Unreferenced blobs survive this long, so an upload racing the sweeper never loses its data. */
const ORPHAN_GRACE_MS = 60 * 60 * 1000;
const SWEEP_BATCH = 100;

type ObjectRow = { repo_id: string; size: number };

type ChainRepo = Pick<RepoRow, "id" | "fork_of_id" | "owner_id" | "visibility">;

/**
 * Whether readers of `repo` may see objects stored in the private `ancestor`: only for a private fork,
 * and only while the fork's owner (the account whose collaborators can read it) can still read the
 * ancestor, so a removed collaborator's fork stops seeing new objects.
 */
async function ownerCanRead(repo: ChainRepo, ancestorId: string): Promise<boolean> {
  if (repo.visibility === "public") return false;
  const ancestor = await loadRepoById(ancestorId);
  if (!ancestor) return false;
  if (ancestor.owner_id === repo.owner_id) return true;
  const owner: Viewer = {
    id: repo.owner_id,
    name: "",
    displayName: null,
    email: "",
    isAdmin: false,
    emailVerified: true,
  };
  return hasPermission(await repoPermission(ancestor, owner), "read");
}

/**