1use chrono::{DateTime, Utc};
13use serde::{Deserialize, Serialize};
14use std::collections::HashMap;
15use uuid::Uuid;
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
19#[serde(tag = "type", rename_all = "snake_case")]
20pub enum SyncEvent {
21 SettingsChanged {
23 version: u64,
24 changes: Vec<SettingsChange>,
25 device_id: String,
26 },
27 FullSyncRequested { reason: String },
29 ConflictDetected {
31 path: String,
32 local_value: serde_json::Value,
33 remote_value: serde_json::Value,
34 local_timestamp: DateTime<Utc>,
35 remote_timestamp: DateTime<Utc>,
36 },
37 SessionInvalidated { device_id: String, reason: String },
39 ApiKeyRotated { key_id: Uuid },
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct SettingsChange {
46 pub path: String,
47 pub operation: ChangeOperation,
48 pub value: Option<serde_json::Value>,
49 pub timestamp: DateTime<Utc>,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
53#[serde(rename_all = "lowercase")]
54pub enum ChangeOperation {
55 Set,
56 Delete,
57 Merge,
58}
59
60#[derive(Debug, Clone)]
62pub struct SyncConfig {
63 pub ping_interval_secs: u64,
65 pub connection_timeout_secs: u64,
67 pub max_offline_queue: usize,
69 pub retry_delay_secs: u64,
71 pub max_retries: u32,
73}
74
75impl Default for SyncConfig {
76 fn default() -> Self {
77 Self {
78 ping_interval_secs: 30,
79 connection_timeout_secs: 60,
80 max_offline_queue: 100,
81 retry_delay_secs: 5,
82 max_retries: 3,
83 }
84 }
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct DeviceSession {
90 pub device_id: String,
92 pub device_name: String,
94 pub platform: String,
96 pub last_active: DateTime<Utc>,
98 pub settings_version: u64,
100 pub connected: bool,
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize)]
106#[serde(rename_all = "snake_case")]
107pub enum ConflictResolution {
108 UseLocal,
110 UseRemote,
112 UseNewest,
114 Merge,
116 Manual,
118}
119
120pub struct SyncService {
122 #[allow(dead_code)]
123 config: SyncConfig,
124 sessions: HashMap<String, DeviceSession>,
125}
126
127impl SyncService {
128 pub fn new(config: SyncConfig) -> Self {
129 Self {
130 config,
131 sessions: HashMap::new(),
132 }
133 }
134
135 pub fn register_session(&mut self, session: DeviceSession) -> Result<(), SyncError> {
137 self.sessions.insert(session.device_id.clone(), session);
138 Ok(())
139 }
140
141 pub fn remove_session(&mut self, device_id: &str) -> Result<(), SyncError> {
143 self.sessions.remove(device_id);
144 Ok(())
145 }
146
147 pub fn get_active_sessions(&self, _user_id: Uuid) -> Vec<&DeviceSession> {
149 self.sessions.values().collect()
151 }
152
153 pub async fn broadcast_event(
155 &self,
156 _user_id: Uuid,
157 _event: SyncEvent,
158 _exclude_device: Option<&str>,
159 ) -> Result<(), SyncError> {
160 Ok(())
162 }
163
164 pub fn resolve_conflict(
166 &self,
167 local: &serde_json::Value,
168 remote: &serde_json::Value,
169 local_timestamp: DateTime<Utc>,
170 remote_timestamp: DateTime<Utc>,
171 strategy: ConflictResolution,
172 ) -> serde_json::Value {
173 match strategy {
174 ConflictResolution::UseLocal => local.clone(),
175 ConflictResolution::UseRemote => remote.clone(),
176 ConflictResolution::UseNewest => {
177 if local_timestamp > remote_timestamp {
178 local.clone()
179 } else {
180 remote.clone()
181 }
182 }
183 ConflictResolution::Merge => {
184 if let (Some(local_obj), Some(remote_obj)) = (local.as_object(), remote.as_object())
186 {
187 let mut merged = local_obj.clone();
188 for (key, value) in remote_obj {
189 merged.insert(key.clone(), value.clone());
190 }
191 serde_json::Value::Object(merged)
192 } else {
193 if local_timestamp > remote_timestamp {
195 local.clone()
196 } else {
197 remote.clone()
198 }
199 }
200 }
201 ConflictResolution::Manual => {
202 local.clone()
204 }
205 }
206 }
207}
208
209impl Default for SyncService {
210 fn default() -> Self {
211 Self::new(SyncConfig::default())
212 }
213}
214
215#[derive(Debug, thiserror::Error)]
217pub enum SyncError {
218 #[error("Session not found")]
219 SessionNotFound,
220 #[error("Connection failed: {0}")]
221 ConnectionFailed(String),
222 #[error("Sync conflict: {0}")]
223 Conflict(String),
224 #[error("Offline queue full")]
225 QueueFull,
226 #[error("Broadcast failed: {0}")]
227 BroadcastError(String),
228}
229
230#[derive(Debug, Clone, Serialize, Deserialize)]
232#[serde(tag = "type", rename_all = "snake_case")]
233pub enum WsMessage {
234 Subscribe {
236 device_id: String,
237 token: String,
238 },
239 Subscribed {
241 session_id: String,
242 },
243 PushChanges {
245 version: u64,
246 changes: Vec<SettingsChange>,
247 },
248 ChangesAccepted {
250 new_version: u64,
251 },
252 Event(SyncEvent),
254 Ping,
256 Pong,
257 Error {
259 code: String,
260 message: String,
261 },
262}