91 lines
4.0 KiB
Python
91 lines
4.0 KiB
Python
"""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)
|