# ================================================================ # services/notification/app/schemas/notifications.py # ================================================================ """ Notification schemas for API validation and serialization """ from pydantic import BaseModel, EmailStr, Field, validator from typing import Optional, Dict, Any, List from datetime import datetime from enum import Enum # Reuse enums from models class NotificationType(str, Enum): EMAIL = "email" WHATSAPP = "whatsapp" PUSH = "push" SMS = "sms" class NotificationStatus(str, Enum): PENDING = "pending" SENT = "sent" DELIVERED = "delivered" FAILED = "failed" CANCELLED = "cancelled" class NotificationPriority(str, Enum): LOW = "low" NORMAL = "normal" HIGH = "high" URGENT = "urgent" # ================================================================ # REQUEST SCHEMAS # ================================================================ class NotificationCreate(BaseModel): """Schema for creating a new notification""" type: NotificationType recipient_id: Optional[str] = None # For individual notifications recipient_email: Optional[EmailStr] = None recipient_phone: Optional[str] = None # Content subject: Optional[str] = None message: str = Field(..., min_length=1, max_length=5000) html_content: Optional[str] = None # Template-based content template_id: Optional[str] = None template_data: Optional[Dict[str, Any]] = None # Configuration priority: NotificationPriority = NotificationPriority.NORMAL scheduled_at: Optional[datetime] = None broadcast: bool = False # Internal fields (set by service) tenant_id: Optional[str] = None sender_id: Optional[str] = None @validator('recipient_phone') def validate_phone(cls, v): """Validate Spanish phone number format""" if v and not v.startswith(('+34', '6', '7', '9')): raise ValueError('Invalid Spanish phone number format') return v @validator('scheduled_at') def validate_scheduled_at(cls, v): """Ensure scheduled time is in the future""" if v and v <= datetime.utcnow(): raise ValueError('Scheduled time must be in the future') return v class NotificationUpdate(BaseModel): """Schema for updating notification status""" status: Optional[NotificationStatus] = None error_message: Optional[str] = None delivered_at: Optional[datetime] = None read: Optional[bool] = None read_at: Optional[datetime] = None class BulkNotificationCreate(BaseModel): """Schema for creating bulk notifications""" type: NotificationType recipients: List[str] = Field(..., min_items=1, max_items=1000) # User IDs or emails # Content subject: Optional[str] = None message: str = Field(..., min_length=1, max_length=5000) html_content: Optional[str] = None # Template-based content template_id: Optional[str] = None template_data: Optional[Dict[str, Any]] = None # Configuration priority: NotificationPriority = NotificationPriority.NORMAL scheduled_at: Optional[datetime] = None # ================================================================ # RESPONSE SCHEMAS # ================================================================ class NotificationResponse(BaseModel): """Schema for notification response""" id: str tenant_id: str sender_id: str recipient_id: Optional[str] type: NotificationType status: NotificationStatus priority: NotificationPriority subject: Optional[str] message: str recipient_email: Optional[str] recipient_phone: Optional[str] scheduled_at: Optional[datetime] sent_at: Optional[datetime] delivered_at: Optional[datetime] broadcast: bool read: bool read_at: Optional[datetime] retry_count: int error_message: Optional[str] created_at: datetime updated_at: datetime class Config: from_attributes = True class NotificationHistory(BaseModel): """Schema for notification history""" notifications: List[NotificationResponse] total: int page: int per_page: int has_next: bool has_prev: bool class NotificationStats(BaseModel): """Schema for notification statistics""" total_sent: int total_delivered: int total_failed: int delivery_rate: float avg_delivery_time_minutes: Optional[float] by_type: Dict[str, int] by_status: Dict[str, int] recent_activity: List[Dict[str, Any]] # ================================================================ # PREFERENCE SCHEMAS # ================================================================ class NotificationPreferences(BaseModel): """Schema for user notification preferences""" user_id: str tenant_id: str # Email preferences email_enabled: bool = True email_alerts: bool = True email_marketing: bool = False email_reports: bool = True # WhatsApp preferences whatsapp_enabled: bool = False whatsapp_alerts: bool = False whatsapp_reports: bool = False # Push notification preferences push_enabled: bool = True push_alerts: bool = True push_reports: bool = False # Timing preferences quiet_hours_start: str = Field(default="22:00", pattern=r"^([01]?[0-9]|2[0-3]):[0-5][0-9]$") quiet_hours_end: str = Field(default="08:00", pattern=r"^([01]?[0-9]|2[0-3]):[0-5][0-9]$") timezone: str = "Europe/Madrid" # Frequency preferences digest_frequency: str = Field(default="daily", pattern=r"^(none|daily|weekly)$") max_emails_per_day: int = Field(default=10, ge=1, le=100) # Language preference language: str = Field(default="es", pattern=r"^(es|en)$") created_at: datetime updated_at: datetime class Config: from_attributes = True class PreferencesUpdate(BaseModel): """Schema for updating notification preferences""" email_enabled: Optional[bool] = None email_alerts: Optional[bool] = None email_marketing: Optional[bool] = None email_reports: Optional[bool] = None whatsapp_enabled: Optional[bool] = None whatsapp_alerts: Optional[bool] = None whatsapp_reports: Optional[bool] = None push_enabled: Optional[bool] = None push_alerts: Optional[bool] = None push_reports: Optional[bool] = None quiet_hours_start: Optional[str] = Field(None, pattern=r"^([01]?[0-9]|2[0-3]):[0-5][0-9]$") quiet_hours_end: Optional[str] = Field(None, pattern=r"^([01]?[0-9]|2[0-3]):[0-5][0-9]$") timezone: Optional[str] = None digest_frequency: Optional[str] = Field(None, pattern=r"^(none|daily|weekly)$") max_emails_per_day: Optional[int] = Field(None, ge=1, le=100) language: Optional[str] = Field(None, pattern=r"^(es|en)$") # ================================================================ # TEMPLATE SCHEMAS # ================================================================ class TemplateCreate(BaseModel): """Schema for creating notification templates""" template_key: str = Field(..., min_length=3, max_length=100) name: str = Field(..., min_length=3, max_length=255) description: Optional[str] = None category: str = Field(..., pattern=r"^(alert|marketing|transactional)$") type: NotificationType subject_template: Optional[str] = None body_template: str = Field(..., min_length=10) html_template: Optional[str] = None language: str = Field(default="es", pattern=r"^(es|en)$") default_priority: NotificationPriority = NotificationPriority.NORMAL required_variables: Optional[List[str]] = None class TemplateResponse(BaseModel): """Schema for template response""" id: str tenant_id: Optional[str] template_key: str name: str description: Optional[str] category: str type: NotificationType subject_template: Optional[str] body_template: str html_template: Optional[str] language: str is_active: bool is_system: bool default_priority: NotificationPriority required_variables: Optional[List[str]] created_at: datetime updated_at: datetime class Config: from_attributes = True # ================================================================ # WEBHOOK SCHEMAS # ================================================================ class DeliveryWebhook(BaseModel): """Schema for delivery status webhooks""" notification_id: str status: NotificationStatus provider: str provider_message_id: Optional[str] = None delivered_at: Optional[datetime] = None error_code: Optional[str] = None error_message: Optional[str] = None metadata: Optional[Dict[str, Any]] = None class ReadReceiptWebhook(BaseModel): """Schema for read receipt webhooks""" notification_id: str read_at: datetime user_agent: Optional[str] = None ip_address: Optional[str] = None