pdf-quiz-generator/backend/app/utils/quiz_questions.py
Daniel f2d15c3c57 Junction table for shared questions; verification blocking; semantic bank search; bug fixes
Question sharing (edits now propagate):
- quiz_question_links junction table: quizzes reference questions, not own copies
- Existing questions migrated to junction (idempotent ON CONFLICT DO NOTHING)
- from-bank and from-category create quiz by adding junction links (no copying)
- Editing a question via QuizEditPage or bank Edit modal updates the ONE record
  visible in all quizzes that reference it
- Delete from quiz: only deletes Question record if no other quiz references it
- quiz_service: fixed position counter (was using len() incorrectly, now enumerate)
- question.quiz_id renamed to source_quiz_id in Python (DB column still quiz_id)
- All attempts/quizzes/search code updated to use junction helper or source_quiz_id

Bug fixes:
- PostgreSQL en_US.utf8 collation B-tree index corruption on users.email
  Fixed by ALTERing column to COLLATE "C" in migration + immediate repair
- get_current_user now blocks unverified users on ALL endpoints (403 + X-Unverified header)
  api/client.js intercepts X-Unverified=true and auto-logs out to /login
- Search (quiz + bank): correct_answer and explanation now included in matching_questions
  so study modals work correctly
- Keyword search: order_by updated to use source_quiz_id
- Bulk category: changed from PATCH with body array to POST with Pydantic body
  (fixes null category_id removal)
- Question bank: edit button added, answer highlighting removed from list view
- QuestionBankPage: Keyword/Semantic/Hybrid search toggle
- Login: login error message stays visible (removed immediate reload)

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 00:55:13 +02:00

47 lines
1.7 KiB
Python

"""Helpers for managing the quiz ↔ question junction table."""
from sqlalchemy.orm import Session
from app.models.question import Question
from app.models.quiz_question_link import QuizQuestionLink
def get_quiz_questions(db: Session, quiz_id: int) -> list[Question]:
"""Fetch questions for a quiz in position order via junction table."""
links = (
db.query(QuizQuestionLink)
.filter(QuizQuestionLink.quiz_id == quiz_id)
.order_by(QuizQuestionLink.position)
.all()
)
if not links:
return []
q_map = {
q.id: q
for q in db.query(Question).filter(Question.id.in_([l.question_id for l in links])).all()
}
return [q_map[l.question_id] for l in links if l.question_id in q_map]
def add_questions_to_quiz(db: Session, quiz_id: int, question_ids: list[int], start_pos: int = 0):
"""Add question links to a quiz (skips duplicates)."""
existing = {
l.question_id
for l in db.query(QuizQuestionLink).filter(QuizQuestionLink.quiz_id == quiz_id).all()
}
for i, qid in enumerate(question_ids):
if qid not in existing:
db.add(QuizQuestionLink(quiz_id=quiz_id, question_id=qid, position=start_pos + i))
def question_in_quiz(db: Session, quiz_id: int, question_id: int) -> bool:
return db.query(QuizQuestionLink).filter(
QuizQuestionLink.quiz_id == quiz_id,
QuizQuestionLink.question_id == question_id,
).first() is not None
def remove_question_from_quiz(db: Session, quiz_id: int, question_id: int):
db.query(QuizQuestionLink).filter(
QuizQuestionLink.quiz_id == quiz_id,
QuizQuestionLink.question_id == question_id,
).delete()