notification_template.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. """Pydantic schemas for notification templates."""
  2. from datetime import datetime
  3. from pydantic import BaseModel, Field
  4. from backend.app.core.compat import StrEnum
  5. class EventType(StrEnum):
  6. """Supported notification event types."""
  7. PRINT_START = "print_start"
  8. PRINT_COMPLETE = "print_complete"
  9. PRINT_FAILED = "print_failed"
  10. PRINT_STOPPED = "print_stopped"
  11. PRINT_PROGRESS = "print_progress"
  12. PRINT_MISSING_SPOOL_ASSIGNMENT = "print_missing_spool_assignment"
  13. BILLING_CHARGE_FAILED = "billing_charge_failed"
  14. PRINTER_OFFLINE = "printer_offline"
  15. PRINTER_ERROR = "printer_error"
  16. FILAMENT_LOW = "filament_low"
  17. MAINTENANCE_DUE = "maintenance_due"
  18. AMS_HUMIDITY_HIGH = "ams_humidity_high"
  19. AMS_TEMPERATURE_HIGH = "ams_temperature_high"
  20. AMS_DRYING_SUSPENDED = "ams_drying_suspended"
  21. BED_COOLED = "bed_cooled"
  22. HA_SENSOR_ALERT = "ha_sensor_alert"
  23. TEST = "test"
  24. # Available variables for each event type
  25. EVENT_VARIABLES: dict[str, list[str]] = {
  26. "print_start": ["printer", "filename", "estimated_time", "eta", "timestamp", "app_name"],
  27. "print_complete": [
  28. "printer",
  29. "filename",
  30. "duration",
  31. "filament_grams",
  32. "filament_details",
  33. "finish_photo_url",
  34. "timestamp",
  35. "app_name",
  36. ],
  37. "print_failed": [
  38. "printer",
  39. "filename",
  40. "duration",
  41. "filament_grams",
  42. "filament_details",
  43. "progress",
  44. "reason",
  45. "finish_photo_url",
  46. "timestamp",
  47. "app_name",
  48. ],
  49. "print_stopped": [
  50. "printer",
  51. "filename",
  52. "duration",
  53. "filament_grams",
  54. "filament_details",
  55. "progress",
  56. "finish_photo_url",
  57. "timestamp",
  58. "app_name",
  59. ],
  60. "print_progress": ["printer", "filename", "progress", "remaining_time", "eta", "timestamp", "app_name"],
  61. "print_missing_spool_assignment": [
  62. "printer",
  63. "missing_slots",
  64. "missing_slot_details",
  65. "timestamp",
  66. "app_name",
  67. ],
  68. "billing_charge_failed": ["printer", "filename", "archive_id", "error", "timestamp", "app_name"],
  69. "printer_offline": ["printer", "timestamp", "app_name"],
  70. "printer_error": ["printer", "error_type", "error_detail", "timestamp", "app_name"],
  71. "filament_low": ["printer", "slot", "remaining_percent", "color", "timestamp", "app_name"],
  72. "maintenance_due": ["printer", "items", "timestamp", "app_name"],
  73. "ams_humidity_high": ["printer", "ams_label", "humidity", "threshold", "timestamp", "app_name"],
  74. "ams_temperature_high": ["printer", "ams_label", "temperature", "threshold", "timestamp", "app_name"],
  75. "ams_drying_suspended": [
  76. "printer",
  77. "ams_label",
  78. "humidity",
  79. "threshold",
  80. "cycles",
  81. "timestamp",
  82. "app_name",
  83. ],
  84. "bed_cooled": ["printer", "bed_temp", "threshold", "filename", "timestamp", "app_name"],
  85. "ha_sensor_alert": ["printer", "sensor", "state", "timestamp", "app_name"],
  86. "location_ha_sensor_alert": ["location", "sensor", "state", "timestamp", "app_name"],
  87. "test": ["app_name", "timestamp"],
  88. # Queue notifications
  89. "queue_job_added": ["job_name", "target", "timestamp", "app_name"],
  90. "queue_job_assigned": ["job_name", "printer", "target_model", "timestamp", "app_name"],
  91. "queue_job_started": ["printer", "job_name", "estimated_time", "eta", "timestamp", "app_name"],
  92. "queue_job_waiting": ["job_name", "target_model", "waiting_reason", "timestamp", "app_name"],
  93. "queue_job_skipped": ["printer", "job_name", "reason", "timestamp", "app_name"],
  94. "queue_job_failed": ["printer", "job_name", "reason", "timestamp", "app_name"],
  95. "queue_completed": ["completed_count", "timestamp", "app_name"],
  96. "stock_reorder_alert": [
  97. "material",
  98. "subtype",
  99. "brand",
  100. "color",
  101. "stock_g",
  102. "rate_g_day",
  103. "days_left",
  104. "timestamp",
  105. "app_name",
  106. ],
  107. "stock_break_alert": [
  108. "material",
  109. "subtype",
  110. "brand",
  111. "color",
  112. "stock_g",
  113. "rate_g_day",
  114. "days_left",
  115. "lead_time_days",
  116. "timestamp",
  117. "app_name",
  118. ],
  119. # User management notifications
  120. "user_created": ["username", "password", "login_url", "app_name", "timestamp"],
  121. "password_reset": ["username", "password", "login_url", "app_name", "timestamp"],
  122. # User email print notifications
  123. "user_print_start": ["username", "printer", "filename", "timestamp", "app_name"],
  124. "user_print_complete": ["username", "printer", "filename", "timestamp", "app_name"],
  125. "user_print_failed": ["username", "printer", "filename", "timestamp", "app_name"],
  126. "user_print_stopped": ["username", "printer", "filename", "timestamp", "app_name"],
  127. }
  128. # Sample data for previewing templates
  129. SAMPLE_DATA: dict[str, dict[str, str]] = {
  130. "print_start": {
  131. "printer": "Bambu X1C",
  132. "filename": "Benchy.3mf",
  133. "estimated_time": "1h 23m",
  134. "eta": "15:53",
  135. "timestamp": "2024-01-15 14:30",
  136. "app_name": "Bambuddy",
  137. },
  138. "print_complete": {
  139. "printer": "Bambu X1C",
  140. "filename": "Benchy.3mf",
  141. "duration": "1h 18m",
  142. "filament_grams": "15.2",
  143. "filament_details": "AMS-A T1 PLA: 12.4g | AMS-A T3 PETG: 2.8g",
  144. "finish_photo_url": "/api/v1/archives/123/photos/finish_20240115_154800_abc12345.jpg",
  145. "timestamp": "2024-01-15 15:48",
  146. "app_name": "Bambuddy",
  147. },
  148. "print_failed": {
  149. "printer": "Bambu X1C",
  150. "filename": "Benchy.3mf",
  151. "duration": "0h 45m",
  152. "filament_grams": "7.6",
  153. "filament_details": "AMS-A T1 PLA: 7.6g",
  154. "progress": "50",
  155. "reason": "Filament runout",
  156. "finish_photo_url": "/api/v1/archives/123/photos/finish_20240115_151500_def67890.jpg",
  157. "timestamp": "2024-01-15 15:15",
  158. "app_name": "Bambuddy",
  159. },
  160. "print_stopped": {
  161. "printer": "Bambu X1C",
  162. "filename": "Benchy.3mf",
  163. "duration": "0h 30m",
  164. "filament_grams": "4.6",
  165. "filament_details": "AMS-A T2 PLA: 4.6g",
  166. "progress": "30",
  167. "finish_photo_url": "/api/v1/archives/123/photos/finish_20240115_150000_ghi11223.jpg",
  168. "timestamp": "2024-01-15 15:00",
  169. "app_name": "Bambuddy",
  170. },
  171. "print_progress": {
  172. "printer": "Bambu X1C",
  173. "filename": "Benchy.3mf",
  174. "progress": "50",
  175. "remaining_time": "0h 41m",
  176. "eta": "15:41",
  177. "timestamp": "2024-01-15 15:00",
  178. "app_name": "Bambuddy",
  179. },
  180. "print_missing_spool_assignment": {
  181. "printer": "Bambu X1C",
  182. "missing_slots": "A1, A3",
  183. "missing_slot_details": "- A1: PLA Basic\n- A3: PETG HF",
  184. "timestamp": "2024-01-15 14:30",
  185. "app_name": "Bambuddy",
  186. },
  187. "billing_charge_failed": {
  188. "printer": "Bambu X1C",
  189. "filename": "Benchy.3mf",
  190. "archive_id": "123",
  191. "error": "The transaction could not be persisted",
  192. "timestamp": "2024-01-15 15:48",
  193. "app_name": "Bambuddy",
  194. },
  195. "printer_offline": {
  196. "printer": "Bambu X1C",
  197. "timestamp": "2024-01-15 14:30",
  198. "app_name": "Bambuddy",
  199. },
  200. "printer_error": {
  201. "printer": "Bambu X1C",
  202. "error_type": "AMS Error",
  203. "error_detail": "Filament slot 1 jammed",
  204. "timestamp": "2024-01-15 14:30",
  205. "app_name": "Bambuddy",
  206. },
  207. "filament_low": {
  208. "printer": "Bambu X1C",
  209. "slot": "1",
  210. "remaining_percent": "15",
  211. "color": "Black PLA",
  212. "timestamp": "2024-01-15 14:30",
  213. "app_name": "Bambuddy",
  214. },
  215. "maintenance_due": {
  216. "printer": "Bambu X1C",
  217. "items": "• Nozzle cleaning (OVERDUE)\n• Carbon rod lubrication (Soon)",
  218. "timestamp": "2024-01-15 14:30",
  219. "app_name": "Bambuddy",
  220. },
  221. "ams_humidity_high": {
  222. "printer": "Bambu X1C",
  223. "ams_label": "AMS-A",
  224. "humidity": "75",
  225. "threshold": "60",
  226. "timestamp": "2024-01-15 14:30",
  227. "app_name": "Bambuddy",
  228. },
  229. "ams_temperature_high": {
  230. "printer": "Bambu X1C",
  231. "ams_label": "AMS-A",
  232. "temperature": "42",
  233. "threshold": "35",
  234. "timestamp": "2024-01-15 14:30",
  235. "app_name": "Bambuddy",
  236. },
  237. "ams_drying_suspended": {
  238. "printer": "Bambu X1C",
  239. "ams_label": "AMS-A",
  240. "humidity": "16",
  241. "threshold": "14",
  242. "cycles": "2",
  243. "timestamp": "2024-01-15 14:30",
  244. "app_name": "Bambuddy",
  245. },
  246. "bed_cooled": {
  247. "printer": "Bambu X1C",
  248. "bed_temp": "34",
  249. "threshold": "35",
  250. "filename": "Benchy",
  251. "timestamp": "2024-01-15 14:30",
  252. "app_name": "Bambuddy",
  253. },
  254. "ha_sensor_alert": {
  255. "printer": "Bambu X1C",
  256. "sensor": "Enclosure Door",
  257. "state": "open",
  258. "timestamp": "2024-01-15 14:30",
  259. "app_name": "Bambuddy",
  260. },
  261. "location_ha_sensor_alert": {
  262. "location": "Drybox 1",
  263. "sensor": "Drybox 1 Humidity",
  264. "state": "68.00 %",
  265. "timestamp": "2024-01-15 14:30",
  266. "app_name": "Bambuddy",
  267. },
  268. "test": {
  269. "app_name": "Bambuddy",
  270. "timestamp": "2024-01-15 14:30",
  271. },
  272. # Queue notifications
  273. "queue_job_added": {
  274. "job_name": "Benchy.3mf",
  275. "target": "Bambu X1C",
  276. "timestamp": "2024-01-15 14:30",
  277. "app_name": "Bambuddy",
  278. },
  279. "queue_job_assigned": {
  280. "job_name": "Benchy.3mf",
  281. "printer": "Bambu X1C #1",
  282. "target_model": "X1C",
  283. "timestamp": "2024-01-15 14:30",
  284. "app_name": "Bambuddy",
  285. },
  286. "queue_job_started": {
  287. "printer": "Bambu X1C",
  288. "job_name": "Benchy.3mf",
  289. "estimated_time": "1h 23m",
  290. "eta": "15:53",
  291. "timestamp": "2024-01-15 14:30",
  292. "app_name": "Bambuddy",
  293. },
  294. "queue_job_waiting": {
  295. "job_name": "Benchy.3mf",
  296. "target_model": "X1C",
  297. "waiting_reason": "Printer1 (needs PLA)",
  298. "timestamp": "2024-01-15 14:30",
  299. "app_name": "Bambuddy",
  300. },
  301. "queue_job_skipped": {
  302. "printer": "Bambu X1C",
  303. "job_name": "Benchy.3mf",
  304. "reason": "Previous print failed",
  305. "timestamp": "2024-01-15 14:30",
  306. "app_name": "Bambuddy",
  307. },
  308. "queue_job_failed": {
  309. "printer": "Bambu X1C",
  310. "job_name": "Benchy.3mf",
  311. "reason": "Upload failed: connection timeout",
  312. "timestamp": "2024-01-15 14:30",
  313. "app_name": "Bambuddy",
  314. },
  315. "queue_completed": {
  316. "completed_count": "5",
  317. "timestamp": "2024-01-15 18:30",
  318. "app_name": "Bambuddy",
  319. },
  320. "stock_reorder_alert": {
  321. "material": "PLA",
  322. "subtype": "Matte",
  323. "brand": "Bambu Lab",
  324. "color": "Charcoal",
  325. "stock_g": "420",
  326. "rate_g_day": "18.5",
  327. "days_left": "22",
  328. "timestamp": "2024-01-15 14:30",
  329. "app_name": "Bambuddy",
  330. },
  331. "stock_break_alert": {
  332. "material": "PLA",
  333. "subtype": "Matte",
  334. "brand": "Bambu Lab",
  335. "color": "Charcoal",
  336. "stock_g": "160",
  337. "rate_g_day": "18.5",
  338. "days_left": "8",
  339. "lead_time_days": "14",
  340. "timestamp": "2024-01-15 14:30",
  341. "app_name": "Bambuddy",
  342. },
  343. # User management notifications
  344. "user_created": {
  345. "username": "john_doe",
  346. "password": "<generated-password>",
  347. "login_url": "https://bambuddy.example.com/login",
  348. "app_name": "Bambuddy",
  349. "timestamp": "2024-01-15 14:30",
  350. },
  351. "password_reset": {
  352. "username": "john_doe",
  353. "password": "<new-password>",
  354. "login_url": "https://bambuddy.example.com/login",
  355. "app_name": "Bambuddy",
  356. "timestamp": "2024-01-15 14:30",
  357. },
  358. # User email print notifications
  359. "user_print_start": {
  360. "username": "john_doe",
  361. "printer": "Bambu X1C",
  362. "filename": "Benchy.3mf",
  363. "timestamp": "2024-01-15 14:30",
  364. "app_name": "Bambuddy",
  365. },
  366. "user_print_complete": {
  367. "username": "john_doe",
  368. "printer": "Bambu X1C",
  369. "filename": "Benchy.3mf",
  370. "timestamp": "2024-01-15 15:48",
  371. "app_name": "Bambuddy",
  372. },
  373. "user_print_failed": {
  374. "username": "john_doe",
  375. "printer": "Bambu X1C",
  376. "filename": "Benchy.3mf",
  377. "timestamp": "2024-01-15 15:15",
  378. "app_name": "Bambuddy",
  379. },
  380. "user_print_stopped": {
  381. "username": "john_doe",
  382. "printer": "Bambu X1C",
  383. "filename": "Benchy.3mf",
  384. "timestamp": "2024-01-15 15:15",
  385. "app_name": "Bambuddy",
  386. },
  387. }
  388. class NotificationTemplateBase(BaseModel):
  389. """Base schema for notification templates."""
  390. title_template: str = Field(..., min_length=1, max_length=200)
  391. body_template: str = Field(..., min_length=1, max_length=2000)
  392. class NotificationTemplateUpdate(BaseModel):
  393. """Schema for updating a notification template."""
  394. title_template: str | None = Field(default=None, min_length=1, max_length=200)
  395. body_template: str | None = Field(default=None, min_length=1, max_length=2000)
  396. class NotificationTemplateResponse(NotificationTemplateBase):
  397. """Schema for notification template API responses."""
  398. id: int
  399. event_type: str
  400. name: str
  401. is_default: bool
  402. created_at: datetime
  403. updated_at: datetime
  404. class Config:
  405. from_attributes = True
  406. class TemplateVariableInfo(BaseModel):
  407. """Information about a template variable."""
  408. name: str
  409. description: str
  410. class EventVariablesResponse(BaseModel):
  411. """Response for available variables per event type."""
  412. event_type: str
  413. event_name: str
  414. variables: list[str]
  415. class TemplatePreviewRequest(BaseModel):
  416. """Request to preview a template with sample data."""
  417. event_type: str
  418. title_template: str
  419. body_template: str
  420. class TemplatePreviewResponse(BaseModel):
  421. """Response with rendered template preview."""
  422. title: str
  423. body: str