from pydantic import BaseModel, Field, model_validator from typing import Optional, Union, Any from datetime import datetime from enum import Enum class MessageType(str, Enum): MESSAGE = "MESSAGE" NOTIFICATION = "NOTIFICATION" class ContentType(str, Enum): TEXT = "TEXT" IMAGE = "IMAGE" VIDEO = "VIDEO" FILE = "FILE" # 用户在应用内发起的“申请通知”,用于在私信界面展示通知样式(携带 action_url/action_text) USER_NOTIFICATION = "USER_NOTIFICATION" class MessageBase(BaseModel): title: str = Field(..., max_length=255) content: Union[str, dict, Any] = Field(..., description="内容") content_type: ContentType = ContentType.TEXT type: MessageType = MessageType.MESSAGE app_id: Optional[str] = None # 改为字符串类型,支持开发者保存的字符串 app_id action_url: Optional[str] = None action_text: Optional[str] = None # SSO 扩展 target_url: Optional[str] = None auto_sso: bool = False class MessageCreate(MessageBase): receiver_id: Optional[int] = None app_user_id: Optional[str] = None sender_app_user_id: Optional[str] = None # 应用发信时可选:帐内发起人,解析后写入 sender_id,用于审计和会话聚合 is_broadcast: bool = False @model_validator(mode='after') def check_receiver(self): if self.is_broadcast: return self if not self.receiver_id and not (self.app_user_id and self.app_id): raise ValueError("非广播消息必须提供 receiver_id,或者同时提供 app_user_id 和 app_id") return self class MessageUpdate(BaseModel): is_read: Optional[bool] = None class MessageResponse(BaseModel): id: int sender_id: Optional[int] receiver_id: int app_id: Optional[int] app_name: Optional[str] = None # 应用名称,用于前端显示 type: MessageType content_type: ContentType title: str content: str # DB 中存的是字符串 action_url: Optional[str] action_text: Optional[str] is_read: bool created_at: datetime read_at: Optional[datetime] class Config: from_attributes = True class ConversationResponse(BaseModel): user_id: int username: str full_name: Optional[str] unread_count: int last_message: str last_message_type: ContentType updated_at: datetime # 系统会话标记及应用信息(用于拆分不同平台的系统通知会话) is_system: bool = False app_id: Optional[int] = None app_name: Optional[str] = None # 列表副文案:有 app 的应用通知为「应用通知」;无 app 的旧系统会话为空;用户会话为对端组织名,无组织为空 remarks: Optional[str] = None class Config: from_attributes = True # Device Schema class DeviceRegister(BaseModel): device_token: str platform: str device_name: Optional[str] = None