Security: - Nginx: X-Frame-Options, X-Content-Type-Options, X-XSS-Protection, CSP, Referrer-Policy, Permissions-Policy headers - Redis-backed login rate limiting (survives container restarts) - Admin litellm/models endpoint: api_key moved from GET query param to POST body - Nextcloud credentials moved from localStorage to sessionStorage (cleared on tab close) UX / Layout: - Login: unverified users see inline "Resend verification email" option - QuizPage mobile: TTS Listen button on its own row below question text - QuizPage mobile: Voice selector on its own row in header card (not squashed with timer) - QuizEditPage: scroll position preserved after saving a question edit Quiz Categories: - New QuizCategory model + quiz_categories table - category_id column added to quizzes table - GET/POST/DELETE /api/categories endpoints - Quizzes grouped by category in QuizzesPage; moderators can assign via 🏷 menu - Uncategorized section shown when categories exist Question Bank: - GET /api/questions/bank — search all questions across quizzes - POST /api/questions/from-bank — create new quiz from selected questions (copies, originals untouched) - QuestionBankPage: search, checkbox select, study modal, create quiz form - "Question Bank" link added to Navbar Search: - "View all N questions →" button expands to full question list - Each question has a Study button opening in-place modal with study mode - Summary view shows 2 questions per quiz with Study button Extraction prompt: - Stronger emphasis on correct_answer field with step-by-step letter → full text example - Explicit instruction never to store just the letter Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
29 lines
1.5 KiB
Python
29 lines
1.5 KiB
Python
from datetime import datetime
|
|
|
|
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Text
|
|
from sqlalchemy.orm import relationship
|
|
|
|
from app.database import Base
|
|
from app.models.quiz_category import QuizCategory # noqa — ensures mapper resolves "QuizCategory"
|
|
|
|
|
|
class Quiz(Base):
|
|
__tablename__ = "quizzes"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
section_id = Column(Integer, ForeignKey("sections.id"), nullable=False)
|
|
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
|
|
category_id = Column(Integer, ForeignKey("quiz_categories.id", ondelete="SET NULL"), nullable=True)
|
|
title = Column(String, nullable=False)
|
|
questions_count = Column(Integer, default=0)
|
|
time_limit_minutes = Column(Integer, nullable=True) # null = no limit
|
|
mode = Column(String, default="timed") # timed, learning
|
|
skipped_questions = Column(Text, nullable=True) # JSON list of skipped question texts
|
|
created_at = Column(DateTime, default=datetime.utcnow)
|
|
|
|
section = relationship("Section", back_populates="quizzes")
|
|
user = relationship("User", back_populates="quizzes")
|
|
category = relationship("QuizCategory", back_populates="quizzes", foreign_keys=[category_id])
|
|
questions = relationship("Question", back_populates="quiz", cascade="all, delete-orphan")
|
|
attempts = relationship("QuizAttempt", back_populates="quiz", cascade="all, delete-orphan")
|
|
reminders = relationship("ReminderSchedule", cascade="all, delete-orphan", foreign_keys="ReminderSchedule.quiz_id")
|