Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | import fs from 'fs' import path from 'path' import { type NextRequest, NextResponse } from 'next/server' import { withAuth } from '@/lib/auth/withAuth' const heroHtmlDirectory = path.join(process.cwd(), 'content', 'blog', 'hero-html') const embedHtmlDirectory = path.join(process.cwd(), 'content', 'blog', 'embed-html') /** * POST /api/admin/blog-images/capture-snapshot * * Fetches an internal URL (e.g. /api/worksheets/preview), extracts content * from the response, and saves it as a hero-html or embed-html file for a blog post. * * When `embedId` is provided, saves to content/blog/embed-html/{slug}/{embedId}.html * Otherwise saves to content/blog/hero-html/{slug}.html */ export const POST = withAuth( async (request: NextRequest) => { let body: { slug: string url: string method?: string body?: unknown extractPath?: string embedId?: string } try { body = await request.json() } catch { return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 }) } const { slug, url, method = 'GET', body: fetchBody, extractPath, embedId } = body if (!slug || !url) { return NextResponse.json({ error: 'slug and url are required' }, { status: 400 }) } // Resolve url against the app's own origin const host = request.headers.get('host') || 'localhost:3000' const protocol = request.headers.get('x-forwarded-proto') || 'http' const origin = `${protocol}://${host}` const resolvedUrl = new URL(url, origin).toString() // Forward cookies for authentication const cookie = request.headers.get('cookie') try { const fetchOptions: RequestInit = { method: method.toUpperCase(), headers: { 'Content-Type': 'application/json', ...(cookie ? { cookie } : {}), }, } if (method.toUpperCase() === 'POST' && fetchBody !== undefined) { fetchOptions.body = JSON.stringify(fetchBody) } const res = await fetch(resolvedUrl, fetchOptions) if (!res.ok) { const text = await res.text().catch(() => 'Unknown error') return NextResponse.json( { error: `Upstream request failed (${res.status}): ${text.slice(0, 200)}` }, { status: 502 } ) } let content: string const contentType = res.headers.get('content-type') || '' if (contentType.includes('application/json') && extractPath) { const json = await res.json() // Drill into the object using dot-notation path (e.g. "pages.0") let current: unknown = json for (const segment of extractPath.split('.')) { if (current === null || current === undefined) break if (typeof current === 'object') { current = (current as Record<string, unknown>)[segment] } else { current = undefined } } if (typeof current !== 'string') { return NextResponse.json( { error: `extractPath "${extractPath}" did not resolve to a string (got ${typeof current})`, }, { status: 422 } ) } content = current } else { content = await res.text() } // Determine save path: embed-html/{slug}/{embedId}.html or hero-html/{slug}.html let targetDir: string let filePath: string if (embedId) { targetDir = path.join(embedHtmlDirectory, slug) filePath = path.join(targetDir, `${embedId}.html`) } else { targetDir = heroHtmlDirectory filePath = path.join(targetDir, `${slug}.html`) } // Ensure directory exists if (!fs.existsSync(targetDir)) { fs.mkdirSync(targetDir, { recursive: true }) } fs.writeFileSync(filePath, content, 'utf8') return NextResponse.json({ success: true, sizeBytes: Buffer.byteLength(content, 'utf8'), }) } catch (err) { return NextResponse.json( { error: err instanceof Error ? err.message : 'Snapshot capture failed' }, { status: 500 } ) } }, { role: 'admin' } ) |