This commit is contained in:
2026-03-31 10:55:40 -04:00
parent 702fc2cd91
commit d94268f2b9
4 changed files with 189 additions and 31 deletions
+26 -17
View File
@@ -29,23 +29,32 @@ depends_on = None
def upgrade():
op.create_table(
'system_settings',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('key', sa.String(100), nullable=False),
sa.Column('value', sa.String(500), nullable=False),
sa.Column('description', sa.String(256), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.PrimaryKeyConstraint('id'),
)
op.create_index('ix_system_settings_key', 'system_settings', ['key'], unique=True)
# Seed the default: registration is open on fresh installs.
op.execute(
"INSERT INTO system_settings (key, value, description, updated_at) "
"VALUES ('registration_enabled', 'true', "
"'Allow new users to self-register via /auth/register', NOW())"
)
# Guard: db.create_all() on first startup already creates this table.
# Only create it if it doesn't exist so the migration is idempotent.
bind = op.get_bind()
inspector = sa.inspect(bind)
if 'system_settings' not in inspector.get_table_names():
op.create_table(
'system_settings',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('key', sa.String(100), nullable=False),
sa.Column('value', sa.String(500), nullable=False),
sa.Column('description', sa.String(256), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.PrimaryKeyConstraint('id'),
)
op.create_index('ix_system_settings_key', 'system_settings', ['key'], unique=True)
op.execute(
"INSERT INTO system_settings (key, value, description, updated_at) "
"VALUES ('registration_enabled', 'true', "
"'Allow new users to self-register via /auth/register', NOW())"
)
# If the table already exists (created by db.create_all), ensure the
# index exists — create_all does not create named Alembic indexes.
else:
existing_indexes = [idx['name'] for idx in inspector.get_indexes('system_settings')]
if 'ix_system_settings_key' not in existing_indexes:
op.create_index('ix_system_settings_key', 'system_settings', ['key'], unique=True)
def downgrade():
@@ -0,0 +1,98 @@
"""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.')