From 1c63f68c0b50224578a8ff001cffdf69fe884a91 Mon Sep 17 00:00:00 2001 From: Peter Meier Date: Wed, 10 Jun 2026 09:24:41 +0200 Subject: [PATCH] feat(social): add post image thumbnail to OG social card Ports the same loadPostImage + thumbBlock pattern from the calendar social route. Reads postImage.src (fallback: image.src), loads via CMS transform API as base64 JPEG, renders next to QR code. Posts without an image are unaffected. Co-Authored-By: Claude Sonnet 4.6 --- src/routes/api/social/post/[slug]/+server.ts | 47 +++++++++++++++++--- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/src/routes/api/social/post/[slug]/+server.ts b/src/routes/api/social/post/[slug]/+server.ts index d4b1b61..b5bbdfe 100644 --- a/src/routes/api/social/post/[slug]/+server.ts +++ b/src/routes/api/social/post/[slug]/+server.ts @@ -98,7 +98,7 @@ function formatDateLong(iso: string | undefined | null): string { const cache = new Map(); const CACHE_TTL = 10 * 60_000; -const CACHE_VERSION = "v1"; +const CACHE_VERSION = "v2"; function parseFormat(v: string | null): Format { if (v === "square") return "square"; @@ -111,6 +111,24 @@ function parseTheme(v: string | null): Theme { return "wald"; } +async function loadPostImage( + imageSrc: string | undefined, + w: number, + h: number, +): Promise { + if (!imageSrc) return null; + try { + const cmsBase = (env.PUBLIC_CMS_URL || "http://localhost:3000").replace(/\/$/, ""); + const transformUrl = `${cmsBase}/api/transform?url=${encodeURIComponent(imageSrc)}&w=${w}&h=${h}&fit=inside&format=jpeg&quality=80`; + const res = await fetch(transformUrl); + if (!res.ok) return null; + const buf = Buffer.from(await res.arrayBuffer()); + return `data:image/jpeg;base64,${buf.toString("base64")}`; + } catch { + return null; + } +} + function escapeHtml(s: string): string { return s.replace(//g, "›"); } @@ -150,12 +168,24 @@ export const GET: RequestHandler = async ({ params, url, setHeaders }) => { tags?: unknown; created?: string; updated?: string; + postImage?: { src?: string } | string; + image?: { src?: string } | string; }; const { w, h } = SIZES[formatParam]; const fonts = await loadFonts(); const logoSvg = await loadLogo(); + const rawImageSrc = post.postImage ?? post.image; + const imageSrc = + typeof rawImageSrc === "string" + ? rawImageSrc + : rawImageSrc && typeof rawImageSrc === "object" + ? (rawImageSrc as { src?: string }).src + : undefined; + const thumbSize = formatParam === "story" ? 280 : 180; + const postThumb = imageSrc ? await loadPostImage(imageSrc, thumbSize * 2, thumbSize * 2) : null; + const headline = (post.headline ?? post.linkName ?? "").trim(); const subheadline = (post.subheadline ?? "").trim(); const excerpt = (post.excerpt ?? "").trim(); @@ -302,12 +332,19 @@ export const GET: RequestHandler = async ({ params, url, setHeaders }) => { `; const qrAlign = verticalStack ? "flex-start" : "center"; + const thumbMax = qrSize + 28; + const thumbBlock = postThumb + ? `
` + : ""; const qrBlock = ` -
-
- +
+
+
+ +
+
Beitrag lesen
-
Beitrag lesen
+ ${thumbBlock}
`;