Phase 1-3 of decoupling plan: - Shared DB, models, infrastructure, browser, config, utils - Event infrastructure (domain_events outbox, bus, processor) - Structured logging - Generic container concept (container_type/container_id) - Alembic migrations for all schema changes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
98 lines
3.3 KiB
Python
98 lines
3.3 KiB
Python
"""add market_places table and nav_tops.market_id
|
|
|
|
Revision ID: b2c3d4e5f6a7
|
|
Revises: a1b2c3d4e5f6
|
|
Create Date: 2026-02-10
|
|
"""
|
|
from alembic import op
|
|
import sqlalchemy as sa
|
|
from sqlalchemy import text
|
|
|
|
revision = 'b2c3d4e5f6a7'
|
|
down_revision = 'a1b2c3d4e5f6'
|
|
branch_labels = None
|
|
depends_on = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
# 1. Create market_places table
|
|
op.create_table(
|
|
'market_places',
|
|
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
|
sa.Column('post_id', sa.Integer(), nullable=False),
|
|
sa.Column('name', sa.String(255), nullable=False),
|
|
sa.Column('slug', sa.String(255), nullable=False),
|
|
sa.Column('description', sa.Text(), nullable=True),
|
|
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
|
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
|
sa.Column('deleted_at', sa.DateTime(timezone=True), nullable=True),
|
|
sa.ForeignKeyConstraint(['post_id'], ['posts.id'], ondelete='CASCADE'),
|
|
sa.PrimaryKeyConstraint('id'),
|
|
)
|
|
op.create_index('ix_market_places_post_id', 'market_places', ['post_id'])
|
|
op.create_index(
|
|
'ux_market_places_slug_active',
|
|
'market_places',
|
|
[sa.text('lower(slug)')],
|
|
unique=True,
|
|
postgresql_where=sa.text('deleted_at IS NULL'),
|
|
)
|
|
|
|
# 2. Add market_id column to nav_tops
|
|
op.add_column(
|
|
'nav_tops',
|
|
sa.Column('market_id', sa.Integer(), nullable=True),
|
|
)
|
|
op.create_foreign_key(
|
|
'fk_nav_tops_market_id',
|
|
'nav_tops',
|
|
'market_places',
|
|
['market_id'],
|
|
['id'],
|
|
ondelete='SET NULL',
|
|
)
|
|
op.create_index('ix_nav_tops_market_id', 'nav_tops', ['market_id'])
|
|
|
|
# 3. Backfill: create default MarketPlace for the 'market' page
|
|
conn = op.get_bind()
|
|
|
|
# Find the market page
|
|
result = conn.execute(text("""
|
|
SELECT id FROM posts
|
|
WHERE slug = 'market' AND is_page = true AND deleted_at IS NULL
|
|
LIMIT 1
|
|
"""))
|
|
row = result.fetchone()
|
|
if row:
|
|
post_id = row[0]
|
|
|
|
# Insert the default market
|
|
conn.execute(text("""
|
|
INSERT INTO market_places (post_id, name, slug, created_at, updated_at)
|
|
VALUES (:post_id, 'Suma Market', 'suma-market', now(), now())
|
|
"""), {"post_id": post_id})
|
|
|
|
# Get the new market_places id
|
|
market_row = conn.execute(text("""
|
|
SELECT id FROM market_places
|
|
WHERE slug = 'suma-market' AND deleted_at IS NULL
|
|
LIMIT 1
|
|
""")).fetchone()
|
|
|
|
if market_row:
|
|
market_id = market_row[0]
|
|
# Assign all active nav_tops to this market
|
|
conn.execute(text("""
|
|
UPDATE nav_tops SET market_id = :market_id
|
|
WHERE deleted_at IS NULL
|
|
"""), {"market_id": market_id})
|
|
|
|
|
|
def downgrade() -> None:
|
|
op.drop_index('ix_nav_tops_market_id', table_name='nav_tops')
|
|
op.drop_constraint('fk_nav_tops_market_id', 'nav_tops', type_='foreignkey')
|
|
op.drop_column('nav_tops', 'market_id')
|
|
op.drop_index('ux_market_places_slug_active', table_name='market_places')
|
|
op.drop_index('ix_market_places_post_id', table_name='market_places')
|
|
op.drop_table('market_places')
|