Clean up accumulated backward-compat shims, deprecated wrappers, unused aliases, and legacy code across the codebase. Since the platform is not live yet, this establishes a clean baseline. Changes: - Delete deprecated middleware/context.py (RequestContext, get_request_context) - Remove unused factory get_store_email_settings_service() - Remove deprecated pagination_full macro, /admin/platform-homepage route - Remove ConversationResponse, InvoiceSettings* unprefixed aliases - Simplify celery_config.py (remove empty LEGACY_TASK_MODULES) - Standardize billing exceptions: *Error aliases → *Exception names - Consolidate duplicate TierNotFoundError/FeatureNotFoundError classes - Remove deprecated is_admin_request() from Store/PlatformContextManager - Remove is_platform_default field, MediaUploadResponse legacy flat fields - Remove MediaItemResponse.url alias, update JS to use file_url - Update all affected tests and documentation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
307 lines
7.9 KiB
Python
307 lines
7.9 KiB
Python
# app/modules/messaging/schemas/message.py
|
|
"""
|
|
Pydantic schemas for the messaging system.
|
|
|
|
Supports three communication channels:
|
|
- Admin <-> Store
|
|
- Store <-> 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"
|
|
if self.file_size < 1024 * 1024:
|
|
return f"{self.file_size / 1024:.1f} KB"
|
|
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
|
|
store_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
|
|
store_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
|
|
store_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
|
|
|
|
# Store info if applicable
|
|
store_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
|
|
store_id: int | None = None # For store users
|
|
store_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 store info for admin views."""
|
|
|
|
store_name: str | None = None
|
|
store_code: str | None = None
|
|
|
|
|
|
class AdminConversationListResponse(BaseModel):
|
|
"""Schema for admin conversation list with store 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_store_conversations: int = 0
|
|
store_customer_conversations: int = 0
|
|
admin_customer_conversations: int = 0
|
|
|
|
# Unread
|
|
unread_admin: int = 0
|