All files / web/src/app/api/admin/page-spots/capture-snapshot route.ts

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

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

const contentDir = path.join(process.cwd(), 'content', 'page-spots')

/**
 * POST /api/admin/page-spots/capture-snapshot
 *
 * Fetches a URL, extracts content, and saves it as HTML for an html-type spot.
 * Body: { pageId, spotId, url, method?, body?, extractPath? }
 */
export const POST = withAuth(
  async (request: NextRequest) => {
    let body: {
      pageId: string
      spotId: string
      url: string
      method?: string
      body?: unknown
      extractPath?: string
    }
    try {
      body = await request.json()
    } catch {
      return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 })
    }

    const { pageId, spotId, url, method = 'GET', body: fetchBody, extractPath } = body

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

    const def = getSpotDefinition(pageId, spotId)
    if (!def) {
      return NextResponse.json({ error: `Unknown spot: ${pageId}/${spotId}` }, { status: 404 })
    }

    // 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()
        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()
      }

      // Save to content/page-spots/{pageId}/{spotId}.html
      const targetDir = path.join(contentDir, pageId)
      const filePath = path.join(targetDir, `${spotId}.html`)

      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' }
)