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

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

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                                                                                                                                                                                             
import { existsSync, readFileSync, statSync } from 'fs'
import { join } from 'path'
import { NextResponse } from 'next/server'
import { getAllPostsMetadata, getPostBySlug } from '@/lib/blog'
import { IMAGE_PROVIDERS } from '@/lib/tasks/blog-image-generate'
import { withAuth } from '@/lib/auth/withAuth'

const BLOG_IMAGES_DIR = join(process.cwd(), 'public', 'blog')
const EMBEDS_DIR = join(process.cwd(), 'content', 'blog', 'embeds')

/**
 * GET /api/admin/blog-images/status
 *
 * Returns blog posts with their hero image status and available providers.
 */
export const GET = withAuth(
  async () => {
    const allPosts = await getAllPostsMetadata()

    // Load full posts to get embeds (parsed from markdown content)
    const fullPosts = await Promise.all(allPosts.map((meta) => getPostBySlug(meta.slug)))

    const posts = allPosts.map((post, idx) => {
      // Check generated image at convention path
      const generatedFile = join(BLOG_IMAGES_DIR, `${post.slug}.png`)
      const generatedExists = existsSync(generatedFile)

      // Check for explicit heroImage from frontmatter (e.g. legacy images with non-standard names)
      let heroImageExists = false
      let heroImageSize: number | undefined
      if (post.heroImage) {
        const heroFile = join(process.cwd(), 'public', post.heroImage.replace(/^\//, ''))
        heroImageExists = existsSync(heroFile)
        if (heroImageExists) {
          heroImageSize = statSync(heroFile).size
        }
      }

      const imageExists = generatedExists || heroImageExists

      // Read embed config to check which embeds are configured
      const fullPost = fullPosts[idx]
      let embedConfig: Record<string, unknown> = {}
      const embedConfigPath = join(EMBEDS_DIR, `${post.slug}.json`)
      if (existsSync(embedConfigPath)) {
        try {
          embedConfig = JSON.parse(readFileSync(embedConfigPath, 'utf8'))
        } catch {
          // ignore
        }
      }

      const embeds = fullPost.embeds.map((embed) => ({
        id: embed.id,
        description: embed.description,
        configured: embed.id in embedConfig,
      }))

      return {
        slug: post.slug,
        title: post.title,
        heroPrompt: post.heroPrompt ?? null,
        heroImage: post.heroImage ?? null,
        heroAspectRatio: post.heroAspectRatio ?? null,
        featured: post.featured,
        heroCrop: post.heroCrop ?? null,
        heroImageUrl: post.heroImageUrl ?? null,
        heroType: post.heroType ?? null,
        heroStoryId: post.heroStoryId ?? null,
        heroComponentId: post.heroComponentId ?? null,
        imageExists,
        sizeBytes: generatedExists ? statSync(generatedFile).size : heroImageSize,
        embeds,
      }
    })

    const providers = IMAGE_PROVIDERS.map((p) => {
      const hasKey =
        'envKeyAlt' in p
          ? !!(process.env[p.envKey] || process.env[p.envKeyAlt!])
          : !!process.env[p.envKey]

      return {
        id: p.id,
        name: p.name,
        available: hasKey,
        models: p.models.map((m) => ({ id: m.id, name: m.name })),
      }
    })

    return NextResponse.json({ posts, providers })
  },
  { role: 'admin' }
)