- profile.ts: Whitelist (null | iFSsEDGbm0FiEd2IVH4w | Gt7OshJCH7MuzX96wFHi) + setLyraVoiceId() - profile/me/lyra-voice.patch.ts: neuer Endpoint, Legend-Gate (403 legend_only), Validation gegen Whitelist (400 invalid_voice_id). DB-Wert bleibt bei Plan-Downgrade. - coach/speak.post.ts: ElevenLabs-Voice-Prioritätskette nimmt userLyraVoiceId zuerst (nur wenn plan === legend), sonst voiceCfg / config / env / FALLBACK. - auth/me.get.ts: lyraVoiceId in der Profile-Response damit Frontend hydriert. Schema-Feld lyraVoiceId existiert bereits aus migration 20260507_profile_demographics_and_trial — keine neue Migration nötig. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
44 lines
1.3 KiB
TypeScript
44 lines
1.3 KiB
TypeScript
import { setLyraVoiceId, isAllowedLyraVoiceId } from "../../../db/profile";
|
|
|
|
/**
|
|
* PATCH /api/profile/me/lyra-voice
|
|
*
|
|
* Legend-only. Setzt die ElevenLabs-Voice-ID für Lyra-TTS.
|
|
*
|
|
* Body: { lyraVoiceId: string | null }
|
|
* null → Default-Voice (kdmDKE6EkgrWrrykO9Qt, Alexandra)
|
|
* iFSsEDGbm0FiEd2IVH4w → Voice 1
|
|
* Gt7OshJCH7MuzX96wFHi → Voice 2
|
|
*
|
|
* Plan-Downgrade-Verhalten: DB-Wert wird NICHT gelöscht. speak.post.ts
|
|
* prüft plan zur Laufzeit — non-legend User erhalten Default-Voice.
|
|
*/
|
|
export default defineEventHandler(async (event) => {
|
|
const user = await requireUser(event);
|
|
|
|
const db = usePrisma();
|
|
const profile = await db.profile.findUnique({
|
|
where: { id: user.id },
|
|
select: { plan: true },
|
|
});
|
|
|
|
if (!profile) {
|
|
throw createError({ statusCode: 404, statusMessage: "profile_not_found" });
|
|
}
|
|
|
|
if ((profile.plan ?? "free").toLowerCase() !== "legend") {
|
|
throw createError({ statusCode: 403, statusMessage: "legend_only" });
|
|
}
|
|
|
|
const body = await readBody(event);
|
|
const { lyraVoiceId } = body as { lyraVoiceId?: unknown };
|
|
|
|
if (!isAllowedLyraVoiceId(lyraVoiceId)) {
|
|
throw createError({ statusCode: 400, statusMessage: "invalid_voice_id" });
|
|
}
|
|
|
|
await setLyraVoiceId(user.id, lyraVoiceId);
|
|
|
|
return { success: true, data: { lyraVoiceId } };
|
|
});
|