CREATE TABLE IF NOT EXISTS tts_voices ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), provider_voice_id TEXT NOT NULL UNIQUE, name TEXT NOT NULL, language TEXT, description TEXT, enabled BOOLEAN NOT NULL DEFAULT true, supported_parameters JSONB NOT NULL DEFAULT '{}'::jsonb, allowed_plans JSONB NOT NULL DEFAULT '["free", "vip"]'::jsonb, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS tts_tasks ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, text TEXT NOT NULL, text_length INTEGER NOT NULL CHECK (text_length > 0), voice_id UUID REFERENCES tts_voices(id), provider_voice_id TEXT NOT NULL, parameters JSONB NOT NULL DEFAULT '{}'::jsonb, status TEXT NOT NULL DEFAULT 'queued' CHECK (status IN ('queued', 'running', 'succeeded', 'failed')), provider_task_id TEXT, idempotency_key TEXT NOT NULL, request_hash TEXT NOT NULL, policy_version INTEGER NOT NULL, quota_account_id UUID NOT NULL REFERENCES quota_accounts(id), reserved_amount BIGINT NOT NULL CHECK (reserved_amount >= 0), error_code TEXT, attempt_count INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), started_at TIMESTAMPTZ, finished_at TIMESTAMPTZ, updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), UNIQUE(user_id, idempotency_key) ); CREATE INDEX IF NOT EXISTS tts_tasks_user_created_idx ON tts_tasks(user_id, created_at DESC); CREATE INDEX IF NOT EXISTS tts_tasks_queue_idx ON tts_tasks(status, updated_at); CREATE TABLE IF NOT EXISTS audio_files ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), task_id UUID NOT NULL UNIQUE REFERENCES tts_tasks(id) ON DELETE CASCADE, owner_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, storage_key TEXT NOT NULL UNIQUE, mime_type TEXT NOT NULL, size_bytes BIGINT NOT NULL CHECK (size_bytes >= 0), checksum TEXT NOT NULL, duration_ms INTEGER, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), expires_at TIMESTAMPTZ, status TEXT NOT NULL DEFAULT 'available' CHECK (status IN ('available', 'expired', 'deleted')) ); INSERT INTO tts_voices(provider_voice_id, name, description, supported_parameters, allowed_plans) VALUES ('default', 'default', '旧 TTS 工程代码中的默认音色标识', '{"format":["wav","mp3"],"speed":{"min":0.5,"max":2}}'::jsonb, '["free","vip"]'::jsonb) ON CONFLICT (provider_voice_id) DO NOTHING;