feat: initial shared library extraction
Contains shared infrastructure for all coop services: - shared/ (factory, urls, user_loader, context, internal_api, jinja_setup) - models/ (User, Order, Calendar, Ticket, Product, Ghost CMS) - db/ (SQLAlchemy async session, base) - suma_browser/app/ (csrf, middleware, errors, authz, redis_cacher, payments, filters, utils) - suma_browser/templates/ (shared base layouts, macros, error pages) - static/ (CSS, JS, fonts, images) - alembic/ (database migrations) - config/ (app-config.yaml) - editor/ (Lexical editor Node.js build) - requirements.txt Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
20
alembic/versions/0000_alembic.py_
Normal file
20
alembic/versions/0000_alembic.py_
Normal file
@@ -0,0 +1,20 @@
|
||||
"""Initial database schema from schema.sql"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
import pathlib
|
||||
|
||||
# revision identifiers, used by Alembic
|
||||
revision = '0000_alembic'
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
def upgrade():
|
||||
op.execute("""
|
||||
CREATE TABLE IF NOT EXISTS alembic_version (
|
||||
version_num VARCHAR(32) NOT NULL,
|
||||
CONSTRAINT alembic_version_pkc PRIMARY KEY (version_num)
|
||||
);
|
||||
""")
|
||||
|
||||
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')
|
||||
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')
|
||||
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')
|
||||
@@ -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')
|
||||
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')
|
||||
Reference in New Issue
Block a user