aboutsummaryrefslogtreecommitdiff
path: root/server/src/routes/admin/users.rs
diff options
context:
space:
mode:
Diffstat (limited to 'server/src/routes/admin/users.rs')
-rw-r--r--server/src/routes/admin/users.rs119
1 files changed, 119 insertions, 0 deletions
diff --git a/server/src/routes/admin/users.rs b/server/src/routes/admin/users.rs
new file mode 100644
index 0000000..01a6403
--- /dev/null
+++ b/server/src/routes/admin/users.rs
@@ -0,0 +1,119 @@
+/*
+ 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 crate::{auth::hash_password, request_info::RequestInfo, routes::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<RawHtml<String>> {
+ 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(())
+ })?;
+
+ Ok(ri.respond_ui(&AdminUserList {
+ ri: &ri.render_info(),
+ users: &users.iter().map(|u| &**u).collect::<Vec<_>>(),
+ }))
+}
+
+#[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::success(
+ Redirect::to(u_admin_users()),
+ format!("User created; password: {password}"),
+ ))
+}
+
+#[get("/admin/user/<name>")]
+pub fn r_admin_user(ri: RequestInfo<'_>, name: &str) -> MyResult<RawHtml<String>> {
+ 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/<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 {
+ 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"),
+ ))
+}