from sqlalchemy import Column, Integer, ForeignKey, DateTime from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy.orm import relationship from sqlalchemy.sql import func from app.database import Base # The default a brand-new account gets before they touch the customization # screen. Kept in sync with assets/avatars/catalog.json (the base_body id) and # with the client's AvatarLoadout.to_json() shape. DEFAULT_LOADOUT: dict = { "base_body": "base_human_male_01", "slots": {}, } class AvatarLoadout(Base): """A user's equipped avatar composition. One row per user. The whole loadout is stored as a single JSON document matching the client's ``AvatarLoadout.to_json()`` shape (``{"base_body": str, "slots": {...}, "skin_tone"?: {...}}``) rather than exploded into columns -- the server has no copy of the asset catalog, so deep validation (does this asset exist, is it valid for this slot) stays on the client. The server only enforces the envelope: valid JSON object with a non-empty ``base_body``. """ __tablename__ = "avatar_loadouts" user_id = Column( Integer, ForeignKey("users.id", ondelete="CASCADE"), primary_key=True, ) data = Column(JSONB, nullable=False) created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False) updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False) user = relationship("User", back_populates="avatar_loadout")