feat: extract shared infrastructure from shared_lib
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>
This commit is contained in:
68
alembic/env.py
Normal file
68
alembic/env.py
Normal file
@@ -0,0 +1,68 @@
|
||||
from __future__ import annotations
|
||||
import os, sys
|
||||
from logging.config import fileConfig
|
||||
from alembic import context
|
||||
from sqlalchemy import engine_from_config, pool
|
||||
|
||||
config = context.config
|
||||
|
||||
if config.config_file_name is not None:
|
||||
try:
|
||||
fileConfig(config.config_file_name)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Add project root so all app model packages are importable
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")))
|
||||
|
||||
from shared.db.base import Base
|
||||
|
||||
# Import ALL models so Base.metadata sees every table
|
||||
import shared.models # noqa: F401 User, KV, MagicLink, MenuItem, Ghost*
|
||||
import blog.models # noqa: F401 Post, Author, Tag, Snippet, TagGroup
|
||||
import market.models # noqa: F401 Product, CartItem, MarketPlace, etc.
|
||||
import cart.models # noqa: F401 Order, OrderItem, PageConfig
|
||||
import events.models # noqa: F401 Calendar, CalendarEntry, Ticket, etc.
|
||||
|
||||
target_metadata = Base.metadata
|
||||
|
||||
def _get_url() -> str:
|
||||
url = os.getenv(
|
||||
"ALEMBIC_DATABASE_URL",
|
||||
os.getenv("DATABASE_URL", config.get_main_option("sqlalchemy.url") or "")
|
||||
)
|
||||
print(url)
|
||||
return url
|
||||
|
||||
def run_migrations_offline() -> None:
|
||||
url = _get_url()
|
||||
context.configure(
|
||||
url=url,
|
||||
target_metadata=target_metadata,
|
||||
literal_binds=True,
|
||||
dialect_opts={"paramstyle": "named"},
|
||||
compare_type=True,
|
||||
)
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
def run_migrations_online() -> None:
|
||||
url = _get_url()
|
||||
if url:
|
||||
config.set_main_option("sqlalchemy.url", url)
|
||||
|
||||
connectable = engine_from_config(
|
||||
config.get_section(config.config_ini_section, {}),
|
||||
prefix="sqlalchemy.",
|
||||
poolclass=pool.NullPool,
|
||||
)
|
||||
|
||||
with connectable.connect() as connection:
|
||||
context.configure(connection=connection, target_metadata=target_metadata, compare_type=True)
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
if context.is_offline_mode():
|
||||
run_migrations_offline()
|
||||
else:
|
||||
run_migrations_online()
|
||||
24
alembic/script.py.mako
Normal file
24
alembic/script.py.mako
Normal file
@@ -0,0 +1,24 @@
|
||||
<%text>
|
||||
# Alembic migration script template
|
||||
</%text>
|
||||
"""empty message
|
||||
|
||||
Revision ID: ${up_revision}
|
||||
Revises: ${down_revision | comma,n}
|
||||
Create Date: ${create_date}
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = ${repr(up_revision)}
|
||||
down_revision = ${repr(down_revision)}
|
||||
branch_labels = ${repr(branch_labels)}
|
||||
depends_on = ${repr(depends_on)}
|
||||
|
||||
def upgrade() -> None:
|
||||
${upgrades if upgrades else "pass"}
|
||||
|
||||
def downgrade() -> None:
|
||||
${downgrades if downgrades else "pass"}
|
||||
33
alembic/versions/0001_initial_schem.py
Normal file
33
alembic/versions/0001_initial_schem.py
Normal file
@@ -0,0 +1,33 @@
|
||||
"""Initial database schema from schema.sql"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
import pathlib
|
||||
|
||||
# revision identifiers, used by Alembic
|
||||
revision = '0001_initial_schema'
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
def upgrade():
|
||||
return
|
||||
schema_path = pathlib.Path(__file__).parent.parent.parent / "schema.sql"
|
||||
with open(schema_path, encoding="utf-8") as f:
|
||||
sql = f.read()
|
||||
conn = op.get_bind()
|
||||
conn.execute(sa.text(sql))
|
||||
|
||||
def downgrade():
|
||||
return
|
||||
# Drop all user-defined tables in the 'public' schema
|
||||
conn = op.get_bind()
|
||||
conn.execute(sa.text("""
|
||||
DO $$ DECLARE
|
||||
r RECORD;
|
||||
BEGIN
|
||||
FOR r IN (SELECT tablename FROM pg_tables WHERE schemaname = 'public') LOOP
|
||||
EXECUTE 'DROP TABLE IF EXISTS public.' || quote_ident(r.tablename) || ' CASCADE';
|
||||
END LOOP;
|
||||
END $$;
|
||||
"""))
|
||||
78
alembic/versions/0002_add_cart_items.py
Normal file
78
alembic/versions/0002_add_cart_items.py
Normal file
@@ -0,0 +1,78 @@
|
||||
"""Add cart_items table for shopping cart"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "0002_add_cart_items"
|
||||
down_revision = "0001_initial_schema"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"cart_items",
|
||||
sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True),
|
||||
|
||||
# Either a logged-in user *or* an anonymous session_id
|
||||
sa.Column(
|
||||
"user_id",
|
||||
sa.Integer(),
|
||||
sa.ForeignKey("users.id", ondelete="CASCADE"),
|
||||
nullable=True,
|
||||
),
|
||||
sa.Column("session_id", sa.String(length=128), nullable=True),
|
||||
|
||||
# IMPORTANT: reference products.id (PK), not slug
|
||||
sa.Column(
|
||||
"product_id",
|
||||
sa.Integer(),
|
||||
sa.ForeignKey("products.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
),
|
||||
|
||||
sa.Column(
|
||||
"quantity",
|
||||
sa.Integer(),
|
||||
nullable=False,
|
||||
server_default="1",
|
||||
),
|
||||
sa.Column(
|
||||
"created_at",
|
||||
sa.DateTime(timezone=True),
|
||||
nullable=False,
|
||||
server_default=sa.text("now()"),
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at",
|
||||
sa.DateTime(timezone=True),
|
||||
nullable=False,
|
||||
server_default=sa.text("now()"),
|
||||
),
|
||||
sa.Column(
|
||||
"deleted_at",
|
||||
sa.DateTime(timezone=True),
|
||||
nullable=True,
|
||||
),
|
||||
)
|
||||
|
||||
# Indexes to speed up cart lookups
|
||||
op.create_index(
|
||||
"ix_cart_items_user_product",
|
||||
"cart_items",
|
||||
["user_id", "product_id"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
"ix_cart_items_session_product",
|
||||
"cart_items",
|
||||
["session_id", "product_id"],
|
||||
unique=False,
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index("ix_cart_items_session_product", table_name="cart_items")
|
||||
op.drop_index("ix_cart_items_user_product", table_name="cart_items")
|
||||
op.drop_table("cart_items")
|
||||
118
alembic/versions/0003_add_orders.py
Normal file
118
alembic/versions/0003_add_orders.py
Normal file
@@ -0,0 +1,118 @@
|
||||
"""Add orders and order_items tables for checkout"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "0003_add_orders"
|
||||
down_revision = "0002_add_cart_items"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"orders",
|
||||
sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True),
|
||||
sa.Column("user_id", sa.Integer(), sa.ForeignKey("users.id"), nullable=True),
|
||||
sa.Column("session_id", sa.String(length=64), nullable=True),
|
||||
|
||||
sa.Column(
|
||||
"status",
|
||||
sa.String(length=32),
|
||||
nullable=False,
|
||||
server_default="pending",
|
||||
),
|
||||
sa.Column(
|
||||
"currency",
|
||||
sa.String(length=16),
|
||||
nullable=False,
|
||||
server_default="GBP",
|
||||
),
|
||||
sa.Column(
|
||||
"total_amount",
|
||||
sa.Numeric(12, 2),
|
||||
nullable=False,
|
||||
),
|
||||
|
||||
# SumUp integration fields
|
||||
sa.Column("sumup_checkout_id", sa.String(length=128), nullable=True),
|
||||
sa.Column("sumup_status", sa.String(length=32), nullable=True),
|
||||
sa.Column("sumup_hosted_url", sa.Text(), nullable=True),
|
||||
|
||||
sa.Column(
|
||||
"created_at",
|
||||
sa.DateTime(timezone=True),
|
||||
nullable=False,
|
||||
server_default=sa.func.now(),
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at",
|
||||
sa.DateTime(timezone=True),
|
||||
nullable=False,
|
||||
server_default=sa.func.now(),
|
||||
),
|
||||
)
|
||||
|
||||
# Indexes to match model hints (session_id + sumup_checkout_id index=True)
|
||||
op.create_index(
|
||||
"ix_orders_session_id",
|
||||
"orders",
|
||||
["session_id"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
"ix_orders_sumup_checkout_id",
|
||||
"orders",
|
||||
["sumup_checkout_id"],
|
||||
unique=False,
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
"order_items",
|
||||
sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True),
|
||||
sa.Column(
|
||||
"order_id",
|
||||
sa.Integer(),
|
||||
sa.ForeignKey("orders.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column(
|
||||
"product_id",
|
||||
sa.Integer(),
|
||||
sa.ForeignKey("products.id"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column("product_title", sa.String(length=512), nullable=True),
|
||||
|
||||
sa.Column(
|
||||
"quantity",
|
||||
sa.Integer(),
|
||||
nullable=False,
|
||||
server_default="1",
|
||||
),
|
||||
sa.Column(
|
||||
"unit_price",
|
||||
sa.Numeric(12, 2),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column(
|
||||
"currency",
|
||||
sa.String(length=16),
|
||||
nullable=False,
|
||||
server_default="GBP",
|
||||
),
|
||||
sa.Column(
|
||||
"created_at",
|
||||
sa.DateTime(timezone=True),
|
||||
nullable=False,
|
||||
server_default=sa.func.now(),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_table("order_items")
|
||||
op.drop_index("ix_orders_sumup_checkout_id", table_name="orders")
|
||||
op.drop_index("ix_orders_session_id", table_name="orders")
|
||||
op.drop_table("orders")
|
||||
27
alembic/versions/0004_add_sumup_reference.py
Normal file
27
alembic/versions/0004_add_sumup_reference.py
Normal file
@@ -0,0 +1,27 @@
|
||||
"""Add sumup_reference to orders"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
revision = "0004_add_sumup_reference"
|
||||
down_revision = "0003_add_orders"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column(
|
||||
"orders",
|
||||
sa.Column("sumup_reference", sa.String(length=255), nullable=True),
|
||||
)
|
||||
op.create_index(
|
||||
"ix_orders_sumup_reference",
|
||||
"orders",
|
||||
["sumup_reference"],
|
||||
unique=False,
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index("ix_orders_sumup_reference", table_name="orders")
|
||||
op.drop_column("orders", "sumup_reference")
|
||||
27
alembic/versions/0005_add_description.py
Normal file
27
alembic/versions/0005_add_description.py
Normal file
@@ -0,0 +1,27 @@
|
||||
"""Add description field to orders"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
revision = "0005_add_description"
|
||||
down_revision = "0004_add_sumup_reference"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column(
|
||||
"orders",
|
||||
sa.Column("description", sa.Text(), nullable=True),
|
||||
)
|
||||
op.create_index(
|
||||
"ix_orders_description",
|
||||
"orders",
|
||||
["description"],
|
||||
unique=False,
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index("ix_orders_description", table_name="orders")
|
||||
op.drop_column("orders", "description")
|
||||
28
alembic/versions/0006_update_calendar_entries.py
Normal file
28
alembic/versions/0006_update_calendar_entries.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
revision = '0006_update_calendar_entries'
|
||||
down_revision = '0005_add_description' # use the appropriate previous revision ID
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
def upgrade():
|
||||
# Add user_id and session_id columns
|
||||
op.add_column('calendar_entries', sa.Column('user_id', sa.Integer(), nullable=True))
|
||||
op.create_foreign_key('fk_calendar_entries_user_id', 'calendar_entries', 'users', ['user_id'], ['id'])
|
||||
op.add_column('calendar_entries', sa.Column('session_id', sa.String(length=128), nullable=True))
|
||||
# Add state and cost columns
|
||||
op.add_column('calendar_entries', sa.Column('state', sa.String(length=20), nullable=False, server_default='pending'))
|
||||
op.add_column('calendar_entries', sa.Column('cost', sa.Numeric(10,2), nullable=False, server_default='10'))
|
||||
# (Optional) Create indexes on the new columns
|
||||
op.create_index('ix_calendar_entries_user_id', 'calendar_entries', ['user_id'])
|
||||
op.create_index('ix_calendar_entries_session_id', 'calendar_entries', ['session_id'])
|
||||
|
||||
def downgrade():
|
||||
op.drop_index('ix_calendar_entries_session_id', table_name='calendar_entries')
|
||||
op.drop_index('ix_calendar_entries_user_id', table_name='calendar_entries')
|
||||
op.drop_column('calendar_entries', 'cost')
|
||||
op.drop_column('calendar_entries', 'state')
|
||||
op.drop_column('calendar_entries', 'session_id')
|
||||
op.drop_constraint('fk_calendar_entries_user_id', 'calendar_entries', type_='foreignkey')
|
||||
op.drop_column('calendar_entries', 'user_id')
|
||||
50
alembic/versions/0007_add_oid_entries.py
Normal file
50
alembic/versions/0007_add_oid_entries.py
Normal file
@@ -0,0 +1,50 @@
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
revision = "0007_add_oid_entries"
|
||||
down_revision = "0006_update_calendar_entries"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# Add order_id column
|
||||
op.add_column(
|
||||
"calendar_entries",
|
||||
sa.Column("order_id", sa.Integer(), nullable=True),
|
||||
)
|
||||
op.create_foreign_key(
|
||||
"fk_calendar_entries_order_id",
|
||||
"calendar_entries",
|
||||
"orders",
|
||||
["order_id"],
|
||||
["id"],
|
||||
ondelete="SET NULL",
|
||||
)
|
||||
op.create_index(
|
||||
"ix_calendar_entries_order_id",
|
||||
"calendar_entries",
|
||||
["order_id"],
|
||||
unique=False,
|
||||
)
|
||||
|
||||
# Optional: add an index on state if you want faster queries by state
|
||||
op.create_index(
|
||||
"ix_calendar_entries_state",
|
||||
"calendar_entries",
|
||||
["state"],
|
||||
unique=False,
|
||||
)
|
||||
|
||||
|
||||
def downgrade():
|
||||
# Drop indexes and FK in reverse order
|
||||
op.drop_index("ix_calendar_entries_state", table_name="calendar_entries")
|
||||
|
||||
op.drop_index("ix_calendar_entries_order_id", table_name="calendar_entries")
|
||||
op.drop_constraint(
|
||||
"fk_calendar_entries_order_id",
|
||||
"calendar_entries",
|
||||
type_="foreignkey",
|
||||
)
|
||||
op.drop_column("calendar_entries", "order_id")
|
||||
33
alembic/versions/0008_add_flexible_to_slots.py
Normal file
33
alembic/versions/0008_add_flexible_to_slots.py
Normal file
@@ -0,0 +1,33 @@
|
||||
"""add flexible flag to calendar_slots
|
||||
|
||||
Revision ID: 0008_add_flexible_to_calendar_slots
|
||||
Revises: 0007_add_order_id_to_calendar_entries
|
||||
Create Date: 2025-12-06 12:34:56.000000
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "0008_add_flexible_to_slots"
|
||||
down_revision = "0007_add_oid_entries"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column(
|
||||
"calendar_slots",
|
||||
sa.Column(
|
||||
"flexible",
|
||||
sa.Boolean(),
|
||||
nullable=False,
|
||||
server_default=sa.false(), # set existing rows to False
|
||||
),
|
||||
)
|
||||
# Optional: drop server_default so future inserts must supply a value
|
||||
op.alter_column("calendar_slots", "flexible", server_default=None)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_column("calendar_slots", "flexible")
|
||||
54
alembic/versions/0009_add_slot_id_to_entries.py
Normal file
54
alembic/versions/0009_add_slot_id_to_entries.py
Normal file
@@ -0,0 +1,54 @@
|
||||
"""add slot_id to calendar_entries
|
||||
|
||||
Revision ID: 0009_add_slot_id_to_entries
|
||||
Revises: 0008_add_flexible_to_slots
|
||||
Create Date: 2025-12-06 13:00:00.000000
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "0009_add_slot_id_to_entries"
|
||||
down_revision = "0008_add_flexible_to_slots"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# Add slot_id column as nullable initially
|
||||
op.add_column(
|
||||
"calendar_entries",
|
||||
sa.Column(
|
||||
"slot_id",
|
||||
sa.Integer(),
|
||||
nullable=True,
|
||||
),
|
||||
)
|
||||
|
||||
# Add foreign key constraint
|
||||
op.create_foreign_key(
|
||||
"fk_calendar_entries_slot_id_calendar_slots",
|
||||
"calendar_entries",
|
||||
"calendar_slots",
|
||||
["slot_id"],
|
||||
["id"],
|
||||
ondelete="SET NULL",
|
||||
)
|
||||
|
||||
# Add index for better query performance
|
||||
op.create_index(
|
||||
"ix_calendar_entries_slot_id",
|
||||
"calendar_entries",
|
||||
["slot_id"],
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index("ix_calendar_entries_slot_id", table_name="calendar_entries")
|
||||
op.drop_constraint(
|
||||
"fk_calendar_entries_slot_id_calendar_slots",
|
||||
"calendar_entries",
|
||||
type_="foreignkey",
|
||||
)
|
||||
op.drop_column("calendar_entries", "slot_id")
|
||||
64
alembic/versions/0010_add_post_likes.py
Normal file
64
alembic/versions/0010_add_post_likes.py
Normal file
@@ -0,0 +1,64 @@
|
||||
"""Add post_likes table for liking blog posts
|
||||
|
||||
Revision ID: 0010_add_post_likes
|
||||
Revises: 0009_add_slot_id_to_entries
|
||||
Create Date: 2025-12-07 13:00:00.000000
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "0010_add_post_likes"
|
||||
down_revision = "0009_add_slot_id_to_entries"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"post_likes",
|
||||
sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True),
|
||||
sa.Column(
|
||||
"user_id",
|
||||
sa.Integer(),
|
||||
sa.ForeignKey("users.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column(
|
||||
"post_id",
|
||||
sa.Integer(),
|
||||
sa.ForeignKey("posts.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column(
|
||||
"created_at",
|
||||
sa.DateTime(timezone=True),
|
||||
nullable=False,
|
||||
server_default=sa.text("now()"),
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at",
|
||||
sa.DateTime(timezone=True),
|
||||
nullable=False,
|
||||
server_default=sa.text("now()"),
|
||||
),
|
||||
sa.Column(
|
||||
"deleted_at",
|
||||
sa.DateTime(timezone=True),
|
||||
nullable=True,
|
||||
),
|
||||
)
|
||||
|
||||
# Index for fast user+post lookups
|
||||
op.create_index(
|
||||
"ix_post_likes_user_post",
|
||||
"post_likes",
|
||||
["user_id", "post_id"],
|
||||
unique=False,
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index("ix_post_likes_user_post", table_name="post_likes")
|
||||
op.drop_table("post_likes")
|
||||
43
alembic/versions/0011_add_entry_tickets.py
Normal file
43
alembic/versions/0011_add_entry_tickets.py
Normal file
@@ -0,0 +1,43 @@
|
||||
"""Add ticket_price and ticket_count to calendar_entries
|
||||
|
||||
Revision ID: 0011_add_entry_tickets
|
||||
Revises: 0010_add_post_likes
|
||||
Create Date: 2025-12-07 14:00:00.000000
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects.postgresql import NUMERIC
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "0011_add_entry_tickets"
|
||||
down_revision = "0010_add_post_likes"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# Add ticket_price column (nullable - NULL means no tickets)
|
||||
op.add_column(
|
||||
"calendar_entries",
|
||||
sa.Column(
|
||||
"ticket_price",
|
||||
NUMERIC(10, 2),
|
||||
nullable=True,
|
||||
),
|
||||
)
|
||||
|
||||
# Add ticket_count column (nullable - NULL means unlimited)
|
||||
op.add_column(
|
||||
"calendar_entries",
|
||||
sa.Column(
|
||||
"ticket_count",
|
||||
sa.Integer(),
|
||||
nullable=True,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_column("calendar_entries", "ticket_count")
|
||||
op.drop_column("calendar_entries", "ticket_price")
|
||||
41
alembic/versions/47fc53fc0d2b_add_ticket_types_table.py
Normal file
41
alembic/versions/47fc53fc0d2b_add_ticket_types_table.py
Normal file
@@ -0,0 +1,41 @@
|
||||
|
||||
# Alembic migration script template
|
||||
|
||||
"""add ticket_types table
|
||||
|
||||
Revision ID: 47fc53fc0d2b
|
||||
Revises: a9f54e4eaf02
|
||||
Create Date: 2025-12-08 07:29:11.422435
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '47fc53fc0d2b'
|
||||
down_revision = 'a9f54e4eaf02'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
'ticket_types',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('entry_id', sa.Integer(), nullable=False),
|
||||
sa.Column('name', sa.String(length=255), nullable=False),
|
||||
sa.Column('cost', sa.Numeric(precision=10, scale=2), nullable=False),
|
||||
sa.Column('count', sa.Integer(), nullable=False),
|
||||
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column('deleted_at', sa.DateTime(timezone=True), nullable=True),
|
||||
sa.ForeignKeyConstraint(['entry_id'], ['calendar_entries.id'], ondelete='CASCADE'),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index('ix_ticket_types_entry_id', 'ticket_types', ['entry_id'], unique=False)
|
||||
op.create_index('ix_ticket_types_name', 'ticket_types', ['name'], unique=False)
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index('ix_ticket_types_name', table_name='ticket_types')
|
||||
op.drop_index('ix_ticket_types_entry_id', table_name='ticket_types')
|
||||
op.drop_table('ticket_types')
|
||||
36
alembic/versions/6cb124491c9d_entry_posts.py
Normal file
36
alembic/versions/6cb124491c9d_entry_posts.py
Normal file
@@ -0,0 +1,36 @@
|
||||
|
||||
# Alembic migration script template
|
||||
|
||||
"""Add calendar_entry_posts association table
|
||||
|
||||
Revision ID: 6cb124491c9d
|
||||
Revises: 0011_add_entry_tickets
|
||||
Create Date: 2025-12-07 03:40:49.194068
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects.postgresql import TIMESTAMP
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '6cb124491c9d'
|
||||
down_revision = '0011_add_entry_tickets'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
'calendar_entry_posts',
|
||||
sa.Column('id', sa.Integer(), primary_key=True, autoincrement=True),
|
||||
sa.Column('entry_id', sa.Integer(), sa.ForeignKey('calendar_entries.id', ondelete='CASCADE'), nullable=False),
|
||||
sa.Column('post_id', sa.Integer(), sa.ForeignKey('posts.id', ondelete='CASCADE'), nullable=False),
|
||||
sa.Column('created_at', TIMESTAMP(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
sa.Column('deleted_at', TIMESTAMP(timezone=True), nullable=True),
|
||||
)
|
||||
op.create_index('ix_entry_posts_entry_id', 'calendar_entry_posts', ['entry_id'])
|
||||
op.create_index('ix_entry_posts_post_id', 'calendar_entry_posts', ['post_id'])
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index('ix_entry_posts_post_id', 'calendar_entry_posts')
|
||||
op.drop_index('ix_entry_posts_entry_id', 'calendar_entry_posts')
|
||||
op.drop_table('calendar_entry_posts')
|
||||
74
alembic/versions/a1b2c3d4e5f6_add_page_configs_table.py
Normal file
74
alembic/versions/a1b2c3d4e5f6_add_page_configs_table.py
Normal file
@@ -0,0 +1,74 @@
|
||||
"""add page_configs table
|
||||
|
||||
Revision ID: a1b2c3d4e5f6
|
||||
Revises: f6d4a1b2c3e7
|
||||
Create Date: 2026-02-10
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy import text
|
||||
|
||||
revision = 'a1b2c3d4e5f6'
|
||||
down_revision = 'f6d4a1b2c3e7'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
'page_configs',
|
||||
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column('post_id', sa.Integer(), nullable=False),
|
||||
sa.Column('features', sa.JSON(), server_default='{}', nullable=False),
|
||||
sa.Column('sumup_merchant_code', sa.String(64), nullable=True),
|
||||
sa.Column('sumup_api_key', sa.Text(), nullable=True),
|
||||
sa.Column('sumup_checkout_prefix', sa.String(64), 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'),
|
||||
sa.UniqueConstraint('post_id'),
|
||||
)
|
||||
|
||||
# Backfill: create PageConfig for every existing page
|
||||
conn = op.get_bind()
|
||||
|
||||
# 1. Pages with calendars -> features={"calendar": true}
|
||||
conn.execute(text("""
|
||||
INSERT INTO page_configs (post_id, features, created_at, updated_at)
|
||||
SELECT p.id, '{"calendar": true}'::jsonb, now(), now()
|
||||
FROM posts p
|
||||
WHERE p.is_page = true
|
||||
AND p.deleted_at IS NULL
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM calendars c
|
||||
WHERE c.post_id = p.id AND c.deleted_at IS NULL
|
||||
)
|
||||
"""))
|
||||
|
||||
# 2. Market page (slug='market', is_page=true) -> features={"market": true}
|
||||
# Only if not already inserted above
|
||||
conn.execute(text("""
|
||||
INSERT INTO page_configs (post_id, features, created_at, updated_at)
|
||||
SELECT p.id, '{"market": true}'::jsonb, now(), now()
|
||||
FROM posts p
|
||||
WHERE p.slug = 'market'
|
||||
AND p.is_page = true
|
||||
AND p.deleted_at IS NULL
|
||||
AND p.id NOT IN (SELECT post_id FROM page_configs)
|
||||
"""))
|
||||
|
||||
# 3. All other pages -> features={}
|
||||
conn.execute(text("""
|
||||
INSERT INTO page_configs (post_id, features, created_at, updated_at)
|
||||
SELECT p.id, '{}'::jsonb, now(), now()
|
||||
FROM posts p
|
||||
WHERE p.is_page = true
|
||||
AND p.deleted_at IS NULL
|
||||
AND p.id NOT IN (SELECT post_id FROM page_configs)
|
||||
"""))
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_table('page_configs')
|
||||
37
alembic/versions/a9f54e4eaf02_add_menu_items_table.py
Normal file
37
alembic/versions/a9f54e4eaf02_add_menu_items_table.py
Normal file
@@ -0,0 +1,37 @@
|
||||
|
||||
# Alembic migration script template
|
||||
|
||||
"""add menu_items table
|
||||
|
||||
Revision ID: a9f54e4eaf02
|
||||
Revises: 6cb124491c9d
|
||||
Create Date: 2025-12-07 17:38:54.839296
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'a9f54e4eaf02'
|
||||
down_revision = '6cb124491c9d'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table('menu_items',
|
||||
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column('post_id', sa.Integer(), nullable=False),
|
||||
sa.Column('sort_order', sa.Integer(), nullable=False),
|
||||
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('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(op.f('ix_menu_items_post_id'), 'menu_items', ['post_id'], unique=False)
|
||||
op.create_index(op.f('ix_menu_items_sort_order'), 'menu_items', ['sort_order'], unique=False)
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index(op.f('ix_menu_items_sort_order'), table_name='menu_items')
|
||||
op.drop_index(op.f('ix_menu_items_post_id'), table_name='menu_items')
|
||||
op.drop_table('menu_items')
|
||||
97
alembic/versions/b2c3d4e5f6a7_add_market_places_table.py
Normal file
97
alembic/versions/b2c3d4e5f6a7_add_market_places_table.py
Normal file
@@ -0,0 +1,97 @@
|
||||
"""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')
|
||||
35
alembic/versions/c3a1f7b9d4e5_add_snippets_table.py
Normal file
35
alembic/versions/c3a1f7b9d4e5_add_snippets_table.py
Normal file
@@ -0,0 +1,35 @@
|
||||
"""add snippets table
|
||||
|
||||
Revision ID: c3a1f7b9d4e5
|
||||
Revises: 47fc53fc0d2b
|
||||
Create Date: 2026-02-07
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
revision = 'c3a1f7b9d4e5'
|
||||
down_revision = '47fc53fc0d2b'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
'snippets',
|
||||
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column('user_id', sa.Integer(), nullable=False),
|
||||
sa.Column('name', sa.String(length=255), nullable=False),
|
||||
sa.Column('value', sa.Text(), nullable=False),
|
||||
sa.Column('visibility', sa.String(length=20), server_default='private', nullable=False),
|
||||
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('user_id', 'name', name='uq_snippets_user_name'),
|
||||
)
|
||||
op.create_index('ix_snippets_visibility', 'snippets', ['visibility'])
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index('ix_snippets_visibility', table_name='snippets')
|
||||
op.drop_table('snippets')
|
||||
55
alembic/versions/c3d4e5f6a7b8_add_page_tracking_to_orders.py
Normal file
55
alembic/versions/c3d4e5f6a7b8_add_page_tracking_to_orders.py
Normal file
@@ -0,0 +1,55 @@
|
||||
"""add page_config_id to orders, market_place_id to cart_items
|
||||
|
||||
Revision ID: c3d4e5f6a7b8
|
||||
Revises: b2c3d4e5f6a7
|
||||
Create Date: 2026-02-10
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
revision = 'c3d4e5f6a7b8'
|
||||
down_revision = 'b2c3d4e5f6a7'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# 1. Add market_place_id to cart_items
|
||||
op.add_column(
|
||||
'cart_items',
|
||||
sa.Column('market_place_id', sa.Integer(), nullable=True),
|
||||
)
|
||||
op.create_foreign_key(
|
||||
'fk_cart_items_market_place_id',
|
||||
'cart_items',
|
||||
'market_places',
|
||||
['market_place_id'],
|
||||
['id'],
|
||||
ondelete='SET NULL',
|
||||
)
|
||||
op.create_index('ix_cart_items_market_place_id', 'cart_items', ['market_place_id'])
|
||||
|
||||
# 2. Add page_config_id to orders
|
||||
op.add_column(
|
||||
'orders',
|
||||
sa.Column('page_config_id', sa.Integer(), nullable=True),
|
||||
)
|
||||
op.create_foreign_key(
|
||||
'fk_orders_page_config_id',
|
||||
'orders',
|
||||
'page_configs',
|
||||
['page_config_id'],
|
||||
['id'],
|
||||
ondelete='SET NULL',
|
||||
)
|
||||
op.create_index('ix_orders_page_config_id', 'orders', ['page_config_id'])
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index('ix_orders_page_config_id', table_name='orders')
|
||||
op.drop_constraint('fk_orders_page_config_id', 'orders', type_='foreignkey')
|
||||
op.drop_column('orders', 'page_config_id')
|
||||
|
||||
op.drop_index('ix_cart_items_market_place_id', table_name='cart_items')
|
||||
op.drop_constraint('fk_cart_items_market_place_id', 'cart_items', type_='foreignkey')
|
||||
op.drop_column('cart_items', 'market_place_id')
|
||||
@@ -0,0 +1,45 @@
|
||||
"""add post user_id, author email, publish_requested
|
||||
|
||||
Revision ID: d4b2e8f1a3c7
|
||||
Revises: c3a1f7b9d4e5
|
||||
Create Date: 2026-02-08
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
revision = 'd4b2e8f1a3c7'
|
||||
down_revision = 'c3a1f7b9d4e5'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# Add author.email
|
||||
op.add_column('authors', sa.Column('email', sa.String(255), nullable=True))
|
||||
|
||||
# Add post.user_id FK
|
||||
op.add_column('posts', sa.Column('user_id', sa.Integer(), nullable=True))
|
||||
op.create_foreign_key('fk_posts_user_id', 'posts', 'users', ['user_id'], ['id'], ondelete='SET NULL')
|
||||
op.create_index('ix_posts_user_id', 'posts', ['user_id'])
|
||||
|
||||
# Add post.publish_requested
|
||||
op.add_column('posts', sa.Column('publish_requested', sa.Boolean(), server_default='false', nullable=False))
|
||||
|
||||
# Backfill: match posts to users via primary_author email
|
||||
op.execute("""
|
||||
UPDATE posts
|
||||
SET user_id = u.id
|
||||
FROM authors a
|
||||
JOIN users u ON lower(a.email) = lower(u.email)
|
||||
WHERE posts.primary_author_id = a.id
|
||||
AND posts.user_id IS NULL
|
||||
AND a.email IS NOT NULL
|
||||
""")
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_column('posts', 'publish_requested')
|
||||
op.drop_index('ix_posts_user_id', table_name='posts')
|
||||
op.drop_constraint('fk_posts_user_id', 'posts', type_='foreignkey')
|
||||
op.drop_column('posts', 'user_id')
|
||||
op.drop_column('authors', 'email')
|
||||
@@ -0,0 +1,45 @@
|
||||
"""add tag_groups and tag_group_tags
|
||||
|
||||
Revision ID: e5c3f9a2b1d6
|
||||
Revises: d4b2e8f1a3c7
|
||||
Create Date: 2026-02-08
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
revision = 'e5c3f9a2b1d6'
|
||||
down_revision = 'd4b2e8f1a3c7'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
'tag_groups',
|
||||
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column('name', sa.String(length=255), nullable=False),
|
||||
sa.Column('slug', sa.String(length=191), nullable=False),
|
||||
sa.Column('feature_image', sa.Text(), nullable=True),
|
||||
sa.Column('colour', sa.String(length=32), nullable=True),
|
||||
sa.Column('sort_order', sa.Integer(), nullable=False, server_default='0'),
|
||||
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('slug'),
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
'tag_group_tags',
|
||||
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column('tag_group_id', sa.Integer(), nullable=False),
|
||||
sa.Column('tag_id', sa.Integer(), nullable=False),
|
||||
sa.ForeignKeyConstraint(['tag_group_id'], ['tag_groups.id'], ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['tag_id'], ['tags.id'], ondelete='CASCADE'),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('tag_group_id', 'tag_id', name='uq_tag_group_tag'),
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_table('tag_group_tags')
|
||||
op.drop_table('tag_groups')
|
||||
40
alembic/versions/f6d4a0b2c3e7_add_domain_events_table.py
Normal file
40
alembic/versions/f6d4a0b2c3e7_add_domain_events_table.py
Normal file
@@ -0,0 +1,40 @@
|
||||
"""add domain_events table
|
||||
|
||||
Revision ID: f6d4a0b2c3e7
|
||||
Revises: e5c3f9a2b1d6
|
||||
Create Date: 2026-02-11
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
revision = 'f6d4a0b2c3e7'
|
||||
down_revision = 'e5c3f9a2b1d6'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
'domain_events',
|
||||
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column('event_type', sa.String(128), nullable=False),
|
||||
sa.Column('aggregate_type', sa.String(64), nullable=False),
|
||||
sa.Column('aggregate_id', sa.Integer(), nullable=False),
|
||||
sa.Column('payload', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
||||
sa.Column('state', sa.String(20), server_default='pending', nullable=False),
|
||||
sa.Column('attempts', sa.Integer(), server_default='0', nullable=False),
|
||||
sa.Column('max_attempts', sa.Integer(), server_default='5', nullable=False),
|
||||
sa.Column('last_error', sa.Text(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('processed_at', sa.DateTime(timezone=True), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
)
|
||||
op.create_index('ix_domain_events_event_type', 'domain_events', ['event_type'])
|
||||
op.create_index('ix_domain_events_state', 'domain_events', ['state'])
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index('ix_domain_events_state', table_name='domain_events')
|
||||
op.drop_index('ix_domain_events_event_type', table_name='domain_events')
|
||||
op.drop_table('domain_events')
|
||||
47
alembic/versions/f6d4a1b2c3e7_add_tickets_table.py
Normal file
47
alembic/versions/f6d4a1b2c3e7_add_tickets_table.py
Normal file
@@ -0,0 +1,47 @@
|
||||
"""add tickets table
|
||||
|
||||
Revision ID: f6d4a1b2c3e7
|
||||
Revises: e5c3f9a2b1d6
|
||||
Create Date: 2026-02-09
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
revision = 'f6d4a1b2c3e7'
|
||||
down_revision = 'e5c3f9a2b1d6'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
'tickets',
|
||||
sa.Column('id', sa.Integer(), primary_key=True),
|
||||
sa.Column('entry_id', sa.Integer(), sa.ForeignKey('calendar_entries.id', ondelete='CASCADE'), nullable=False),
|
||||
sa.Column('ticket_type_id', sa.Integer(), sa.ForeignKey('ticket_types.id', ondelete='SET NULL'), nullable=True),
|
||||
sa.Column('user_id', sa.Integer(), sa.ForeignKey('users.id'), nullable=True),
|
||||
sa.Column('session_id', sa.String(64), nullable=True),
|
||||
sa.Column('order_id', sa.Integer(), sa.ForeignKey('orders.id', ondelete='SET NULL'), nullable=True),
|
||||
sa.Column('code', sa.String(64), unique=True, nullable=False),
|
||||
sa.Column('state', sa.String(20), nullable=False, server_default=sa.text("'reserved'")),
|
||||
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
sa.Column('checked_in_at', sa.DateTime(timezone=True), nullable=True),
|
||||
)
|
||||
op.create_index('ix_tickets_entry_id', 'tickets', ['entry_id'])
|
||||
op.create_index('ix_tickets_ticket_type_id', 'tickets', ['ticket_type_id'])
|
||||
op.create_index('ix_tickets_user_id', 'tickets', ['user_id'])
|
||||
op.create_index('ix_tickets_session_id', 'tickets', ['session_id'])
|
||||
op.create_index('ix_tickets_order_id', 'tickets', ['order_id'])
|
||||
op.create_index('ix_tickets_code', 'tickets', ['code'], unique=True)
|
||||
op.create_index('ix_tickets_state', 'tickets', ['state'])
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index('ix_tickets_state', 'tickets')
|
||||
op.drop_index('ix_tickets_code', 'tickets')
|
||||
op.drop_index('ix_tickets_order_id', 'tickets')
|
||||
op.drop_index('ix_tickets_session_id', 'tickets')
|
||||
op.drop_index('ix_tickets_user_id', 'tickets')
|
||||
op.drop_index('ix_tickets_ticket_type_id', 'tickets')
|
||||
op.drop_index('ix_tickets_entry_id', 'tickets')
|
||||
op.drop_table('tickets')
|
||||
115
alembic/versions/g7e5b1c3d4f8_generic_containers.py
Normal file
115
alembic/versions/g7e5b1c3d4f8_generic_containers.py
Normal file
@@ -0,0 +1,115 @@
|
||||
"""replace post_id FKs with container_type + container_id
|
||||
|
||||
Revision ID: g7e5b1c3d4f8
|
||||
Revises: f6d4a0b2c3e7
|
||||
Create Date: 2026-02-11
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
revision = 'g7e5b1c3d4f8'
|
||||
down_revision = 'f6d4a0b2c3e7'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# --- calendars: post_id → container_type + container_id ---
|
||||
op.add_column('calendars', sa.Column('container_type', sa.String(32), nullable=True))
|
||||
op.add_column('calendars', sa.Column('container_id', sa.Integer(), nullable=True))
|
||||
op.execute("UPDATE calendars SET container_type = 'page', container_id = post_id")
|
||||
op.alter_column('calendars', 'container_type', nullable=False, server_default=sa.text("'page'"))
|
||||
op.alter_column('calendars', 'container_id', nullable=False)
|
||||
op.drop_index('ix_calendars_post_id', table_name='calendars')
|
||||
op.drop_index('ux_calendars_post_slug_active', table_name='calendars')
|
||||
op.drop_constraint('calendars_post_id_fkey', 'calendars', type_='foreignkey')
|
||||
op.drop_column('calendars', 'post_id')
|
||||
op.create_index('ix_calendars_container', 'calendars', ['container_type', 'container_id'])
|
||||
op.create_index(
|
||||
'ux_calendars_container_slug_active',
|
||||
'calendars',
|
||||
['container_type', 'container_id', sa.text('lower(slug)')],
|
||||
unique=True,
|
||||
postgresql_where=sa.text('deleted_at IS NULL'),
|
||||
)
|
||||
|
||||
# --- market_places: post_id → container_type + container_id ---
|
||||
op.add_column('market_places', sa.Column('container_type', sa.String(32), nullable=True))
|
||||
op.add_column('market_places', sa.Column('container_id', sa.Integer(), nullable=True))
|
||||
op.execute("UPDATE market_places SET container_type = 'page', container_id = post_id")
|
||||
op.alter_column('market_places', 'container_type', nullable=False, server_default=sa.text("'page'"))
|
||||
op.alter_column('market_places', 'container_id', nullable=False)
|
||||
op.drop_index('ix_market_places_post_id', table_name='market_places')
|
||||
op.drop_constraint('market_places_post_id_fkey', 'market_places', type_='foreignkey')
|
||||
op.drop_column('market_places', 'post_id')
|
||||
op.create_index('ix_market_places_container', 'market_places', ['container_type', 'container_id'])
|
||||
|
||||
# --- page_configs: post_id → container_type + container_id ---
|
||||
op.add_column('page_configs', sa.Column('container_type', sa.String(32), nullable=True))
|
||||
op.add_column('page_configs', sa.Column('container_id', sa.Integer(), nullable=True))
|
||||
op.execute("UPDATE page_configs SET container_type = 'page', container_id = post_id")
|
||||
op.alter_column('page_configs', 'container_type', nullable=False, server_default=sa.text("'page'"))
|
||||
op.alter_column('page_configs', 'container_id', nullable=False)
|
||||
op.drop_constraint('page_configs_post_id_fkey', 'page_configs', type_='foreignkey')
|
||||
op.drop_column('page_configs', 'post_id')
|
||||
op.create_index('ix_page_configs_container', 'page_configs', ['container_type', 'container_id'])
|
||||
|
||||
# --- calendar_entry_posts: post_id → content_type + content_id ---
|
||||
op.add_column('calendar_entry_posts', sa.Column('content_type', sa.String(32), nullable=True))
|
||||
op.add_column('calendar_entry_posts', sa.Column('content_id', sa.Integer(), nullable=True))
|
||||
op.execute("UPDATE calendar_entry_posts SET content_type = 'post', content_id = post_id")
|
||||
op.alter_column('calendar_entry_posts', 'content_type', nullable=False, server_default=sa.text("'post'"))
|
||||
op.alter_column('calendar_entry_posts', 'content_id', nullable=False)
|
||||
op.drop_index('ix_entry_posts_post_id', table_name='calendar_entry_posts')
|
||||
op.drop_constraint('calendar_entry_posts_post_id_fkey', 'calendar_entry_posts', type_='foreignkey')
|
||||
op.drop_column('calendar_entry_posts', 'post_id')
|
||||
op.create_index('ix_entry_posts_content', 'calendar_entry_posts', ['content_type', 'content_id'])
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# --- calendar_entry_posts: restore post_id ---
|
||||
op.add_column('calendar_entry_posts', sa.Column('post_id', sa.Integer(), nullable=True))
|
||||
op.execute("UPDATE calendar_entry_posts SET post_id = content_id WHERE content_type = 'post'")
|
||||
op.alter_column('calendar_entry_posts', 'post_id', nullable=False)
|
||||
op.create_foreign_key('calendar_entry_posts_post_id_fkey', 'calendar_entry_posts', 'posts', ['post_id'], ['id'], ondelete='CASCADE')
|
||||
op.create_index('ix_entry_posts_post_id', 'calendar_entry_posts', ['post_id'])
|
||||
op.drop_index('ix_entry_posts_content', table_name='calendar_entry_posts')
|
||||
op.drop_column('calendar_entry_posts', 'content_id')
|
||||
op.drop_column('calendar_entry_posts', 'content_type')
|
||||
|
||||
# --- page_configs: restore post_id ---
|
||||
op.add_column('page_configs', sa.Column('post_id', sa.Integer(), nullable=True))
|
||||
op.execute("UPDATE page_configs SET post_id = container_id WHERE container_type = 'page'")
|
||||
op.alter_column('page_configs', 'post_id', nullable=False)
|
||||
op.create_foreign_key('page_configs_post_id_fkey', 'page_configs', 'posts', ['post_id'], ['id'], ondelete='CASCADE')
|
||||
op.drop_index('ix_page_configs_container', table_name='page_configs')
|
||||
op.drop_column('page_configs', 'container_id')
|
||||
op.drop_column('page_configs', 'container_type')
|
||||
|
||||
# --- market_places: restore post_id ---
|
||||
op.add_column('market_places', sa.Column('post_id', sa.Integer(), nullable=True))
|
||||
op.execute("UPDATE market_places SET post_id = container_id WHERE container_type = 'page'")
|
||||
op.alter_column('market_places', 'post_id', nullable=False)
|
||||
op.create_foreign_key('market_places_post_id_fkey', 'market_places', 'posts', ['post_id'], ['id'], ondelete='CASCADE')
|
||||
op.create_index('ix_market_places_post_id', 'market_places', ['post_id'])
|
||||
op.drop_index('ix_market_places_container', table_name='market_places')
|
||||
op.drop_column('market_places', 'container_id')
|
||||
op.drop_column('market_places', 'container_type')
|
||||
|
||||
# --- calendars: restore post_id ---
|
||||
op.add_column('calendars', sa.Column('post_id', sa.Integer(), nullable=True))
|
||||
op.execute("UPDATE calendars SET post_id = container_id WHERE container_type = 'page'")
|
||||
op.alter_column('calendars', 'post_id', nullable=False)
|
||||
op.create_foreign_key('calendars_post_id_fkey', 'calendars', 'posts', ['post_id'], ['id'], ondelete='CASCADE')
|
||||
op.create_index('ix_calendars_post_id', 'calendars', ['post_id'])
|
||||
op.create_index(
|
||||
'ux_calendars_post_slug_active',
|
||||
'calendars',
|
||||
['post_id', sa.text('lower(slug)')],
|
||||
unique=True,
|
||||
postgresql_where=sa.text('deleted_at IS NULL'),
|
||||
)
|
||||
op.drop_index('ux_calendars_container_slug_active', table_name='calendars')
|
||||
op.drop_index('ix_calendars_container', table_name='calendars')
|
||||
op.drop_column('calendars', 'container_id')
|
||||
op.drop_column('calendars', 'container_type')
|
||||
Reference in New Issue
Block a user