trading-platform-database-v2/ddl/schemas/education/tables/15-videos.sql
Adrian Flores Cortes 2a6d8367d8 feat: Update DDL schemas and add new structures
DDL updates:
- Update extensions and schemas configuration
- Add sessions table for auth schema
- Update education schema (videos, install/uninstall scripts)
- Add backtest_runs and llm_signals tables for ML schema

Scripts:
- Update database creation and migration scripts
- Add DDL validation script

New:
- Add migrations directory structure
- Add production seeds for auth, investment, market_data, trading

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 12:24:23 -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 auth.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)';