All files / web/src/app/api/admin/constant-images/generate route.ts

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

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                                                                                                                                                                                                               
import { NextResponse } from 'next/server'
import { MATH_CONSTANTS } from '@/components/toys/number-line/constants/constantsData'
import { startImageGeneration } from '@/lib/tasks/image-generate'
import type { ImageGenerateInput } from '@/lib/tasks/image-generate'
import { withAuth } from '@/lib/auth/withAuth'

const VALID_PROVIDERS = ['gemini', 'openai'] as const
const VALID_STYLES = ['metaphor', 'math'] as const
const VALID_THEMES = ['light', 'dark'] as const

/**
 * POST /api/admin/constant-images/generate
 *
 * Starts a background task to generate constant illustrations.
 * Body: { provider: string, model: string, targets?: Array<{constantId, style}>, forceRegenerate?: boolean }
 * Response: { taskId: string }
 */
export const POST = withAuth(
  async (request, { userId }) => {
    try {
      const body = await request.json()
      const { provider, model, targets, forceRegenerate, theme } = body

      // Validate provider
      if (!provider || !VALID_PROVIDERS.includes(provider)) {
        return NextResponse.json(
          { error: `provider must be one of: ${VALID_PROVIDERS.join(', ')}` },
          { status: 400 }
        )
      }

      // Validate model
      if (typeof model !== 'string' || model.trim().length === 0) {
        return NextResponse.json({ error: 'model must be a non-empty string' }, { status: 400 })
      }

      // Validate top-level theme (used for default "generate all" path)
      if (theme !== undefined && !VALID_THEMES.includes(theme)) {
        return NextResponse.json(
          { error: `theme must be one of: ${VALID_THEMES.join(', ')}` },
          { status: 400 }
        )
      }

      // Build targets list: either user-specified or all constants x both styles
      let resolvedTargets: ImageGenerateInput['targets']

      if (targets && Array.isArray(targets) && targets.length > 0) {
        // Validate each target
        const constantIds = new Set(MATH_CONSTANTS.map((c) => c.id))
        for (const t of targets) {
          if (!constantIds.has(t.constantId)) {
            return NextResponse.json(
              { error: `Unknown constantId: ${t.constantId}` },
              { status: 400 }
            )
          }
          if (!VALID_STYLES.includes(t.style)) {
            return NextResponse.json(
              { error: `style must be one of: ${VALID_STYLES.join(', ')}` },
              { status: 400 }
            )
          }
          if (t.theme !== undefined && !VALID_THEMES.includes(t.theme)) {
            return NextResponse.json(
              { error: `target theme must be one of: ${VALID_THEMES.join(', ')}` },
              { status: 400 }
            )
          }
        }
        resolvedTargets = targets
      } else {
        // Default: all constants, both styles (with optional top-level theme)
        resolvedTargets = MATH_CONSTANTS.flatMap((c) => [
          {
            constantId: c.id,
            style: 'metaphor' as const,
            ...(theme && { theme: theme as 'light' | 'dark' }),
          },
          {
            constantId: c.id,
            style: 'math' as const,
            ...(theme && { theme: theme as 'light' | 'dark' }),
          },
        ])
      }

      const taskId = await startImageGeneration({
        provider,
        model: model.trim(),
        targets: resolvedTargets,
        forceRegenerate: !!forceRegenerate,
        _userId: userId,
      })

      return NextResponse.json({ taskId })
    } catch (error) {
      console.error('Error starting image generation:', error)
      return NextResponse.json({ error: 'Failed to start image generation' }, { status: 500 })
    }
  },
  { role: 'admin' }
)