from __future__ import annotations from datetime import datetime from typing import Any, Literal from pydantic import BaseModel, ConfigDict, Field NotificationStatus = Literal["pending", "queued", "sending", "sent", "failed", "skipped", "cancelled"] class NotificationCreateRequest(BaseModel): model_config = ConfigDict(extra="forbid") source_module: str = Field(min_length=1, max_length=100) source_resource_type: str = Field(min_length=1, max_length=100) source_resource_id: str | None = Field(default=None, max_length=255) event_kind: str = Field(min_length=1, max_length=100) channel: str = Field(default="inbox", max_length=40) recipient: str | None = Field(default=None, max_length=500) recipient_type: str | None = Field(default=None, max_length=40) recipient_id: str | None = Field(default=None, max_length=255) recipient_label: str | None = Field(default=None, max_length=500) subject: str | None = Field(default=None, max_length=500) body_text: str | None = None body_html: str | None = None action_url: str | None = Field(default=None, max_length=1000) priority: int = 0 not_before_at: datetime | None = None enqueue_delivery: bool = True payload: dict[str, Any] = Field(default_factory=dict) metadata: dict[str, Any] = Field(default_factory=dict) class NotificationUpdateRequest(BaseModel): model_config = ConfigDict(extra="forbid") status: Literal["read", "acknowledged", "cancelled"] | None = None metadata: dict[str, Any] | None = None class NotificationDeliverPendingRequest(BaseModel): model_config = ConfigDict(extra="forbid") tenant_id: str | None = Field(default=None, max_length=36) limit: int = Field(default=50, ge=1, le=500) class NotificationPreferencesUpdateRequest(BaseModel): model_config = ConfigDict(extra="forbid") show_unread_badge: bool | None = None email_enabled: bool | None = None email_digest_enabled: bool | None = None muted_source_modules: list[str] | None = None metadata: dict[str, Any] | None = None class NotificationPreferencesResponse(BaseModel): tenant_id: str user_id: str show_unread_badge: bool = True email_enabled: bool = False email_digest_enabled: bool = False muted_source_modules: list[str] = Field(default_factory=list) metadata: dict[str, Any] = Field(default_factory=dict) class NotificationDeliveryAttemptResponse(BaseModel): id: str notification_id: str attempt_no: int channel: str provider: str | None = None status: str started_at: datetime | None = None finished_at: datetime | None = None external_message_id: str | None = None error: str | None = None details: dict[str, Any] = Field(default_factory=dict) created_at: datetime updated_at: datetime class NotificationResponse(BaseModel): id: str tenant_id: str source_module: str source_resource_type: str source_resource_id: str | None = None event_kind: str channel: str recipient: str | None = None recipient_type: str | None = None recipient_id: str | None = None recipient_label: str | None = None subject: str | None = None body_text: str | None = None body_html: str | None = None action_url: str | None = None priority: int status: str not_before_at: datetime | None = None queued_at: datetime | None = None sent_at: datetime | None = None failed_at: datetime | None = None read_at: datetime | None = None acknowledged_at: datetime | None = None cancelled_at: datetime | None = None attempt_count: int last_error: str | None = None external_message_id: str | None = None payload: dict[str, Any] = Field(default_factory=dict) metadata: dict[str, Any] = Field(default_factory=dict) attempts: list[NotificationDeliveryAttemptResponse] = Field(default_factory=list) created_at: datetime updated_at: datetime class NotificationListResponse(BaseModel): notifications: list[NotificationResponse] = Field(default_factory=list) class NotificationSummaryResponse(BaseModel): total: int = 0 unread: int = 0 pending: int = 0 failed: int = 0 show_unread_badge: bool = True class NotificationDeliveryResultResponse(BaseModel): notification: NotificationResponse | None = None processed: int = 0 sent: int = 0 failed: int = 0 skipped: int = 0 errors: list[str] = Field(default_factory=list)