import { env } from "cloudflare:workers";
import { registerLocator } from "../scoped-db/locators";
import { registerSite, siteDb } from "./store";
import { hmacSha256Hex, randomId, randomToken, sha256Hex } from "../crypto";
import { HttpError, badRequest } from "../errors";
import { enforceRateLimit, numberVar } from "../limits";
import { DeployError } from "./collect";
import { cookieSecret, labelUrl, SCRATCH_LIMITS } from "./config";
import { activateSiteDeployment, failSiteDeployment, insertDeployment } from "./deploy";
import { scratchLabel } from "./naming";
import { ipBucket } from "~/lib/ip";
import type { CollectedFiles } from "./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). IPv6 is bucketed to
* its /64 here too, so a caller passing a raw address can't rotate past the cap.
*/
export async function assertScratchQuota(ip: string): Promise<string> {
const bucket = ipBucket(ip);
await enforceRateLimit("HEAVY_LIMITER", `scratch:${bucket}`);
const creator = await ipHash(bucket);
// Scratch sites live in separate objects, so the per-IP count is a KV counter per UTC day.