notifications.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. """API routes for notification providers."""
  2. import json
  3. import logging
  4. from fastapi import APIRouter, Depends, HTTPException
  5. from sqlalchemy import select
  6. from sqlalchemy.ext.asyncio import AsyncSession
  7. from backend.app.core.database import get_db
  8. from backend.app.models.notification import NotificationProvider
  9. from backend.app.schemas.notification import (
  10. NotificationProviderCreate,
  11. NotificationProviderResponse,
  12. NotificationProviderUpdate,
  13. NotificationTestRequest,
  14. NotificationTestResponse,
  15. )
  16. from backend.app.services.notification_service import notification_service
  17. logger = logging.getLogger(__name__)
  18. router = APIRouter(prefix="/notifications", tags=["notifications"])
  19. def _provider_to_dict(provider: NotificationProvider) -> dict:
  20. """Convert a NotificationProvider model to a response dictionary."""
  21. return {
  22. "id": provider.id,
  23. "name": provider.name,
  24. "provider_type": provider.provider_type,
  25. "enabled": provider.enabled,
  26. "config": json.loads(provider.config) if isinstance(provider.config, str) else provider.config,
  27. # Print lifecycle events
  28. "on_print_start": provider.on_print_start,
  29. "on_print_complete": provider.on_print_complete,
  30. "on_print_failed": provider.on_print_failed,
  31. "on_print_stopped": provider.on_print_stopped,
  32. "on_print_progress": provider.on_print_progress,
  33. # Printer status events
  34. "on_printer_offline": provider.on_printer_offline,
  35. "on_printer_error": provider.on_printer_error,
  36. "on_filament_low": provider.on_filament_low,
  37. "on_maintenance_due": provider.on_maintenance_due,
  38. # Quiet hours
  39. "quiet_hours_enabled": provider.quiet_hours_enabled,
  40. "quiet_hours_start": provider.quiet_hours_start,
  41. "quiet_hours_end": provider.quiet_hours_end,
  42. # Printer filter
  43. "printer_id": provider.printer_id,
  44. # Status tracking
  45. "last_success": provider.last_success,
  46. "last_error": provider.last_error,
  47. "last_error_at": provider.last_error_at,
  48. # Timestamps
  49. "created_at": provider.created_at,
  50. "updated_at": provider.updated_at,
  51. }
  52. @router.get("/", response_model=list[NotificationProviderResponse])
  53. async def list_notification_providers(db: AsyncSession = Depends(get_db)):
  54. """List all notification providers."""
  55. result = await db.execute(
  56. select(NotificationProvider).order_by(NotificationProvider.created_at.desc())
  57. )
  58. providers = result.scalars().all()
  59. return [_provider_to_dict(provider) for provider in providers]
  60. @router.post("/", response_model=NotificationProviderResponse)
  61. async def create_notification_provider(
  62. provider_data: NotificationProviderCreate,
  63. db: AsyncSession = Depends(get_db),
  64. ):
  65. """Create a new notification provider."""
  66. provider = NotificationProvider(
  67. name=provider_data.name,
  68. provider_type=provider_data.provider_type.value,
  69. enabled=provider_data.enabled,
  70. config=json.dumps(provider_data.config),
  71. # Print lifecycle events
  72. on_print_start=provider_data.on_print_start,
  73. on_print_complete=provider_data.on_print_complete,
  74. on_print_failed=provider_data.on_print_failed,
  75. on_print_progress=provider_data.on_print_progress,
  76. # Printer status events
  77. on_printer_offline=provider_data.on_printer_offline,
  78. on_printer_error=provider_data.on_printer_error,
  79. on_filament_low=provider_data.on_filament_low,
  80. # Quiet hours
  81. quiet_hours_enabled=provider_data.quiet_hours_enabled,
  82. quiet_hours_start=provider_data.quiet_hours_start,
  83. quiet_hours_end=provider_data.quiet_hours_end,
  84. # Printer filter
  85. printer_id=provider_data.printer_id,
  86. )
  87. db.add(provider)
  88. await db.commit()
  89. await db.refresh(provider)
  90. logger.info(f"Created notification provider: {provider.name} ({provider.provider_type})")
  91. return _provider_to_dict(provider)
  92. @router.get("/{provider_id}", response_model=NotificationProviderResponse)
  93. async def get_notification_provider(
  94. provider_id: int,
  95. db: AsyncSession = Depends(get_db),
  96. ):
  97. """Get a specific notification provider."""
  98. result = await db.execute(
  99. select(NotificationProvider).where(NotificationProvider.id == provider_id)
  100. )
  101. provider = result.scalar_one_or_none()
  102. if not provider:
  103. raise HTTPException(status_code=404, detail="Notification provider not found")
  104. return _provider_to_dict(provider)
  105. @router.patch("/{provider_id}", response_model=NotificationProviderResponse)
  106. async def update_notification_provider(
  107. provider_id: int,
  108. update_data: NotificationProviderUpdate,
  109. db: AsyncSession = Depends(get_db),
  110. ):
  111. """Update a notification provider."""
  112. result = await db.execute(
  113. select(NotificationProvider).where(NotificationProvider.id == provider_id)
  114. )
  115. provider = result.scalar_one_or_none()
  116. if not provider:
  117. raise HTTPException(status_code=404, detail="Notification provider not found")
  118. # Update only provided fields
  119. update_dict = update_data.model_dump(exclude_unset=True)
  120. for key, value in update_dict.items():
  121. if key == "config" and value is not None:
  122. setattr(provider, key, json.dumps(value))
  123. elif key == "provider_type" and value is not None:
  124. setattr(provider, key, value.value)
  125. else:
  126. setattr(provider, key, value)
  127. await db.commit()
  128. await db.refresh(provider)
  129. logger.info(f"Updated notification provider: {provider.name}")
  130. return _provider_to_dict(provider)
  131. @router.delete("/{provider_id}")
  132. async def delete_notification_provider(
  133. provider_id: int,
  134. db: AsyncSession = Depends(get_db),
  135. ):
  136. """Delete a notification provider."""
  137. result = await db.execute(
  138. select(NotificationProvider).where(NotificationProvider.id == provider_id)
  139. )
  140. provider = result.scalar_one_or_none()
  141. if not provider:
  142. raise HTTPException(status_code=404, detail="Notification provider not found")
  143. name = provider.name
  144. await db.delete(provider)
  145. await db.commit()
  146. logger.info(f"Deleted notification provider: {name}")
  147. return {"message": f"Notification provider '{name}' deleted"}
  148. @router.post("/{provider_id}/test", response_model=NotificationTestResponse)
  149. async def test_notification_provider(
  150. provider_id: int,
  151. db: AsyncSession = Depends(get_db),
  152. ):
  153. """Send a test notification using an existing provider."""
  154. result = await db.execute(
  155. select(NotificationProvider).where(NotificationProvider.id == provider_id)
  156. )
  157. provider = result.scalar_one_or_none()
  158. if not provider:
  159. raise HTTPException(status_code=404, detail="Notification provider not found")
  160. config = json.loads(provider.config) if isinstance(provider.config, str) else provider.config
  161. success, message = await notification_service.send_test_notification(
  162. provider.provider_type, config
  163. )
  164. # Update provider status
  165. if success:
  166. from datetime import datetime
  167. provider.last_success = datetime.utcnow()
  168. else:
  169. from datetime import datetime
  170. provider.last_error = message
  171. provider.last_error_at = datetime.utcnow()
  172. await db.commit()
  173. return NotificationTestResponse(success=success, message=message)
  174. @router.post("/test-config", response_model=NotificationTestResponse)
  175. async def test_notification_config(
  176. test_request: NotificationTestRequest,
  177. ):
  178. """Test notification configuration before saving."""
  179. success, message = await notification_service.send_test_notification(
  180. test_request.provider_type.value, test_request.config
  181. )
  182. return NotificationTestResponse(success=success, message=message)