All files / web/src/app/api/admin/audio 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 { readdirSync, statSync, existsSync } from 'fs'
import { join } from 'path'
import { eq, sql } from 'drizzle-orm'
import { NextResponse } from 'next/server'
import { db } from '@/db'
import { appSettings, DEFAULT_APP_SETTINGS, ttsCollectedClips } from '@/db/schema'
import { AUDIO_MANIFEST } from '@/lib/audio/audioManifest'
import { ALL_VOICES } from '@/lib/audio/voices'
import { withAuth } from '@/lib/auth/withAuth'

const AUDIO_DIR = join(process.cwd(), 'data', 'audio')

/**
 * GET /api/admin/audio
 *
 * Returns the audio manifest, active voice, and per-voice clip counts.
 */
export const GET = withAuth(
  async () => {
    try {
      // Fetch active voice from DB
      const [settings] = await db
        .select()
        .from(appSettings)
        .where(eq(appSettings.id, 'default'))
        .limit(1)

      const activeVoice = settings?.audioVoice ?? DEFAULT_APP_SETTINGS.audioVoice

      // Scan data/audio/ for voice directories
      const voices: Record<string, { total: number; existing: number }> = {}
      const manifestFilenames = new Set(AUDIO_MANIFEST.map((e) => e.filename))

      if (existsSync(AUDIO_DIR)) {
        const entries = readdirSync(AUDIO_DIR)
        for (const entry of entries) {
          const entryPath = join(AUDIO_DIR, entry)
          if (statSync(entryPath).isDirectory()) {
            const files = readdirSync(entryPath)
            const existingCount = files.filter((f) => manifestFilenames.has(f)).length
            voices[entry] = {
              total: AUDIO_MANIFEST.length,
              existing: existingCount,
            }
          }
        }
      }

      // Count total collected clips (denominator for health metric)
      const [{ count: totalCollectedClips }] = await db
        .select({ count: sql<number>`count(*)` })
        .from(ttsCollectedClips)

      // Count audio files per voice directory for all known + custom voices
      const voiceClipCounts: Record<string, number> = {}
      const knownVoices = new Set<string>(ALL_VOICES)

      // Scan all directories under data/audio/ to catch custom voices too
      const allVoiceDirs: string[] = [...ALL_VOICES]
      if (existsSync(AUDIO_DIR)) {
        for (const entry of readdirSync(AUDIO_DIR)) {
          const entryPath = join(AUDIO_DIR, entry)
          if (statSync(entryPath).isDirectory() && !knownVoices.has(entry)) {
            allVoiceDirs.push(entry)
          }
        }
      }

      for (const voice of allVoiceDirs) {
        const voiceDir = join(AUDIO_DIR, voice)
        if (existsSync(voiceDir)) {
          const files = readdirSync(voiceDir)
          voiceClipCounts[voice] = files.filter(
            (f) => f.endsWith('.mp3') || f.endsWith('.webm')
          ).length
        } else {
          voiceClipCounts[voice] = 0
        }
      }

      return NextResponse.json({
        activeVoice,
        manifest: AUDIO_MANIFEST,
        voices,
        totalCollectedClips,
        voiceClipCounts,
      })
    } catch (error) {
      console.error('Error fetching audio status:', error)
      return NextResponse.json({ error: 'Failed to fetch audio status' }, { status: 500 })
    }
  },
  { role: 'admin' }
)