feat: add audios table and ElevenLabs TTS endpoint

- New audios table with voice params, S3 link, alignment JSON
- POST /api/audios/generate calls ElevenLabs with-timestamps, uploads to S3
- GET/PATCH/DELETE /api/audios endpoints
- Requires ELEVENLABS_API_KEY env var

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-01 13:05:34 +02:00
parent 2f0e08e264
commit 75f05f45f2
4 changed files with 197 additions and 0 deletions

View File

@@ -494,6 +494,62 @@ async function migrate() {
AND bbox_x IS NULL
`).catch(() => {});
// user_pair_progress
await query(`
CREATE TABLE IF NOT EXISTS user_pair_progress (
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
pair_id UUID NOT NULL REFERENCES pairs(id) ON DELETE CASCADE,
seen_count INTEGER NOT NULL DEFAULT 1,
correct_count INTEGER NOT NULL DEFAULT 0,
wrong_count INTEGER NOT NULL DEFAULT 0,
last_seen_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (user_id, pair_id)
)
`);
await query(`
CREATE OR REPLACE FUNCTION update_last_seen_at()
RETURNS TRIGGER AS $$
BEGIN NEW.last_seen_at = NOW(); RETURN NEW; END;
$$ LANGUAGE plpgsql
`);
await query(`
DROP TRIGGER IF EXISTS user_pair_progress_last_seen_at ON user_pair_progress;
CREATE TRIGGER user_pair_progress_last_seen_at
BEFORE UPDATE ON user_pair_progress
FOR EACH ROW EXECUTE FUNCTION update_last_seen_at()
`);
// audios
await query(`
CREATE TABLE IF NOT EXISTS audios (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
status VARCHAR(20) NOT NULL DEFAULT 'generated'
CHECK (status IN ('generated', 'published', 'blocked')),
text TEXT,
audio_link TEXT,
alignment JSONB,
voice_id TEXT,
model_id TEXT,
speed NUMERIC(4,2),
stability NUMERIC(4,2),
similarity_boost NUMERIC(4,2),
style NUMERIC(4,2),
published_at TIMESTAMPTZ,
blocked_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
)
`);
await query(`
DROP TRIGGER IF EXISTS audios_updated_at ON audios;
CREATE TRIGGER audios_updated_at
BEFORE UPDATE ON audios
FOR EACH ROW EXECUTE FUNCTION update_updated_at()
`);
// ── Migrate old {{uuid}} placeholders → new {{label.w:uuid}} / {{label.o:uuid}} ──
await migratePlaceholders();