/* This file is part of jellything (https://codeberg.org/metamuffin/jellything) which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2026 metamuffin */ use std::str::FromStr; use crate::{auth::hash_password, request_info::RequestInfo, ui::error::MyResult}; use anyhow::anyhow; use base64::{Engine, prelude::BASE64_URL_SAFE}; use jellycommon::{ jellyobject::{ObjectBufferBuilder, Path}, routes::u_admin_users, *, }; use jellydb::{Filter, Query}; use jellyui::{ components::admin::{AdminUser, AdminUserList}, tr, }; use rand::random; use rocket::{ FromForm, form::Form, get, post, response::{Flash, Redirect, content::RawHtml}, }; #[get("/admin/users")] pub fn r_admin_users(ri: RequestInfo) -> MyResult> { ri.require_admin()?; let mut users = Vec::new(); ri.state.database.transaction(&mut |txn| { users.clear(); let rows = txn .query(Query::from_str("FILTER Ulgn")?)? .collect::>(); for row in rows { let (row, _) = row?; users.push(txn.get(row)?.unwrap()); } Ok(()) })?; Ok(ri.respond_ui(&AdminUserList { ri: &ri.render_info(), users: &users.iter().map(|u| &**u).collect::>(), })) } #[derive(FromForm)] pub struct NewUser { login: String, } #[post("/admin/new_user", data = "
")] pub fn r_admin_new_user(ri: RequestInfo, form: Form) -> MyResult> { ri.require_admin()?; let password = BASE64_URL_SAFE.encode([(); 12].map(|()| random())); let password_hashed = hash_password(&form.login, &password); ri.state.database.transaction(&mut |txn| { let mut user = ObjectBufferBuilder::default(); user.push(USER_LOGIN, &form.login); user.push(USER_PASSWORD, &password_hashed); user.push(USER_PASSWORD_REQUIRE_CHANGE, ()); txn.insert(user.finish())?; Ok(()) })?; Ok(Flash::new( Redirect::to(u_admin_users()), "success", format!("User created; password: {password}"), )) } #[get("/admin/user/")] pub fn r_admin_user(ri: RequestInfo<'_>, name: &str) -> MyResult> { ri.require_admin()?; let mut user = None; ri.state.database.transaction(&mut |txn| { if let Some(row) = txn.query_single(Query { filter: Filter::Match(Path(vec![USER_LOGIN.0]), name.into()), ..Default::default() })? { user = Some(txn.get(row)?.unwrap()); } Ok(()) })?; let Some(user) = user else { Err(anyhow!("no such user"))? }; Ok(ri.respond_ui(&AdminUser { ri: &ri.render_info(), user: &user, })) } #[post("/admin/user//remove")] pub fn r_admin_user_remove(ri: RequestInfo<'_>, name: &str) -> MyResult> { ri.require_admin()?; ri.state.database.transaction(&mut |txn| { if let Some(row) = txn.query_single(Query { filter: Filter::Match(Path(vec![USER_LOGIN.0]), name.into()), ..Default::default() })? { txn.remove(row)?; } Ok(()) })?; Ok(Flash::success( Redirect::to(u_admin_users()), tr(ri.lang, "admin.users.remove_success"), )) }