notification.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. """Pydantic schemas for notification providers."""
  2. from datetime import datetime
  3. from enum import Enum
  4. from typing import Any
  5. from pydantic import BaseModel, Field, field_validator
  6. class ProviderType(str, Enum):
  7. """Supported notification provider types."""
  8. CALLMEBOT = "callmebot"
  9. NTFY = "ntfy"
  10. PUSHOVER = "pushover"
  11. TELEGRAM = "telegram"
  12. EMAIL = "email"
  13. WEBPUSH = "webpush"
  14. class NotificationProviderBase(BaseModel):
  15. """Base schema for notification providers."""
  16. name: str = Field(..., min_length=1, max_length=100, description="User-defined name")
  17. provider_type: ProviderType = Field(..., description="Type of notification provider")
  18. enabled: bool = Field(default=True, description="Whether notifications are enabled")
  19. config: dict[str, Any] = Field(..., description="Provider-specific configuration")
  20. # Event triggers - print lifecycle
  21. on_print_start: bool = Field(default=False, description="Notify on print start")
  22. on_print_complete: bool = Field(default=True, description="Notify on print complete")
  23. on_print_failed: bool = Field(default=True, description="Notify on print failed")
  24. on_print_stopped: bool = Field(default=True, description="Notify when print is stopped/cancelled")
  25. on_print_progress: bool = Field(default=False, description="Notify at 25%, 50%, 75% progress")
  26. # Event triggers - printer status
  27. on_printer_offline: bool = Field(default=False, description="Notify when printer goes offline")
  28. on_printer_error: bool = Field(default=False, description="Notify on printer errors (AMS, etc.)")
  29. on_filament_low: bool = Field(default=False, description="Notify when filament is running low")
  30. on_maintenance_due: bool = Field(default=False, description="Notify when maintenance is due")
  31. # Event triggers - AMS environmental alarms
  32. on_ams_humidity_high: bool = Field(default=False, description="Notify when AMS humidity exceeds threshold")
  33. on_ams_temperature_high: bool = Field(default=False, description="Notify when AMS temperature exceeds threshold")
  34. # Quiet hours
  35. quiet_hours_enabled: bool = Field(default=False, description="Enable quiet hours")
  36. quiet_hours_start: str | None = Field(default=None, description="Start time in HH:MM format")
  37. quiet_hours_end: str | None = Field(default=None, description="End time in HH:MM format")
  38. # Daily digest
  39. daily_digest_enabled: bool = Field(default=False, description="Batch notifications into daily digest")
  40. daily_digest_time: str | None = Field(default=None, description="Time to send digest in HH:MM format")
  41. # Printer filter
  42. printer_id: int | None = Field(default=None, description="Specific printer ID or null for all")
  43. @field_validator("quiet_hours_start", "quiet_hours_end", "daily_digest_time")
  44. @classmethod
  45. def validate_time_format(cls, v: str | None) -> str | None:
  46. if v is None:
  47. return v
  48. try:
  49. parts = v.split(":")
  50. if len(parts) != 2:
  51. raise ValueError("Invalid time format")
  52. hour, minute = int(parts[0]), int(parts[1])
  53. if not (0 <= hour <= 23 and 0 <= minute <= 59):
  54. raise ValueError("Invalid time range")
  55. return f"{hour:02d}:{minute:02d}"
  56. except (ValueError, TypeError):
  57. raise ValueError("Time must be in HH:MM format (e.g., 22:00)")
  58. class NotificationProviderCreate(NotificationProviderBase):
  59. """Schema for creating a notification provider."""
  60. pass
  61. class NotificationProviderUpdate(BaseModel):
  62. """Schema for updating a notification provider (all fields optional)."""
  63. name: str | None = Field(default=None, min_length=1, max_length=100)
  64. provider_type: ProviderType | None = None
  65. enabled: bool | None = None
  66. config: dict[str, Any] | None = None
  67. # Event triggers - print lifecycle
  68. on_print_start: bool | None = None
  69. on_print_complete: bool | None = None
  70. on_print_failed: bool | None = None
  71. on_print_stopped: bool | None = None
  72. on_print_progress: bool | None = None
  73. # Event triggers - printer status
  74. on_printer_offline: bool | None = None
  75. on_printer_error: bool | None = None
  76. on_filament_low: bool | None = None
  77. on_maintenance_due: bool | None = None
  78. # Event triggers - AMS environmental alarms
  79. on_ams_humidity_high: bool | None = None
  80. on_ams_temperature_high: bool | None = None
  81. # Quiet hours
  82. quiet_hours_enabled: bool | None = None
  83. quiet_hours_start: str | None = None
  84. quiet_hours_end: str | None = None
  85. # Daily digest
  86. daily_digest_enabled: bool | None = None
  87. daily_digest_time: str | None = None
  88. # Printer filter
  89. printer_id: int | None = None
  90. class NotificationProviderResponse(NotificationProviderBase):
  91. """Schema for notification provider API responses."""
  92. id: int
  93. last_success: datetime | None = None
  94. last_error: str | None = None
  95. last_error_at: datetime | None = None
  96. created_at: datetime
  97. updated_at: datetime
  98. class Config:
  99. from_attributes = True
  100. class NotificationTestRequest(BaseModel):
  101. """Schema for testing notification configuration."""
  102. provider_type: ProviderType
  103. config: dict[str, Any]
  104. class NotificationTestResponse(BaseModel):
  105. """Schema for test notification response."""
  106. success: bool
  107. message: str
  108. # Provider-specific config schemas for documentation/validation reference
  109. class CallMeBotConfig(BaseModel):
  110. """CallMeBot/WhatsApp configuration."""
  111. phone: str = Field(..., description="Phone number with country code (e.g., +1234567890)")
  112. apikey: str = Field(..., description="API key from CallMeBot")
  113. class NtfyConfig(BaseModel):
  114. """ntfy configuration."""
  115. server: str = Field(default="https://ntfy.sh", description="ntfy server URL")
  116. topic: str = Field(..., description="Topic name to publish to")
  117. auth_token: str | None = Field(default=None, description="Optional authentication token")
  118. class PushoverConfig(BaseModel):
  119. """Pushover configuration."""
  120. user_key: str = Field(..., description="Your Pushover user key")
  121. app_token: str = Field(..., description="Your Pushover application token")
  122. priority: int = Field(default=0, ge=-2, le=2, description="Message priority (-2 to 2)")
  123. class TelegramConfig(BaseModel):
  124. """Telegram bot configuration."""
  125. bot_token: str = Field(..., description="Bot token from @BotFather")
  126. chat_id: str = Field(..., description="Chat ID to send messages to")
  127. class EmailConfig(BaseModel):
  128. """Email/SMTP configuration."""
  129. smtp_server: str = Field(..., description="SMTP server hostname")
  130. smtp_port: int = Field(default=587, description="SMTP port (587 for TLS, 465 for SSL)")
  131. username: str = Field(..., description="SMTP username/email")
  132. password: str = Field(..., description="SMTP password or app password")
  133. from_email: str = Field(..., description="From email address")
  134. to_email: str = Field(..., description="Recipient email address")
  135. use_tls: bool = Field(default=True, description="Use TLS encryption")
  136. # Notification Log schemas
  137. class NotificationLogResponse(BaseModel):
  138. """Schema for notification log API responses."""
  139. id: int
  140. provider_id: int
  141. provider_name: str | None = None
  142. provider_type: str | None = None
  143. event_type: str
  144. title: str
  145. message: str
  146. success: bool
  147. error_message: str | None = None
  148. printer_id: int | None = None
  149. printer_name: str | None = None
  150. created_at: datetime
  151. class Config:
  152. from_attributes = True
  153. class NotificationLogStats(BaseModel):
  154. """Statistics for notification logs."""
  155. total: int
  156. success_count: int
  157. failure_count: int
  158. by_event_type: dict[str, int]
  159. by_provider: dict[str, int]
  160. # Push Subscription schemas
  161. class PushSubscriptionCreate(BaseModel):
  162. """Schema for creating a push subscription."""
  163. endpoint: str = Field(..., description="Push service endpoint URL")
  164. p256dh_key: str = Field(..., description="P-256 public key for encryption")
  165. auth_key: str = Field(..., description="Authentication secret")
  166. name: str | None = Field(default=None, max_length=100, description="User-friendly name")
  167. user_agent: str | None = Field(default=None, max_length=255, description="Browser user agent")
  168. class PushSubscriptionResponse(BaseModel):
  169. """Schema for push subscription API responses."""
  170. id: int
  171. endpoint: str
  172. name: str | None
  173. user_agent: str | None
  174. enabled: bool
  175. last_success: datetime | None
  176. last_error: str | None
  177. last_error_at: datetime | None
  178. created_at: datetime
  179. class Config:
  180. from_attributes = True
  181. class PushSubscriptionUpdate(BaseModel):
  182. """Schema for updating a push subscription."""
  183. name: str | None = Field(default=None, max_length=100)
  184. enabled: bool | None = None
  185. class VapidPublicKeyResponse(BaseModel):
  186. """Schema for VAPID public key response."""
  187. public_key: str = Field(..., description="VAPID public key for push subscription")