A Rust CLI for publishing thought records. Designed to work with thought.stream.
1use anyhow::{Context, Result};
2use directories::ProjectDirs;
3use serde::{Deserialize, Serialize};
4use std::fs;
5use std::path::PathBuf;
6
7use crate::client::Session;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct Credentials {
11 pub username: String,
12 pub password: String,
13 pub pds_uri: String,
14}
15
16impl Credentials {
17 pub fn new(username: String, password: String, pds_uri: String) -> Self {
18 Self {
19 username,
20 password,
21 pds_uri,
22 }
23 }
24}
25
26pub struct CredentialStore {
27 config_dir: PathBuf,
28}
29
30impl CredentialStore {
31 pub fn new() -> Result<Self> {
32 let project_dirs = ProjectDirs::from("com", "thoughtstream", "think")
33 .context("Failed to determine project directories")?;
34
35 let config_dir = project_dirs.config_dir().to_path_buf();
36
37 // Create config directory if it doesn't exist
38 fs::create_dir_all(&config_dir)
39 .context("Failed to create config directory")?;
40
41 Ok(Self { config_dir })
42 }
43
44 fn credentials_path(&self) -> PathBuf {
45 self.config_dir.join("credentials.json")
46 }
47
48 fn session_path(&self) -> PathBuf {
49 self.config_dir.join("session.json")
50 }
51
52 pub fn store(&self, credentials: &Credentials) -> Result<()> {
53 let json = serde_json::to_string_pretty(credentials)
54 .context("Failed to serialize credentials")?;
55
56 fs::write(self.credentials_path(), json)
57 .context("Failed to write credentials file")?;
58
59 println!("Credentials stored successfully");
60 Ok(())
61 }
62
63 pub fn load(&self) -> Result<Option<Credentials>> {
64 let path = self.credentials_path();
65
66 if !path.exists() {
67 return Ok(None);
68 }
69
70 let json = fs::read_to_string(&path)
71 .context("Failed to read credentials file")?;
72
73 let credentials: Credentials = serde_json::from_str(&json)
74 .context("Failed to parse credentials file")?;
75
76 Ok(Some(credentials))
77 }
78
79 pub fn clear(&self) -> Result<()> {
80 let path = self.credentials_path();
81
82 if path.exists() {
83 fs::remove_file(&path)
84 .context("Failed to remove credentials file")?;
85 println!("Credentials cleared successfully");
86 } else {
87 println!("No credentials found to clear");
88 }
89
90 self.clear_session()?;
91
92 Ok(())
93 }
94
95 pub fn exists(&self) -> bool {
96 self.credentials_path().exists()
97 }
98
99 pub fn store_session(&self, session: &Session) -> Result<()> {
100 let json = serde_json::to_string_pretty(session)
101 .context("Failed to serialize session")?;
102
103 fs::write(self.session_path(), json)
104 .context("Failed to write session file")?;
105
106 Ok(())
107 }
108
109 pub fn load_session(&self) -> Result<Option<Session>> {
110 let path = self.session_path();
111
112 if !path.exists() {
113 return Ok(None);
114 }
115
116 let json = fs::read_to_string(&path)
117 .context("Failed to read session file")?;
118
119 let session: Session = serde_json::from_str(&json)
120 .context("Failed to parse session file")?;
121
122 Ok(Some(session))
123 }
124
125 pub fn clear_session(&self) -> Result<()> {
126 let path = self.session_path();
127
128 if path.exists() {
129 fs::remove_file(&path)
130 .context("Failed to remove session file")?;
131 }
132
133 Ok(())
134 }
135
136 pub fn session_exists(&self) -> bool {
137 self.session_path().exists()
138 }
139}