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    #[must_use]
60    pub fn new(
61        workspace_id: Uuid,
62        change_type: ChangeType,
63        user_id: Uuid,
64        resource_id: Option<Uuid>,
65        payload: serde_json::Value,
66    ) -> Self {
67        Self {
68            id: Uuid::new_v4(),
69            workspace_id,
70            change_type,
71            user_id,
72            resource_id,
73            payload,
74            timestamp: Utc::now(),
75        }
76    }
77}
78
79/// Event listener trait
80#[async_trait::async_trait]
81pub trait EventListener: Send + Sync {
82    /// Handle an event
83    async fn on_event(&self, event: ChangeEvent) -> Result<()>;
84}
85
86/// Event bus for broadcasting changes
87pub struct EventBus {
88    /// Broadcast channel for events
89    sender: broadcast::Sender<ChangeEvent>,
90}
91
92impl EventBus {
93    /// Create a new event bus
94    #[must_use]
95    pub fn new(capacity: usize) -> Self {
96        let (sender, _) = broadcast::channel(capacity);
97        Self { sender }
98    }
99
100    /// Publish an event
101    pub fn publish(&self, event: ChangeEvent) -> Result<()> {
102        // Ignore error if no receivers (it's ok)
103        let _ = self.sender.send(event);
104        Ok(())
105    }
106
107    /// Subscribe to events
108    #[must_use]
109    pub fn subscribe(&self) -> broadcast::Receiver<ChangeEvent> {
110        self.sender.subscribe()
111    }
112
113    /// Get number of active subscribers
114    #[must_use]
115    pub fn subscriber_count(&self) -> usize {
116        self.sender.receiver_count()
117    }
118}
119
120/// Workspace-specific event bus
121pub struct WorkspaceEventBus {
122    /// Main event bus
123    event_bus: Arc<EventBus>,
124    /// Workspace ID
125    workspace_id: Uuid,
126}
127
128impl WorkspaceEventBus {
129    /// Create a new workspace event bus
130    #[must_use]
131    pub const fn new(event_bus: Arc<EventBus>, workspace_id: Uuid) -> Self {
132        Self {
133            event_bus,
134            workspace_id,
135        }
136    }
137
138    /// Publish an event for this workspace
139    pub fn publish(
140        &self,
141        change_type: ChangeType,
142        user_id: Uuid,
143        resource_id: Option<Uuid>,
144        payload: serde_json::Value,
145    ) -> Result<()> {
146        let event = ChangeEvent::new(self.workspace_id, change_type, user_id, resource_id, payload);
147        self.event_bus.publish(event)
148    }
149
150    /// Subscribe to events (need to filter by `workspace_id`)
151    #[must_use]
152    pub fn subscribe(&self) -> broadcast::Receiver<ChangeEvent> {
153        self.event_bus.subscribe()
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160
161    #[test]
162    fn test_change_event_creation() {
163        let workspace_id = Uuid::new_v4();
164        let user_id = Uuid::new_v4();
165        let event = ChangeEvent::new(
166            workspace_id,
167            ChangeType::MockCreated,
168            user_id,
169            None,
170            serde_json::json!({"mock_id": "123"}),
171        );
172
173        assert_eq!(event.workspace_id, workspace_id);
174        assert_eq!(event.change_type, ChangeType::MockCreated);
175        assert_eq!(event.user_id, user_id);
176    }
177
178    #[test]
179    fn test_event_bus() {
180        let bus = EventBus::new(100);
181        assert_eq!(bus.subscriber_count(), 0);
182
183        let _rx1 = bus.subscribe();
184        assert_eq!(bus.subscriber_count(), 1);
185
186        let _rx2 = bus.subscribe();
187        assert_eq!(bus.subscriber_count(), 2);
188    }
189
190    #[tokio::test]
191    async fn test_event_publishing() {
192        let bus = EventBus::new(100);
193        let mut rx = bus.subscribe();
194
195        let workspace_id = Uuid::new_v4();
196        let user_id = Uuid::new_v4();
197        let event = ChangeEvent::new(
198            workspace_id,
199            ChangeType::MockCreated,
200            user_id,
201            None,
202            serde_json::json!({}),
203        );
204
205        bus.publish(event.clone()).unwrap();
206
207        let received = rx.recv().await.unwrap();
208        assert_eq!(received.workspace_id, workspace_id);
209        assert_eq!(received.change_type, ChangeType::MockCreated);
210    }
211}