Feature: серверная часть, ФИО и результаты теста в БД; правка ответа эскалация 80%
Made-with: Cursor
This commit is contained in:
109
backend/app/main.py
Normal file
109
backend/app/main.py
Normal file
@@ -0,0 +1,109 @@
|
||||
"""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)
|
||||
Reference in New Issue
Block a user