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>
47 lines
1.7 KiB
Python
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()
|