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
31 lines
967 B
Python
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")
|