notification_templates.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. """API routes for notification template management."""
  2. from fastapi import APIRouter, Depends, HTTPException
  3. from sqlalchemy import select
  4. from sqlalchemy.ext.asyncio import AsyncSession
  5. from backend.app.core.database import get_db
  6. from backend.app.models.notification_template import DEFAULT_TEMPLATES, NotificationTemplate
  7. from backend.app.schemas.notification_template import (
  8. EVENT_VARIABLES,
  9. SAMPLE_DATA,
  10. EventVariablesResponse,
  11. NotificationTemplateResponse,
  12. NotificationTemplateUpdate,
  13. TemplatePreviewRequest,
  14. TemplatePreviewResponse,
  15. )
  16. from backend.app.services.notification_service import notification_service
  17. router = APIRouter(prefix="/notification-templates", tags=["notification-templates"])
  18. # Event type display names
  19. EVENT_NAMES = {
  20. "print_start": "Print Started",
  21. "print_complete": "Print Completed",
  22. "print_failed": "Print Failed",
  23. "print_stopped": "Print Stopped",
  24. "print_progress": "Print Progress",
  25. "printer_offline": "Printer Offline",
  26. "printer_error": "Printer Error",
  27. "filament_low": "Filament Low",
  28. "maintenance_due": "Maintenance Due",
  29. "test": "Test Notification",
  30. # Queue notifications
  31. "queue_job_added": "Queue Job Added",
  32. "queue_job_assigned": "Queue Job Assigned",
  33. "queue_job_started": "Queue Job Started",
  34. "queue_job_waiting": "Queue Job Waiting",
  35. "queue_job_skipped": "Queue Job Skipped",
  36. "queue_job_failed": "Queue Job Failed",
  37. "queue_completed": "Queue Completed",
  38. }
  39. @router.get("", response_model=list[NotificationTemplateResponse])
  40. @router.get("/", response_model=list[NotificationTemplateResponse])
  41. async def get_templates(db: AsyncSession = Depends(get_db)):
  42. """Get all notification templates."""
  43. result = await db.execute(select(NotificationTemplate).order_by(NotificationTemplate.id))
  44. return result.scalars().all()
  45. @router.get("/variables", response_model=list[EventVariablesResponse])
  46. async def get_variables():
  47. """Get available variables for each event type."""
  48. return [
  49. EventVariablesResponse(
  50. event_type=event_type,
  51. event_name=EVENT_NAMES.get(event_type, event_type),
  52. variables=variables,
  53. )
  54. for event_type, variables in EVENT_VARIABLES.items()
  55. ]
  56. @router.get("/{template_id}", response_model=NotificationTemplateResponse)
  57. async def get_template(template_id: int, db: AsyncSession = Depends(get_db)):
  58. """Get a single notification template."""
  59. result = await db.execute(select(NotificationTemplate).where(NotificationTemplate.id == template_id))
  60. template = result.scalar_one_or_none()
  61. if not template:
  62. raise HTTPException(status_code=404, detail="Template not found")
  63. return template
  64. @router.put("/{template_id}", response_model=NotificationTemplateResponse)
  65. async def update_template(
  66. template_id: int,
  67. update: NotificationTemplateUpdate,
  68. db: AsyncSession = Depends(get_db),
  69. ):
  70. """Update a notification template."""
  71. result = await db.execute(select(NotificationTemplate).where(NotificationTemplate.id == template_id))
  72. template = result.scalar_one_or_none()
  73. if not template:
  74. raise HTTPException(status_code=404, detail="Template not found")
  75. if update.title_template is not None:
  76. template.title_template = update.title_template
  77. if update.body_template is not None:
  78. template.body_template = update.body_template
  79. await db.commit()
  80. await db.refresh(template)
  81. # Clear template cache so changes take effect immediately
  82. notification_service.clear_template_cache()
  83. return template
  84. @router.post("/{template_id}/reset", response_model=NotificationTemplateResponse)
  85. async def reset_template(template_id: int, db: AsyncSession = Depends(get_db)):
  86. """Reset a notification template to its default values."""
  87. result = await db.execute(select(NotificationTemplate).where(NotificationTemplate.id == template_id))
  88. template = result.scalar_one_or_none()
  89. if not template:
  90. raise HTTPException(status_code=404, detail="Template not found")
  91. # Find the default template
  92. default = next(
  93. (t for t in DEFAULT_TEMPLATES if t["event_type"] == template.event_type),
  94. None,
  95. )
  96. if not default:
  97. raise HTTPException(status_code=500, detail="Default template not found")
  98. template.title_template = default["title_template"]
  99. template.body_template = default["body_template"]
  100. await db.commit()
  101. await db.refresh(template)
  102. # Clear template cache so changes take effect immediately
  103. notification_service.clear_template_cache()
  104. return template
  105. @router.post("/preview", response_model=TemplatePreviewResponse)
  106. async def preview_template(request: TemplatePreviewRequest):
  107. """Preview a template with sample data."""
  108. sample = SAMPLE_DATA.get(request.event_type, {})
  109. # Safe template rendering - replace missing vars with empty string
  110. def safe_format(template: str, data: dict) -> str:
  111. result = template
  112. for key, value in data.items():
  113. result = result.replace("{" + key + "}", str(value))
  114. # Remove any remaining unreplaced placeholders
  115. import re
  116. result = re.sub(r"\{[a-z_]+\}", "", result)
  117. return result
  118. return TemplatePreviewResponse(
  119. title=safe_format(request.title_template, sample),
  120. body=safe_format(request.body_template, sample),
  121. )