From 4cdb3bfd4e927cb89d1df7192ccf68d4de001afe Mon Sep 17 00:00:00 2001 From: Innei Date: Thu, 6 Nov 2025 21:33:30 +0800 Subject: [PATCH] feat(static-web): implement static OG image generation service and add font support - Updated Dockerfile to use Alpine variant for Node.js and added Perl installation. - Introduced StaticOgService for generating Open Graph images with customizable templates. - Added Geist-Medium font for rendering OG images. - Updated TypeScript configuration to include JSX support and adjusted paths. - Created type declarations for assets and Vercel OG module. - Added new static assets for OG image generation. Signed-off-by: Innei --- be/apps/core/Dockerfile | 8 +- .../src/modules/static-web/Geist-Medium.ttf | Bin 0 -> 78324 bytes ...ic-og.service.ts => static-og.service.tsx} | 870 +++++++++--------- be/apps/core/src/types/assets.d.ts | 4 + be/apps/core/src/types/vercel-og.d.ts | 25 + be/apps/core/tsconfig.json | 9 +- 6 files changed, 467 insertions(+), 449 deletions(-) create mode 100644 be/apps/core/src/modules/static-web/Geist-Medium.ttf rename be/apps/core/src/modules/static-web/{static-og.service.ts => static-og.service.tsx} (52%) create mode 100644 be/apps/core/src/types/assets.d.ts create mode 100644 be/apps/core/src/types/vercel-og.d.ts diff --git a/be/apps/core/Dockerfile b/be/apps/core/Dockerfile index c67a6ff6..1e99bbcc 100644 --- a/be/apps/core/Dockerfile +++ b/be/apps/core/Dockerfile @@ -1,8 +1,9 @@ # syntax=docker/dockerfile:1.7 -ARG NODE_VERSION=22.11.0 +ARG NODE_VERSION=lts +ARG NODE_VARIANT=alpine -FROM node:${NODE_VERSION}-slim AS builder +FROM node:${NODE_VERSION}-${NODE_VARIANT} AS builder ENV PNPM_HOME=/pnpm ENV PATH="$PNPM_HOME:$PATH" RUN corepack enable && corepack prepare pnpm@10.19.0 --activate @@ -27,10 +28,11 @@ RUN pnpm --filter core build RUN mkdir -p be/apps/core/dist/static/web && cp -r apps/web/dist/. be/apps/core/dist/static/web/ RUN mkdir -p be/apps/core/dist/static/dashboard && cp -r be/apps/dashboard/dist/. be/apps/core/dist/static/dashboard/ -FROM node:${NODE_VERSION}-slim AS runner +FROM node:${NODE_VERSION}-${NODE_VARIANT} AS runner ENV NODE_ENV=production WORKDIR /app +RUN apk add --no-cache perl COPY --from=builder /workspace/be/apps/core/dist ./dist COPY --from=builder /workspace/be/apps/core/drizzle ./drizzle diff --git a/be/apps/core/src/modules/static-web/Geist-Medium.ttf b/be/apps/core/src/modules/static-web/Geist-Medium.ttf new file mode 100644 index 0000000000000000000000000000000000000000..97712e55158b9d480bb143583e6b410fdcc6a34b GIT binary patch literal 78324 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 z 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 z≠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 -} - interface ExifInfo { focalLength?: string | null aperture?: string | null @@ -46,7 +45,7 @@ interface OgTemplateContext { @injectable() export class StaticOgService { private readonly logger = createLogger('StaticOgService') - private fontDataPromise?: Promise<{ geist: ArrayBuffer; sans: ArrayBuffer }> + private geistFontPromise?: Promise constructor(private readonly manifestService: StaticWebManifestService) {} @@ -59,14 +58,11 @@ export class StaticOgService { } try { - const [{ geist, sans }, template] = await Promise.all([ - this.loadFonts(), - this.buildTemplateContext(context, photo), - ]) + const [geist, template] = await Promise.all([this.loadGeistFont(), this.buildTemplateContext(context, photo)]) const element = this.renderTemplate(template) - return new ImageResponse(element as unknown as OgElement, { + return new ImageResponse(element, { width: OG_IMAGE_WIDTH, height: OG_IMAGE_HEIGHT, emoji: 'noto', @@ -77,12 +73,6 @@ export class StaticOgService { style: 'normal', weight: 400, }, - { - name: 'SF Pro Display', - data: sans, - style: 'normal', - weight: 400, - }, ], headers: { 'Cache-Control': 'public, max-age=31536000, stale-while-revalidate=31536000', @@ -118,19 +108,79 @@ export class StaticOgService { } } - private async loadFonts(): Promise<{ geist: ArrayBuffer; sans: ArrayBuffer }> { - if (!this.fontDataPromise) { - this.fontDataPromise = Promise.resolve({ - geist: this.bufferToArrayBuffer(geistFont), - sans: this.bufferToArrayBuffer(sansFont), - }) + private async loadGeistFont(): Promise { + if (!this.geistFontPromise) { + this.geistFontPromise = this.loadFontData(geistFont) } - return this.fontDataPromise + return this.geistFontPromise } - private bufferToArrayBuffer(buffer: Buffer): ArrayBuffer { - return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength) + private async loadFontData(assetPath: string): Promise { + if (/^https?:\/\//.test(assetPath)) { + const response = await fetch(assetPath) + if (!response.ok) { + throw new Error(`Failed to fetch font from ${assetPath}`) + } + + const arrayBuffer = await response.arrayBuffer() + return this.toArrayBuffer(arrayBuffer) + } + + if (assetPath.startsWith('data:')) { + const base64 = assetPath.split(',')[1] ?? '' + return this.toArrayBuffer(Buffer.from(base64, 'base64')) + } + + const url = assetPath.startsWith('file:') ? new URL(assetPath) : new URL(assetPath, import.meta.url) + + const buffer = await readFile(url) + return this.toArrayBuffer(buffer) + } + + private toArrayBuffer(data: ArrayBuffer | SharedArrayBuffer | Uint8Array): ArrayBuffer { + if (data instanceof ArrayBuffer) { + return data + } + + if (data instanceof SharedArrayBuffer) { + const copy = new Uint8Array(data.byteLength) + copy.set(new Uint8Array(data)) + return copy.buffer + } + + return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength) + } + + private arrayBufferToDataUri(buffer: ArrayBuffer, mimeType: string): string { + const base64 = Buffer.from(buffer).toString('base64') + return `data:${mimeType};base64,${base64}` + } + + private inferThumbnailMimeType(thumbnailUrl?: string): string { + if (!thumbnailUrl) { + return 'image/jpeg' + } + + const normalized = thumbnailUrl.toLowerCase() + + if (normalized.endsWith('.png')) { + return 'image/png' + } + + if (normalized.endsWith('.webp')) { + return 'image/webp' + } + + if (normalized.endsWith('.gif')) { + return 'image/gif' + } + + if (normalized.endsWith('.avif')) { + return 'image/avif' + } + + return 'image/jpeg' } private formatDate(photo: PhotoManifestItem): string { @@ -252,30 +302,9 @@ export class StaticOgService { throw new Error('Unable to load thumbnail image') } - private renderTemplate(context: OgTemplateContext): OgElement { + private renderTemplate(context: OgTemplateContext): JSX.Element { const { photo, formattedDate, tags, exifInfo, layout, thumbnailBuffer } = context - const h = ( - type: string, - props: Record | null, - ...children: Array - ): OgElement => { - const filteredChildren = children - .flat() - .filter((child): child is OgElement | string => child !== null && child !== undefined && child !== false) - - const normalizedProps: Record = { ...props } - - if (filteredChildren.length > 0) { - normalizedProps.children = filteredChildren.length === 1 ? filteredChildren[0] : filteredChildren - } - - return { - type, - props: normalizedProps, - } - } - const decorativeOverlays = [ { position: 'absolute', @@ -316,25 +345,22 @@ export class StaticOgService { 'linear-gradient(45deg, transparent 0%, rgba(255,255,255,0.02) 40%, rgba(255,255,255,0.05) 50%, rgba(255,255,255,0.02) 60%, transparent 100%)', transform: 'rotate(15deg)', }, - ].map((style) => h('div', { style }, null)) + ].map((style, index) =>
) - const filmHoles = Array.from({ length: 7 }, () => - h( - 'div', - { - style: { - width: '10px', - height: '10px', - background: 'radial-gradient(circle, #000 40%, #222 70%, #333 100%)', - borderRadius: '50%', - boxShadow: 'inset 0 1px 2px rgba(0,0,0,0.8)', - }, - }, - null, - ), - ) + const filmHoles = Array.from({ length: 7 }).map((_, index) => ( +
+ )) - const filmBorderStyles = [ + const filmBorderElements = [ { position: 'absolute', top: '30%', @@ -364,12 +390,11 @@ export class StaticOgService { border: '1px solid rgba(255,255,255,0.07)', borderRadius: '50%', }, - ].map((style) => h('div', { style }, null)) + ].map((style, index) =>
) - const apertureDecor = h( - 'div', - { - style: { + const apertureDecor = ( +
+
+
+
+
) - const tagElements = - tags.length > 0 - ? h( - 'div', - { - style: { - display: 'flex', - flexWrap: 'wrap', - gap: '16px', - margin: '0 0 32px 0', - }, - }, - ...tags.map((tag) => - h( - 'div', - { - style: { - fontSize: '26px', - color: 'rgba(255,255,255,0.9)', - backgroundColor: 'rgba(255,255,255,0.15)', - padding: '12px 20px', - borderRadius: '24px', - letterSpacing: '0.3px', - display: 'flex', - alignItems: 'center', - border: '1px solid rgba(255,255,255,0.2)', - backdropFilter: 'blur(8px)', - fontFamily: 'Geist, SF Pro Display', - }, - }, - `#${tag}`, - ), - ), - ) - : null + const tagElements = tags.slice(0, 3).map((tag) => ( +
+ #{tag} +
+ )) - const leftFilmColumn = h( - 'div', - { - style: { + const exifBadges: JSX.Element[] = [] + if (exifInfo?.aperture) { + exifBadges.push( +
+ {`⚫ ${exifInfo.aperture}`} +
, + ) + } + + if (exifInfo?.shutterSpeed) { + exifBadges.push( +
+ {`⏱️ ${exifInfo.shutterSpeed}`} +
, + ) + } + + if (exifInfo?.iso) { + exifBadges.push( +
+ {`📊 ISO ${exifInfo.iso}`} +
, + ) + } + + if (exifInfo?.focalLength) { + exifBadges.push( +
+ {`🔍 ${exifInfo.focalLength}`} +
, + ) + } + + const footerItems: JSX.Element[] = [] + + if (formattedDate) { + footerItems.push( +
+ {`📸 ${formattedDate}`} +
, + ) + } + + if (exifInfo?.camera) { + footerItems.push( +
+ {`📷 ${exifInfo.camera}`} +
, + ) + } + + if (exifBadges.length > 0) { + footerItems.push( +
+ {exifBadges} +
, + ) + } + + const footer = + footerItems.length > 0 ? ( +
+ {footerItems} +
+ ) : null + + const thumbnailSrc = + thumbnailBuffer && this.arrayBufferToDataUri(thumbnailBuffer, this.inferThumbnailMimeType(photo.thumbnailUrl)) + + const leftFilmColumn = ( +
+ {filmHoles} +
) - const rightFilmColumn = h( - 'div', - { - style: { + const rightFilmColumn = ( +
+ {filmHoles} +
) const filmTexture = [ - h( - 'div', - { - style: { - position: 'absolute', - top: '0', - left: '30px', - width: `${layout.imageAreaWidth}px`, - height: '30px', - background: 'linear-gradient(180deg, #1a1a1a 0%, #2a2a2a 30%, #1a1a1a 100%)', - borderBottom: '1px solid rgba(255,255,255,0.05)', - }, - }, - null, - ), - h( - 'div', - { - style: { - position: 'absolute', - bottom: '0', - left: '30px', - width: `${layout.imageAreaWidth}px`, - height: '30px', - background: 'linear-gradient(0deg, #1a1a1a 0%, #2a2a2a 30%, #1a1a1a 100%)', - borderTop: '1px solid rgba(255,255,255,0.05)', - }, - }, - null, - ), +
, +
, ] - const photoFrame = - photo.thumbnailUrl && thumbnailBuffer - ? h( - 'div', - { - style: { - position: 'absolute', - top: '75px', - right: '45px', - width: `${layout.frameWidth}px`, - height: `${layout.frameHeight}px`, - background: 'linear-gradient(180deg, #1a1a1a 0%, #0d0d0d 100%)', - borderRadius: '6px', - border: '1px solid #2a2a2a', - boxShadow: '0 12px 48px rgba(0,0,0,0.6), inset 0 1px 0 rgba(255,255,255,0.03)', - display: 'flex', - overflow: 'hidden', - }, - }, - leftFilmColumn, - rightFilmColumn, - h( - 'div', - { - style: { - position: 'absolute', - left: '30px', - top: '30px', - width: `${layout.imageAreaWidth}px`, - height: `${layout.imageAreaHeight}px`, - background: '#000', - borderRadius: '2px', - border: '2px solid #1a1a1a', - overflow: 'hidden', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - boxShadow: 'inset 0 0 8px rgba(0,0,0,0.5)', - }, - }, - h( - 'div', - { - style: { - position: 'relative', - width: `${layout.displayWidth}px`, - height: `${layout.displayHeight}px`, - overflow: 'hidden', - display: 'flex', - }, - }, - h( - 'img', - { - src: thumbnailBuffer, - style: { - width: `${layout.displayWidth}px`, - height: `${layout.displayHeight}px`, - objectFit: 'cover', - }, - }, - null, - ), - ), - h( - 'div', - { - style: { - position: 'absolute', - top: '0', - left: '0', - width: '100%', - height: '100%', - background: - 'linear-gradient(135deg, transparent 0%, rgba(255,255,255,0.06) 25%, transparent 45%, transparent 55%, rgba(255,255,255,0.03) 75%, transparent 100%)', - pointerEvents: 'none', - }, - }, - null, - ), - ...filmTexture, - ), - ) - : null - - const footerItems: Array = [] - - if (formattedDate) { - footerItems.push( - h( - 'div', - { - style: { - fontSize: '28px', - color: 'rgba(255,255,255,0.7)', - letterSpacing: '0.3px', - display: 'flex', - alignItems: 'center', - gap: '12px', - }, - }, - `📸 ${formattedDate}`, - ), - ) - } - - if (exifInfo?.camera) { - footerItems.push( - h( - 'div', - { - style: { - fontSize: '25px', - color: 'rgba(255,255,255,0.6)', - letterSpacing: '0.3px', - display: 'flex', - }, - }, - `📷 ${exifInfo.camera}`, - ), - ) - } - - if (exifInfo && (exifInfo.aperture || exifInfo.shutterSpeed || exifInfo.iso || exifInfo.focalLength)) { - const exifBadges: OgElement[] = [] - - if (exifInfo.aperture) { - exifBadges.push( - h( - 'div', - { - style: { - display: 'flex', - alignItems: 'center', - gap: '8px', - backgroundColor: 'rgba(255,255,255,0.1)', - padding: '12px 18px', - borderRadius: '12px', - backdropFilter: 'blur(8px)', - }, - }, - `⚫ ${exifInfo.aperture}`, - ), - ) - } - - if (exifInfo.shutterSpeed) { - exifBadges.push( - h( - 'div', - { - style: { - display: 'flex', - alignItems: 'center', - gap: '8px', - backgroundColor: 'rgba(255,255,255,0.1)', - padding: '12px 18px', - borderRadius: '12px', - backdropFilter: 'blur(8px)', - }, - }, - `⏱️ ${exifInfo.shutterSpeed}`, - ), - ) - } - - if (exifInfo.iso) { - exifBadges.push( - h( - 'div', - { - style: { - display: 'flex', - alignItems: 'center', - gap: '8px', - backgroundColor: 'rgba(255,255,255,0.1)', - padding: '12px 18px', - borderRadius: '12px', - backdropFilter: 'blur(8px)', - }, - }, - `📊 ISO ${exifInfo.iso}`, - ), - ) - } - - if (exifInfo.focalLength) { - exifBadges.push( - h( - 'div', - { - style: { - display: 'flex', - alignItems: 'center', - gap: '8px', - backgroundColor: 'rgba(255,255,255,0.1)', - padding: '12px 18px', - borderRadius: '12px', - backdropFilter: 'blur(8px)', - }, - }, - `🔍 ${exifInfo.focalLength}`, - ), - ) - } - - footerItems.push( - h( - 'div', - { - style: { - display: 'flex', - flexWrap: 'wrap', - gap: '18px', - fontSize: '25px', - color: 'rgba(255,255,255,0.8)', - }, - }, - ...exifBadges, - ), - ) - } - - const footer = h( - 'div', - { - style: { - display: 'flex', - flexDirection: 'column', - alignItems: 'flex-start', - gap: '28px', - }, - }, - ...footerItems, - ) - - return h( - 'div', - { - style: { + return ( +
+ {decorativeOverlays} + {filmBorderElements} + {apertureDecor} + +
+

+ {photo.title || 'Untitled Photo'} +

+ +

+ {photo.description || siteConfig.name || siteConfig.title} +

+ + {tagElements.length > 0 && ( +
+ {tagElements} +
+ )} +
+ + {thumbnailSrc && ( +
+ {leftFilmColumn} + {rightFilmColumn} + +
+
+ {photo.title +
+ +
+ + {filmTexture} +
+
+ )} + + {footer} +
) } } diff --git a/be/apps/core/src/types/assets.d.ts b/be/apps/core/src/types/assets.d.ts new file mode 100644 index 00000000..3afc367c --- /dev/null +++ b/be/apps/core/src/types/assets.d.ts @@ -0,0 +1,4 @@ +declare module '*.ttf?url' { + const url: string + export default url +} diff --git a/be/apps/core/src/types/vercel-og.d.ts b/be/apps/core/src/types/vercel-og.d.ts new file mode 100644 index 00000000..e66aa348 --- /dev/null +++ b/be/apps/core/src/types/vercel-og.d.ts @@ -0,0 +1,25 @@ +declare module '@vercel/og' { + import type { ReactElement } from 'react' + + export type EmojiStyle = 'twemoji' | 'apple' | 'blobmoji' | 'noto' + + export interface FontConfig { + name: string + data: ArrayBuffer + weight?: number + style?: 'normal' | 'italic' + } + + export interface ImageResponseOptions { + width?: number + height?: number + emoji?: EmojiStyle + fonts?: FontConfig[] + headers?: Record + debug?: boolean + } + + export class ImageResponse extends Response { + constructor(element: ReactElement, options?: ImageResponseOptions) + } +} diff --git a/be/apps/core/tsconfig.json b/be/apps/core/tsconfig.json index 97e362c1..25039a8e 100644 --- a/be/apps/core/tsconfig.json +++ b/be/apps/core/tsconfig.json @@ -8,16 +8,11 @@ "module": "ESNext", "moduleResolution": "Bundler", "verbatimModuleSyntax": true, + "jsx": "react", "types": ["node"], "paths": { "core": ["./src"], - "core/*": ["./src/*"], - "@afilmory/db": ["../../packages/db/src"], - "@afilmory/db/*": ["../../packages/db/src/*"], - "@afilmory/be-utils": ["../../packages/utils/src"], - "@afilmory/be-utils/*": ["../../packages/utils/src/*"], - "@afilmory/websocket": ["../../packages/websocket/src"], - "@afilmory/websocket/*": ["../../packages/websocket/src/*"] + "core/*": ["./src/*"] }, "resolveJsonModule": true, "allowJs": true,