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