1- import { fileURLToPath } from 'node:url'
1+ import { mkdir , readFile , writeFile } from 'node:fs/promises'
2+ import { tmpdir } from 'node:os'
3+ import { join } from 'node:path'
24import type { APIRoute } from 'astro'
35import { getCollection } from 'astro:content'
46import { generateOpenGraphImage } from 'astro-og-canvas'
7+ import kevinBrownAvatar from '@assets/images/avatars/kevin-brown.webp'
58import { buildApiErrorResponse , handleApiFunctionError } from '@pages/api/_utils/errors'
69import { createApiFunctionContext } from '@pages/api/_utils/requestContext'
710
811export const prerender = false
912
1013const ROUTE = '/api/social-card'
11- const DEFAULT_TITLE = 'Webstack Builders'
12- const DEFAULT_DESCRIPTION = 'Professional Web Development Services'
13- const LOGO_PATH = fileURLToPath ( new URL ( '../../../../assets/images/site/logo.png' , import . meta. url ) )
14+ const DEFAULT_TITLE = 'Platform Engineering by Kevin Brown'
15+ const DEFAULT_DESCRIPTION =
16+ 'Platform engineer helping teams harden delivery, modernize cloud platforms, and improve developer experience.'
17+ const AVATAR_CACHE_DIR = join ( tmpdir ( ) , 'webstackbuilders-social-card' )
18+ const AVATAR_CACHE_PATH = join ( AVATAR_CACHE_DIR , 'kevin-brown.webp' )
1419
1520type CollectionKey = 'articles' | 'caseStudies' | 'services' | 'downloads'
1621type PaletteKey = 'articles' | 'case-studies' | 'services' | 'downloads' | 'default'
@@ -56,6 +61,26 @@ const gradientPalette: Record<PaletteKey, [number, number, number][]> = {
5661 ] ,
5762}
5863
64+ const getAvatarUrl = ( requestUrl : URL ) : URL => new URL ( kevinBrownAvatar . src , requestUrl )
65+
66+ const ensureAvatarFile = async ( requestUrl : URL ) : Promise < string > => {
67+ try {
68+ await readFile ( AVATAR_CACHE_PATH )
69+ return AVATAR_CACHE_PATH
70+ } catch {
71+ const response = await fetch ( getAvatarUrl ( requestUrl ) )
72+
73+ if ( ! response . ok ) {
74+ throw new Error ( `Unable to load avatar image: ${ response . status } ${ response . statusText } ` )
75+ }
76+
77+ const avatarBuffer = Buffer . from ( await response . arrayBuffer ( ) )
78+ await mkdir ( AVATAR_CACHE_DIR , { recursive : true } )
79+ await writeFile ( AVATAR_CACHE_PATH , avatarBuffer )
80+ return AVATAR_CACHE_PATH
81+ }
82+ }
83+
5984/** Normalize slug parameters to a consistent format */
6085const normalizeSlug = ( value : string | null ) : string => {
6186 if ( ! value ) return 'home'
@@ -119,6 +144,7 @@ export const GET: APIRoute = async ({ request, clientAddress, cookies }) => {
119144 const slug = normalizeSlug ( url . searchParams . get ( 'slug' ) )
120145 const titleOverride = url . searchParams . get ( 'title' )
121146 const descriptionOverride = url . searchParams . get ( 'description' )
147+ const avatarPath = await ensureAvatarFile ( url )
122148
123149 const contentIndex = await buildContentIndex ( )
124150 const matchedEntry = contentIndex [ slug ]
@@ -138,8 +164,8 @@ export const GET: APIRoute = async ({ request, clientAddress, cookies }) => {
138164 bgGradient : gradientPalette [ palette ] ,
139165 padding : 80 ,
140166 logo : {
141- path : LOGO_PATH ,
142- size : [ 180 ] ,
167+ path : avatarPath ,
168+ size : [ 140 , 140 ] ,
143169 } ,
144170 font : {
145171 title : {
0 commit comments