|
1 | 1 | import type { APIRoute } from 'astro' |
2 | 2 | import { getCollection } from 'astro:content' |
3 | 3 | import { generateOpenGraphImage } from 'astro-og-canvas' |
4 | | -import kevinBrownAvatar from '@assets/images/avatars/kevin-brown.webp' |
| 4 | +import { access, mkdir, writeFile } from 'node:fs/promises' |
| 5 | +import { tmpdir } from 'node:os' |
| 6 | +import { join } from 'node:path' |
5 | 7 | import { buildApiErrorResponse, handleApiFunctionError } from '@pages/api/_utils/errors' |
6 | 8 | import { createApiFunctionContext } from '@pages/api/_utils/requestContext' |
7 | 9 |
|
8 | 10 | export const prerender = false |
9 | 11 |
|
10 | 12 | const ROUTE = '/api/social-card' |
| 13 | +const PUBLIC_AVATAR_PATH = '/assets/images/kevin-brown.webp' |
| 14 | +const CACHED_AVATAR_DIRECTORY = join(tmpdir(), 'webstackbuilders-social-cards') |
| 15 | +const CACHED_AVATAR_PATH = join(CACHED_AVATAR_DIRECTORY, 'kevin-brown.webp') |
11 | 16 | const DEFAULT_TITLE = 'Platform Engineering by Kevin Brown' |
12 | 17 | const DEFAULT_DESCRIPTION = |
13 | 18 | 'Platform engineer helping teams harden delivery, modernize cloud platforms, and improve developer experience.' |
14 | 19 |
|
15 | | -type ImageMetadataWithFsPath = typeof kevinBrownAvatar & { |
16 | | - fsPath?: string |
17 | | -} |
18 | | - |
19 | 20 | type CollectionKey = 'articles' | 'caseStudies' | 'services' | 'downloads' |
20 | 21 | type PaletteKey = 'articles' | 'case-studies' | 'services' | 'downloads' | 'default' |
21 | 22 |
|
@@ -60,14 +61,27 @@ const gradientPalette: Record<PaletteKey, [number, number, number][]> = { |
60 | 61 | ], |
61 | 62 | } |
62 | 63 |
|
63 | | -const getAvatarFilePath = (): string => { |
64 | | - const avatarFilePath = (kevinBrownAvatar as ImageMetadataWithFsPath).fsPath |
| 64 | +const getAvatarFilePath = async (requestUrl: string): Promise<string> => { |
| 65 | + try { |
| 66 | + await access(CACHED_AVATAR_PATH) |
| 67 | + return CACHED_AVATAR_PATH |
| 68 | + } catch { |
| 69 | + // Cache miss; fetch from the public asset URL and persist locally for astro-og-canvas. |
| 70 | + } |
65 | 71 |
|
66 | | - if (!avatarFilePath) { |
67 | | - throw new Error('Kevin Brown avatar asset is missing a local file path') |
| 72 | + const avatarUrl = new URL(PUBLIC_AVATAR_PATH, requestUrl) |
| 73 | + const response = await fetch(avatarUrl) |
| 74 | + |
| 75 | + if (!response.ok) { |
| 76 | + throw new Error(`Unable to fetch Kevin Brown avatar from ${avatarUrl.toString()}`) |
68 | 77 | } |
69 | 78 |
|
70 | | - return avatarFilePath |
| 79 | + const avatarBuffer = Buffer.from(await response.arrayBuffer()) |
| 80 | + |
| 81 | + await mkdir(CACHED_AVATAR_DIRECTORY, { recursive: true }) |
| 82 | + await writeFile(CACHED_AVATAR_PATH, avatarBuffer) |
| 83 | + |
| 84 | + return CACHED_AVATAR_PATH |
71 | 85 | } |
72 | 86 |
|
73 | 87 | /** Normalize slug parameters to a consistent format */ |
@@ -133,7 +147,7 @@ export const GET: APIRoute = async ({ request, clientAddress, cookies }) => { |
133 | 147 | const slug = normalizeSlug(url.searchParams.get('slug')) |
134 | 148 | const titleOverride = url.searchParams.get('title') |
135 | 149 | const descriptionOverride = url.searchParams.get('description') |
136 | | - const avatarPath = getAvatarFilePath() |
| 150 | + const avatarPath = await getAvatarFilePath(request.url) |
137 | 151 |
|
138 | 152 | const contentIndex = await buildContentIndex() |
139 | 153 | const matchedEntry = contentIndex[slug] |
|
0 commit comments