use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::fs;
use std::io;
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct PlatformSession {
pub project_id: Option<String>,
pub project_name: Option<String>,
pub org_id: Option<String>,
pub org_name: Option<String>,
pub environment_id: Option<String>,
pub environment_name: Option<String>,
pub last_updated: Option<DateTime<Utc>>,
}
impl PlatformSession {
pub fn new() -> Self {
Self::default()
}
pub fn with_project(
project_id: String,
project_name: String,
org_id: String,
org_name: String,
) -> Self {
Self {
project_id: Some(project_id),
project_name: Some(project_name),
org_id: Some(org_id),
org_name: Some(org_name),
environment_id: None,
environment_name: None,
last_updated: Some(Utc::now()),
}
}
pub fn with_environment(
project_id: String,
project_name: String,
org_id: String,
org_name: String,
environment_id: String,
environment_name: String,
) -> Self {
Self {
project_id: Some(project_id),
project_name: Some(project_name),
org_id: Some(org_id),
org_name: Some(org_name),
environment_id: Some(environment_id),
environment_name: Some(environment_name),
last_updated: Some(Utc::now()),
}
}
pub fn clear(&mut self) {
self.project_id = None;
self.project_name = None;
self.org_id = None;
self.org_name = None;
self.environment_id = None;
self.environment_name = None;
self.last_updated = Some(Utc::now());
}
pub fn clear_environment(&mut self) {
self.environment_id = None;
self.environment_name = None;
self.last_updated = Some(Utc::now());
}
pub fn is_project_selected(&self) -> bool {
self.project_id.is_some()
}
pub fn is_environment_selected(&self) -> bool {
self.environment_id.is_some()
}
pub fn session_path() -> PathBuf {
dirs::home_dir()
.unwrap_or_else(|| PathBuf::from("."))
.join(".syncable")
.join("platform-session.json")
}
pub fn load() -> io::Result<Self> {
let path = Self::session_path();
if !path.exists() {
return Ok(Self::default());
}
let content = fs::read_to_string(&path)?;
serde_json::from_str(&content).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
}
pub fn save(&self) -> io::Result<()> {
let path = Self::session_path();
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
let json = serde_json::to_string_pretty(self)?;
fs::write(&path, json)?;
Ok(())
}
pub fn display_context(&self) -> String {
match (&self.org_name, &self.project_name, &self.environment_name) {
(Some(org), Some(project), Some(env)) => format!("[{}/{}/{}]", org, project, env),
(Some(org), Some(project), None) => format!("[{}/{}]", org, project),
(None, Some(project), Some(env)) => format!("[{}/{}]", project, env),
(None, Some(project), None) => format!("[{}]", project),
_ => "[no project selected]".to_string(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
#[test]
fn test_new_session_is_empty() {
let session = PlatformSession::new();
assert!(!session.is_project_selected());
assert_eq!(session.display_context(), "[no project selected]");
}
#[test]
fn test_with_project() {
let session = PlatformSession::with_project(
"proj-123".to_string(),
"my-project".to_string(),
"org-456".to_string(),
"my-org".to_string(),
);
assert!(session.is_project_selected());
assert_eq!(session.project_id, Some("proj-123".to_string()));
assert_eq!(session.display_context(), "[my-org/my-project]");
}
#[test]
fn test_clear() {
let mut session = PlatformSession::with_project(
"proj-123".to_string(),
"my-project".to_string(),
"org-456".to_string(),
"my-org".to_string(),
);
session.clear();
assert!(!session.is_project_selected());
assert!(session.last_updated.is_some()); }
#[test]
fn test_display_context() {
let session = PlatformSession::with_environment(
"id".to_string(),
"project".to_string(),
"oid".to_string(),
"org".to_string(),
"env-id".to_string(),
"prod".to_string(),
);
assert_eq!(session.display_context(), "[org/project/prod]");
let session = PlatformSession::with_project(
"id".to_string(),
"project".to_string(),
"oid".to_string(),
"org".to_string(),
);
assert_eq!(session.display_context(), "[org/project]");
let session = PlatformSession {
project_id: Some("id".to_string()),
project_name: Some("project".to_string()),
org_id: None,
org_name: None,
environment_id: None,
environment_name: None,
last_updated: None,
};
assert_eq!(session.display_context(), "[project]");
let session = PlatformSession::new();
assert_eq!(session.display_context(), "[no project selected]");
}
#[test]
fn test_with_environment() {
let session = PlatformSession::with_environment(
"proj-123".to_string(),
"my-project".to_string(),
"org-456".to_string(),
"my-org".to_string(),
"env-789".to_string(),
"production".to_string(),
);
assert!(session.is_project_selected());
assert!(session.is_environment_selected());
assert_eq!(session.project_id, Some("proj-123".to_string()));
assert_eq!(session.environment_id, Some("env-789".to_string()));
assert_eq!(session.environment_name, Some("production".to_string()));
assert_eq!(session.display_context(), "[my-org/my-project/production]");
}
#[test]
fn test_clear_environment() {
let mut session = PlatformSession::with_environment(
"proj-123".to_string(),
"my-project".to_string(),
"org-456".to_string(),
"my-org".to_string(),
"env-789".to_string(),
"production".to_string(),
);
assert!(session.is_environment_selected());
session.clear_environment();
assert!(session.is_project_selected()); assert!(!session.is_environment_selected()); assert_eq!(session.display_context(), "[my-org/my-project]");
}
#[test]
fn test_is_environment_selected() {
let session = PlatformSession::new();
assert!(!session.is_environment_selected());
let session = PlatformSession::with_project(
"proj-123".to_string(),
"my-project".to_string(),
"org-456".to_string(),
"my-org".to_string(),
);
assert!(!session.is_environment_selected());
let session = PlatformSession::with_environment(
"proj-123".to_string(),
"my-project".to_string(),
"org-456".to_string(),
"my-org".to_string(),
"env-789".to_string(),
"staging".to_string(),
);
assert!(session.is_environment_selected());
}
#[test]
fn test_save_and_load() {
let temp_dir = tempdir().unwrap();
let temp_path = temp_dir.path().join("platform-session.json");
let session = PlatformSession::with_project(
"proj-789".to_string(),
"test-project".to_string(),
"org-abc".to_string(),
"test-org".to_string(),
);
let json = serde_json::to_string_pretty(&session).unwrap();
fs::write(&temp_path, json).unwrap();
let content = fs::read_to_string(&temp_path).unwrap();
let loaded: PlatformSession = serde_json::from_str(&content).unwrap();
assert_eq!(loaded.project_id, session.project_id);
assert_eq!(loaded.project_name, session.project_name);
assert_eq!(loaded.org_id, session.org_id);
assert_eq!(loaded.org_name, session.org_name);
}
#[test]
fn test_load_missing_file() {
let default = PlatformSession::default();
assert!(!default.is_project_selected());
}
}