pdf-quiz-generator/backend/alembic/versions/a9b0c1d2e3f4_question_versions.py
Daniel 817cff569d feat: question version history with restore; TODO list
Editing a question now snapshots its previous state. The last 5 are kept — the
value is undoing a recent mistake, not an audit trail, and an uncapped history of
full question bodies grows without bound (migration a9b0c1d2e3f4).

A restore snapshots the current state first, so the restore is itself undoable.
History is gated by the same per-category grant that gates editing, so it cannot
be read by someone who could not have made the edit. The question editor shows
the versions with their dates and a Restore action.

Also added docs/TODO.md tracking everything requested and not yet delivered:
AI Mode and its citation contract, global search, study-plan editing and
articles-in-blocks, admin settings revamp, image libraries and question folders,
media management, nested article sections with references and per-section notes
and feedback, per-question notes and feedback in the runner, tutorial mode, the
per-question performance table, the Overview dashboard, systems subsystems, and
dropping "Pediatrics" as a discipline.

Tests: 6 new backend (snapshot on edit, cap at five newest-first, restore,
restore is undoable, refused without edit rights, unknown version). Full suites
green: 119 backend, 136 frontend, build clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PpfzbZ1QTLMeVYxM2kyq8m
2026-09-10 02:27:44 +02:00

31 lines
967 B
Python

"""Keep the last few versions of each question, so an edit can be undone.
Capped per question rather than kept forever: the value is undoing a recent
mistake, and an uncapped history of full question bodies grows without bound.
Revision ID: a9b0c1d2e3f4
Revises: z8f9a0b1c2d3
"""
from alembic import op
revision = "a9b0c1d2e3f4"
down_revision = "z8f9a0b1c2d3"
branch_labels = None
depends_on = None
def upgrade():
op.execute("""
CREATE TABLE IF NOT EXISTS question_versions (
id SERIAL PRIMARY KEY,
question_id INTEGER NOT NULL REFERENCES questions(id) ON DELETE CASCADE,
snapshot JSONB NOT NULL,
edited_by INTEGER REFERENCES users(id) ON DELETE SET NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
)
""")
op.execute("CREATE INDEX IF NOT EXISTS ix_qv_question ON question_versions(question_id, created_at DESC)")
def downgrade():
op.execute("DROP TABLE IF EXISTS question_versions")