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}