From 57135fc85940bcedef5122b0c89dd3bc0e267dc7 Mon Sep 17 00:00:00 2001 From: coder-bot Date: Tue, 18 Aug 2026 18:38:20 +0000 Subject: [PATCH] WIP checkpoint: Trust & Safety: Moderation data model + Alembic migration --- backend/app/models/__init__.py | 5 +++- backend/app/models/appeal.py | 34 ++++++++++++++++++++++++ backend/app/models/moderation_action.py | 35 +++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 backend/app/models/appeal.py create mode 100644 backend/app/models/moderation_action.py diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py index 96e2dc3..8e95c5b 100644 --- a/backend/app/models/__init__.py +++ b/backend/app/models/__init__.py @@ -2,5 +2,8 @@ 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.report import Report +from app.models.moderation_action import ModerationAction +from app.models.appeal import Appeal -__all__ = ["User", "Session", "World", "Asset"] +__all__ = ["User", "Session", "World", "Asset", "Report", "ModerationAction", "Appeal"] diff --git a/backend/app/models/appeal.py b/backend/app/models/appeal.py new file mode 100644 index 0000000..12d33af --- /dev/null +++ b/backend/app/models/appeal.py @@ -0,0 +1,34 @@ +"""Appeal model for moderation actions.""" + +from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey, Enum +from sqlalchemy.orm import relationship +from sqlalchemy.sql import func + +from app.database import Base + + +class Appeal(Base): + __tablename__ = "appeals" + + id = Column(Integer, primary_key=True) + moderation_action_id = Column( + Integer, ForeignKey("moderation_actions.id"), nullable=False + ) + user_id = Column(Integer, ForeignKey("users.id"), nullable=False) + reason = Column(Text, nullable=False) + status = Column( + Enum( + "pending", "approved", "rejected", + name="appeal_status_enum", + ), + default="pending", + nullable=False, + ) + reviewed_by = Column(Integer, ForeignKey("users.id"), nullable=True) + reviewed_at = Column(DateTime(timezone=True), nullable=True) + created_at = Column(DateTime(timezone=True), server_default=func.now()) + + # Relationships + moderation_action = relationship("ModerationAction", back_populates="appeals") + user = relationship("User", back_populates="appeals") + reviewer = relationship("User", foreign_keys=[reviewed_by]) diff --git a/backend/app/models/moderation_action.py b/backend/app/models/moderation_action.py new file mode 100644 index 0000000..1d7d943 --- /dev/null +++ b/backend/app/models/moderation_action.py @@ -0,0 +1,35 @@ +from datetime import datetime +from typing import Optional + +from sqlalchemy import Column, Enum, ForeignKey, Integer, String, Text, DateTime +from sqlalchemy.orm import relationship +from sqlalchemy.sql import func + +from app.database import Base + + +class ModerationAction(Base): + __tablename__ = "moderation_actions" + + id = Column(Integer, primary_key=True) + moderator_id = Column(Integer, ForeignKey("users.id"), nullable=False) + target_user_id = Column(Integer, ForeignKey("users.id"), nullable=False) + target_type = Column( + Enum("user", "world", "asset", "chat_message", name="target_type_enum"), + nullable=False, + ) + target_id = Column(String(64), nullable=False) + action = Column( + Enum("warn", "suspend", "ban", "restrict", "remove", "restore", name="action_enum"), + nullable=False, + ) + reason = Column(Text, nullable=True) + report_id = Column(Integer, ForeignKey("reports.id"), nullable=True) + expires_at = Column(DateTime(timezone=True), nullable=True) + revoked_at = Column(DateTime(timezone=True), nullable=True) + created_at = Column(DateTime(timezone=True), server_default=func.now()) + + # Relationships + moderator = relationship("User", foreign_keys=[moderator_id]) + target_user = relationship("User", foreign_keys=[target_user_id]) + report = relationship("Report", back_populates="moderation_actions")