Harden auth and persist report schedules

This commit is contained in:
dwindown
2026-06-06 19:40:32 +07:00
parent aaf64264f7
commit fd7989f673
18 changed files with 748 additions and 105 deletions

View File

@@ -11,6 +11,7 @@ from typing import Literal, Optional
from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import BaseModel, Field
from sqlalchemy import select
from sqlalchemy.exc import IntegrityError
from sqlalchemy.ext.asyncio import AsyncSession
from app.database import get_db
@@ -20,7 +21,7 @@ from app.core.auth import (
get_auth_context,
require_website_auth,
)
from app.models import Item, Session, Tryout
from app.models import Item, Session, Tryout, UserAnswer
from app.services.cat_selection import (
CATSelectionError,
get_next_item,
@@ -65,9 +66,6 @@ class SubmitAnswerRequest(BaseModel):
class SubmitAnswerResponse(BaseModel):
"""Response for submitting an answer."""
is_correct: bool
correct_answer: str
explanation: Optional[str] = None
theta: Optional[float] = None
theta_se: Optional[float] = None
@@ -283,6 +281,18 @@ async def submit_answer_endpoint(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Item {request.item_id} not found"
)
existing_answer_result = await db.execute(
select(UserAnswer.id).where(
UserAnswer.session_id == session_id,
UserAnswer.item_id == request.item_id,
)
)
if existing_answer_result.scalar_one_or_none() is not None:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="Item was already answered for this session",
)
# Check correctness
is_correct = request.response.upper() == item.correct_answer.upper()
@@ -290,9 +300,6 @@ async def submit_answer_endpoint(
# Update theta
theta, theta_se = await update_theta(db, session_id, request.item_id, is_correct)
# Create user answer record
from app.models import UserAnswer
user_answer = UserAnswer(
session_id=session_id,
wp_user_id=session.wp_user_id,
@@ -307,12 +314,15 @@ async def submit_answer_endpoint(
)
db.add(user_answer)
await db.commit()
try:
await db.commit()
except IntegrityError as exc:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="Item was already answered for this session",
) from exc
return SubmitAnswerResponse(
is_correct=is_correct,
correct_answer=item.correct_answer,
explanation=item.explanation,
theta=theta,
theta_se=theta_se
)