# app/modules/messaging/schemas/email.py """ Email template Pydantic schemas for API responses and requests. Provides schemas for: - EmailTemplate: Platform email templates - StoreEmailTemplate: Store-specific template overrides """ from datetime import datetime from pydantic import BaseModel, ConfigDict, Field class EmailTemplateBase(BaseModel): """Base schema for email templates.""" code: str = Field(..., description="Template code (e.g., 'password_reset')") language: str = Field(default="en", description="Language code (en, fr, de, lb)") name: str = Field(..., description="Human-readable template name") description: str | None = Field(None, description="Template description") category: str = Field(..., description="Template category (auth, orders, billing, etc.)") subject: str = Field(..., description="Email subject (supports Jinja2 variables)") body_html: str = Field(..., description="HTML email body") body_text: str | None = Field(None, description="Plain text fallback") variables: list[str] = Field(default_factory=list, description="Available variables") class EmailTemplateCreate(EmailTemplateBase): """Schema for creating an email template.""" required_variables: list[str] = Field( default_factory=list, description="Required variables" ) is_platform_only: bool = Field( default=False, description="Cannot be overridden by stores" ) class EmailTemplateUpdate(BaseModel): """Schema for updating an email template.""" name: str | None = Field(None, description="Human-readable template name") description: str | None = Field(None, description="Template description") subject: str | None = Field(None, description="Email subject") body_html: str | None = Field(None, description="HTML email body") body_text: str | None = Field(None, description="Plain text fallback") variables: list[str] | None = Field(None, description="Available variables") required_variables: list[str] | None = Field(None, description="Required variables") is_active: bool | None = Field(None, description="Template active status") class EmailTemplateResponse(BaseModel): """Schema for email template API response.""" model_config = ConfigDict(from_attributes=True) id: int code: str language: str name: str description: str | None category: str subject: str body_html: str body_text: str | None variables: list[str] = Field(default_factory=list) required_variables: list[str] = Field(default_factory=list) is_active: bool is_platform_only: bool created_at: datetime updated_at: datetime @classmethod def from_db(cls, template) -> "EmailTemplateResponse": """Create response from database model.""" return cls( id=template.id, code=template.code, language=template.language, name=template.name, description=template.description, category=template.category, subject=template.subject, body_html=template.body_html, body_text=template.body_text, variables=template.variables_list, required_variables=template.required_variables_list, is_active=template.is_active, is_platform_only=template.is_platform_only, created_at=template.created_at, updated_at=template.updated_at, ) class EmailTemplateSummary(BaseModel): """Summary schema for template list views.""" model_config = ConfigDict(from_attributes=True) id: int code: str name: str category: str languages: list[str] = Field(default_factory=list) is_platform_only: bool is_active: bool @classmethod def from_db_list(cls, templates: list) -> list["EmailTemplateSummary"]: """ Create summaries from database models, grouping by code. Args: templates: List of EmailTemplate models Returns: List of EmailTemplateSummary grouped by template code """ # Group templates by code by_code: dict[str, list] = {} for t in templates: if t.code not in by_code: by_code[t.code] = [] by_code[t.code].append(t) summaries = [] for code, group in by_code.items(): first = group[0] summaries.append( cls( id=first.id, code=code, name=first.name, category=first.category, languages=[t.language for t in group], is_platform_only=first.is_platform_only, is_active=first.is_active, ) ) return summaries # Store Email Template Schemas class StoreEmailTemplateCreate(BaseModel): """Schema for creating a store email template override.""" template_code: str = Field(..., description="Template code to override") language: str = Field(default="en", description="Language code") name: str | None = Field(None, description="Custom name (uses platform default if None)") subject: str = Field(..., description="Custom email subject") body_html: str = Field(..., description="Custom HTML body") body_text: str | None = Field(None, description="Custom plain text body") class StoreEmailTemplateUpdate(BaseModel): """Schema for updating a store email template override.""" name: str | None = Field(None, description="Custom name") subject: str | None = Field(None, description="Custom email subject") body_html: str | None = Field(None, description="Custom HTML body") body_text: str | None = Field(None, description="Custom plain text body") is_active: bool | None = Field(None, description="Override active status") class StoreEmailTemplateResponse(BaseModel): """Schema for store email template override API response.""" model_config = ConfigDict(from_attributes=True) id: int store_id: int template_code: str language: str name: str | None subject: str body_html: str body_text: str | None is_active: bool created_at: datetime updated_at: datetime class EmailTemplateWithOverrideStatus(BaseModel): """ Schema showing template with store override status. Used in store UI to show which templates have been customized. """ model_config = ConfigDict(from_attributes=True) code: str name: str category: str languages: list[str] is_platform_only: bool has_override: bool = Field( default=False, description="Whether store has customized this template" ) override_languages: list[str] = Field( default_factory=list, description="Languages with store overrides", ) # Email Preview/Test Schemas class EmailPreviewRequest(BaseModel): """Schema for requesting an email preview.""" template_code: str = Field(..., description="Template code") language: str = Field(default="en", description="Language code") variables: dict[str, str] = Field( default_factory=dict, description="Variables to inject" ) class EmailPreviewResponse(BaseModel): """Schema for email preview response.""" subject: str body_html: str body_text: str | None class EmailTestRequest(BaseModel): """Schema for sending a test email.""" template_code: str = Field(..., description="Template code") language: str = Field(default="en", description="Language code") to_email: str = Field(..., description="Recipient email address") variables: dict[str, str] = Field( default_factory=dict, description="Variables to inject" ) class EmailTestResponse(BaseModel): """Schema for test email response.""" success: bool message: str email_log_id: int | None = None # ============================================================================= # Email Log (Audit) Schemas # ============================================================================= class EmailLogListItem(BaseModel): """Compact email log item (no body content).""" id: int recipient_email: str recipient_name: str | None = None subject: str status: str template_code: str | None = None provider: str | None = None store_id: int | None = None related_type: str | None = None related_id: int | None = None created_at: str | None = None sent_at: str | None = None error_message: str | None = None class EmailLogDetail(BaseModel): """Full email log detail including body content.""" id: int recipient_email: str recipient_name: str | None = None subject: str status: str template_code: str | None = None provider: str | None = None store_id: int | None = None user_id: int | None = None related_type: str | None = None related_id: int | None = None from_email: str | None = None from_name: str | None = None reply_to: str | None = None body_html: str | None = None body_text: str | None = None error_message: str | None = None retry_count: int = 0 provider_message_id: str | None = None created_at: str | None = None sent_at: str | None = None delivered_at: str | None = None opened_at: str | None = None clicked_at: str | None = None extra_data: str | None = None class EmailLogListResponse(BaseModel): """Paginated email log list.""" items: list[EmailLogListItem] total: int page: int per_page: int total_pages: int class EmailLogStatsResponse(BaseModel): """Email log statistics.""" by_status: dict[str, int] = Field(default_factory=dict) by_template: dict[str, int] = Field(default_factory=dict) total: int = 0