Files
bakery-ia/services/notification/app/schemas/notifications.py

291 lines
8.9 KiB
Python

# ================================================================
# 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