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