1use anyhow::Result;
2use dirs;
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use std::fs;
6use std::path::PathBuf;
7
8use crate::{Config, Identity, SessionManager, UserCredentials};
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct UserProfile {
12 pub email: String,
13 pub name: String,
14 pub created_at: String,
15 pub last_used: String,
16 pub config_path: PathBuf,
17}
18
19pub struct UserManager {
20 users_file: PathBuf,
21 current_user_file: PathBuf,
22}
23
24impl UserManager {
25 pub fn new() -> Result<Self> {
26 let config_dir = dirs::config_dir()
27 .ok_or_else(|| anyhow::anyhow!("Could not find config directory"))?
28 .join("rustalk");
29
30 fs::create_dir_all(&config_dir)?;
31
32 Ok(UserManager {
33 users_file: config_dir.join("users.json"),
34 current_user_file: config_dir.join("current_user"),
35 })
36 }
37
38 pub fn register_user(&self, credentials: &UserCredentials, config_path: PathBuf) -> Result<()> {
39 let mut users = self.load_users()?;
40
41 let profile = UserProfile {
42 email: credentials.email.clone(),
43 name: credentials.name.clone(),
44 created_at: chrono::Utc::now().to_rfc3339(),
45 last_used: chrono::Utc::now().to_rfc3339(),
46 config_path,
47 };
48
49 users.insert(credentials.email.clone(), profile);
50 self.save_users(&users)?;
51
52 fs::write(&self.current_user_file, &credentials.email)?;
54
55 Ok(())
56 }
57
58 pub fn list_users(&self) -> Result<Vec<UserProfile>> {
59 let users = self.load_users()?;
60 Ok(users.values().cloned().collect())
61 }
62
63 pub fn switch_user(&self, email: &str) -> Result<()> {
64 let users = self.load_users()?;
65
66 if users.contains_key(email) {
67 fs::write(&self.current_user_file, email)?;
68 Ok(())
69 } else {
70 Err(anyhow::anyhow!("User '{}' not found", email))
71 }
72 }
73
74 pub fn remove_user(&self, email: &str) -> Result<()> {
75 let mut users = self.load_users()?;
76
77 if let Some(user) = users.remove(email) {
78 if user.config_path.exists() {
80 fs::remove_file(&user.config_path)?;
81 }
82
83 self.save_users(&users)?;
84
85 if let Ok(current) = self.get_current_user() {
87 if current == email {
88 let _ = fs::remove_file(&self.current_user_file);
89 }
90 }
91
92 Ok(())
93 } else {
94 Err(anyhow::anyhow!("User '{}' not found", email))
95 }
96 }
97
98 pub fn get_current_user(&self) -> Result<String> {
99 fs::read_to_string(&self.current_user_file)
100 .map(|s| s.trim().to_string())
101 .map_err(|_| anyhow::anyhow!("No current user set"))
102 }
103
104 pub fn get_user_profile(&self, email: &str) -> Result<UserProfile> {
105 let users = self.load_users()?;
106 users
107 .get(email)
108 .cloned()
109 .ok_or_else(|| anyhow::anyhow!("User '{}' not found", email))
110 }
111
112 fn load_users(&self) -> Result<HashMap<String, UserProfile>> {
113 if !self.users_file.exists() {
114 return Ok(HashMap::new());
115 }
116
117 let contents = fs::read_to_string(&self.users_file)?;
118 let users: HashMap<String, UserProfile> =
119 serde_json::from_str(&contents).unwrap_or_else(|_| HashMap::new());
120
121 Ok(users)
122 }
123
124 fn save_users(&self, users: &HashMap<String, UserProfile>) -> Result<()> {
125 let contents = serde_json::to_string_pretty(users)?;
126 fs::write(&self.users_file, contents)?;
127 Ok(())
128 }
129}
130
131pub struct PathManager;
132
133impl PathManager {
134 pub fn add_to_path() -> Result<String> {
135 let exe_path = std::env::current_exe()?;
136 let exe_dir = exe_path
137 .parent()
138 .ok_or_else(|| anyhow::anyhow!("Could not get executable directory"))?;
139
140 #[cfg(target_os = "windows")]
141 {
142 use std::process::Command;
143 let path_str = exe_dir.to_string_lossy();
144
145 let output = Command::new("powershell")
146 .args(&[
147 "-Command",
148 &format!(
149 "[Environment]::SetEnvironmentVariable('PATH', [Environment]::GetEnvironmentVariable('PATH', 'User') + ';{}', 'User')",
150 path_str
151 )
152 ])
153 .output()?;
154
155 if output.status.success() {
156 Ok(format!("Added {} to PATH", path_str))
157 } else {
158 Err(anyhow::anyhow!(
159 "Failed to add to PATH: {}",
160 String::from_utf8_lossy(&output.stderr)
161 ))
162 }
163 }
164
165 #[cfg(not(target_os = "windows"))]
166 {
167 let home =
169 dirs::home_dir().ok_or_else(|| anyhow::anyhow!("Could not find home directory"))?;
170
171 let path_str = exe_dir.to_string_lossy();
172 let export_line = format!("export PATH=\"$PATH:{}\"", path_str);
173
174 for shell_config in &[".bashrc", ".zshrc"] {
175 let config_file = home.join(shell_config);
176 if config_file.exists() {
177 let contents = fs::read_to_string(&config_file)?;
178 if !contents.contains(&path_str) {
179 fs::write(&config_file, format!("{}\n{}", contents, export_line))?;
180 }
181 }
182 }
183
184 Ok(format!("Added {} to shell configurations", path_str))
185 }
186 }
187
188 pub fn remove_from_path() -> Result<String> {
189 let exe_path = std::env::current_exe()?;
190 let exe_dir = exe_path
191 .parent()
192 .ok_or_else(|| anyhow::anyhow!("Could not get executable directory"))?;
193
194 #[cfg(target_os = "windows")]
195 {
196 use std::process::Command;
197 let path_str = exe_dir.to_string_lossy();
198
199 let output = Command::new("powershell")
200 .args(&[
201 "-Command",
202 &format!(
203 "[Environment]::SetEnvironmentVariable('PATH', ([Environment]::GetEnvironmentVariable('PATH', 'User') -replace ';?{}', ''), 'User')",
204 regex::escape(&path_str)
205 )
206 ])
207 .output()?;
208
209 if output.status.success() {
210 Ok(format!("Removed {} from PATH", path_str))
211 } else {
212 Err(anyhow::anyhow!(
213 "Failed to remove from PATH: {}",
214 String::from_utf8_lossy(&output.stderr)
215 ))
216 }
217 }
218
219 #[cfg(not(target_os = "windows"))]
220 {
221 let home =
222 dirs::home_dir().ok_or_else(|| anyhow::anyhow!("Could not find home directory"))?;
223
224 let path_str = exe_dir.to_string_lossy();
225
226 for shell_config in &[".bashrc", ".zshrc"] {
227 let config_file = home.join(shell_config);
228 if config_file.exists() {
229 let contents = fs::read_to_string(&config_file)?;
230 let new_contents = contents
231 .lines()
232 .filter(|line| !line.contains(&path_str))
233 .collect::<Vec<_>>()
234 .join("\n");
235 fs::write(&config_file, new_contents)?;
236 }
237 }
238
239 Ok(format!("Removed {} from shell configurations", path_str))
240 }
241 }
242
243 pub fn check_in_path() -> Result<String> {
244 let exe_path = std::env::current_exe()?;
245 let exe_dir = exe_path
246 .parent()
247 .ok_or_else(|| anyhow::anyhow!("Could not get executable directory"))?;
248
249 let path_env = std::env::var("PATH")?;
250 let path_str = exe_dir.to_string_lossy();
251
252 if path_env.contains(path_str.as_ref()) {
253 Ok(format!("ā {} is in PATH", path_str))
254 } else {
255 Ok(format!("ā {} is NOT in PATH", path_str))
256 }
257 }
258}
259
260pub struct CliOperations;
261
262impl CliOperations {
263 pub async fn setup_user(
264 email: Option<String>,
265 name: Option<String>,
266 password: Option<String>,
267 ) -> Result<UserCredentials> {
268 use std::io::{self, Write};
269
270 let email = match email {
271 Some(e) => e,
272 None => {
273 print!("Enter your email: ");
274 io::stdout().flush()?;
275 let mut input = String::new();
276 io::stdin().read_line(&mut input)?;
277 input.trim().to_string()
278 }
279 };
280
281 let name = match name {
282 Some(n) => n,
283 None => {
284 print!("Enter your display name: ");
285 io::stdout().flush()?;
286 let mut input = String::new();
287 io::stdin().read_line(&mut input)?;
288 input.trim().to_string()
289 }
290 };
291
292 let password = match password {
293 Some(p) => p,
294 None => {
295 print!("Enter your password: ");
296 io::stdout().flush()?;
297 let mut input = String::new();
299 io::stdin().read_line(&mut input)?;
300 input.trim().to_string()
301 }
302 };
303
304 let credentials = UserCredentials {
305 email,
306 name,
307 password,
308 };
309
310 let identity = Identity::new(credentials.clone())?;
312 let config = Config::new(identity);
313
314 let config_file = crate::config::get_config_file()?;
315 crate::config::save_config(&config)?;
316
317 let user_manager = UserManager::new()?;
319 user_manager.register_user(&credentials, config_file)?;
320
321 Ok(credentials)
322 }
323
324 pub async fn start_chat_session(port: u16) -> Result<SessionManager> {
325 let user_manager = UserManager::new()?;
327 let current_email = user_manager.get_current_user()?;
328 let _profile = user_manager.get_user_profile(¤t_email)?;
329
330 let config = crate::config::load_config()?;
332
333 let session_manager = SessionManager::new(config.identity).await?;
334 session_manager.start_session(port).await?;
335
336 Ok(session_manager)
337 }
338
339 pub async fn get_user_info() -> Result<String> {
340 let user_manager = UserManager::new()?;
341 let current_email = user_manager.get_current_user()?;
342 let profile = user_manager.get_user_profile(¤t_email)?;
343
344 Ok(format!(
345 "š§ Email: {}\nš¤ Name: {}\nš
Created: {}\nš Last used: {}",
346 profile.email, profile.name, profile.created_at, profile.last_used
347 ))
348 }
349
350 pub async fn reset_config() -> Result<String> {
351 let config_file = crate::config::get_config_file()?;
352 if config_file.exists() {
353 fs::remove_file(&config_file)?;
354 Ok("Configuration reset successfully".to_string())
355 } else {
356 Ok("No configuration to reset".to_string())
357 }
358 }
359}