- bd/__init__.py: добавлены get_engine() и get_session() — один SQLAlchemy engine на весь процесс - make_engine(): pool_size=5, max_overflow=5, pool_timeout=30, pool_recycle=1800 - Все 17 route-файлов: убраны локальные get_session()/make_engine(), импорт из bd - transfer_crud.py: _get_session() → get_session() из bd - init_data_base.py: get_engine() вместо make_engine() - Результат: 78 idle соединений → 1
158 lines
5.7 KiB
Python
158 lines
5.7 KiB
Python
from fastapi import APIRouter, HTTPException
|
||
from pydantic import BaseModel
|
||
from typing import List, Optional
|
||
import uuid
|
||
|
||
from bd import get_session
|
||
|
||
from bd.tables.poll import Poll
|
||
from bd.tables.question import Question
|
||
from bd.tables.response import Response, Answer
|
||
from bd.tables.scale import ScaleDimension
|
||
|
||
router = APIRouter(tags=["responses"], prefix="/polls")
|
||
|
||
|
||
class AnswerIn(BaseModel):
|
||
question_id: str
|
||
choice_id: Optional[str]
|
||
text: Optional[str]
|
||
|
||
|
||
class ResponseIn(BaseModel):
|
||
user_id: Optional[str] = None
|
||
group_id: Optional[str] = None
|
||
organization_id: Optional[str] = None
|
||
answers: List[AnswerIn]
|
||
|
||
|
||
|
||
|
||
@router.post("/{poll_id}/responses", status_code=201)
|
||
def submit_response(poll_id: str, payload: ResponseIn):
|
||
Session = get_session()
|
||
try:
|
||
pid = uuid.UUID(poll_id)
|
||
except Exception:
|
||
raise HTTPException(status_code=400, detail="Invalid poll UUID")
|
||
with Session() as session:
|
||
poll = session.get(Poll, pid)
|
||
if not poll:
|
||
raise HTTPException(status_code=404, detail="Poll not found")
|
||
resp = Response(poll_id=pid)
|
||
if payload.user_id:
|
||
try:
|
||
resp.user_id = uuid.UUID(payload.user_id)
|
||
except Exception:
|
||
raise HTTPException(status_code=400, detail="Invalid user_id UUID")
|
||
if payload.group_id:
|
||
try:
|
||
resp.group_id = uuid.UUID(payload.group_id)
|
||
except Exception:
|
||
raise HTTPException(status_code=400, detail="Invalid group_id UUID")
|
||
if payload.organization_id:
|
||
try:
|
||
resp.organization_id = uuid.UUID(payload.organization_id)
|
||
except Exception:
|
||
raise HTTPException(status_code=400, detail="Invalid organization_id UUID")
|
||
session.add(resp)
|
||
# validate answers
|
||
for a in payload.answers:
|
||
try:
|
||
qid = uuid.UUID(a.question_id)
|
||
except Exception:
|
||
raise HTTPException(status_code=400, detail="Invalid question_id UUID")
|
||
question = session.get(Question, qid)
|
||
if not question or question.poll_id != pid:
|
||
raise HTTPException(status_code=400, detail="Question does not belong to poll")
|
||
choice_uuid = None
|
||
if a.choice_id:
|
||
try:
|
||
choice_uuid = uuid.UUID(a.choice_id)
|
||
except Exception:
|
||
raise HTTPException(status_code=400, detail="Invalid choice_id UUID")
|
||
answer = Answer(response=resp, question_id=qid, choice_id=choice_uuid, text=a.text)
|
||
session.add(answer)
|
||
session.commit()
|
||
session.refresh(resp)
|
||
|
||
# Автоматически считаем радарный результат, если у теста есть оси
|
||
has_dimensions = (
|
||
session.query(ScaleDimension)
|
||
.filter(ScaleDimension.poll_id == pid)
|
||
.first()
|
||
) is not None
|
||
radar_id: Optional[str] = None
|
||
if has_dimensions:
|
||
try:
|
||
from route.radar_crud import compute_radar
|
||
radar = compute_radar(resp.id, session)
|
||
radar_id = str(radar.id)
|
||
except Exception:
|
||
pass # не ломаем сдачу, если расчёт не удался
|
||
|
||
result: dict = {"id": str(resp.id)}
|
||
if radar_id:
|
||
result["radar_result_id"] = radar_id
|
||
return result
|
||
|
||
|
||
@router.get("/responses/")
|
||
def list_all_responses():
|
||
Session = get_session()
|
||
with Session() as session:
|
||
rows = session.query(Response).all()
|
||
return [
|
||
{"id": str(r.id), "poll_id": str(r.poll_id), "user_id": str(r.user_id) if r.user_id else None, "submitted_at": r.submitted_at.isoformat()}
|
||
for r in rows
|
||
]
|
||
|
||
|
||
@router.get("/{poll_id}/responses")
|
||
def list_responses(poll_id: str):
|
||
Session = get_session()
|
||
try:
|
||
pid = uuid.UUID(poll_id)
|
||
except Exception:
|
||
raise HTTPException(status_code=400, detail="Invalid poll UUID")
|
||
with Session() as session:
|
||
rows = session.query(Response).filter(Response.poll_id == pid).all()
|
||
return [{"id": str(r.id), "user_id": str(r.user_id) if r.user_id else None, "submitted_at": r.submitted_at.isoformat()} for r in rows]
|
||
|
||
|
||
@router.get("/responses/{response_id}")
|
||
def get_response(response_id: str):
|
||
Session = get_session()
|
||
try:
|
||
rid = uuid.UUID(response_id)
|
||
except Exception:
|
||
raise HTTPException(status_code=400, detail="Invalid UUID")
|
||
with Session() as session:
|
||
r = session.get(Response, rid)
|
||
if not r:
|
||
raise HTTPException(status_code=404, detail="Response not found")
|
||
return {
|
||
"id": str(r.id),
|
||
"poll_id": str(r.poll_id),
|
||
"user_id": str(r.user_id) if r.user_id else None,
|
||
"group_id": str(r.group_id) if r.group_id else None,
|
||
"organization_id": str(r.organization_id) if r.organization_id else None,
|
||
"submitted_at": r.submitted_at.isoformat(),
|
||
"answers": [
|
||
{"id": str(a.id), "question_id": str(a.question_id), "choice_id": str(a.choice_id) if a.choice_id else None, "text": a.text}
|
||
for a in r.answers
|
||
],
|
||
}
|
||
|
||
|
||
@router.get("/users/{user_id}/responses")
|
||
def list_responses_by_user(user_id: str):
|
||
Session = get_session()
|
||
try:
|
||
uid = uuid.UUID(user_id)
|
||
except Exception:
|
||
raise HTTPException(status_code=400, detail="Invalid user UUID")
|
||
with Session() as session:
|
||
rows = session.query(Response).filter(Response.user_id == uid).all()
|
||
return [{"id": str(r.id), "poll_id": str(r.poll_id), "submitted_at": r.submitted_at.isoformat()} for r in rows]
|