#![allow(dead_code)] use crate::error::{AppError, AppResult}; use serde::{Deserialize, Serialize}; use std::path::PathBuf; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AppConfig { pub backend_base_url: String, pub mdm_server_url: String, pub supabase_url: String, pub supabase_anon_key: String, /// Stable device identity known to the backend (UserDevice.deviceId). /// Filled after first successful device registration. #[serde(skip_serializing_if = "Option::is_none")] pub device_id: Option, /// Hardware-bound identity (UserDevice.hardwareId). #[serde(skip_serializing_if = "Option::is_none")] pub hardware_id: Option, } impl Default for AppConfig { fn default() -> Self { Self { backend_base_url: "https://staging.rebreak.org".to_string(), mdm_server_url: "https://mdm.rebreak.org".to_string(), supabase_url: String::new(), supabase_anon_key: String::new(), device_id: None, hardware_id: None, } } } const KEYRING_SERVICE: &str = "org.rebreak.magic"; const KEYRING_ACCOUNT: &str = "magic-session"; const SESSION_FILE: &str = "session.json"; const DESKTOP_PROTECTION_FILE: &str = "desktop-protection.json"; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct MagicSession { pub access_token: String, pub session_id: String, pub user_id: String, #[serde(with = "chrono::serde::ts_seconds")] pub created_at: chrono::DateTime, pub label: Option, } impl MagicSession { pub fn new(access_token: String, session_id: String, label: Option) -> Self { Self { access_token, session_id, user_id: String::new(), created_at: chrono::Utc::now(), label, } } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BinderConfig { #[serde(rename = "mdmServer")] pub mdm_server: String, #[serde(rename = "mdmUser")] pub mdm_user: String, #[serde(rename = "mdmApiKey")] pub mdm_api_key: String, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DesktopProtectionState { pub active: bool, pub platform: String, #[serde(with = "chrono::serde::ts_seconds")] pub activated_at: chrono::DateTime, } impl AppConfig { pub fn config_dir() -> AppResult { let dir = dirs::config_dir() .ok_or_else(|| AppError::new("Could not find config directory"))? .join("org.rebreak.magic"); std::fs::create_dir_all(&dir)?; Ok(dir) } pub fn config_path() -> AppResult { Ok(Self::config_dir()?.join("config.json")) } pub fn binder_config_path() -> AppResult { Ok(dirs::config_dir() .ok_or_else(|| AppError::new("Could not find config directory"))? .join("rebreak-binder") .join("config.json")) } pub fn load_binder_config() -> AppResult { let path = Self::binder_config_path()?; if !path.exists() { return Err(AppError::new( "rebreak-binder config nicht gefunden. Bitte README → 'Config (lokal)'.".to_string(), )); } let json = std::fs::read_to_string(&path)?; serde_json::from_str(&json) .map_err(|e| AppError::new(format!("rebreak-binder config kaputt: {}", e))) } pub fn load() -> Self { match Self::config_path() { Ok(path) if path.exists() => { std::fs::read_to_string(&path) .ok() .and_then(|s| serde_json::from_str(&s).ok()) .unwrap_or_default() } _ => Self::default(), } } pub fn save(&self) -> AppResult<()> { let path = Self::config_path()?; let contents = serde_json::to_string_pretty(self)?; std::fs::write(path, contents)?; Ok(()) } fn session_path() -> AppResult { Ok(Self::config_dir()?.join(SESSION_FILE)) } pub fn save_magic_session(session: &MagicSession) -> AppResult<()> { let path = Self::session_path()?; let json = serde_json::to_string_pretty(session) .map_err(|e| AppError::new(format!("Failed to serialize session: {}", e)))?; std::fs::write(&path, json)?; Ok(()) } pub fn load_magic_session() -> AppResult> { let path = match Self::session_path() { Ok(p) => p, Err(_) => return Ok(None), }; if !path.exists() { return Ok(None); } let json = std::fs::read_to_string(&path)?; if json.trim().is_empty() { return Ok(None); } let session = serde_json::from_str(&json) .map_err(|e| AppError::new(format!("Failed to parse session: {}", e)))?; Ok(Some(session)) } pub fn clear_magic_session() -> AppResult<()> { let path = match Self::session_path() { Ok(p) => p, Err(_) => return Ok(()), }; if path.exists() { std::fs::remove_file(&path)?; } Ok(()) } fn desktop_protection_path() -> AppResult { Ok(Self::config_dir()?.join(DESKTOP_PROTECTION_FILE)) } pub fn save_desktop_protection(state: &DesktopProtectionState) -> AppResult<()> { let path = Self::desktop_protection_path()?; let json = serde_json::to_string_pretty(state) .map_err(|e| AppError::new(format!("Failed to serialize desktop protection state: {}", e)))?; std::fs::write(&path, json)?; Ok(()) } pub fn load_desktop_protection() -> AppResult> { let path = match Self::desktop_protection_path() { Ok(p) => p, Err(_) => return Ok(None), }; if !path.exists() { return Ok(None); } let json = std::fs::read_to_string(&path)?; if json.trim().is_empty() { return Ok(None); } let state = serde_json::from_str(&json) .map_err(|e| AppError::new(format!("Failed to parse desktop protection state: {}", e)))?; Ok(Some(state)) } pub fn clear_desktop_protection() -> AppResult<()> { let path = match Self::desktop_protection_path() { Ok(p) => p, Err(_) => return Ok(()), }; if path.exists() { std::fs::remove_file(&path)?; } Ok(()) } pub fn set_device_id(&mut self, id: String) -> AppResult<()> { self.device_id = Some(id); self.save() } pub fn set_hardware_id(&mut self, id: String) -> AppResult<()> { self.hardware_id = Some(id); self.save() } }