import { env } from "cloudflare:workers";
import { first, run } from "../db";
import { hmacSha256Hex, randomId, randomToken, sha256Hex } from "../crypto";
import { HttpError, badRequest } from "../errors";
import { enforceRateLimit, numberVar } from "../limits";
import { DeployError } from "../pages/collect";
import { cookieSecret, labelUrl, SCRATCH_LIMITS } from "./config";
import { activateSiteDeployment, failSiteDeployment, insertDeployment, loadSiteById } from "./deploy";
import { scratchLabel } from "./naming";
import type { CollectedFiles } from "../pages/collect";
import type { SiteRow } from "./config";
const DAY = 24 * 60 * 60 * 1000;
const HTTP_TOO_MANY = 429;
export const CLAIM_PREFIX = "gitfor_claim";
export type ScratchResult = {
url: string | null;
claimToken: string;
claimUrl: string;
expiresAt: number;
repo?: string;
};
/** Keyed so stored hashes can't be reversed by enumerating the IPv4 space. */
export function ipHash(ip: string): Promise<string> {
return hmacSha256Hex(cookieSecret(), `site-ip:${ip}`);
}
/** Rate limit and per-IP daily cap shared by every scratch entry point (tarball, files, git). */
async function assertScratchQuota(ip: string): Promise<string> {
await enforceRateLimit("HEAVY_LIMITER", `scratch:${ip}`);
const creator = await ipHash(ip);
const recent = await first<{ count: number }>(
"SELECT COUNT(*) AS count FROM sites WHERE creator_ip_hash = ? AND created_at > ?",
creator,
Date.now() - DAY,
);
const perDay = numberVar(env.SCRATCH_SITES_PER_IP_PER_DAY, SCRATCH_LIMITS.perIpPerDay);
if ((recent?.count ?? 0) >= perDay) throw new HttpError(HTTP_TOO_MANY, `At most ${perDay} scratch sites per day`);