from datetime import datetime from uuid import uuid4 from sqlalchemy import DateTime, ForeignKey, Integer, String, Text, UniqueConstraint from sqlalchemy.orm import Mapped, mapped_column, relationship from app.core.database import Base from app.core.utils.time import utc_now from app.modules.feishu_users.models import FeishuUser from app.modules.personalization.constants import ( CONVERSATION_CODE_PREFIX, PREFERENCE_CODE_PREFIX, PreferenceSource, ) def _public_code(prefix: str) -> str: return f"{prefix}-{uuid4().hex}" class UserPreference(Base): __tablename__ = "user_preferences" __table_args__ = ( UniqueConstraint( "owner_id", "category", "normalized_value", name="uq_user_preference_owner_category_value", ), ) id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) code: Mapped[str] = mapped_column( String(64), default=lambda: _public_code(PREFERENCE_CODE_PREFIX), unique=True, index=True, ) owner_id: Mapped[int] = mapped_column( ForeignKey("feishu_users.id", ondelete="CASCADE"), index=True, ) owner: Mapped[FeishuUser] = relationship() category: Mapped[str] = mapped_column(String(32), index=True) value: Mapped[str] = mapped_column(Text) normalized_value: Mapped[str] = mapped_column(String(1000)) source: Mapped[str] = mapped_column( String(32), default=PreferenceSource.EXPLICIT, index=True, ) created_at: Mapped[datetime] = mapped_column(DateTime, default=utc_now, index=True) updated_at: Mapped[datetime] = mapped_column( DateTime, default=utc_now, onupdate=utc_now, ) class AIConversation(Base): __tablename__ = "ai_conversations" __table_args__ = ( UniqueConstraint( "owner_id", "chat_type", "chat_key", name="uq_ai_conversation_owner_chat", ), ) id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) code: Mapped[str] = mapped_column( String(64), default=lambda: _public_code(CONVERSATION_CODE_PREFIX), unique=True, index=True, ) owner_id: Mapped[int] = mapped_column( ForeignKey("feishu_users.id", ondelete="CASCADE"), index=True, ) owner: Mapped[FeishuUser] = relationship() chat_type: Mapped[str] = mapped_column(String(32), index=True) chat_key: Mapped[str] = mapped_column(String(256), index=True) created_at: Mapped[datetime] = mapped_column(DateTime, default=utc_now, index=True) updated_at: Mapped[datetime] = mapped_column( DateTime, default=utc_now, onupdate=utc_now, index=True, ) messages: Mapped[list["AIConversationMessage"]] = relationship( back_populates="conversation", cascade="all, delete-orphan", passive_deletes=True, order_by="AIConversationMessage.id", ) class AIConversationMessage(Base): __tablename__ = "ai_conversation_messages" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) conversation_id: Mapped[int] = mapped_column( ForeignKey("ai_conversations.id", ondelete="CASCADE"), index=True, ) role: Mapped[str] = mapped_column(String(32), index=True) content: Mapped[str] = mapped_column(Text) created_at: Mapped[datetime] = mapped_column(DateTime, default=utc_now, index=True) conversation: Mapped[AIConversation] = relationship(back_populates="messages") class PersonalDataErasureRequest(Base): __tablename__ = "personal_data_erasure_requests" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) owner_id: Mapped[int] = mapped_column( ForeignKey("feishu_users.id", ondelete="CASCADE"), unique=True, index=True, ) owner: Mapped[FeishuUser] = relationship() token_hash: Mapped[str] = mapped_column(String(64)) expires_at: Mapped[datetime] = mapped_column(DateTime, index=True) created_at: Mapped[datetime] = mapped_column(DateTime, default=utc_now, index=True) updated_at: Mapped[datetime] = mapped_column( DateTime, default=utc_now, onupdate=utc_now, )