diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py index 96e2dc3..631137a 100644 --- a/backend/app/models/__init__.py +++ b/backend/app/models/__init__.py @@ -2,5 +2,14 @@ from app.models.user import User from app.models.session import Session from app.models.world import World from app.models.asset import Asset +from app.models.wallet import Transaction, TransactionType, Wallet -__all__ = ["User", "Session", "World", "Asset"] +__all__ = [ + "User", + "Session", + "World", + "Asset", + "Wallet", + "Transaction", + "TransactionType", +] diff --git a/backend/app/models/wallet.py b/backend/app/models/wallet.py new file mode 100644 index 0000000..c164db5 --- /dev/null +++ b/backend/app/models/wallet.py @@ -0,0 +1,73 @@ +"""Wallet and transaction ledger models.""" + +import enum + +from sqlalchemy import ( + BigInteger, + CheckConstraint, + Column, + DateTime, + ForeignKey, + Integer, + Text, +) +from sqlalchemy.dialects.postgresql import ENUM, JSONB +from sqlalchemy.sql import func + +from app.database import Base + + +class TransactionType(str, enum.Enum): + CREDIT_PURCHASE = "credit_purchase" + MARKETPLACE_PURCHASE = "marketplace_purchase" + MARKETPLACE_SALE = "marketplace_sale" + ESCROW_RELEASE = "escrow_release" + PAYOUT = "payout" + REFUND = "refund" + ADMIN_ADJUSTMENT = "admin_adjustment" + + +class Wallet(Base): + __tablename__ = "wallets" + __table_args__ = ( + CheckConstraint("balance >= 0", name="ck_wallets_balance_nonnegative"), + ) + + id = Column(BigInteger, primary_key=True, index=True) + user_id = Column( + Integer, + ForeignKey("users.id", ondelete="CASCADE"), + unique=True, + index=True, + nullable=False, + ) + balance = Column(BigInteger, nullable=False, default=0, server_default="0") + created_at = Column(DateTime(timezone=True), server_default=func.now()) + updated_at = Column( + DateTime(timezone=True), server_default=func.now(), onupdate=func.now() + ) + + +class Transaction(Base): + __tablename__ = "transactions" + + id = Column(BigInteger, primary_key=True, index=True) + wallet_id = Column( + BigInteger, ForeignKey("wallets.id"), nullable=False + ) + type = Column( + ENUM( + TransactionType, + name="transaction_type", + create_type=False, + values_callable=lambda enum_cls: [member.value for member in enum_cls], + ), + nullable=False, + ) + amount = Column(BigInteger, nullable=False) + balance_after = Column(BigInteger, nullable=False) + reference_type = Column(Text, nullable=True) + reference_id = Column(Text, nullable=True) + idempotency_key = Column(Text, unique=True, index=True, nullable=True) + metadata_json = Column(JSONB, nullable=True) + created_at = Column(DateTime(timezone=True), server_default=func.now())