Something went wrong. Try again.
Monorepo for Tangled
Something went wrong. Try again.
TSX
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127import { 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<Response> { 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 = <RepositoryCard {...payload} />; break; case "issue": component = <IssueCard {...payload} />; break; case "pullRequest": component = <PullRequestCard {...payload} />; 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" }, }, ); } },};