import { env } from "cloudflare:workers";
import { z } from "zod";
import { all, first } from "./db";
import { accountDb, repoDb } from "./scoped-db";
import { forEachScope } from "./account-scope";
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;
}
/** Per-repository queries for authored content (`?1` the user, `?2` the repository), run in its RepoState. */
const AUTHORED_SQL = {
issues: `SELECT i.number, i.is_pull, i.title, i.body, i.state, i.created_at, i.updated_at FROM issues i
WHERE i.author_id = ?1 AND i.repo_id = ?2 ORDER BY i.created_at`,
comments: `SELECT i.number, c.body, c.created_at, c.updated_at FROM comments c JOIN issues i ON i.id = c.issue_id