Skip to main content

flatland_cli/
config.rs

1use std::path::PathBuf;
2
3use serde::{Deserialize, Serialize};
4use uuid::Uuid;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct SessionConfig {
8    pub api_base: String,
9    pub session_token: String,
10    /// Last character used for play — preferred over name matching on reconnect.
11    #[serde(default)]
12    pub last_character_id: Option<Uuid>,
13}
14
15impl SessionConfig {
16    pub fn path() -> anyhow::Result<PathBuf> {
17        if let Ok(p) = std::env::var("FLATLAND_SESSION_PATH") {
18            if !p.is_empty() {
19                return Ok(PathBuf::from(p));
20            }
21        }
22        let base = dirs::config_dir()
23            .ok_or_else(|| anyhow::anyhow!("could not resolve config directory"))?;
24        Ok(base.join("flatland").join("session.json"))
25    }
26
27    pub fn load() -> anyhow::Result<Self> {
28        let path = Self::path()?;
29        let bytes = std::fs::read(&path)
30            .map_err(|_| anyhow::anyhow!("not logged in; run `flatland3 auth login`"))?;
31        Ok(serde_json::from_slice(&bytes)?)
32    }
33
34    pub fn save(&self) -> anyhow::Result<()> {
35        let path = Self::path()?;
36        if let Some(parent) = path.parent() {
37            std::fs::create_dir_all(parent)?;
38        }
39        std::fs::write(path, serde_json::to_vec_pretty(self)?)?;
40        Ok(())
41    }
42
43    pub fn remember_character(&mut self, character_id: Uuid) -> anyhow::Result<()> {
44        if self.last_character_id == Some(character_id) {
45            return Ok(());
46        }
47        self.last_character_id = Some(character_id);
48        self.save()
49    }
50
51    /// Remove the local session file (after logout or revoke).
52    #[allow(dead_code)] // used by flatland3-gfx via the lib crate
53    pub fn clear() -> anyhow::Result<()> {
54        let path = Self::path()?;
55        match std::fs::remove_file(&path) {
56            Ok(()) => Ok(()),
57            Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(()),
58            Err(err) => Err(err.into()),
59        }
60    }
61
62    #[allow(dead_code)] // used via flatland_cli lib; bin also compiles this module
63    pub fn exists() -> bool {
64        Self::path().ok().map(|p| p.is_file()).unwrap_or(false)
65    }
66}
67
68/// Last successful email/password login (survives Redis/session expiry).
69///
70/// Stored separately from [`SessionConfig`] so explicit logout can clear the
71/// session token without wiping the form autofill. Passwords are local-only
72/// convenience for single-player / LAN clients — not a substitute for a vault.
73#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct SavedLogin {
75    pub email: String,
76    pub password: String,
77    #[serde(default, skip_serializing_if = "Option::is_none")]
78    pub api_base: Option<String>,
79}
80
81impl SavedLogin {
82    pub fn path() -> anyhow::Result<PathBuf> {
83        let base = dirs::config_dir()
84            .ok_or_else(|| anyhow::anyhow!("could not resolve config directory"))?;
85        Ok(base.join("flatland").join("login.json"))
86    }
87
88    #[allow(dead_code)] // used via flatland_cli lib; bin also compiles this module
89    pub fn load() -> Option<Self> {
90        let path = Self::path().ok()?;
91        let bytes = std::fs::read(path).ok()?;
92        serde_json::from_slice(&bytes).ok()
93    }
94
95    #[allow(dead_code)] // used via flatland_cli lib; bin also compiles this module
96    pub fn save(&self) -> anyhow::Result<()> {
97        let path = Self::path()?;
98        if let Some(parent) = path.parent() {
99            std::fs::create_dir_all(parent)?;
100        }
101        std::fs::write(&path, serde_json::to_vec_pretty(self)?)?;
102        #[cfg(unix)]
103        {
104            use std::os::unix::fs::PermissionsExt;
105            let _ = std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600));
106        }
107        Ok(())
108    }
109
110    #[allow(dead_code)]
111    pub fn clear() -> anyhow::Result<()> {
112        let path = Self::path()?;
113        match std::fs::remove_file(&path) {
114            Ok(()) => Ok(()),
115            Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(()),
116            Err(err) => Err(err.into()),
117        }
118    }
119}
120
121pub fn default_api_base() -> String {
122    flatland_client_lib::default_api_base_url()
123}