"""Marketplace models: catalog items, listings, purchases, and creator balances. docs/planning/economy.md US-3, US-4 (task 166836d55365). """ from datetime import datetime, timezone import enum from sqlalchemy import ( BigInteger, CheckConstraint, Column, DateTime, Enum, ForeignKey, Integer, String, func, ) from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy.types import JSON from sqlalchemy.orm import relationship from app.database import Base class ItemType(str, enum.Enum): asset = "asset" avatar_item = "avatar_item" experience_pass = "experience_pass" class ListingStatus(str, enum.Enum): active = "active" sold = "sold" delisted = "delisted" removed_by_moderation = "removed_by_moderation" class PurchaseStatus(str, enum.Enum): pending_escrow = "pending_escrow" completed = "completed" refunded = "refunded" reversed = "reversed" class CatalogItem(Base): __tablename__ = "catalog_items" id = Column(BigInteger, primary_key=True, autoincrement=True) owner_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True) asset_id = Column(Integer, ForeignKey("assets.id", ondelete="SET NULL"), nullable=True, index=True) item_type = Column(Enum(ItemType, name="catalog_item_type"), nullable=False) name = Column(String(100), nullable=False) description = Column(String(2000), nullable=True) metadata_json = Column(JSONB().with_variant(JSON, "sqlite"), nullable=True) created_at = Column( DateTime(timezone=True), nullable=False, default=lambda: datetime.now(timezone.utc), server_default=func.now(), ) owner = relationship("User", foreign_keys=[owner_id]) asset = relationship("Asset", foreign_keys=[asset_id]) listings = relationship("MarketplaceListing", back_populates="catalog_item", cascade="all, delete-orphan") class MarketplaceListing(Base): __tablename__ = "marketplace_listings" id = Column(BigInteger, primary_key=True, autoincrement=True) catalog_item_id = Column(BigInteger, ForeignKey("catalog_items.id", ondelete="CASCADE"), nullable=False, index=True) seller_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True) price = Column(BigInteger, nullable=False) status = Column( Enum(ListingStatus, name="marketplace_listing_status"), nullable=False, default=ListingStatus.active, server_default=ListingStatus.active.value, ) created_at = Column( DateTime(timezone=True), nullable=False, default=lambda: datetime.now(timezone.utc), server_default=func.now(), ) updated_at = Column( DateTime(timezone=True), nullable=False, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc), server_default=func.now(), ) __table_args__ = ( CheckConstraint("price > 0", name="check_listing_price_positive"), ) catalog_item = relationship("CatalogItem", back_populates="listings") seller = relationship("User", foreign_keys=[seller_id]) class Purchase(Base): __tablename__ = "purchases" id = Column(BigInteger, primary_key=True, autoincrement=True) listing_id = Column(BigInteger, ForeignKey("marketplace_listings.id", ondelete="SET NULL"), nullable=True, index=True) buyer_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True) seller_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True) price = Column(BigInteger, nullable=False) platform_fee = Column(BigInteger, nullable=False) seller_net = Column(BigInteger, nullable=False) status = Column( Enum(PurchaseStatus, name="purchase_status"), nullable=False, default=PurchaseStatus.pending_escrow, server_default=PurchaseStatus.pending_escrow.value, ) escrow_hold_until = Column(DateTime(timezone=True), nullable=True) created_at = Column( DateTime(timezone=True), nullable=False, default=lambda: datetime.now(timezone.utc), server_default=func.now(), ) listing = relationship("MarketplaceListing", foreign_keys=[listing_id]) buyer = relationship("User", foreign_keys=[buyer_id]) seller = relationship("User", foreign_keys=[seller_id]) class CreatorBalance(Base): __tablename__ = "creator_balances" id = Column(BigInteger, primary_key=True, autoincrement=True) user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), unique=True, nullable=False, index=True) available = Column(BigInteger, nullable=False, default=0, server_default="0") pending = Column(BigInteger, nullable=False, default=0, server_default="0") lifetime_earned = Column(BigInteger, nullable=False, default=0, server_default="0") updated_at = Column( DateTime(timezone=True), nullable=False, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc), server_default=func.now(), ) __table_args__ = ( CheckConstraint("available >= 0", name="check_creator_available_non_negative"), CheckConstraint("pending >= 0", name="check_creator_pending_non_negative"), CheckConstraint("lifetime_earned >= 0", name="check_creator_lifetime_non_negative"), ) user = relationship("User", foreign_keys=[user_id])