"""Render existing plain-text comment bodies to sanitized HTML. Revision ID: 003_render_comments Revises: 002_add_system_settings Create Date: 2026-03-31 Rationale --------- Feature #8 (Markdown rendering) stores rendered HTML in Comment.body at write time. Comments created before this feature are stored as plain text and must be migrated so all bodies are consistently sanitized HTML. Also widens alembic_version.version_num from VARCHAR(32) to VARCHAR(64) to accommodate longer revision ID strings. Apply ----- flask db upgrade Rollback -------- flask db downgrade """ from alembic import op import sqlalchemy as sa from sqlalchemy.orm import Session revision = '003_render_comments' down_revision = '002_add_system_settings' branch_labels = None depends_on = None def upgrade(): bind = op.get_bind() inspector = sa.inspect(bind) # ── 1. Widen alembic_version.version_num to VARCHAR(64) ────────────────── # Flask-Migrate creates this column as VARCHAR(32). Long revision IDs # (>32 chars) cause DataError when Alembic tries to record the new head. # This widen is idempotent — safe to run even if already widened. op.execute( "ALTER TABLE alembic_version " "MODIFY COLUMN version_num VARCHAR(64) NOT NULL" ) # ── 2. Add body_plain_backup column (idempotent) ────────────────────────── existing_cols = [c['name'] for c in inspector.get_columns('comments')] if 'body_plain_backup' not in existing_cols: op.add_column( 'comments', sa.Column('body_plain_backup', sa.Text(), nullable=True), ) # ── 3. Render plain-text comment bodies to sanitized HTML ───────────────── session = Session(bind=bind) from app.services.validation_service import render_comment_body rows = session.execute(sa.text('SELECT id, body FROM comments')).fetchall() updated = 0 for row in rows: comment_id, body = row[0], row[1] if not body: continue # Skip bodies already rendered as HTML (start with an opening tag). if body.lstrip().startswith('<'): continue rendered = render_comment_body(body) session.execute( sa.text( 'UPDATE comments ' 'SET body_plain_backup = :plain, body = :rendered ' 'WHERE id = :id' ), {'plain': body, 'rendered': rendered, 'id': comment_id}, ) updated += 1 session.commit() print(f'[MIGRATION 003] Rendered {updated} plain-text comment bodies to HTML.') def downgrade(): bind = op.get_bind() session = Session(bind=bind) session.execute(sa.text( 'UPDATE comments ' 'SET body = body_plain_backup ' 'WHERE body_plain_backup IS NOT NULL' )) session.commit() op.drop_column('comments', 'body_plain_backup') print('[MIGRATION 003] Downgrade complete — plain-text bodies restored.')