# models/schema/message.py """ Pydantic schemas for the messaging system. Supports three communication channels: - Admin <-> Vendor - Vendor <-> Customer - Admin <-> Customer """ from datetime import datetime from pydantic import BaseModel, ConfigDict, Field from models.database.message import ConversationType, ParticipantType # ============================================================================ # Attachment Schemas # ============================================================================ class AttachmentResponse(BaseModel): """Schema for message attachment in responses.""" model_config = ConfigDict(from_attributes=True) id: int filename: str original_filename: str file_size: int mime_type: str is_image: bool image_width: int | None = None image_height: int | None = None download_url: str | None = None thumbnail_url: str | None = None @property def file_size_display(self) -> str: """Human-readable file size.""" if self.file_size < 1024: return f"{self.file_size} B" elif self.file_size < 1024 * 1024: return f"{self.file_size / 1024:.1f} KB" else: return f"{self.file_size / 1024 / 1024:.1f} MB" # ============================================================================ # Message Schemas # ============================================================================ class MessageCreate(BaseModel): """Schema for sending a new message.""" content: str = Field(..., min_length=1, max_length=10000) class MessageResponse(BaseModel): """Schema for a single message in responses.""" model_config = ConfigDict(from_attributes=True) id: int conversation_id: int sender_type: ParticipantType sender_id: int content: str is_system_message: bool is_deleted: bool created_at: datetime # Enriched sender info (populated by API) sender_name: str | None = None sender_email: str | None = None # Attachments attachments: list[AttachmentResponse] = [] # ============================================================================ # Participant Schemas # ============================================================================ class ParticipantInfo(BaseModel): """Schema for participant information.""" id: int type: ParticipantType name: str email: str | None = None avatar_url: str | None = None class ParticipantResponse(BaseModel): """Schema for conversation participant in responses.""" model_config = ConfigDict(from_attributes=True) id: int participant_type: ParticipantType participant_id: int unread_count: int last_read_at: datetime | None email_notifications: bool muted: bool # Enriched info (populated by API) participant_info: ParticipantInfo | None = None # ============================================================================ # Conversation Schemas # ============================================================================ class ConversationCreate(BaseModel): """Schema for creating a new conversation.""" conversation_type: ConversationType subject: str = Field(..., min_length=1, max_length=500) recipient_type: ParticipantType recipient_id: int vendor_id: int | None = None initial_message: str | None = Field(None, min_length=1, max_length=10000) class ConversationSummary(BaseModel): """Schema for conversation in list views.""" model_config = ConfigDict(from_attributes=True) id: int conversation_type: ConversationType subject: str vendor_id: int | None = None is_closed: bool closed_at: datetime | None last_message_at: datetime | None message_count: int created_at: datetime # Unread count for current user (from participant) unread_count: int = 0 # Other participant info (enriched by API) other_participant: ParticipantInfo | None = None # Last message preview last_message_preview: str | None = None class ConversationDetailResponse(BaseModel): """Schema for full conversation detail with messages.""" model_config = ConfigDict(from_attributes=True) id: int conversation_type: ConversationType subject: str vendor_id: int | None = None is_closed: bool closed_at: datetime | None closed_by_type: ParticipantType | None = None closed_by_id: int | None = None last_message_at: datetime | None message_count: int created_at: datetime updated_at: datetime # Participants with enriched info participants: list[ParticipantResponse] = [] # Messages ordered by created_at messages: list[MessageResponse] = [] # Current user's unread count unread_count: int = 0 # Vendor info if applicable vendor_name: str | None = None class ConversationListResponse(BaseModel): """Schema for paginated conversation list.""" conversations: list[ConversationSummary] total: int total_unread: int skip: int limit: int # ============================================================================ # Unread Count Schemas # ============================================================================ class UnreadCountResponse(BaseModel): """Schema for unread message count (for header badge).""" total_unread: int # ============================================================================ # Notification Preferences Schemas # ============================================================================ class NotificationPreferencesUpdate(BaseModel): """Schema for updating notification preferences.""" email_notifications: bool | None = None muted: bool | None = None # ============================================================================ # Conversation Action Schemas # ============================================================================ class CloseConversationResponse(BaseModel): """Response after closing a conversation.""" success: bool message: str conversation_id: int class ReopenConversationResponse(BaseModel): """Response after reopening a conversation.""" success: bool message: str conversation_id: int class MarkReadResponse(BaseModel): """Response after marking conversation as read.""" success: bool conversation_id: int unread_count: int # ============================================================================ # Recipient Selection Schemas (for compose modal) # ============================================================================ class RecipientOption(BaseModel): """Schema for a selectable recipient in compose modal.""" id: int type: ParticipantType name: str email: str | None = None vendor_id: int | None = None # For vendor users vendor_name: str | None = None class RecipientListResponse(BaseModel): """Schema for list of available recipients.""" recipients: list[RecipientOption] total: int # ============================================================================ # Admin-specific Schemas # ============================================================================ class AdminConversationSummary(ConversationSummary): """Extended conversation summary with vendor info for admin views.""" vendor_name: str | None = None vendor_code: str | None = None class AdminConversationListResponse(BaseModel): """Schema for admin conversation list with vendor info.""" conversations: list[AdminConversationSummary] total: int total_unread: int skip: int limit: int class AdminMessageStats(BaseModel): """Messaging statistics for admin dashboard.""" total_conversations: int = 0 open_conversations: int = 0 closed_conversations: int = 0 total_messages: int = 0 # By type admin_vendor_conversations: int = 0 vendor_customer_conversations: int = 0 admin_customer_conversations: int = 0 # Unread unread_admin: int = 0