diff options
| author | metamuffin <metamuffin@disroot.org> | 2026-02-25 15:21:24 +0100 |
|---|---|---|
| committer | metamuffin <metamuffin@disroot.org> | 2026-02-25 15:21:24 +0100 |
| commit | 12868c6bce4287adef760a76f3f1ef9d74fb9bda (patch) | |
| tree | 5a7a7ff0d7feb970551eab4f855a1837b3c3fff4 | |
| parent | 9c08495cca8b9aaf297c88da9ec98a619eb90762 (diff) | |
| download | jellything-12868c6bce4287adef760a76f3f1ef9d74fb9bda.tar jellything-12868c6bce4287adef760a76f3f1ef9d74fb9bda.tar.bz2 jellything-12868c6bce4287adef760a76f3f1ef9d74fb9bda.tar.zst | |
user removal
| -rw-r--r-- | common/src/api.rs | 1 | ||||
| -rw-r--r-- | common/src/routes.rs | 9 | ||||
| -rw-r--r-- | server/src/routes.rs | 8 | ||||
| -rw-r--r-- | server/src/ui/admin/mod.rs | 73 | ||||
| -rw-r--r-- | server/src/ui/admin/users.rs | 120 | ||||
| -rw-r--r-- | server/src/ui/mod.rs | 3 | ||||
| -rw-r--r-- | server/src/ui/user.rs | 28 | ||||
| -rw-r--r-- | ui/src/components/admin.rs | 12 | ||||
| -rw-r--r-- | ui/src/components/mod.rs | 5 |
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 } + } } } |