Files
orion/app/modules/messaging/schemas/message.py
Samir Boulahtit de83875d0a refactor: migrate modules from re-exports to canonical implementations
Move actual code implementations into module directories:
- orders: 5 services, 4 models, order/invoice schemas
- inventory: 3 services, 2 models, 30+ schemas
- customers: 3 services, 2 models, customer schemas
- messaging: 3 services, 2 models, message/notification schemas
- monitoring: background_tasks_service
- marketplace: 5+ services including letzshop submodule
- dev_tools: code_quality_service, test_runner_service
- billing: billing_service
- contracts: definition.py

Legacy files in app/services/, models/database/, models/schema/
now re-export from canonical module locations for backwards
compatibility. Architecture validator passes with 0 errors.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 21:28:56 +01:00

313 lines
8.0 KiB
Python

# app/modules/messaging/schemas/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 app.modules.messaging.models.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
# Backward compatibility alias
ConversationResponse = ConversationDetailResponse
# ============================================================================
# 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