import { env } from "cloudflare:workers";
import { z } from "zod";
import { all, first } from "./db";
import { randomId } from "./crypto";
import { notFoundError, HttpError } from "./errors";
import { repoReadableSql } from "./repos";
import { listSigningKeys } from "./security/signatures";
import type { Viewer } from "~/lib/types";
/** One archive per user per day; the archive itself lives in KV for the same window and then expires. */
const EXPORT_TTL_SECONDS = 24 * 60 * 60;
/** Authored issues/comments per kind; a busier account gets a truncated flag rather than a timeout. */
export const EXPORT_ROW_CAP = 5000;
const MAX_EXPORT_BYTES = 20 * 1024 * 1024;
const HTTP_TOO_MANY = 429;
const exportMeta = z.object({ id: z.string(), createdAt: z.number(), bytes: z.number() });
export type ExportMeta = z.infer<typeof exportMeta> & { expiresAt: number };
const metaKey = (userId: string) => `account-export:${userId}`;
const dataKey = (userId: string, id: string) => `account-export:${userId}:${id}`;
function withExpiry(meta: z.infer<typeof exportMeta>): ExportMeta {
return { ...meta, expiresAt: meta.createdAt + EXPORT_TTL_SECONDS * 1000 };
}
export async function currentExport(userId: string): Promise<ExportMeta | null> {
const parsed = exportMeta.safeParse(await env.KV.get(metaKey(userId), "json"));
if (!parsed.success) return null;
const meta = withExpiry(parsed.data);
return meta.expiresAt > Date.now() ? meta : null;
}
function capped<T>(rows: T[]): { items: T[]; truncated: boolean } {
return { items: rows.slice(0, EXPORT_ROW_CAP), truncated: rows.length > EXPORT_ROW_CAP };
}
/**
* The personal-data archive behind Settings → Export: profile, emails, public key material, token metadata
* (never hashes), authored issues and comments on repositories the user can still read, stars, follows,