All files / web/src/app/api/admin/blog-images/capture-storybook route.ts

0% Statements 0/81
0% Branches 0/1
0% Functions 0/1
0% Lines 0/81

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                                                                                                                                                                   
import fs from 'fs'
import path from 'path'
import { type NextRequest, NextResponse } from 'next/server'
import { withAuth } from '@/lib/auth/withAuth'

const BLOG_IMAGES_DIR = path.join(process.cwd(), 'public', 'blog')

/**
 * POST /api/admin/blog-images/capture-storybook
 *
 * Captures a screenshot of a Storybook story using Puppeteer.
 * Requires Storybook running locally on port 6006.
 */
export const POST = withAuth(
  async (request: NextRequest) => {
    let body: { slug: string; storyId: string; width?: number; height?: number }
    try {
      body = await request.json()
    } catch {
      return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 })
    }

    const { slug, storyId, width = 1200, height = 500 } = body

    if (!slug || !storyId) {
      return NextResponse.json({ error: 'slug and storyId are required' }, { status: 400 })
    }

    let puppeteer: typeof import('puppeteer')
    try {
      puppeteer = await import('puppeteer')
    } catch {
      return NextResponse.json(
        { error: 'puppeteer is not installed. Run: pnpm add -D puppeteer' },
        { status: 500 }
      )
    }

    let browser
    try {
      browser = await puppeteer.default.launch({
        headless: true,
        protocolTimeout: 120000,
        args: ['--no-sandbox', '--disable-setuid-sandbox'],
      })
      const page = await browser.newPage()
      await page.setViewport({ width, height })

      const storybookUrl = `http://localhost:6006/iframe.html?id=${encodeURIComponent(storyId)}&viewMode=story`
      await page.goto(storybookUrl, { waitUntil: 'domcontentloaded', timeout: 30000 })
      // Wait for story content to render (Storybook HMR keeps connections open, so networkidle0 times out)
      await new Promise((r) => setTimeout(r, 5000))

      // Ensure output directory exists
      if (!fs.existsSync(BLOG_IMAGES_DIR)) {
        fs.mkdirSync(BLOG_IMAGES_DIR, { recursive: true })
      }

      const outputPath = path.join(BLOG_IMAGES_DIR, `${slug}.png`)
      await page.screenshot({ path: outputPath, type: 'png' })

      const stats = fs.statSync(outputPath)

      return NextResponse.json({
        success: true,
        path: `/blog/${slug}.png`,
        sizeBytes: stats.size,
      })
    } catch (err) {
      return NextResponse.json(
        { error: err instanceof Error ? err.message : 'Screenshot capture failed' },
        { status: 500 }
      )
    } finally {
      if (browser) {
        await browser.close()
      }
    }
  },
  { role: 'admin' }
)