first commit
This commit is contained in:
commit
34ea15a54d
246 changed files with 18660 additions and 0 deletions
67
backend/alembic/env.py
Normal file
67
backend/alembic/env.py
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
from logging.config import fileConfig
|
||||
|
||||
from alembic import context
|
||||
from sqlalchemy import engine_from_config, pool
|
||||
|
||||
from app.core.config import get_settings
|
||||
from app.db.base import Base
|
||||
from app.models import ( # noqa: F401
|
||||
AdminUser,
|
||||
AlertDispatch,
|
||||
Event,
|
||||
EventEdition,
|
||||
EventMedia,
|
||||
MediaAsset,
|
||||
NewsletterSubscriber,
|
||||
Order,
|
||||
OrderItem,
|
||||
OutboundClick,
|
||||
PaymentWebhookEvent,
|
||||
PlatformLink,
|
||||
Ticket,
|
||||
TicketType,
|
||||
User,
|
||||
Venue,
|
||||
)
|
||||
|
||||
config = context.config
|
||||
settings = get_settings()
|
||||
config.set_main_option("sqlalchemy.url", settings.database_url)
|
||||
|
||||
if config.config_file_name is not None:
|
||||
fileConfig(config.config_file_name)
|
||||
|
||||
target_metadata = Base.metadata
|
||||
|
||||
|
||||
def run_migrations_offline() -> None:
|
||||
url = config.get_main_option("sqlalchemy.url")
|
||||
context.configure(
|
||||
url=url,
|
||||
target_metadata=target_metadata,
|
||||
literal_binds=True,
|
||||
dialect_opts={"paramstyle": "named"},
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
def run_migrations_online() -> None:
|
||||
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)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
if context.is_offline_mode():
|
||||
run_migrations_offline()
|
||||
else:
|
||||
run_migrations_online()
|
||||
26
backend/alembic/script.py.mako
Normal file
26
backend/alembic/script.py.mako
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
"""${message}
|
||||
|
||||
Revision ID: ${up_revision}
|
||||
Revises: ${down_revision | comma,n}
|
||||
Create Date: ${create_date}
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
${imports if imports else ""}
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = ${repr(up_revision)}
|
||||
down_revision: Union[str, None] = ${repr(down_revision)}
|
||||
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
|
||||
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
${upgrades if upgrades else "pass"}
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
${downgrades if downgrades else "pass"}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
"""edition ticket urls and meta description
|
||||
|
||||
Revision ID: 20260404_0001
|
||||
Revises:
|
||||
Create Date: 2026-04-04
|
||||
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "20260404_0001"
|
||||
down_revision: Union[str, None] = None
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.execute("ALTER TABLE event_editions ADD COLUMN IF NOT EXISTS bookmyshow_url VARCHAR(512)")
|
||||
op.execute("ALTER TABLE event_editions ADD COLUMN IF NOT EXISTS district_url VARCHAR(512)")
|
||||
op.execute("ALTER TABLE event_editions ADD COLUMN IF NOT EXISTS instagram_url VARCHAR(512)")
|
||||
op.execute("ALTER TABLE event_editions ADD COLUMN IF NOT EXISTS meta_description VARCHAR(320)")
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_column("event_editions", "meta_description")
|
||||
op.drop_column("event_editions", "instagram_url")
|
||||
op.drop_column("event_editions", "district_url")
|
||||
op.drop_column("event_editions", "bookmyshow_url")
|
||||
39
backend/alembic/versions/20260404_0002_outbound_clicks.py
Normal file
39
backend/alembic/versions/20260404_0002_outbound_clicks.py
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
"""outbound click analytics table
|
||||
|
||||
Revision ID: 20260404_0002
|
||||
Revises: 20260404_0001
|
||||
Create Date: 2026-04-04
|
||||
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "20260404_0002"
|
||||
down_revision: Union[str, None] = "20260404_0001"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"outbound_clicks",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("link_kind", sa.String(length=64), nullable=False),
|
||||
sa.Column("destination_url", sa.String(length=512), nullable=False),
|
||||
sa.Column("edition_id", sa.Integer(), nullable=True),
|
||||
sa.Column("referrer", sa.String(length=512), nullable=True),
|
||||
sa.Column("user_agent", sa.String(length=512), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.ForeignKeyConstraint(["edition_id"], ["event_editions.id"], ondelete="SET NULL"),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index("ix_outbound_clicks_link_kind", "outbound_clicks", ["link_kind"])
|
||||
op.create_index("ix_outbound_clicks_edition_id", "outbound_clicks", ["edition_id"])
|
||||
op.create_index("ix_outbound_clicks_created_at", "outbound_clicks", ["created_at"])
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_table("outbound_clicks")
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
"""admin roles, subscribers, alert log
|
||||
|
||||
Revision ID: 20260404_0003
|
||||
Revises: 20260404_0002
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "20260404_0003"
|
||||
down_revision: Union[str, None] = "20260404_0002"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.execute(
|
||||
"""
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE adminrole AS ENUM ('super_admin', 'editor');
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
"""
|
||||
)
|
||||
op.execute(
|
||||
"ALTER TABLE admin_users ADD COLUMN IF NOT EXISTS role adminrole NOT NULL DEFAULT 'super_admin'"
|
||||
)
|
||||
|
||||
op.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS newsletter_subscribers (
|
||||
id SERIAL PRIMARY KEY,
|
||||
email VARCHAR(255) NOT NULL UNIQUE,
|
||||
neighborhood VARCHAR(120),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
)
|
||||
"""
|
||||
)
|
||||
op.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS alert_dispatches (
|
||||
id SERIAL PRIMARY KEY,
|
||||
alert_key VARCHAR(120) NOT NULL UNIQUE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
)
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.execute("DROP TABLE IF EXISTS alert_dispatches")
|
||||
op.execute("DROP TABLE IF EXISTS newsletter_subscribers")
|
||||
op.execute("ALTER TABLE admin_users DROP COLUMN IF EXISTS role")
|
||||
22
backend/alembic/versions/20260404_0004_platform_highape.py
Normal file
22
backend/alembic/versions/20260404_0004_platform_highape.py
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
"""add highape platform kind
|
||||
|
||||
Revision ID: 20260404_0004
|
||||
Revises: 20260404_0003
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
|
||||
revision: str = "20260404_0004"
|
||||
down_revision: Union[str, None] = "20260404_0003"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.execute("ALTER TYPE platformkind ADD VALUE IF NOT EXISTS 'highape'")
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
pass
|
||||
123
backend/alembic/versions/20260404_0005_events_venues_catalog.py
Normal file
123
backend/alembic/versions/20260404_0005_events_venues_catalog.py
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
"""events venues event_media ticket_types catalog
|
||||
|
||||
Revision ID: 20260404_0005
|
||||
Revises: 20260404_0004
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
revision: str = "20260404_0005"
|
||||
down_revision: Union[str, None] = "20260404_0004"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
event_category = postgresql.ENUM(
|
||||
"holi",
|
||||
"garba",
|
||||
"dandiya",
|
||||
"bhajan_clubbing",
|
||||
"special",
|
||||
name="eventtype",
|
||||
create_type=False,
|
||||
)
|
||||
event_status = postgresql.ENUM(
|
||||
"draft",
|
||||
"published",
|
||||
"cancelled",
|
||||
name="eventstatus",
|
||||
create_type=False,
|
||||
)
|
||||
ticket_type_status = postgresql.ENUM(
|
||||
"active",
|
||||
"sold_out",
|
||||
"inactive",
|
||||
name="tickettypestatus",
|
||||
create_type=False,
|
||||
)
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"venues",
|
||||
sa.Column("id", sa.Integer(), primary_key=True),
|
||||
sa.Column("slug", sa.String(length=120), nullable=False),
|
||||
sa.Column("name", sa.String(length=200), nullable=False),
|
||||
sa.Column("area", sa.String(length=120), nullable=False),
|
||||
sa.Column("city", sa.String(length=80), nullable=False, server_default="Bengaluru"),
|
||||
sa.Column("address", sa.String(length=512), nullable=False),
|
||||
sa.Column("map_url", sa.String(length=512), nullable=True),
|
||||
sa.Column("description", sa.Text(), nullable=True),
|
||||
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),
|
||||
)
|
||||
op.create_index("ix_venues_slug", "venues", ["slug"], unique=True)
|
||||
|
||||
event_category.create(op.get_bind(), checkfirst=True)
|
||||
event_status.create(op.get_bind(), checkfirst=True)
|
||||
ticket_type_status.create(op.get_bind(), checkfirst=True)
|
||||
|
||||
op.create_table(
|
||||
"events",
|
||||
sa.Column("id", sa.Integer(), primary_key=True),
|
||||
sa.Column("slug", sa.String(length=200), nullable=False),
|
||||
sa.Column("title", sa.String(length=240), nullable=False),
|
||||
sa.Column("category", event_category, nullable=False),
|
||||
sa.Column("status", event_status, nullable=False, server_default="draft"),
|
||||
sa.Column("venue_id", sa.Integer(), sa.ForeignKey("venues.id", ondelete="RESTRICT"), nullable=False),
|
||||
sa.Column("description", sa.Text(), nullable=True),
|
||||
sa.Column("starts_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("ends_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("is_featured", sa.Boolean(), nullable=False, server_default=sa.false()),
|
||||
sa.Column("cover_image_url", sa.String(length=1024), nullable=True),
|
||||
sa.Column("cover_image_alt", sa.String(length=300), nullable=True),
|
||||
sa.Column("meta_description", sa.String(length=320), nullable=True),
|
||||
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),
|
||||
)
|
||||
op.create_index("ix_events_slug", "events", ["slug"], unique=True)
|
||||
op.create_index("ix_events_category", "events", ["category"])
|
||||
op.create_index("ix_events_status", "events", ["status"])
|
||||
op.create_index("ix_events_venue_id", "events", ["venue_id"])
|
||||
op.create_index("ix_events_starts_at", "events", ["starts_at"])
|
||||
op.create_index("ix_events_status_starts_at", "events", ["status", "starts_at"])
|
||||
op.create_index("ix_events_category_status", "events", ["category", "status"])
|
||||
|
||||
op.create_table(
|
||||
"event_media",
|
||||
sa.Column("id", sa.Integer(), primary_key=True),
|
||||
sa.Column("event_id", sa.Integer(), sa.ForeignKey("events.id", ondelete="CASCADE"), nullable=False),
|
||||
sa.Column("image_url", sa.String(length=1024), nullable=False),
|
||||
sa.Column("alt_text", sa.String(length=300), 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),
|
||||
)
|
||||
op.create_index("ix_event_media_event_id", "event_media", ["event_id"])
|
||||
|
||||
op.create_table(
|
||||
"ticket_types",
|
||||
sa.Column("id", sa.Integer(), primary_key=True),
|
||||
sa.Column("event_id", sa.Integer(), sa.ForeignKey("events.id", ondelete="CASCADE"), nullable=False),
|
||||
sa.Column("name", sa.String(length=120), nullable=False),
|
||||
sa.Column("description", sa.String(length=512), nullable=True),
|
||||
sa.Column("price_paise", sa.Integer(), nullable=False),
|
||||
sa.Column("quantity_total", sa.Integer(), nullable=False),
|
||||
sa.Column("quantity_sold", sa.Integer(), nullable=False, server_default="0"),
|
||||
sa.Column("status", ticket_type_status, nullable=False, server_default="active"),
|
||||
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),
|
||||
)
|
||||
op.create_index("ix_ticket_types_event_id", "ticket_types", ["event_id"])
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_table("ticket_types")
|
||||
op.drop_table("event_media")
|
||||
op.drop_table("events")
|
||||
op.drop_table("venues")
|
||||
ticket_type_status.drop(op.get_bind(), checkfirst=True)
|
||||
event_status.drop(op.get_bind(), checkfirst=True)
|
||||
34
backend/alembic/versions/20260404_0006_users.py
Normal file
34
backend/alembic/versions/20260404_0006_users.py
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
"""attendee users table
|
||||
|
||||
Revision ID: 20260404_0006
|
||||
Revises: 20260404_0005
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "20260404_0006"
|
||||
down_revision: Union[str, None] = "20260404_0005"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"users",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("email", sa.String(length=255), nullable=False),
|
||||
sa.Column("hashed_password", sa.String(length=255), nullable=False),
|
||||
sa.Column("full_name", sa.String(length=120), nullable=True),
|
||||
sa.Column("is_active", sa.Boolean(), server_default=sa.text("true"), nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index(op.f("ix_users_email"), "users", ["email"], unique=True)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index(op.f("ix_users_email"), table_name="users")
|
||||
op.drop_table("users")
|
||||
129
backend/alembic/versions/20260404_0007_orders_tickets.py
Normal file
129
backend/alembic/versions/20260404_0007_orders_tickets.py
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
"""orders order_items tickets
|
||||
|
||||
Revision ID: 20260404_0007
|
||||
Revises: 20260404_0006
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
revision: str = "20260404_0007"
|
||||
down_revision: Union[str, None] = "20260404_0006"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
order_status = postgresql.ENUM(
|
||||
"pending",
|
||||
"paid",
|
||||
"cancelled",
|
||||
"failed",
|
||||
name="orderstatus",
|
||||
create_type=False,
|
||||
)
|
||||
ticket_status = postgresql.ENUM(
|
||||
"valid",
|
||||
"cancelled",
|
||||
"redeemed",
|
||||
name="ticketstatus",
|
||||
create_type=False,
|
||||
)
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
order_status.create(op.get_bind(), checkfirst=True)
|
||||
ticket_status.create(op.get_bind(), checkfirst=True)
|
||||
|
||||
op.create_table(
|
||||
"orders",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("user_id", sa.Integer(), nullable=False),
|
||||
sa.Column("event_id", sa.Integer(), nullable=False),
|
||||
sa.Column("status", order_status, nullable=False, server_default="pending"),
|
||||
sa.Column("total_paise", sa.Integer(), nullable=False),
|
||||
sa.Column("currency", sa.String(length=3), nullable=False, server_default="INR"),
|
||||
sa.Column("payment_reference", sa.String(length=64), nullable=True),
|
||||
sa.Column("paid_at", sa.DateTime(timezone=True), nullable=True),
|
||||
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(["event_id"], ["events.id"], ondelete="RESTRICT"),
|
||||
sa.ForeignKeyConstraint(["user_id"], ["users.id"], ondelete="RESTRICT"),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index(op.f("ix_orders_event_id"), "orders", ["event_id"], unique=False)
|
||||
op.create_index(op.f("ix_orders_status"), "orders", ["status"], unique=False)
|
||||
op.create_index(op.f("ix_orders_user_id"), "orders", ["user_id"], unique=False)
|
||||
|
||||
op.create_table(
|
||||
"order_items",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("order_id", sa.Integer(), nullable=False),
|
||||
sa.Column("ticket_type_id", sa.Integer(), nullable=False),
|
||||
sa.Column("quantity", sa.Integer(), nullable=False),
|
||||
sa.Column("unit_price_paise", sa.Integer(), nullable=False),
|
||||
sa.Column("line_total_paise", sa.Integer(), nullable=False),
|
||||
sa.CheckConstraint("quantity > 0", name="ck_order_items_quantity_positive"),
|
||||
sa.ForeignKeyConstraint(["order_id"], ["orders.id"], ondelete="CASCADE"),
|
||||
sa.ForeignKeyConstraint(["ticket_type_id"], ["ticket_types.id"], ondelete="RESTRICT"),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index(op.f("ix_order_items_order_id"), "order_items", ["order_id"], unique=False)
|
||||
op.create_index(op.f("ix_order_items_ticket_type_id"), "order_items", ["ticket_type_id"], unique=False)
|
||||
|
||||
op.create_table(
|
||||
"tickets",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("code", sa.String(length=32), nullable=False),
|
||||
sa.Column("order_id", sa.Integer(), nullable=False),
|
||||
sa.Column("order_item_id", sa.Integer(), nullable=False),
|
||||
sa.Column("ticket_type_id", sa.Integer(), nullable=False),
|
||||
sa.Column("event_id", sa.Integer(), nullable=False),
|
||||
sa.Column("user_id", sa.Integer(), nullable=False),
|
||||
sa.Column("status", ticket_status, nullable=False, server_default="valid"),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.ForeignKeyConstraint(["event_id"], ["events.id"], ondelete="RESTRICT"),
|
||||
sa.ForeignKeyConstraint(["order_id"], ["orders.id"], ondelete="CASCADE"),
|
||||
sa.ForeignKeyConstraint(["order_item_id"], ["order_items.id"], ondelete="CASCADE"),
|
||||
sa.ForeignKeyConstraint(["ticket_type_id"], ["ticket_types.id"], ondelete="RESTRICT"),
|
||||
sa.ForeignKeyConstraint(["user_id"], ["users.id"], ondelete="RESTRICT"),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.UniqueConstraint("code"),
|
||||
)
|
||||
op.create_index(op.f("ix_tickets_code"), "tickets", ["code"], unique=True)
|
||||
op.create_index(op.f("ix_tickets_event_id"), "tickets", ["event_id"], unique=False)
|
||||
op.create_index(op.f("ix_tickets_order_id"), "tickets", ["order_id"], unique=False)
|
||||
op.create_index(op.f("ix_tickets_order_item_id"), "tickets", ["order_item_id"], unique=False)
|
||||
op.create_index(op.f("ix_tickets_status"), "tickets", ["status"], unique=False)
|
||||
op.create_index(op.f("ix_tickets_ticket_type_id"), "tickets", ["ticket_type_id"], unique=False)
|
||||
op.create_index(op.f("ix_tickets_user_id"), "tickets", ["user_id"], unique=False)
|
||||
op.create_index("ix_tickets_user_status", "tickets", ["user_id", "status"], unique=False)
|
||||
|
||||
op.create_check_constraint(
|
||||
"ck_ticket_types_sold_not_exceed_total",
|
||||
"ticket_types",
|
||||
"quantity_sold <= quantity_total",
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_constraint("ck_ticket_types_sold_not_exceed_total", "ticket_types", type_="check")
|
||||
op.drop_index("ix_tickets_user_status", table_name="tickets")
|
||||
op.drop_index(op.f("ix_tickets_user_id"), table_name="tickets")
|
||||
op.drop_index(op.f("ix_tickets_ticket_type_id"), table_name="tickets")
|
||||
op.drop_index(op.f("ix_tickets_status"), table_name="tickets")
|
||||
op.drop_index(op.f("ix_tickets_order_item_id"), table_name="tickets")
|
||||
op.drop_index(op.f("ix_tickets_order_id"), table_name="tickets")
|
||||
op.drop_index(op.f("ix_tickets_event_id"), table_name="tickets")
|
||||
op.drop_index(op.f("ix_tickets_code"), table_name="tickets")
|
||||
op.drop_table("tickets")
|
||||
op.drop_index(op.f("ix_order_items_ticket_type_id"), table_name="order_items")
|
||||
op.drop_index(op.f("ix_order_items_order_id"), table_name="order_items")
|
||||
op.drop_table("order_items")
|
||||
op.drop_index(op.f("ix_orders_user_id"), table_name="orders")
|
||||
op.drop_index(op.f("ix_orders_status"), table_name="orders")
|
||||
op.drop_index(op.f("ix_orders_event_id"), table_name="orders")
|
||||
op.drop_table("orders")
|
||||
ticket_status.drop(op.get_bind(), checkfirst=True)
|
||||
order_status.drop(op.get_bind(), checkfirst=True)
|
||||
73
backend/alembic/versions/20260404_0008_payments.py
Normal file
73
backend/alembic/versions/20260404_0008_payments.py
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
"""payment fields and webhook idempotency
|
||||
|
||||
Revision ID: 20260404_0008
|
||||
Revises: 20260404_0007
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "20260404_0008"
|
||||
down_revision: Union[str, None] = "20260404_0007"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.alter_column(
|
||||
"orders",
|
||||
"payment_reference",
|
||||
existing_type=sa.String(length=64),
|
||||
type_=sa.String(length=128),
|
||||
existing_nullable=True,
|
||||
)
|
||||
op.add_column("orders", sa.Column("provider_order_id", sa.String(length=64), nullable=True))
|
||||
op.add_column("orders", sa.Column("provider_payment_id", sa.String(length=64), nullable=True))
|
||||
op.add_column("orders", sa.Column("idempotency_key", sa.String(length=64), nullable=True))
|
||||
op.add_column("orders", sa.Column("expires_at", sa.DateTime(timezone=True), nullable=True))
|
||||
op.create_index(op.f("ix_orders_provider_order_id"), "orders", ["provider_order_id"], unique=True)
|
||||
op.create_index(op.f("ix_orders_provider_payment_id"), "orders", ["provider_payment_id"], unique=False)
|
||||
op.create_index(op.f("ix_orders_idempotency_key"), "orders", ["idempotency_key"], unique=True)
|
||||
op.create_index(op.f("ix_orders_expires_at"), "orders", ["expires_at"], unique=False)
|
||||
|
||||
op.create_table(
|
||||
"payment_webhook_events",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("provider", sa.String(length=32), nullable=False),
|
||||
sa.Column("provider_event_id", sa.String(length=128), nullable=False),
|
||||
sa.Column("event_type", sa.String(length=64), nullable=False),
|
||||
sa.Column("payload", sa.Text(), nullable=False),
|
||||
sa.Column("processed_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.UniqueConstraint("provider_event_id"),
|
||||
)
|
||||
op.create_index(op.f("ix_payment_webhook_events_provider"), "payment_webhook_events", ["provider"], unique=False)
|
||||
op.create_index(
|
||||
op.f("ix_payment_webhook_events_provider_event_id"),
|
||||
"payment_webhook_events",
|
||||
["provider_event_id"],
|
||||
unique=True,
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index(op.f("ix_payment_webhook_events_provider_event_id"), table_name="payment_webhook_events")
|
||||
op.drop_index(op.f("ix_payment_webhook_events_provider"), table_name="payment_webhook_events")
|
||||
op.drop_table("payment_webhook_events")
|
||||
op.drop_index(op.f("ix_orders_expires_at"), table_name="orders")
|
||||
op.drop_index(op.f("ix_orders_idempotency_key"), table_name="orders")
|
||||
op.drop_index(op.f("ix_orders_provider_payment_id"), table_name="orders")
|
||||
op.drop_index(op.f("ix_orders_provider_order_id"), table_name="orders")
|
||||
op.drop_column("orders", "expires_at")
|
||||
op.drop_column("orders", "idempotency_key")
|
||||
op.drop_column("orders", "provider_payment_id")
|
||||
op.drop_column("orders", "provider_order_id")
|
||||
op.alter_column(
|
||||
"orders",
|
||||
"payment_reference",
|
||||
existing_type=sa.String(length=128),
|
||||
type_=sa.String(length=64),
|
||||
existing_nullable=True,
|
||||
)
|
||||
42
backend/alembic/versions/20260404_0009_digital_tickets.py
Normal file
42
backend/alembic/versions/20260404_0009_digital_tickets.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
"""digital ticket qr tokens and used status
|
||||
|
||||
Revision ID: 20260404_0009
|
||||
Revises: 20260404_0008
|
||||
"""
|
||||
|
||||
import secrets
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "20260404_0009"
|
||||
down_revision: Union[str, None] = "20260404_0008"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.execute("ALTER TYPE ticketstatus RENAME VALUE 'redeemed' TO 'used'")
|
||||
|
||||
op.add_column("tickets", sa.Column("qr_token", sa.String(length=64), nullable=True))
|
||||
op.add_column("tickets", sa.Column("used_at", sa.DateTime(timezone=True), nullable=True))
|
||||
|
||||
connection = op.get_bind()
|
||||
rows = connection.execute(sa.text("SELECT id FROM tickets WHERE qr_token IS NULL")).fetchall()
|
||||
for row in rows:
|
||||
token = secrets.token_urlsafe(32)
|
||||
connection.execute(
|
||||
sa.text("UPDATE tickets SET qr_token = :token WHERE id = :id"),
|
||||
{"token": token, "id": row.id},
|
||||
)
|
||||
|
||||
op.alter_column("tickets", "qr_token", nullable=False)
|
||||
op.create_index(op.f("ix_tickets_qr_token"), "tickets", ["qr_token"], unique=True)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index(op.f("ix_tickets_qr_token"), table_name="tickets")
|
||||
op.drop_column("tickets", "used_at")
|
||||
op.drop_column("tickets", "qr_token")
|
||||
op.execute("ALTER TYPE ticketstatus RENAME VALUE 'used' TO 'redeemed'")
|
||||
Loading…
Add table
Add a link
Reference in a new issue