import { cardPayloadSchema } from "./validation"; import { renderCard } from "./lib/render"; import { RepositoryCard } from "./components/cards/repository"; import { IssueCard } from "./components/cards/issue"; import { PullRequestCard } from "./components/cards/pull-request"; import { z } from "zod"; declare global { interface CacheStorage { default: Cache; } } interface Env { ENVIRONMENT: string; } export function decodePayload(raw: string): unknown { const trimmed = raw.trim(); if (trimmed.startsWith("{")) { return JSON.parse(trimmed); } const base64 = trimmed.replace(/-/g, "+").replace(/_/g, "/"); const pad = base64.length % 4; const padded = pad ? base64 + "=".repeat(4 - pad) : base64; const bin = atob(padded); const bytes = Uint8Array.from(bin, (c) => c.charCodeAt(0)); const jsonStr = new TextDecoder().decode(bytes); return JSON.parse(jsonStr); } export default { async fetch(request: Request, env: Env): Promise { const url = new URL(request.url); let rawPayload: unknown; if (request.method === "POST") { try { rawPayload = await request.json(); } catch { return new Response("Invalid JSON body", { status: 400 }); } } else if (request.method === "GET") { const param = url.searchParams.get("payload"); if (!param) { return new Response("Missing payload parameter", { status: 400 }); } try { rawPayload = decodePayload(param); } catch { return new Response("Invalid payload encoding", { status: 400 }); } } else { return new Response("Method not allowed", { status: 405 }); } try { const payload = cardPayloadSchema.parse(rawPayload); let component; switch (payload.type) { case "repository": component = ; break; case "issue": component = ; break; case "pullRequest": component = ; break; default: return new Response("Unknown card type", { status: 400 }); } const cacheKeyUrl = new URL(request.url); cacheKeyUrl.pathname = `/card/${payload.type}`; cacheKeyUrl.search = ""; cacheKeyUrl.searchParams.set("payload", JSON.stringify(payload)); const cacheKey = new Request(cacheKeyUrl.toString(), { method: "GET" }); const cache = typeof caches !== "undefined" ? caches.default : null; const cached = cache ? await cache.match(cacheKey) : null; if (cached) { return cached; } const { png: pngBuffer } = await renderCard(component); const response = new Response(pngBuffer as any, { headers: { "Content-Type": "image/png", "Cache-Control": "public, max-age=86400, stale-while-revalidate=604800", }, }); if (cache) { await cache.put(cacheKey, response.clone()); } return response; } catch (error) { if (error instanceof z.ZodError) { return new Response( JSON.stringify({ errors: (error as z.ZodError).issues }), { status: 400, headers: { "Content-Type": "application/json" }, }, ); } console.error("Error generating card:", error); const errorMessage = error instanceof Error ? error.message : String(error); return new Response( JSON.stringify({ error: errorMessage, }), { status: 500, headers: { "Content-Type": "application/json" }, }, ); } }, };