aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2026-02-25 15:21:24 +0100
committermetamuffin <metamuffin@disroot.org>2026-02-25 15:21:24 +0100
commit12868c6bce4287adef760a76f3f1ef9d74fb9bda (patch)
tree5a7a7ff0d7feb970551eab4f855a1837b3c3fff4
parent9c08495cca8b9aaf297c88da9ec98a619eb90762 (diff)
downloadjellything-12868c6bce4287adef760a76f3f1ef9d74fb9bda.tar
jellything-12868c6bce4287adef760a76f3f1ef9d74fb9bda.tar.bz2
jellything-12868c6bce4287adef760a76f3f1ef9d74fb9bda.tar.zst
user removal
-rw-r--r--common/src/api.rs1
-rw-r--r--common/src/routes.rs9
-rw-r--r--server/src/routes.rs8
-rw-r--r--server/src/ui/admin/mod.rs73
-rw-r--r--server/src/ui/admin/users.rs120
-rw-r--r--server/src/ui/mod.rs3
-rw-r--r--server/src/ui/user.rs28
-rw-r--r--ui/src/components/admin.rs12
-rw-r--r--ui/src/components/mod.rs5
9 files changed, 150 insertions, 109 deletions
diff --git a/common/src/api.rs b/common/src/api.rs
index ea1a0c6..5d9c72d 100644
--- a/common/src/api.rs
+++ b/common/src/api.rs
@@ -28,6 +28,7 @@ fields! {
VIEW_ADMIN_INFO: Object = b"adin";
VIEW_ADMIN_LOG: Object = b"adlo";
VIEW_ADMIN_USER_LIST: Object = b"adul";
+ VIEW_ADMIN_USER: Object = b"adus";
VIEW_USER_SETTINGS: Object = b"uset";
ADMIN_IMPORT_BUSY: () = b"busy";
diff --git a/common/src/routes.rs b/common/src/routes.rs
index b2a10b1..b76fc98 100644
--- a/common/src/routes.rs
+++ b/common/src/routes.rs
@@ -24,9 +24,6 @@ pub fn u_image(path: &str, size: usize) -> String {
pub fn u_image_fallback_person(name: &str, size: usize) -> String {
format!("/image_fallback/person/{name}?size={size}")
}
-pub fn u_user(login: &str) -> String {
- format!("/u/{login}")
-}
pub fn u_node_slug_watched(node: &str, state: &str) -> String {
format!("/n/{node}/watched?state={state}")
}
@@ -82,3 +79,9 @@ pub fn u_stats() -> String {
pub fn u_search() -> String {
"/search".to_owned()
}
+pub fn u_admin_user(login: &str) -> String {
+ format!("/admin/user/{login}")
+}
+pub fn u_admin_user_remove(login: &str) -> String {
+ format!("/admin/user/{login}/remove")
+}
diff --git a/server/src/routes.rs b/server/src/routes.rs
index 0df3aa7..f17952a 100644
--- a/server/src/routes.rs
+++ b/server/src/routes.rs
@@ -19,7 +19,8 @@ use crate::{
admin::{
import::{r_admin_import, r_admin_import_post, r_admin_import_stream},
log::{r_admin_log, r_admin_log_stream},
- r_admin_dashboard, r_admin_new_user, r_admin_users,
+ r_admin_dashboard,
+ users::{r_admin_new_user, r_admin_user, r_admin_user_remove, r_admin_users},
},
assets::{r_image, r_image_fallback_person},
error::{r_api_catch, r_catch},
@@ -28,7 +29,6 @@ use crate::{
player::r_player,
r_favicon, r_index,
style::{r_assets_css, r_assets_font, r_assets_js, r_assets_js_map},
- user::{r_user, r_user_remove},
},
};
use rocket::{
@@ -89,6 +89,8 @@ pub(super) fn build_rocket(state: Arc<State>) -> Rocket<Build> {
r_admin_log,
r_admin_new_user,
r_admin_users,
+ r_admin_user,
+ r_admin_user_remove,
r_api_root,
r_assets_css,
r_assets_font,
@@ -103,8 +105,6 @@ pub(super) fn build_rocket(state: Arc<State>) -> Rocket<Build> {
r_player,
r_playersync,
r_stream,
- r_user_remove,
- r_user,
r_version,
// Compat
// r_jellyfin_artists,
diff --git a/server/src/ui/admin/mod.rs b/server/src/ui/admin/mod.rs
index 79c9241..10037b5 100644
--- a/server/src/ui/admin/mod.rs
+++ b/server/src/ui/admin/mod.rs
@@ -6,25 +6,16 @@
pub mod import;
pub mod log;
+pub mod users;
use super::error::MyResult;
-use crate::{auth::hash_password, request_info::RequestInfo, ui_responder::UiResponse};
-use base64::{Engine, prelude::BASE64_URL_SAFE};
+use crate::{request_info::RequestInfo, ui_responder::UiResponse};
use jellycommon::{
- jellyobject::{OBB, ObjectBuffer, ObjectBufferBuilder},
- routes::u_admin_users,
+ jellyobject::{OBB, ObjectBuffer},
*,
};
-use jellydb::Query;
use jellyui::tr;
-use rand::random;
-use rocket::{
- FromForm,
- form::Form,
- get, post,
- response::{Flash, Redirect},
-};
-use std::str::FromStr;
+use rocket::get;
#[get("/admin/dashboard")]
pub async fn r_admin_dashboard(ri: RequestInfo<'_>) -> MyResult<UiResponse> {
@@ -49,59 +40,3 @@ pub async fn r_admin_dashboard(ri: RequestInfo<'_>) -> MyResult<UiResponse> {
);
Ok(ri.respond_ui(page))
}
-
-#[get("/admin/users")]
-pub fn r_admin_users(ri: RequestInfo) -> MyResult<UiResponse> {
- 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::<Vec<_>>();
- for row in rows {
- let (row, _) = row?;
- users.push(txn.get(row)?.unwrap());
- }
- Ok(())
- })?;
-
- let mut list = ObjectBufferBuilder::default();
- for u in users {
- list.push(ADMIN_USER_LIST_ITEM, u.as_object());
- }
-
- let mut page = ObjectBufferBuilder::default();
- page.push(VIEW_TITLE, &*tr(ri.lang, "admin.users"));
- page.push(VIEW_ADMIN_USER_LIST, list.finish().as_object());
- Ok(ri.respond_ui(page))
-}
-
-#[derive(FromForm)]
-pub struct NewUser {
- login: String,
-}
-
-#[post("/admin/new_user", data = "<form>")]
-pub fn r_admin_new_user(ri: RequestInfo, form: Form<NewUser>) -> MyResult<Flash<Redirect>> {
- 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}"),
- ))
-}
diff --git a/server/src/ui/admin/users.rs b/server/src/ui/admin/users.rs
new file mode 100644
index 0000000..654a6b9
--- /dev/null
+++ b/server/src/ui/admin/users.rs
@@ -0,0 +1,120 @@
+/*
+ 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 <metamuffin.org>
+*/
+
+use std::str::FromStr;
+
+use base64::{Engine, prelude::BASE64_URL_SAFE};
+use jellycommon::{
+ jellyobject::{OBB, ObjectBufferBuilder, Path},
+ routes::u_admin_users,
+ *,
+};
+use jellydb::{Filter, Query, Sort};
+use jellyui::tr;
+use rand::random;
+use rocket::{
+ FromForm,
+ form::Form,
+ get, post,
+ response::{Flash, Redirect},
+};
+
+use crate::{
+ auth::hash_password, request_info::RequestInfo, ui::error::MyResult, ui_responder::UiResponse,
+};
+
+#[get("/admin/users")]
+pub fn r_admin_users(ri: RequestInfo) -> MyResult<UiResponse> {
+ 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::<Vec<_>>();
+ for row in rows {
+ let (row, _) = row?;
+ users.push(txn.get(row)?.unwrap());
+ }
+ Ok(())
+ })?;
+
+ let mut list = ObjectBufferBuilder::default();
+ for u in users {
+ list.push(ADMIN_USER_LIST_ITEM, u.as_object());
+ }
+
+ let mut page = ObjectBufferBuilder::default();
+ page.push(VIEW_TITLE, &*tr(ri.lang, "admin.users"));
+ page.push(VIEW_ADMIN_USER_LIST, list.finish().as_object());
+ Ok(ri.respond_ui(page))
+}
+
+#[derive(FromForm)]
+pub struct NewUser {
+ login: String,
+}
+
+#[post("/admin/new_user", data = "<form>")]
+pub fn r_admin_new_user(ri: RequestInfo, form: Form<NewUser>) -> MyResult<Flash<Redirect>> {
+ 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/<name>")]
+pub fn r_admin_user(ri: RequestInfo<'_>, name: &str) -> MyResult<UiResponse> {
+ ri.require_admin()?;
+ let mut page = OBB::new();
+ ri.state.database.transaction(&mut |txn| {
+ if let Some(row) = txn.query_single(Query {
+ sort: Sort::None,
+ filter: Filter::Match(Path(vec![USER_LOGIN.0]), name.into()),
+ })? {
+ let user = txn.get(row)?.unwrap();
+ page = OBB::new();
+ page.push(VIEW_ADMIN_USER, user.as_object());
+ }
+ Ok(())
+ })?;
+
+ Ok(ri.respond_ui(page))
+}
+
+#[post("/admin/user/<name>/remove")]
+pub fn r_admin_user_remove(ri: RequestInfo<'_>, name: &str) -> MyResult<Flash<Redirect>> {
+ ri.require_admin()?;
+ ri.state.database.transaction(&mut |txn| {
+ if let Some(row) = txn.query_single(Query {
+ sort: Sort::None,
+ filter: Filter::Match(Path(vec![USER_LOGIN.0]), name.into()),
+ })? {
+ txn.remove(row)?;
+ }
+ Ok(())
+ })?;
+ Ok(Flash::success(
+ Redirect::to(u_admin_users()),
+ tr(ri.lang, "admin.users.remove_success"),
+ ))
+}
diff --git a/server/src/ui/mod.rs b/server/src/ui/mod.rs
index 28762f1..27535fa 100644
--- a/server/src/ui/mod.rs
+++ b/server/src/ui/mod.rs
@@ -16,9 +16,8 @@ pub mod assets;
pub mod error;
pub mod home;
pub mod node;
-pub mod style;
pub mod player;
-pub mod user;
+pub mod style;
#[get("/")]
pub async fn r_index(ri: RequestInfo<'_>) -> MyResult<Redirect> {
diff --git a/server/src/ui/user.rs b/server/src/ui/user.rs
deleted file mode 100644
index 31e521e..0000000
--- a/server/src/ui/user.rs
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- 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 <metamuffin.org>
-*/
-use crate::{request_info::RequestInfo, ui::error::MyResult};
-use jellycommon::routes::u_admin_users;
-use jellyui::tr;
-use rocket::{
- get, post,
- response::{Flash, Redirect, content::RawHtml},
-};
-
-#[get("/u/<name>")]
-pub fn r_user(ri: RequestInfo<'_>, name: &str) -> MyResult<RawHtml<String>> {
- ri.require_user()?;
- todo!()
-}
-
-#[post("/u/<name>/remove")]
-pub fn r_user_remove(ri: RequestInfo<'_>, name: &str) -> MyResult<Flash<Redirect>> {
- ri.require_admin()?;
- todo!();
- Ok(Flash::success(
- Redirect::to(u_admin_users()),
- tr(ri.lang, "admin.users.remove_success"),
- ))
-}
diff --git a/ui/src/components/admin.rs b/ui/src/components/admin.rs
index 7e7ccdf..1d5595f 100644
--- a/ui/src/components/admin.rs
+++ b/ui/src/components/admin.rs
@@ -8,7 +8,8 @@ use crate::RenderInfo;
use jellycommon::{
jellyobject::Object,
routes::{
- u_admin_import, u_admin_import_post, u_admin_log, u_admin_new_user, u_admin_users, u_user,
+ u_admin_import, u_admin_import_post, u_admin_log, u_admin_new_user, u_admin_user,
+ u_admin_user_remove, u_admin_users,
},
*,
};
@@ -66,8 +67,15 @@ markup::define!(
input[type="submit", value="Create new user"];
}
ul { @for u in data.iter(ADMIN_USER_LIST_ITEM) {
- li { a[href=u_user(u.get(USER_LOGIN).unwrap_or_default())] { @u.get(USER_LOGIN) } }
+ li { a[href=u_admin_user(u.get(USER_LOGIN).unwrap_or_default())] { @u.get(USER_LOGIN) } }
}}
}
+ AdminUser<'a>(ri: &'a RenderInfo<'a>, user: Object<'a>) {
+ h2 { @user.get(USER_NAME).unwrap_or("nameless user") }
+ p { @tr(ri.lang, "tag.Ulgn") ": " @user.get(USER_LOGIN) }
+ form[method="POST", action=u_admin_user_remove(user.get(USER_LOGIN).unwrap())] {
+ input.danger[type="submit", value="Remove this user(!)"];
+ }
+ }
);
diff --git a/ui/src/components/mod.rs b/ui/src/components/mod.rs
index e7e5f9c..5460090 100644
--- a/ui/src/components/mod.rs
+++ b/ui/src/components/mod.rs
@@ -18,7 +18,7 @@ pub mod user;
use crate::{
RenderInfo,
components::{
- admin::{AdminDashboard, AdminImport, AdminInfo, AdminUserList},
+ admin::{AdminDashboard, AdminImport, AdminInfo, AdminUser, AdminUserList},
login::{AccountLogin, AccountLogout, AccountSetPassword},
message::Message,
node_list::NodeList,
@@ -67,5 +67,8 @@ define! {
@if let Some(data) = view.get(VIEW_ADMIN_USER_LIST) {
@AdminUserList { ri, data }
}
+ @if let Some(user) = view.get(VIEW_ADMIN_USER) {
+ @AdminUser { ri, user }
+ }
}
}