mockforge_collab/
events.rs

1//! Event system for real-time updates
2
3use crate::error::Result;
4use chrono::{DateTime, Utc};
5use serde::{Deserialize, Serialize};
6use std::sync::Arc;
7use tokio::sync::broadcast;
8use uuid::Uuid;
9
10/// Type of change event
11#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
12#[serde(rename_all = "snake_case")]
13pub enum ChangeType {
14    /// Mock/route created
15    MockCreated,
16    /// Mock/route updated
17    MockUpdated,
18    /// Mock/route deleted
19    MockDeleted,
20    /// Workspace settings updated
21    WorkspaceUpdated,
22    /// Member added
23    MemberAdded,
24    /// Member removed
25    MemberRemoved,
26    /// Member role changed
27    RoleChanged,
28    /// Snapshot created
29    SnapshotCreated,
30    /// User cursor moved (presence)
31    CursorMoved,
32    /// User joined workspace
33    UserJoined,
34    /// User left workspace
35    UserLeft,
36}
37
38/// A change event
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct ChangeEvent {
41    /// Event ID
42    pub id: Uuid,
43    /// Workspace ID
44    pub workspace_id: Uuid,
45    /// Type of change
46    pub change_type: ChangeType,
47    /// User who triggered the change
48    pub user_id: Uuid,
49    /// Resource ID (mock ID, member ID, etc.)
50    pub resource_id: Option<Uuid>,
51    /// Event payload
52    pub payload: serde_json::Value,
53    /// Timestamp
54    pub timestamp: DateTime<Utc>,
55}
56
57impl ChangeEvent {
58    /// Create a new change event
59    pub fn new(
60        workspace_id: Uuid,
61        change_type: ChangeType,
62        user_id: Uuid,
63        resource_id: Option<Uuid>,
64        payload: serde_json::Value,
65    ) -> Self {
66        Self {
67            id: Uuid::new_v4(),
68            workspace_id,
69            change_type,
70            user_id,
71            resource_id,
72            payload,
73            timestamp: Utc::now(),
74        }
75    }
76}
77
78/// Event listener trait
79#[async_trait::async_trait]
80pub trait EventListener: Send + Sync {
81    /// Handle an event
82    async fn on_event(&self, event: ChangeEvent) -> Result<()>;
83}
84
85/// Event bus for broadcasting changes
86pub struct EventBus {
87    /// Broadcast channel for events
88    sender: broadcast::Sender<ChangeEvent>,
89}
90
91impl EventBus {
92    /// Create a new event bus
93    pub fn new(capacity: usize) -> Self {
94        let (sender, _) = broadcast::channel(capacity);
95        Self { sender }
96    }
97
98    /// Publish an event
99    pub fn publish(&self, event: ChangeEvent) -> Result<()> {
100        // Ignore error if no receivers (it's ok)
101        let _ = self.sender.send(event);
102        Ok(())
103    }
104
105    /// Subscribe to events
106    pub fn subscribe(&self) -> broadcast::Receiver<ChangeEvent> {
107        self.sender.subscribe()
108    }
109
110    /// Get number of active subscribers
111    pub fn subscriber_count(&self) -> usize {
112        self.sender.receiver_count()
113    }
114}
115
116/// Workspace-specific event bus
117pub struct WorkspaceEventBus {
118    /// Main event bus
119    event_bus: Arc<EventBus>,
120    /// Workspace ID
121    workspace_id: Uuid,
122}
123
124impl WorkspaceEventBus {
125    /// Create a new workspace event bus
126    pub fn new(event_bus: Arc<EventBus>, workspace_id: Uuid) -> Self {
127        Self {
128            event_bus,
129            workspace_id,
130        }
131    }
132
133    /// Publish an event for this workspace
134    pub fn publish(
135        &self,
136        change_type: ChangeType,
137        user_id: Uuid,
138        resource_id: Option<Uuid>,
139        payload: serde_json::Value,
140    ) -> Result<()> {
141        let event = ChangeEvent::new(self.workspace_id, change_type, user_id, resource_id, payload);
142        self.event_bus.publish(event)
143    }
144
145    /// Subscribe to events (need to filter by workspace_id)
146    pub fn subscribe(&self) -> broadcast::Receiver<ChangeEvent> {
147        self.event_bus.subscribe()
148    }
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154
155    #[test]
156    fn test_change_event_creation() {
157        let workspace_id = Uuid::new_v4();
158        let user_id = Uuid::new_v4();
159        let event = ChangeEvent::new(
160            workspace_id,
161            ChangeType::MockCreated,
162            user_id,
163            None,
164            serde_json::json!({"mock_id": "123"}),
165        );
166
167        assert_eq!(event.workspace_id, workspace_id);
168        assert_eq!(event.change_type, ChangeType::MockCreated);
169        assert_eq!(event.user_id, user_id);
170    }
171
172    #[test]
173    fn test_event_bus() {
174        let bus = EventBus::new(100);
175        assert_eq!(bus.subscriber_count(), 0);
176
177        let _rx1 = bus.subscribe();
178        assert_eq!(bus.subscriber_count(), 1);
179
180        let _rx2 = bus.subscribe();
181        assert_eq!(bus.subscriber_count(), 2);
182    }
183
184    #[tokio::test]
185    async fn test_event_publishing() {
186        let bus = EventBus::new(100);
187        let mut rx = bus.subscribe();
188
189        let workspace_id = Uuid::new_v4();
190        let user_id = Uuid::new_v4();
191        let event = ChangeEvent::new(
192            workspace_id,
193            ChangeType::MockCreated,
194            user_id,
195            None,
196            serde_json::json!({}),
197        );
198
199        bus.publish(event.clone()).unwrap();
200
201        let received = rx.recv().await.unwrap();
202        assert_eq!(received.workspace_id, workspace_id);
203        assert_eq!(received.change_type, ChangeType::MockCreated);
204    }
205}