aboutsummaryrefslogtreecommitdiff
path: root/server/src/ui/admin/user.rs
diff options
context:
space:
mode:
Diffstat (limited to 'server/src/ui/admin/user.rs')
-rw-r--r--server/src/ui/admin/user.rs176
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())))
+}