"""add wallets and transactions ledger tables Revision ID: 0012_add_wallet_and_transactions Revises: 0011_add_dm_last_read Create Date: 2026-09-15 00:00:00.000000 docs/planning/economy.md US-1 (task 8343bc0a730f). """ from typing import Sequence, Union from alembic import op import sqlalchemy as sa from sqlalchemy.dialects import postgresql # revision identifiers, used by Alembic. revision: str = "0012_add_wallet_and_transactions" down_revision: Union[str, None] = "0011_add_dm_last_read" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None TRANSACTION_TYPES = ( "credit_purchase", "marketplace_purchase", "marketplace_sale", "escrow_release", "payout", "refund", "admin_adjustment", ) def upgrade() -> None: # 1. Create transaction_type enum for Postgres bind = op.get_bind() if bind.dialect.name == "postgresql": tx_type_enum = postgresql.ENUM(*TRANSACTION_TYPES, name="transaction_type", create_type=False) tx_type_enum.create(bind, checkfirst=True) tx_type_col = postgresql.ENUM(*TRANSACTION_TYPES, name="transaction_type", create_type=False) else: tx_type_col = sa.Enum(*TRANSACTION_TYPES, name="transaction_type") # 2. Create wallets table op.create_table( "wallets", sa.Column("id", sa.BigInteger(), autoincrement=True, nullable=False), sa.Column("user_id", sa.Integer(), nullable=False), sa.Column("balance", sa.BigInteger(), server_default="0", 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.CheckConstraint("balance >= 0", name="check_wallet_balance_non_negative"), sa.ForeignKeyConstraint(["user_id"], ["users.id"], ondelete="CASCADE"), sa.PrimaryKeyConstraint("id"), sa.UniqueConstraint("user_id", name="uq_wallets_user_id"), ) op.create_index(op.f("ix_wallets_user_id"), "wallets", ["user_id"], unique=True) # 3. Create transactions table op.create_table( "transactions", sa.Column("id", sa.BigInteger(), autoincrement=True, nullable=False), sa.Column("wallet_id", sa.BigInteger(), nullable=False), sa.Column("type", tx_type_col, nullable=False), sa.Column("amount", sa.BigInteger(), nullable=False), sa.Column("balance_after", sa.BigInteger(), nullable=False), sa.Column("reference_type", sa.String(), nullable=True), sa.Column("reference_id", sa.String(), nullable=True), sa.Column("idempotency_key", sa.String(), nullable=True), sa.Column("metadata_json", postgresql.JSONB(astext_type=sa.Text()) if bind.dialect.name == "postgresql" else sa.JSON(), nullable=True), sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False), sa.ForeignKeyConstraint(["wallet_id"], ["wallets.id"], ondelete="CASCADE"), sa.PrimaryKeyConstraint("id"), sa.UniqueConstraint("idempotency_key", name="uq_transactions_idempotency_key"), ) op.create_index(op.f("ix_transactions_wallet_id"), "transactions", ["wallet_id"], unique=False) op.create_index(op.f("ix_transactions_idempotency_key"), "transactions", ["idempotency_key"], unique=True) op.create_index(op.f("ix_transactions_created_at"), "transactions", ["created_at"], unique=False) def downgrade() -> None: op.drop_index(op.f("ix_transactions_created_at"), table_name="transactions") op.drop_index(op.f("ix_transactions_idempotency_key"), table_name="transactions") op.drop_index(op.f("ix_transactions_wallet_id"), table_name="transactions") op.drop_table("transactions") op.drop_index(op.f("ix_wallets_user_id"), table_name="wallets") op.drop_table("wallets") bind = op.get_bind() if bind.dialect.name == "postgresql": sa.Enum(*TRANSACTION_TYPES, name="transaction_type").drop(bind, checkfirst=True)