1use crate::auth::{AuthError, JsonUserStore, UserStore};
2use std::path::PathBuf;
3
4#[derive(Debug)]
6pub enum CliError {
7 Io(String),
8 Auth(AuthError),
9}
10
11impl std::fmt::Display for CliError {
12 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
13 match self {
14 CliError::Io(msg) => write!(f, "{}", msg),
15 CliError::Auth(e) => write!(f, "{}", e),
16 }
17 }
18}
19
20impl std::error::Error for CliError {}
21
22impl From<AuthError> for CliError {
23 fn from(e: AuthError) -> Self {
24 CliError::Auth(e)
25 }
26}
27
28impl From<String> for CliError {
29 fn from(e: String) -> Self {
30 CliError::Io(e)
31 }
32}
33
34pub fn print_help(bin_name: &str) {
36 println!("Actix Web Admin CLI");
37 println!();
38 println!("Usage: {} <command> [options]", bin_name);
39 println!();
40 println!("Commands:");
41 println!(" createsuperuser | create | add Create a new superuser (interactive)");
42 println!(" deleteuser <username> Delete a user by username");
43 println!(" listusers | list | ls List all users");
44 println!(" help Show this help");
45 println!();
46 println!("Options:");
47 println!(" --file <path> Path to users.json (default: users.json)");
48 println!();
49 println!("Examples:");
50 println!(" {} createsuperuser", bin_name);
51 println!(" {} createsuperuser --file /path/to/users.json", bin_name);
52 println!(" {} deleteuser admin", bin_name);
53 println!(" {} listusers", bin_name);
54}
55
56pub fn parse_file_arg(args: &[String]) -> PathBuf {
58 if args.len() >= 2 && args[0] == "--file" {
59 PathBuf::from(&args[1])
60 } else {
61 PathBuf::from("users.json")
62 }
63}
64
65pub async fn create_superuser_interactive(args: &[String]) -> Result<(), CliError> {
69 let file_path = parse_file_arg(args);
70
71 println!("Create Superuser");
72 println!("Users will be stored in: {:?}", file_path);
73 println!();
74
75 let username: String = dialoguer::Input::new()
76 .with_prompt("Username")
77 .interact_text()
78 .map_err(|e| CliError::Io(format!("input error: {}", e)))?;
79
80 let email: String = dialoguer::Input::new()
81 .with_prompt("Email")
82 .interact_text()
83 .map_err(|e| CliError::Io(format!("input error: {}", e)))?;
84
85 let name: String = dialoguer::Input::new()
86 .with_prompt("Name")
87 .interact_text()
88 .map_err(|e| CliError::Io(format!("input error: {}", e)))?;
89
90 let password: String = dialoguer::Password::new()
91 .with_prompt("Password")
92 .with_confirmation("Confirm password", "Passwords do not match")
93 .interact()
94 .map_err(|e| CliError::Io(format!("input error: {}", e)))?;
95
96 let store = JsonUserStore::new(&file_path);
97
98 store
99 .create_user(&username, &email, &name, &password, true)
100 .await
101 .map(|user| {
102 println!();
103 println!(
104 "Superuser '{}' created successfully! (id: {})",
105 user.name, user.id
106 );
107 })
108 .map_err(CliError::from)
109}
110
111pub async fn delete_user(args: &[String]) -> Result<(), CliError> {
115 let (file_path, username) = parse_delete_args(args)?;
116 let store = JsonUserStore::new(&file_path);
117 store
118 .delete_user(&username)
119 .await
120 .map(|_| println!("User '{}' deleted successfully.", username))
121 .map_err(CliError::from)
122}
123
124pub async fn list_users(args: &[String]) -> Result<(), CliError> {
128 let file_path = parse_file_arg(args);
129 let store = JsonUserStore::new(&file_path);
130
131 let users = store.all_users().map_err(CliError::from)?;
132 if users.is_empty() {
133 println!("No users found.");
134 return Ok(());
135 }
136 println!("Users stored in: {:?}", file_path);
137 println!();
138 println!("{:<20} {:<30} {:<20} {}", "Username", "Email", "Name", "Superuser");
139 println!("{}", "-".repeat(80));
140 for u in &users {
141 println!(
142 "{:<20} {:<30} {:<20} {}",
143 u.username,
144 u.email,
145 u.name,
146 if u.is_superuser { "yes" } else { "no" }
147 );
148 }
149 Ok(())
150}
151
152pub(crate) fn parse_delete_args(args: &[String]) -> Result<(PathBuf, String), CliError> {
153 if args.is_empty() {
154 return Err(CliError::Io("Usage: admin-cli deleteuser <username> [--file <path>]".to_string()));
155 }
156 if args[0] == "--file" {
157 let file_path = args.get(1).ok_or_else(|| {
158 CliError::Io("--file requires a path argument".to_string())
159 })?;
160 let username = args.get(2).ok_or_else(|| {
161 CliError::Io("Usage: admin-cli deleteuser <username> --file <path>".to_string())
162 })?;
163 Ok((PathBuf::from(file_path), username.clone()))
164 } else {
165 Ok((PathBuf::from("users.json"), args[0].clone()))
166 }
167}