"""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)