intent_engine/notifications.rs
1/// Unified notification infrastructure for Dashboard WebSocket communication
2///
3/// This module provides a centralized NotificationSender that handles the common
4/// pattern of sending database operation notifications via WebSocket.
5use crate::dashboard::websocket::{DatabaseOperationPayload, ProtocolMessage, WebSocketState};
6use std::sync::Arc;
7
8/// Centralized notification sender for database operations
9///
10/// Handles the common 3-step pattern:
11/// 1. Create ProtocolMessage from payload
12/// 2. Serialize to JSON
13/// 3. Send via WebSocket (Dashboard UI)
14pub struct NotificationSender {
15 ws_state: Option<Arc<WebSocketState>>,
16}
17
18impl NotificationSender {
19 /// Create a new NotificationSender
20 ///
21 /// # Arguments
22 /// * `ws_state` - Optional WebSocket state for Dashboard UI notifications
23 pub fn new(ws_state: Option<Arc<WebSocketState>>) -> Self {
24 Self { ws_state }
25 }
26
27 /// Send a database operation notification via all available channels
28 ///
29 /// This method handles:
30 /// - Creating a ProtocolMessage wrapper
31 /// - Serializing to JSON (with error handling)
32 /// - Broadcasting to Dashboard WebSocket (if connected)
33 /// - Sending to MCP channel (if connected)
34 ///
35 /// # Arguments
36 /// * `payload` - The database operation payload to send
37 pub async fn send(&self, payload: DatabaseOperationPayload) {
38 use ProtocolMessage as PM;
39
40 // Step 1: Wrap payload in protocol message
41 let msg = PM::new("db_operation", payload);
42
43 // Step 2: Serialize to JSON
44 let json = match msg.to_json() {
45 Ok(j) => j,
46 Err(e) => {
47 tracing::warn!(error = %e, "Failed to serialize notification message");
48 return;
49 },
50 };
51
52 // Step 3: Send via Dashboard WebSocket (if available)
53 if let Some(ws) = &self.ws_state {
54 ws.broadcast_to_ui(&json).await;
55 }
56 }
57}
58
59#[cfg(test)]
60mod tests {
61 use super::*;
62
63 #[test]
64 fn test_notification_sender_new() {
65 let sender = NotificationSender::new(None);
66 assert!(sender.ws_state.is_none());
67 }
68
69 #[tokio::test]
70 async fn test_send_with_no_channels() {
71 // Should not panic when no channels are configured
72 let sender = NotificationSender::new(None);
73 let payload = DatabaseOperationPayload {
74 operation: "create".to_string(),
75 entity: "task".to_string(),
76 affected_ids: vec![1],
77 data: None,
78 project_path: "/test".to_string(),
79 };
80
81 sender.send(payload).await; // Should complete without error
82 }
83}