1use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8use std::fs;
9use std::io;
10use std::path::PathBuf;
11
12#[derive(Debug, Clone, Serialize, Deserialize, Default)]
17pub struct PlatformSession {
18 pub project_id: Option<String>,
20 pub project_name: Option<String>,
22 pub org_id: Option<String>,
24 pub org_name: Option<String>,
26 pub environment_id: Option<String>,
28 pub environment_name: Option<String>,
30 pub last_updated: Option<DateTime<Utc>>,
32}
33
34impl PlatformSession {
35 pub fn new() -> Self {
37 Self::default()
38 }
39
40 pub fn with_project(
42 project_id: String,
43 project_name: String,
44 org_id: String,
45 org_name: String,
46 ) -> Self {
47 Self {
48 project_id: Some(project_id),
49 project_name: Some(project_name),
50 org_id: Some(org_id),
51 org_name: Some(org_name),
52 environment_id: None,
53 environment_name: None,
54 last_updated: Some(Utc::now()),
55 }
56 }
57
58 pub fn with_environment(
60 project_id: String,
61 project_name: String,
62 org_id: String,
63 org_name: String,
64 environment_id: String,
65 environment_name: String,
66 ) -> Self {
67 Self {
68 project_id: Some(project_id),
69 project_name: Some(project_name),
70 org_id: Some(org_id),
71 org_name: Some(org_name),
72 environment_id: Some(environment_id),
73 environment_name: Some(environment_name),
74 last_updated: Some(Utc::now()),
75 }
76 }
77
78 pub fn clear(&mut self) {
80 self.project_id = None;
81 self.project_name = None;
82 self.org_id = None;
83 self.org_name = None;
84 self.environment_id = None;
85 self.environment_name = None;
86 self.last_updated = Some(Utc::now());
87 }
88
89 pub fn clear_environment(&mut self) {
91 self.environment_id = None;
92 self.environment_name = None;
93 self.last_updated = Some(Utc::now());
94 }
95
96 pub fn is_project_selected(&self) -> bool {
98 self.project_id.is_some()
99 }
100
101 pub fn is_environment_selected(&self) -> bool {
103 self.environment_id.is_some()
104 }
105
106 pub fn session_path() -> PathBuf {
110 dirs::home_dir()
111 .unwrap_or_else(|| PathBuf::from("."))
112 .join(".syncable")
113 .join("platform-session.json")
114 }
115
116 pub fn load() -> io::Result<Self> {
120 let path = Self::session_path();
121
122 if !path.exists() {
123 return Ok(Self::default());
124 }
125
126 let content = fs::read_to_string(&path)?;
127 serde_json::from_str(&content).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
128 }
129
130 pub fn save(&self) -> io::Result<()> {
134 let path = Self::session_path();
135
136 if let Some(parent) = path.parent() {
138 fs::create_dir_all(parent)?;
139 }
140
141 let json = serde_json::to_string_pretty(self)?;
142 fs::write(&path, json)?;
143 Ok(())
144 }
145
146 pub fn display_context(&self) -> String {
150 match (&self.org_name, &self.project_name, &self.environment_name) {
151 (Some(org), Some(project), Some(env)) => format!("[{}/{}/{}]", org, project, env),
152 (Some(org), Some(project), None) => format!("[{}/{}]", org, project),
153 (None, Some(project), Some(env)) => format!("[{}/{}]", project, env),
154 (None, Some(project), None) => format!("[{}]", project),
155 _ => "[no project selected]".to_string(),
156 }
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163 use tempfile::tempdir;
164
165 #[test]
166 fn test_new_session_is_empty() {
167 let session = PlatformSession::new();
168 assert!(!session.is_project_selected());
169 assert_eq!(session.display_context(), "[no project selected]");
170 }
171
172 #[test]
173 fn test_with_project() {
174 let session = PlatformSession::with_project(
175 "proj-123".to_string(),
176 "my-project".to_string(),
177 "org-456".to_string(),
178 "my-org".to_string(),
179 );
180
181 assert!(session.is_project_selected());
182 assert_eq!(session.project_id, Some("proj-123".to_string()));
183 assert_eq!(session.display_context(), "[my-org/my-project]");
184 }
185
186 #[test]
187 fn test_clear() {
188 let mut session = PlatformSession::with_project(
189 "proj-123".to_string(),
190 "my-project".to_string(),
191 "org-456".to_string(),
192 "my-org".to_string(),
193 );
194
195 session.clear();
196 assert!(!session.is_project_selected());
197 assert!(session.last_updated.is_some()); }
199
200 #[test]
201 fn test_display_context() {
202 let session = PlatformSession::with_environment(
204 "id".to_string(),
205 "project".to_string(),
206 "oid".to_string(),
207 "org".to_string(),
208 "env-id".to_string(),
209 "prod".to_string(),
210 );
211 assert_eq!(session.display_context(), "[org/project/prod]");
212
213 let session = PlatformSession::with_project(
215 "id".to_string(),
216 "project".to_string(),
217 "oid".to_string(),
218 "org".to_string(),
219 );
220 assert_eq!(session.display_context(), "[org/project]");
221
222 let session = PlatformSession {
224 project_id: Some("id".to_string()),
225 project_name: Some("project".to_string()),
226 org_id: None,
227 org_name: None,
228 environment_id: None,
229 environment_name: None,
230 last_updated: None,
231 };
232 assert_eq!(session.display_context(), "[project]");
233
234 let session = PlatformSession::new();
236 assert_eq!(session.display_context(), "[no project selected]");
237 }
238
239 #[test]
240 fn test_with_environment() {
241 let session = PlatformSession::with_environment(
242 "proj-123".to_string(),
243 "my-project".to_string(),
244 "org-456".to_string(),
245 "my-org".to_string(),
246 "env-789".to_string(),
247 "production".to_string(),
248 );
249
250 assert!(session.is_project_selected());
251 assert!(session.is_environment_selected());
252 assert_eq!(session.project_id, Some("proj-123".to_string()));
253 assert_eq!(session.environment_id, Some("env-789".to_string()));
254 assert_eq!(session.environment_name, Some("production".to_string()));
255 assert_eq!(session.display_context(), "[my-org/my-project/production]");
256 }
257
258 #[test]
259 fn test_clear_environment() {
260 let mut session = PlatformSession::with_environment(
261 "proj-123".to_string(),
262 "my-project".to_string(),
263 "org-456".to_string(),
264 "my-org".to_string(),
265 "env-789".to_string(),
266 "production".to_string(),
267 );
268
269 assert!(session.is_environment_selected());
270
271 session.clear_environment();
272
273 assert!(session.is_project_selected()); assert!(!session.is_environment_selected()); assert_eq!(session.display_context(), "[my-org/my-project]");
276 }
277
278 #[test]
279 fn test_is_environment_selected() {
280 let session = PlatformSession::new();
281 assert!(!session.is_environment_selected());
282
283 let session = PlatformSession::with_project(
284 "proj-123".to_string(),
285 "my-project".to_string(),
286 "org-456".to_string(),
287 "my-org".to_string(),
288 );
289 assert!(!session.is_environment_selected());
290
291 let session = PlatformSession::with_environment(
292 "proj-123".to_string(),
293 "my-project".to_string(),
294 "org-456".to_string(),
295 "my-org".to_string(),
296 "env-789".to_string(),
297 "staging".to_string(),
298 );
299 assert!(session.is_environment_selected());
300 }
301
302 #[test]
303 fn test_save_and_load() {
304 let temp_dir = tempdir().unwrap();
306 let temp_path = temp_dir.path().join("platform-session.json");
307
308 let session = PlatformSession::with_project(
310 "proj-789".to_string(),
311 "test-project".to_string(),
312 "org-abc".to_string(),
313 "test-org".to_string(),
314 );
315
316 let json = serde_json::to_string_pretty(&session).unwrap();
318 fs::write(&temp_path, json).unwrap();
319
320 let content = fs::read_to_string(&temp_path).unwrap();
322 let loaded: PlatformSession = serde_json::from_str(&content).unwrap();
323
324 assert_eq!(loaded.project_id, session.project_id);
325 assert_eq!(loaded.project_name, session.project_name);
326 assert_eq!(loaded.org_id, session.org_id);
327 assert_eq!(loaded.org_name, session.org_name);
328 }
329
330 #[test]
331 fn test_load_missing_file() {
332 let default = PlatformSession::default();
336 assert!(!default.is_project_selected());
337 }
338}