trading-platform-database-v2/ddl/schemas/education/tables/15-videos.sql
Adrian Flores Cortes 3f7816d4ec feat(database): Add videos table for education module (ST4.3.1)
Table: education.videos

Features:
- Multipart upload tracking (upload_id, parts, progress)
- Storage integration (S3/R2/Cloudflare Stream)
- Video processing status (uploading → processing → ready)
- Transcoded versions JSONB (multiple resolutions)
- CDN URLs (video + thumbnail)
- Metadata JSONB (tags, language, difficulty, captions, transcript)
- Soft delete (deleted_at)
- GIN index for metadata search
- Helper functions (soft_delete_video)
- View for active videos

Constraints:
- Valid status enum
- Valid storage provider enum
- Positive file size & duration
- Progress 0-100%

Blocker: BLOCKER-003 (ST4.3 Video Upload Backend)
Epic: OQI-002

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 20:06:10 -06:00

151 lines
6.3 KiB
PL/PgSQL

-- =====================================================
-- TABLE: education.videos
-- =====================================================
-- Proyecto: OrbiQuant IA (Trading Platform)
-- Módulo: OQI-002 - Education
-- Especificación: ET-EDU-008-video-upload-architecture.md
-- Blocker: BLOCKER-003 (ST4.3)
-- =====================================================
CREATE TABLE education.videos (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- Relaciones
course_id UUID NOT NULL REFERENCES education.courses(id) ON DELETE CASCADE,
lesson_id UUID REFERENCES education.lessons(id) ON DELETE SET NULL,
uploaded_by UUID NOT NULL REFERENCES core.users(id) ON DELETE RESTRICT,
-- Información básica
title VARCHAR(200) NOT NULL,
description TEXT,
original_filename VARCHAR(500) NOT NULL,
-- Storage (S3/R2)
storage_provider VARCHAR(50) NOT NULL DEFAULT 's3', -- 's3', 'r2', 'cloudflare_stream'
storage_bucket VARCHAR(200) NOT NULL,
storage_key VARCHAR(500) NOT NULL, -- S3/R2 key (path)
storage_region VARCHAR(50),
-- File info
file_size_bytes BIGINT NOT NULL,
mime_type VARCHAR(100) NOT NULL DEFAULT 'video/mp4',
duration_seconds INTEGER, -- Detectado después de upload
-- Status & Processing
status VARCHAR(50) NOT NULL DEFAULT 'uploading',
-- 'uploading', 'uploaded', 'processing', 'ready', 'error', 'deleted'
processing_started_at TIMESTAMPTZ,
processing_completed_at TIMESTAMPTZ,
processing_error TEXT,
-- CDN & URLs
cdn_url VARCHAR(1000), -- URL pública del video (CDN)
thumbnail_url VARCHAR(1000),
-- Transcoded versions (múltiples resoluciones)
transcoded_versions JSONB,
-- Ejemplo: [
-- {resolution: "1080p", storage_key: "...", cdn_url: "...", file_size_bytes: 123456},
-- {resolution: "720p", storage_key: "...", cdn_url: "...", file_size_bytes: 67890},
-- {resolution: "480p", storage_key: "...", cdn_url: "...", file_size_bytes: 34567}
-- ]
-- Metadata educativo
metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
-- Ejemplo: {
-- "tags": ["trading", "stocks", "technical-analysis"],
-- "language": "en",
-- "difficulty": "intermediate",
-- "captions": [{language: "en", url: "...srt"}, {language: "es", url: "...srt"}],
-- "transcript": "Full text transcript...",
-- "video_codec": "h264",
-- "audio_codec": "aac",
-- "bitrate_kbps": 5000,
-- "fps": 30,
-- "resolution": "1920x1080"
-- }
-- Multipart upload tracking
upload_id VARCHAR(500), -- AWS/R2 multipart upload ID
upload_parts_completed INTEGER DEFAULT 0,
upload_parts_total INTEGER,
upload_progress_percent INTEGER DEFAULT 0,
-- Timestamps
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
uploaded_at TIMESTAMPTZ, -- Cuando se completó el upload
deleted_at TIMESTAMPTZ, -- Soft delete
-- Constraints
CONSTRAINT valid_status CHECK (status IN ('uploading', 'uploaded', 'processing', 'ready', 'error', 'deleted')),
CONSTRAINT valid_storage_provider CHECK (storage_provider IN ('s3', 'r2', 'cloudflare_stream')),
CONSTRAINT valid_progress CHECK (upload_progress_percent >= 0 AND upload_progress_percent <= 100),
CONSTRAINT positive_duration CHECK (duration_seconds IS NULL OR duration_seconds > 0),
CONSTRAINT positive_file_size CHECK (file_size_bytes > 0)
);
-- Índices
CREATE INDEX idx_videos_course ON education.videos(course_id);
CREATE INDEX idx_videos_lesson ON education.videos(lesson_id);
CREATE INDEX idx_videos_uploader ON education.videos(uploaded_by);
CREATE INDEX idx_videos_status ON education.videos(status);
CREATE INDEX idx_videos_created ON education.videos(created_at DESC);
CREATE INDEX idx_videos_storage_key ON education.videos(storage_key);
-- Índice GIN para búsqueda en metadata (tags, language, etc.)
CREATE INDEX idx_videos_metadata ON education.videos USING GIN (metadata jsonb_path_ops);
-- Índice para soft delete (excluir deleted)
CREATE INDEX idx_videos_active ON education.videos(id) WHERE deleted_at IS NULL;
-- Índice compuesto para queries frecuentes
CREATE INDEX idx_videos_course_status ON education.videos(course_id, status) WHERE deleted_at IS NULL;
-- Comentarios
COMMENT ON TABLE education.videos IS 'Videos educativos con soporte de multipart upload, transcoding y CDN';
COMMENT ON COLUMN education.videos.storage_key IS 'S3/R2 object key (ruta completa del archivo)';
COMMENT ON COLUMN education.videos.status IS 'uploading: En proceso de upload | uploaded: Upload completo | processing: Transcoding en progreso | ready: Listo para uso | error: Falló processing | deleted: Soft deleted';
COMMENT ON COLUMN education.videos.transcoded_versions IS 'Array de versiones transcodificadas en diferentes resoluciones (1080p, 720p, 480p, etc.)';
COMMENT ON COLUMN education.videos.metadata IS 'Metadata educativo: tags, language, difficulty, captions, transcript, codecs, etc.';
COMMENT ON COLUMN education.videos.upload_id IS 'AWS/R2 multipart upload ID para tracking de upload en progreso';
COMMENT ON COLUMN education.videos.cdn_url IS 'URL pública del video servido desde CDN (CloudFront/Cloudflare)';
COMMENT ON COLUMN education.videos.thumbnail_url IS 'URL del thumbnail generado automáticamente del video';
COMMENT ON COLUMN education.videos.deleted_at IS 'Soft delete: NULL = activo, NOT NULL = eliminado';
-- Función para actualizar updated_at automáticamente
CREATE OR REPLACE FUNCTION education.update_videos_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trigger_update_videos_updated_at
BEFORE UPDATE ON education.videos
FOR EACH ROW
EXECUTE FUNCTION education.update_videos_updated_at();
-- Función helper para soft delete
CREATE OR REPLACE FUNCTION education.soft_delete_video(video_uuid UUID)
RETURNS VOID AS $$
BEGIN
UPDATE education.videos
SET
deleted_at = NOW(),
status = 'deleted',
updated_at = NOW()
WHERE id = video_uuid AND deleted_at IS NULL;
END;
$$ LANGUAGE plpgsql;
COMMENT ON FUNCTION education.soft_delete_video IS 'Soft delete de un video (marca deleted_at en lugar de eliminar el registro)';
-- View para videos activos (excluye soft deleted)
CREATE OR REPLACE VIEW education.active_videos AS
SELECT * FROM education.videos
WHERE deleted_at IS NULL;
COMMENT ON VIEW education.active_videos IS 'Vista de videos activos (excluye soft deleted)';