Files
Mireya Cueto Garrido 946f16a633 Add React frontend and Sinbad2IA LLM integration.
Introduce a full Vite/React UI for exams, auth, materials, images, generation, and export.
Adapt backend for Sinbad2IA chat API, bcrypt passwords, CORS on port 5173, and schema migrations.
2026-06-01 13:27:41 +02:00

105 lines
3.1 KiB
Python

"""
Migraciones ligeras e idempotentes para bases de datos creadas antes de nuevas columnas.
Se ejecutan en cada arranque; solo aplican cambios que falten.
"""
from sqlalchemy import inspect, text
from sqlalchemy.engine import Engine
def run_migrations(engine: Engine) -> None:
inspector = inspect(engine)
table_names = set(inspector.get_table_names())
with engine.begin() as conn:
if "users" in table_names and "exam_templates" in table_names:
_ensure_exam_templates_user_id(conn, inspector)
if "questions" in table_names:
_ensure_questions_image_id(conn, inspector)
if "exam_materials" in table_names:
_ensure_material_status_enum(conn)
def _column_names(inspector, table: str) -> set[str]:
return {col["name"] for col in inspector.get_columns(table)}
def _ensure_exam_templates_user_id(conn, inspector) -> None:
if "user_id" in _column_names(inspector, "exam_templates"):
return
conn.execute(
text("ALTER TABLE exam_templates ADD COLUMN user_id UUID")
)
# Plantillas antiguas (sin usuario): asignar al primer usuario registrado.
conn.execute(
text(
"""
UPDATE exam_templates
SET user_id = (SELECT id FROM users ORDER BY created_at ASC LIMIT 1)
WHERE user_id IS NULL
AND EXISTS (SELECT 1 FROM users)
"""
)
)
# Si no hay usuarios o filas huérfanas, eliminar plantillas sin dueño.
conn.execute(text("DELETE FROM exam_templates WHERE user_id IS NULL"))
conn.execute(text("ALTER TABLE exam_templates ALTER COLUMN user_id SET NOT NULL"))
conn.execute(
text(
"""
ALTER TABLE exam_templates
ADD CONSTRAINT fk_exam_templates_user_id
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
"""
)
)
conn.execute(
text(
"CREATE INDEX IF NOT EXISTS ix_exam_templates_user_id ON exam_templates (user_id)"
)
)
def _ensure_questions_image_id(conn, inspector) -> None:
if "image_id" in _column_names(inspector, "questions"):
return
if "exam_images" not in set(inspector.get_table_names()):
return
conn.execute(text("ALTER TABLE questions ADD COLUMN image_id UUID"))
conn.execute(
text(
"""
ALTER TABLE questions
ADD CONSTRAINT fk_questions_image_id
FOREIGN KEY (image_id) REFERENCES exam_images(id) ON DELETE SET NULL
"""
)
)
conn.execute(
text("CREATE INDEX IF NOT EXISTS ix_questions_image_id ON questions (image_id)")
)
def _ensure_material_status_enum(conn) -> None:
# Asegura el tipo enum de PostgreSQL usado por exam_materials.status.
conn.execute(
text(
"""
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'materialstatus') THEN
CREATE TYPE materialstatus AS ENUM ('processed', 'failed');
END IF;
END
$$;
"""
)
)