This repository has been archived on 2025-08-25. You can view files and clone it, but you cannot make any changes to it's state, such as pushing and creating new issues, pull requests or comments.
Alexandria/src/utils/cli.rs
2025-08-14 12:29:47 +02:00

154 lines
6.7 KiB
Rust

use std::{fmt::Display, sync::Arc};
use inquire::{min_length, prompt_text, Confirm, Password, Select, Text};
use sea_orm::{ActiveModelTrait, ActiveValue::{NotSet, Set}, ColumnTrait, DatabaseConnection, EntityTrait, ModelTrait, QueryFilter};
use crate::{entities::{owner, prelude::User, user::{self, ActiveModel}}, utils::auth::hash_password};
#[derive(Debug, Copy, Clone)]
enum Action {
Add,
Delete,
Update
}
impl Action {
const VARIANTS: &'static [Action] = &[
Self::Add,
Self::Delete,
Self::Update,
];
}
impl Display for Action {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{self:?}")
}
}
#[derive(Debug, Copy, Clone)]
enum Update {
Username,
Password
}
impl Update {
const VARIANTS: &'static [Update] = &[
Self::Username,
Self::Password,
];
}
impl Display for Update {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{self:?}")
}
}
pub async fn manage_users(db: Arc<DatabaseConnection>) {
loop {
match Select::new("User Manager (ESC to quit)", Action::VARIANTS.to_vec()).prompt_skippable() {
Ok(Some(action)) => {
match action {
Action::Add => {
let username = Text::new("Username").with_validator(min_length!(3)).prompt().unwrap();
if User::find().filter(user::Column::Username.eq(username.clone())).one(db.as_ref()).await.is_ok_and(|r| r.is_some()) {
println!("Username already in use !");
} else {
let password = Password::new("Password")
.with_validator(min_length!(10))
.with_display_mode(inquire::PasswordDisplayMode::Masked)
.prompt().unwrap();
let new_user = user::ActiveModel {
id: NotSet,
username: Set(username),
hashed_password: Set(hash_password(password))
};
let res = new_user.clone().insert(db.as_ref()).await.unwrap();
println!("Create corresponding owner:");
let first_name = prompt_text("First Name: ").unwrap();
let last_name = prompt_text("Last Name: ").unwrap();
let contact = prompt_text("Contact: ").unwrap();
let new_owner = owner::ActiveModel {
id: NotSet,
user_id: Set(res.id),
first_name: Set(first_name),
last_name: Set(last_name),
contact: Set(contact),
user: Set(true)
};
let _ = new_owner.insert(db.as_ref()).await.unwrap();
}
},
Action::Delete => {
match select_user(db.clone()).await {
Some(user) => {
let username = user.username.clone();
match Confirm::new(format!("Delete {username} ?").as_ref())
.with_default(false)
.with_help_message("The user and all associated data will *permanently* be deleted")
.prompt() {
Ok(true) => {
let _ = user.delete(db.as_ref()).await;
println!("{username} has been permanently deleted")
},
Ok(false) | Err(_) => println!("Cancelled deletion of {username}"),
}
},
None => println!("Could not find user")
}
},
Action::Update => {
match select_user(db.clone()).await {
Some(user) => {
match Select::new(format!("Editing {}", user.username).as_ref(), Update::VARIANTS.to_vec()).prompt() {
Ok(v) => {
let mut updated_user = ActiveModel::from(user);
match v {
Update::Username => {
updated_user.username = Set(Text::new("New username")
.with_initial_value(updated_user.username.unwrap().as_ref())
.prompt().unwrap()
)
},
Update::Password => {
match Password::new("Password")
.with_validator(min_length!(10))
.with_display_mode(inquire::PasswordDisplayMode::Masked)
.prompt() {
Ok(new_password) => updated_user.hashed_password = Set(hash_password(new_password)),
Err(_) => println!("Cancelled user update")
}
}
}
let _ = updated_user.save(db.as_ref()).await;
},
Err(_) => {}
}
},
None => println!("Could not find user")
}
}
}
},
Ok(None) | Err(_) => {
break;
}
}
}
}
async fn select_user(db: Arc<DatabaseConnection>) -> Option<user::Model> {
let users = User::find().all(db.as_ref()).await.unwrap();
if users.is_empty() {
return None;
}
match Select::new("Select a user", users.iter().map(|u| u.username.clone()).collect()).prompt() {
Ok(selection) => User::find().filter(user::Column::Username.eq(selection)).one(db.as_ref()).await.unwrap(),
Err(_) => None
}
}