Files
lms-it-oms/backend/app/main.py

110 lines
3.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""FastAPI-приложение LMS «Управление ИТ (ОМС)»."""
import logging
from contextlib import asynccontextmanager
from datetime import datetime, timezone
from pathlib import Path
from fastapi import FastAPI, Depends, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from sqlalchemy.orm import Session
from app.config import settings
from app.database import get_db, init_db
from app.models import Participant
from app.schemas import (
StartRequest,
StartResponse,
CompleteRequest,
CompleteResponse,
)
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
)
logger = logging.getLogger(__name__)
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Инициализация при старте, создание таблиц."""
init_db()
yield
# shutdown при необходимости
app = FastAPI(
title="LMS Управление ИТ (ОМС)",
description="API для фиксации прохождения обучения и результатов тестирования",
lifespan=lifespan,
)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Статика: курс (HTML) — из CONTENT_PATH в Docker или content/ рядом с backend
static_path = Path(settings.content_path) if settings.content_path else (Path(__file__).resolve().parent.parent.parent / "content")
if static_path.exists():
app.mount("/content", StaticFiles(directory=str(static_path), html=True), name="content")
@app.get("/")
async def root():
"""Редирект на страницу курса."""
from fastapi.responses import RedirectResponse
return RedirectResponse(url="/content/it_course_v2.html", status_code=302)
@app.get("/api/health")
async def health():
"""Проверка доступности API."""
return {"status": "ok"}
@app.post("/api/start", response_model=StartResponse)
def api_start(body: StartRequest, db: Session = Depends(get_db)):
"""
Регистрация начала обучения.
Принимает ФИО, создаёт запись в БД, возвращает participant_id для последующей отправки результата.
"""
fio = body.fio.strip()
if not fio:
raise HTTPException(status_code=400, detail="ФИО не может быть пустым")
participant = Participant(fio=fio)
db.add(participant)
db.commit()
db.refresh(participant)
logger.info("Обучение начато: participant_id=%s, fio=%s", participant.id, participant.fio)
return StartResponse(participant_id=participant.id, fio=participant.fio)
@app.post("/api/complete", response_model=CompleteResponse)
def api_complete(body: CompleteRequest, db: Session = Depends(get_db)):
"""
Фиксация результата тестирования.
Обновляет запись участника по participant_id: score, percent, passed, completed_at.
"""
participant = db.query(Participant).filter(Participant.id == body.participant_id).first()
if not participant:
raise HTTPException(status_code=404, detail="Участник не найден")
participant.completed_at = datetime.now(timezone.utc)
participant.score = body.score
participant.total_questions = body.total_questions
participant.percent = body.percent
participant.passed = body.passed
db.commit()
logger.info(
"Тест завершён: participant_id=%s, score=%s/%s, percent=%s, passed=%s",
participant.id,
body.score,
body.total_questions,
body.percent,
body.passed,
)
return CompleteResponse(participant_id=participant.id, saved=True)