feat(server): per-user AI usage tracking, cost alerting, and endpoint hardening

Usage tracking:
- new UsageEvent collection records every STT/TTS/LLM call per user with
  provider, model, volume (audio seconds, characters, tokens) and an
  estimated USD cost; self-hosted providers (Speaches/Piper) and cache
  hits record at $0
- pricing table for whisper-1, tts-1, gpt-5-nano & co. in server/utils/usage.ts
- weekly KPI mail gains an "AI-Nutzung & Kosten" section: weekly and
  rolling 30-day cost, per-kind breakdown, top 5 users by cost
- quota alert mail when rolling 30-day cost exceeds USAGE_ALERT_USD
  (default $5), at most once per calendar month (UsageAlertDelivery)

Hardening:
- /api/atc/say now requires an authenticated session (middleware
  exemption removed); useFlightLabAudio sends the bearer token
- /api/service/tools/latency requires auth (was a public LLM endpoint)
- per-user rate limits: PTT 20/min, say 60/min, latency 5/min
- cron endpoints (waitlist-drip, weekly-kpi-report) require a shared
  secret via ?secret= or x-cron-secret (CRON_SECRET, falls back to
  KPI_CRON_SECRET); allowed with a warning while unset so existing
  deployments keep working
- PTT records the actual transcribed audio duration for billing accuracy

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
leubeem
2026-06-10 23:17:03 +02:00
parent c48f5fe40e
commit f8fdd8bc79
14 changed files with 568 additions and 25 deletions

View File

@@ -1,5 +1,6 @@
// shared/composables/flightlab/useFlightLabAudio.ts
import { ref, computed } from 'vue'
import { useAuthStore } from '~/stores/auth'
import type { FlightLabSound } from '../../data/flightlab/types'
import { getReadabilityProfile, createNoiseGenerators } from '../../utils/radioEffects'
import type { PizzicatoLite as PizzicatoLiteType } from '../../utils/pizzicatoLite'
@@ -134,9 +135,11 @@ export function useFlightLabAudio() {
}
isSpeaking.value = true
try {
// Call the existing TTS API
// Call the existing TTS API (now requires an authenticated session)
const auth = useAuthStore()
const res = await $fetch<any>('/api/atc/say', {
method: 'POST',
headers: auth.accessToken ? { Authorization: `Bearer ${auth.accessToken}` } : undefined,
body: {
text,
level: options?.readability ?? 5,