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.
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
from app.db.base import Base
|
||||
from app.db.migrations import run_migrations
|
||||
from app.db.session import engine
|
||||
from app.models import exam, user # noqa: F401
|
||||
|
||||
|
||||
def init_db() -> None:
|
||||
Base.metadata.create_all(bind=engine)
|
||||
run_migrations(engine)
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
"""
|
||||
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
|
||||
$$;
|
||||
"""
|
||||
)
|
||||
)
|
||||
Reference in New Issue
Block a user