import { parseTopics } from "~/lib/topics";
import type { z } from "zod";
import { first } from "../db";
import { accountDb } from "../scoped-db";
import { badRequest, forbidden, HttpError, notFoundError, unauthorized } from "../errors";
import { publicUrl } from "../events/emit";
import { assertNotArchived, hasPermission, loadRepoFor } from "../repos";
import { scopedRepoPermission, tokenCredential } from "../token-scope";
import type { RequestCredential, TokenArea } from "../token-scope";
import { loadPull } from "../pulls";
import type { PullRow } from "../pulls";
import type { Permission, RepoRow, Viewer } from "~/lib/types";
export type ApiContext = { request: Request; url: URL; credential: RequestCredential; viewer: Viewer };
export function unprocessable(message: string): HttpError {
return new HttpError(422, message);
}
export const noContent = (): Response => new Response(null, { status: 204 });
/**
* Token-only: session cookies are never read, and Basic is refused because browsers replay cached Basic
* credentials cross-site. This is why /api/v1 can skip the Worker's same-origin check.
*/
export async function apiContext(request: Request, url: URL): Promise<ApiContext> {
const scheme = (request.headers.get("authorization") ?? "").split(" ", 1)[0].toLowerCase();
if (scheme !== "bearer" && scheme !== "token") {
throw unauthorized("A personal access token is required (Authorization: Bearer <token>)");
}
const credential = await tokenCredential(request);
if (!credential?.viewer) throw unauthorized("A valid personal access token is required");
return { request, url, credential, viewer: credential.viewer };
}
/** Account-level actions (creating repos, managing tokens) need an all-repositories token with admin:write. */
export function hasAccountScope(ctx: ApiContext): boolean {
const grant = ctx.credential.grant;
return grant?.kind === "fine" && grant.allRepos && grant.permissions.admin === "write";
}