diff options
Diffstat (limited to 'server/src/ui/admin/user.rs')
-rw-r--r-- | server/src/ui/admin/user.rs | 176 |
1 files changed, 176 insertions, 0 deletions
diff --git a/server/src/ui/admin/user.rs b/server/src/ui/admin/user.rs new file mode 100644 index 0000000..c5239f7 --- /dev/null +++ b/server/src/ui/admin/user.rs @@ -0,0 +1,176 @@ +/* + 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) 2025 metamuffin <metamuffin.org> +*/ +use crate::{ + database::Database, + logic::session::AdminSession, + ui::{ + error::MyResult, + layout::{DynLayoutPage, FlashDisplay, LayoutPage}, + }, + uri, +}; +use anyhow::{anyhow, Context}; +use jellycommon::user::{PermissionSet, UserPermission}; +use rocket::{form::Form, get, post, FromForm, FromFormField, State}; + +#[get("/admin/users")] +pub fn r_admin_users( + _session: AdminSession, + database: &State<Database>, +) -> MyResult<DynLayoutPage<'static>> { + user_management(database, None) +} + +fn user_management<'a>( + database: &Database, + flash: Option<MyResult<String>>, +) -> MyResult<DynLayoutPage<'a>> { + // TODO this doesnt scale, pagination! + let users = database.list_users()?; + let flash = flash.map(|f| f.map_err(|e| format!("{e:?}"))); + + Ok(LayoutPage { + title: "User management".to_string(), + content: markup::new! { + h1 { "User Management" } + @FlashDisplay { flash: flash.clone() } + h2 { "All Users" } + ul { @for u in &users { + li { + a[href=uri!(r_admin_user(&u.name))] { @format!("{:?}", u.display_name) " (" @u.name ")" } + } + }} + }, + ..Default::default() + }) +} + +#[get("/admin/user/<name>")] +pub fn r_admin_user<'a>( + _session: AdminSession, + database: &State<Database>, + name: &'a str, +) -> MyResult<DynLayoutPage<'a>> { + manage_single_user(database, None, name.to_string()) +} + +fn manage_single_user<'a>( + database: &Database, + flash: Option<MyResult<String>>, + name: String, +) -> MyResult<DynLayoutPage<'a>> { + let user = database + .get_user(&name)? + .ok_or(anyhow!("user does not exist"))?; + let flash = flash.map(|f| f.map_err(|e| format!("{e:?}"))); + + Ok(LayoutPage { + title: "User management".to_string(), + content: markup::new! { + h1 { @format!("{:?}", user.display_name) " (" @user.name ")" } + a[href=uri!(r_admin_users())] "Back to the User List" + @FlashDisplay { flash: flash.clone() } + form[method="POST", action=uri!(r_admin_remove_user())] { + input[type="text", name="name", value=&user.name, hidden]; + input.danger[type="submit", value="Remove user(!)"]; + } + + h2 { "Permissions" } + @PermissionDisplay { perms: &user.permissions } + + form[method="POST", action=uri!(r_admin_user_permission())] { + input[type="text", name="name", value=&user.name, hidden]; + fieldset.perms { + legend { "Permission" } + @for p in UserPermission::ALL_ENUMERABLE { + label { + input[type="radio", name="permission", value=serde_json::to_string(p).unwrap()]; + @format!("{p}") + } br; + } + } + fieldset.perms { + legend { "Permission" } + label { input[type="radio", name="action", value="unset"]; "Unset" } br; + label { input[type="radio", name="action", value="grant"]; "Grant" } br; + label { input[type="radio", name="action", value="revoke"]; "Revoke" } br; + } + input[type="submit", value="Update"]; + } + + }, + ..Default::default() + }) +} + +markup::define! { + PermissionDisplay<'a>(perms: &'a PermissionSet) { + ul { @for (perm,grant) in &perms.0 { + @if *grant { + li[class="perm-grant"] { @format!("Allow {}", perm) } + } else { + li[class="perm-revoke"] { @format!("Deny {}", perm) } + } + }} + } +} + +#[derive(FromForm)] +pub struct DeleteUser { + name: String, +} +#[derive(FromForm)] +pub struct UserPermissionForm { + name: String, + permission: String, + action: GrantState, +} + +#[derive(FromFormField)] +pub enum GrantState { + Grant, + Revoke, + Unset, +} + +#[post("/admin/update_user_permission", data = "<form>")] +pub fn r_admin_user_permission( + session: AdminSession, + database: &State<Database>, + form: Form<UserPermissionForm>, +) -> MyResult<DynLayoutPage<'static>> { + drop(session); + let perm = serde_json::from_str::<UserPermission>(&form.permission) + .context("parsing provided permission")?; + + database.update_user(&form.name, |user| { + match form.action { + GrantState::Grant => drop(user.permissions.0.insert(perm.clone(), true)), + GrantState::Revoke => drop(user.permissions.0.insert(perm.clone(), false)), + GrantState::Unset => drop(user.permissions.0.remove(&perm)), + } + Ok(()) + })?; + + manage_single_user( + database, + Some(Ok("Permissions update".into())), + form.name.clone(), + ) +} + +#[post("/admin/remove_user", data = "<form>")] +pub fn r_admin_remove_user( + session: AdminSession, + database: &State<Database>, + form: Form<DeleteUser>, +) -> MyResult<DynLayoutPage<'static>> { + drop(session); + if !database.delete_user(&form.name)? { + Err(anyhow!("user did not exist"))?; + } + user_management(database, Some(Ok("User removed".into()))) +} |