UI/UX Polishing: CBT dashboard, sidebar tiers, landing pricing, and exam disclaimer
This commit is contained in:
@@ -85,6 +85,40 @@ def get_test_by_id(test_id: UUID, user_id: UUID, session: Session = Depends(get_
|
||||
raise HTTPException(status_code=404, detail="Requested examination structure not found.")
|
||||
return test
|
||||
|
||||
@app.get("/api/tests/{test_id}/questions", response_model=List[models.Question], tags=["CBT Engine"])
|
||||
def get_questions_by_test_id(test_id: UUID, session: Session = Depends(get_session)):
|
||||
test = session.get(models.Test, test_id)
|
||||
if not test:
|
||||
raise HTTPException(status_code=404, detail="Requested examination structure not found.")
|
||||
|
||||
# Extract all question IDs from sections
|
||||
question_ids = []
|
||||
for section_name, section_data in test.sections.items():
|
||||
question_ids.extend(section_data.get("questions", []))
|
||||
|
||||
# Fetch all questions in one query
|
||||
if not question_ids:
|
||||
return []
|
||||
|
||||
# Ensure they are UUID objects
|
||||
import uuid
|
||||
valid_ids = []
|
||||
for q_id in question_ids:
|
||||
if isinstance(q_id, str):
|
||||
try:
|
||||
valid_ids.append(uuid.UUID(q_id))
|
||||
except ValueError:
|
||||
continue
|
||||
elif isinstance(q_id, uuid.UUID):
|
||||
valid_ids.append(q_id)
|
||||
|
||||
query = select(models.Question).where(models.Question.id.in_(valid_ids))
|
||||
questions = session.exec(query).all()
|
||||
|
||||
# Order them to match the section order if needed, but the frontend currently doesn't rely on it strictly
|
||||
# except for section bounds which it reconstructs.
|
||||
return questions
|
||||
|
||||
@app.get("/api/questions/{question_id}", response_model=models.Question, tags=["CBT Engine"])
|
||||
def get_question_by_id(question_id: UUID, session: Session = Depends(get_session)):
|
||||
question = session.get(models.Question, question_id)
|
||||
@@ -143,7 +177,7 @@ def submit_test_attempt(payload: Dict[str, Any], session: Session = Depends(get_
|
||||
if not question:
|
||||
continue
|
||||
|
||||
correct_ans = question.correct_answer
|
||||
correct_ans = question.correct_answer or {}
|
||||
# Support both {"value": "A"} and {"correct": "A"} key formats
|
||||
correct_value = correct_ans.get("value") or correct_ans.get("correct")
|
||||
|
||||
@@ -151,8 +185,13 @@ def submit_test_attempt(payload: Dict[str, Any], session: Session = Depends(get_
|
||||
# Subjective: award credit for any non-trivial written answer
|
||||
student_text = str(selected or "").strip()
|
||||
is_correct = len(student_text) > 5
|
||||
elif isinstance(correct_value, list):
|
||||
# Multiple Choice (Multi-Correct)
|
||||
selected_list = sorted([s.strip() for s in str(selected or "").split(",") if s.strip()])
|
||||
correct_list = sorted([str(c).strip() for c in correct_value])
|
||||
is_correct = (selected_list == correct_list)
|
||||
else:
|
||||
is_correct = bool(correct_value) and correct_value == selected
|
||||
is_correct = bool(correct_value) and str(correct_value).strip() == str(selected or "").strip()
|
||||
|
||||
if is_correct:
|
||||
correct_count += 1
|
||||
@@ -241,8 +280,16 @@ def submit_mistake_review(payload: Dict[str, Any], session: Session = Depends(ge
|
||||
raise HTTPException(status_code=404, detail="Mistake not found.")
|
||||
|
||||
question = session.get(models.Question, mistake.question_id)
|
||||
correct_value = question.correct_answer.get("value") or question.correct_answer.get("correct")
|
||||
is_correct = correct_value == selected_answer
|
||||
correct_ans = question.correct_answer or {}
|
||||
correct_value = correct_ans.get("value") or correct_ans.get("correct")
|
||||
|
||||
if isinstance(correct_value, list):
|
||||
# Multiple Choice (Multi-Correct)
|
||||
selected_list = sorted([s.strip() for s in str(selected_answer or "").split(",") if s.strip()])
|
||||
correct_list = sorted([str(c).strip() for c in correct_value])
|
||||
is_correct = (selected_list == correct_list)
|
||||
else:
|
||||
is_correct = bool(correct_value) and str(correct_value).strip() == str(selected_answer or "").strip()
|
||||
|
||||
q_value = 4 if is_correct else 1
|
||||
days_since_creation = (datetime.utcnow() - mistake.created_at).days
|
||||
@@ -450,6 +497,21 @@ def update_user_profile(user_id: UUID, payload: UserUpdate, session: Session = D
|
||||
session.refresh(user)
|
||||
return user
|
||||
|
||||
class UserSessionUpdate(BaseModel):
|
||||
session_token: str
|
||||
|
||||
@app.post("/api/users/{user_id}/session", response_model=models.User, tags=["Users"])
|
||||
def update_user_session(user_id: UUID, payload: UserSessionUpdate, session: Session = Depends(get_session)):
|
||||
user = session.get(models.User, user_id)
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
user.active_session_token = payload.session_token
|
||||
session.add(user)
|
||||
session.commit()
|
||||
session.refresh(user)
|
||||
return user
|
||||
|
||||
from storage import upload_file
|
||||
|
||||
@app.post("/api/users/{user_id}/upload-avatar", tags=["Users"])
|
||||
|
||||
@@ -62,6 +62,7 @@ class User(SQLModel, table=True):
|
||||
longest_streak: int = Field(default=0)
|
||||
created_at: datetime = Field(default_factory=datetime.utcnow)
|
||||
last_active: datetime = Field(default_factory=datetime.utcnow)
|
||||
active_session_token: Optional[str] = None
|
||||
|
||||
class Parent(SQLModel, table=True):
|
||||
id: UUID = Field(default_factory=uuid4, primary_key=True)
|
||||
|
||||
Reference in New Issue
Block a user