From c0579d05a4dc19d8fdb7e12b8b768b8725f4c21f Mon Sep 17 00:00:00 2001 From: Kyattsukuro Date: Mon, 24 Nov 2025 19:41:12 +0100 Subject: [PATCH 01/10] adds sso_identifier to /admin/users --- src/api/admin.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/api/admin.rs b/src/api/admin.rs index 7037bfb1..ddae19ac 100644 --- a/src/api/admin.rs +++ b/src/api/admin.rs @@ -355,7 +355,7 @@ fn logout(cookies: &CookieJar<'_>) -> Redirect { async fn get_users_json(_token: AdminToken, conn: DbConn) -> Json { let users = User::get_all(&conn).await; let mut users_json = Vec::with_capacity(users.len()); - for (u, _) in users { + for (u, sso_u) in users { let mut usr = u.to_json(&conn).await; usr["userEnabled"] = json!(u.enabled); usr["createdAt"] = json!(format_naive_datetime_local(&u.created_at, DT_FMT)); @@ -363,9 +363,10 @@ async fn get_users_json(_token: AdminToken, conn: DbConn) -> Json { Some(dt) => json!(format_naive_datetime_local(&dt, DT_FMT)), None => json!(None::), }; + usr["sso_identifier"] = json!(sso_u.map(|u| u.identifier.to_string()).unwrap_or(String::new())); + users_json.push(usr); } - Json(Value::Array(users_json)) } From c915f5fc3e3d5f36558cc08a327ae3ba391ca07d Mon Sep 17 00:00:00 2001 From: Kyattsukuro Date: Fri, 8 May 2026 13:01:15 +0200 Subject: [PATCH 02/10] return same json object for all user queries --- src/api/admin.rs | 81 +++++++++++++++++++------------------------ src/db/models/user.rs | 11 ++++++ 2 files changed, 47 insertions(+), 45 deletions(-) diff --git a/src/api/admin.rs b/src/api/admin.rs index ddae19ac..b428759f 100644 --- a/src/api/admin.rs +++ b/src/api/admin.rs @@ -25,7 +25,7 @@ use crate::{ ACTIVE_DB_TYPE, DbConn, DbConnType, backup_sqlite, get_sql_server_version, models::{ Attachment, Cipher, Collection, Device, Event, EventType, Group, Invitation, Membership, MembershipId, - MembershipType, OrgPolicy, Organization, OrganizationId, SsoUser, TwoFactor, User, UserId, + MembershipType, OrgPolicy, Organization, OrganizationId, SsoUser, TwoFactor, User, UserId }, }, error::{Error, MapResult}, @@ -296,8 +296,8 @@ struct InviteData { email: String, } -async fn get_user_or_404(user_id: &UserId, conn: &DbConn) -> ApiResult { - if let Some(user) = User::find_by_uuid(user_id, conn).await { +async fn get_user_or_404(user_id: &UserId, conn: &DbConn) -> ApiResult<(User, Option)> { + if let Some(user) = SsoUser::find_by_uuid(user_id, conn).await { Ok(user) } else { err_code!("User doesn't exist", Status::NotFound.code); @@ -351,37 +351,16 @@ fn logout(cookies: &CookieJar<'_>) -> Redirect { Redirect::to(admin_path()) } -#[get("/users")] -async fn get_users_json(_token: AdminToken, conn: DbConn) -> Json { - let users = User::get_all(&conn).await; +async fn get_users_property(users: Vec<(User, Option)>, conn: &DbConn) -> Vec { let mut users_json = Vec::with_capacity(users.len()); for (u, sso_u) in users { - let mut usr = u.to_json(&conn).await; - usr["userEnabled"] = json!(u.enabled); - usr["createdAt"] = json!(format_naive_datetime_local(&u.created_at, DT_FMT)); - usr["lastActive"] = match u.last_active(&conn).await { - Some(dt) => json!(format_naive_datetime_local(&dt, DT_FMT)), - None => json!(None::), - }; - usr["sso_identifier"] = json!(sso_u.map(|u| u.identifier.to_string()).unwrap_or(String::new())); - - users_json.push(usr); - } - Json(Value::Array(users_json)) -} - -#[get("/users/overview")] -async fn users_overview(_token: AdminToken, conn: DbConn) -> ApiResult> { - let users = User::get_all(&conn).await; - let mut users_json = Vec::with_capacity(users.len()); - for (u, sso_u) in users { - let mut usr = u.to_json(&conn).await; - usr["cipher_count"] = json!(Cipher::count_owned_by_user(&u.uuid, &conn).await); - usr["attachment_count"] = json!(Attachment::count_by_user(&u.uuid, &conn).await); - usr["attachment_size"] = json!(get_display_size(Attachment::size_by_user(&u.uuid, &conn).await)); + let mut usr = u.to_json(conn).await; + usr["cipher_count"] = json!(Cipher::count_owned_by_user(&u.uuid, conn).await); + usr["attachment_count"] = json!(Attachment::count_by_user(&u.uuid, conn).await); + usr["attachment_size"] = json!(get_display_size(Attachment::size_by_user(&u.uuid, conn).await)); usr["user_enabled"] = json!(u.enabled); usr["created_at"] = json!(format_naive_datetime_local(&u.created_at, DT_FMT)); - usr["last_active"] = match u.last_active(&conn).await { + usr["last_active"] = match u.last_active(conn).await { Some(dt) => json!(format_naive_datetime_local(&dt, DT_FMT)), None => json!("Never"), }; @@ -390,18 +369,30 @@ async fn users_overview(_token: AdminToken, conn: DbConn) -> ApiResult Json { + let users = User::get_all(&conn).await; + let users_json = get_users_property(users, &conn).await; + Json(Value::Array(users_json)) +} + +#[get("/users/overview")] +async fn users_overview(_token: AdminToken, conn: DbConn) -> ApiResult> { + let users = User::get_all(&conn).await; + let users_json = get_users_property(users, &conn).await; let text = AdminTemplateData::new("admin/users", json!(users_json)).render()?; Ok(Html(text)) } #[get("/users/by-mail/")] async fn get_user_by_mail_json(mail: &str, _token: AdminToken, conn: DbConn) -> JsonResult { - if let Some(u) = User::find_by_mail(mail, &conn).await { - let mut usr = u.to_json(&conn).await; - usr["userEnabled"] = json!(u.enabled); - usr["createdAt"] = json!(format_naive_datetime_local(&u.created_at, DT_FMT)); - Ok(Json(usr)) + if let Some((u, sso)) = SsoUser::find_by_mail(mail, &conn).await { + let user_json = get_users_property(vec!((u, sso)), &conn).await[0].clone(); + Ok(Json(user_json)) } else { err_code!("User doesn't exist", Status::NotFound.code); } @@ -409,16 +400,15 @@ async fn get_user_by_mail_json(mail: &str, _token: AdminToken, conn: DbConn) -> #[get("/users/")] async fn get_user_json(user_id: UserId, _token: AdminToken, conn: DbConn) -> JsonResult { - let u = get_user_or_404(&user_id, &conn).await?; - let mut usr = u.to_json(&conn).await; - usr["userEnabled"] = json!(u.enabled); - usr["createdAt"] = json!(format_naive_datetime_local(&u.created_at, DT_FMT)); - Ok(Json(usr)) + let u_sso = get_user_or_404(&user_id, &conn).await?; + let user_json = get_users_property(vec!(u_sso), &conn).await[0].clone(); + + Ok(Json(user_json)) } #[post("/users//delete", format = "application/json")] async fn delete_user(user_id: UserId, token: AdminToken, conn: DbConn) -> EmptyResult { - let user = get_user_or_404(&user_id, &conn).await?; + let (user, _) = get_user_or_404(&user_id, &conn).await?; // Get the membership records before deleting the actual user let memberships = Membership::find_any_state_by_user(&user_id, &conn).await; @@ -463,7 +453,7 @@ async fn delete_sso_user(user_id: UserId, token: AdminToken, conn: DbConn) -> Em #[post("/users//deauth", format = "application/json")] async fn deauth_user(user_id: UserId, _token: AdminToken, conn: DbConn, nt: Notify<'_>) -> EmptyResult { - let mut user = get_user_or_404(&user_id, &conn).await?; + let (mut user, _) = get_user_or_404(&user_id, &conn).await?; nt.send_logout(&user, None, &conn).await; @@ -484,7 +474,8 @@ async fn deauth_user(user_id: UserId, _token: AdminToken, conn: DbConn, nt: Noti #[post("/users//disable", format = "application/json")] async fn disable_user(user_id: UserId, _token: AdminToken, conn: DbConn, nt: Notify<'_>) -> EmptyResult { - let mut user = get_user_or_404(&user_id, &conn).await?; + let (mut user, _) = get_user_or_404(&user_id, &conn).await?; + Device::delete_all_by_user(&user.uuid, &conn).await?; user.reset_security_stamp(&conn).await?; user.enabled = false; @@ -499,7 +490,7 @@ async fn disable_user(user_id: UserId, _token: AdminToken, conn: DbConn, nt: Not #[post("/users//enable", format = "application/json")] async fn enable_user(user_id: UserId, _token: AdminToken, conn: DbConn) -> EmptyResult { - let mut user = get_user_or_404(&user_id, &conn).await?; + let (mut user, _) = get_user_or_404(&user_id, &conn).await?; user.enabled = true; user.save(&conn).await @@ -507,7 +498,7 @@ async fn enable_user(user_id: UserId, _token: AdminToken, conn: DbConn) -> Empty #[post("/users//remove-2fa", format = "application/json")] async fn remove_2fa(user_id: UserId, token: AdminToken, conn: DbConn) -> EmptyResult { - let mut user = get_user_or_404(&user_id, &conn).await?; + let (mut user, _) = get_user_or_404(&user_id, &conn).await?; TwoFactor::delete_all_by_user(&user.uuid, &conn).await?; two_factor::enforce_2fa_policy(&user, &ACTING_ADMIN_USER.into(), 14, &token.ip.ip, &conn).await?; user.totp_recover = None; diff --git a/src/db/models/user.rs b/src/db/models/user.rs index 24bee751..35a41dfc 100644 --- a/src/db/models/user.rs +++ b/src/db/models/user.rs @@ -552,6 +552,17 @@ impl SsoUser { .await } + pub async fn find_by_uuid(uuid: &UserId, conn: &DbConn) -> Option<(User, Option)> { + db_run! { conn: { + users::table + .left_join(sso_users::table) + .select(<(User, Option)>::as_select()) + .filter(users::uuid.eq(uuid)) + .first::<(User, Option)>(conn) + .ok() + }} + } + pub async fn delete(user_uuid: &UserId, conn: &DbConn) -> EmptyResult { conn.run(move |conn| { diesel::delete(sso_users::table.filter(sso_users::user_uuid.eq(user_uuid))) From 428db6b23a75b62c98e9539a8b29311d16fc98a1 Mon Sep 17 00:00:00 2001 From: Kyattsukuro Date: Sat, 29 Nov 2025 19:40:36 +0100 Subject: [PATCH 03/10] pass formatting checks --- src/api/admin.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/api/admin.rs b/src/api/admin.rs index b428759f..a0f2b9e9 100644 --- a/src/api/admin.rs +++ b/src/api/admin.rs @@ -25,7 +25,7 @@ use crate::{ ACTIVE_DB_TYPE, DbConn, DbConnType, backup_sqlite, get_sql_server_version, models::{ Attachment, Cipher, Collection, Device, Event, EventType, Group, Invitation, Membership, MembershipId, - MembershipType, OrgPolicy, Organization, OrganizationId, SsoUser, TwoFactor, User, UserId + MembershipType, OrgPolicy, Organization, OrganizationId, SsoUser, TwoFactor, User, UserId, }, }, error::{Error, MapResult}, @@ -351,7 +351,7 @@ fn logout(cookies: &CookieJar<'_>) -> Redirect { Redirect::to(admin_path()) } -async fn get_users_property(users: Vec<(User, Option)>, conn: &DbConn) -> Vec { +async fn get_users_property(users: Vec<(User, Option)>, conn: &DbConn) -> Vec { let mut users_json = Vec::with_capacity(users.len()); for (u, sso_u) in users { let mut usr = u.to_json(conn).await; @@ -369,7 +369,7 @@ async fn get_users_property(users: Vec<(User, Option)>, conn: &DbConn) users_json.push(usr); } - return users_json + users_json } #[get("/users")] @@ -379,7 +379,6 @@ async fn get_users_json(_token: AdminToken, conn: DbConn) -> Json { Json(Value::Array(users_json)) } - #[get("/users/overview")] async fn users_overview(_token: AdminToken, conn: DbConn) -> ApiResult> { let users = User::get_all(&conn).await; @@ -391,7 +390,7 @@ async fn users_overview(_token: AdminToken, conn: DbConn) -> ApiResult")] async fn get_user_by_mail_json(mail: &str, _token: AdminToken, conn: DbConn) -> JsonResult { if let Some((u, sso)) = SsoUser::find_by_mail(mail, &conn).await { - let user_json = get_users_property(vec!((u, sso)), &conn).await[0].clone(); + let user_json = get_users_property(vec![(u, sso)], &conn).await[0].clone(); Ok(Json(user_json)) } else { err_code!("User doesn't exist", Status::NotFound.code); @@ -401,7 +400,7 @@ async fn get_user_by_mail_json(mail: &str, _token: AdminToken, conn: DbConn) -> #[get("/users/")] async fn get_user_json(user_id: UserId, _token: AdminToken, conn: DbConn) -> JsonResult { let u_sso = get_user_or_404(&user_id, &conn).await?; - let user_json = get_users_property(vec!(u_sso), &conn).await[0].clone(); + let user_json = get_users_property(vec![u_sso], &conn).await[0].clone(); Ok(Json(user_json)) } From aa62a27a8d39927b1db2b6d709cb1fa92626c18f Mon Sep 17 00:00:00 2001 From: Kyattsukuro Date: Mon, 22 Jun 2026 15:34:41 +0200 Subject: [PATCH 04/10] Resolves conflicts: src/static/templates/admin/users.hbs --- src/api/admin.rs | 12 ++++++------ src/static/templates/admin/users.hbs | 16 ++++++++-------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/api/admin.rs b/src/api/admin.rs index a0f2b9e9..daa3bad9 100644 --- a/src/api/admin.rs +++ b/src/api/admin.rs @@ -355,12 +355,12 @@ async fn get_users_property(users: Vec<(User, Option)>, conn: &DbConn) let mut users_json = Vec::with_capacity(users.len()); for (u, sso_u) in users { let mut usr = u.to_json(conn).await; - usr["cipher_count"] = json!(Cipher::count_owned_by_user(&u.uuid, conn).await); - usr["attachment_count"] = json!(Attachment::count_by_user(&u.uuid, conn).await); - usr["attachment_size"] = json!(get_display_size(Attachment::size_by_user(&u.uuid, conn).await)); - usr["user_enabled"] = json!(u.enabled); - usr["created_at"] = json!(format_naive_datetime_local(&u.created_at, DT_FMT)); - usr["last_active"] = match u.last_active(conn).await { + usr["cipherCount"] = json!(Cipher::count_owned_by_user(&u.uuid, conn).await); + usr["attachmentCount"] = json!(Attachment::count_by_user(&u.uuid, conn).await); + usr["attachmentSize"] = json!(get_display_size(Attachment::size_by_user(&u.uuid, conn).await)); + usr["userEnabled"] = json!(u.enabled); + usr["createdAt"] = json!(format_naive_datetime_local(&u.created_at, DT_FMT)); + usr["lastActive"] = match u.last_active(conn).await { Some(dt) => json!(format_naive_datetime_local(&dt, DT_FMT)), None => json!("Never"), }; diff --git a/src/static/templates/admin/users.hbs b/src/static/templates/admin/users.hbs index 4c91bc0e..efbf7241 100644 --- a/src/static/templates/admin/users.hbs +++ b/src/static/templates/admin/users.hbs @@ -26,7 +26,7 @@ {{name}} {{email}} - {{#unless user_enabled}} + {{#unless userEnabled}} Disabled {{/unless}} {{#if twoFactorEnabled}} @@ -43,22 +43,22 @@ {{#if ../sso_enabled}} - {{sso_identifier}} + {{ssoIdentifier}} {{/if}} - {{created_at}} + {{createdAt}} - {{last_active}} + {{lastActive}} - {{cipher_count}} + {{cipherCount}} - Amount: {{attachment_count}} + Amount: {{attachmentCount}} {{#if attachment_count}} - Size: {{attachment_size}} + Size: {{attachmentSize}} {{/if}} @@ -78,7 +78,7 @@ {{#if ../sso_enabled}}
{{/if}} - {{#if user_enabled}} + {{#if userEnabled}}
{{else}}
From c6717ae8967b0212c5af0f1b128195ad8211e85f Mon Sep 17 00:00:00 2001 From: Kyattsukuro Date: Tue, 3 Feb 2026 15:04:18 +0100 Subject: [PATCH 05/10] moved SsoUser from get_user_or_404 to get_sso_user --- src/api/admin.rs | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/api/admin.rs b/src/api/admin.rs index daa3bad9..487861c4 100644 --- a/src/api/admin.rs +++ b/src/api/admin.rs @@ -296,14 +296,18 @@ struct InviteData { email: String, } -async fn get_user_or_404(user_id: &UserId, conn: &DbConn) -> ApiResult<(User, Option)> { - if let Some(user) = SsoUser::find_by_uuid(user_id, conn).await { +async fn get_user_or_404(user_id: &UserId, conn: &DbConn) -> ApiResult { + if let Some(user) = User::find_by_uuid(user_id, conn).await { Ok(user) } else { err_code!("User doesn't exist", Status::NotFound.code); } } +async fn get_sso_user(user_id: &UserId, conn: &DbConn) -> Option { + SsoUser::find_by_uuid(user_id, conn).await.and_then(|user_and_sso| user_and_sso.1) +} + #[post("/invite", format = "application/json", data = "")] async fn invite_user(data: Json, _token: AdminToken, conn: DbConn) -> JsonResult { async fn generate_invite(user: &User, conn: &DbConn) -> EmptyResult { @@ -399,15 +403,16 @@ async fn get_user_by_mail_json(mail: &str, _token: AdminToken, conn: DbConn) -> #[get("/users/")] async fn get_user_json(user_id: UserId, _token: AdminToken, conn: DbConn) -> JsonResult { - let u_sso = get_user_or_404(&user_id, &conn).await?; - let user_json = get_users_property(vec![u_sso], &conn).await[0].clone(); + let user = get_user_or_404(&user_id, &conn).await?; + let sso_user = get_sso_user(&user_id, &conn).await; + let user_json = get_users_property(vec![(user, sso_user)], &conn).await[0].clone(); Ok(Json(user_json)) } #[post("/users//delete", format = "application/json")] async fn delete_user(user_id: UserId, token: AdminToken, conn: DbConn) -> EmptyResult { - let (user, _) = get_user_or_404(&user_id, &conn).await?; + let user = get_user_or_404(&user_id, &conn).await?; // Get the membership records before deleting the actual user let memberships = Membership::find_any_state_by_user(&user_id, &conn).await; @@ -452,7 +457,7 @@ async fn delete_sso_user(user_id: UserId, token: AdminToken, conn: DbConn) -> Em #[post("/users//deauth", format = "application/json")] async fn deauth_user(user_id: UserId, _token: AdminToken, conn: DbConn, nt: Notify<'_>) -> EmptyResult { - let (mut user, _) = get_user_or_404(&user_id, &conn).await?; + let mut user = get_user_or_404(&user_id, &conn).await?; nt.send_logout(&user, None, &conn).await; @@ -473,7 +478,7 @@ async fn deauth_user(user_id: UserId, _token: AdminToken, conn: DbConn, nt: Noti #[post("/users//disable", format = "application/json")] async fn disable_user(user_id: UserId, _token: AdminToken, conn: DbConn, nt: Notify<'_>) -> EmptyResult { - let (mut user, _) = get_user_or_404(&user_id, &conn).await?; + let mut user = get_user_or_404(&user_id, &conn).await?; Device::delete_all_by_user(&user.uuid, &conn).await?; user.reset_security_stamp(&conn).await?; user.enabled = false; @@ -489,7 +494,7 @@ async fn disable_user(user_id: UserId, _token: AdminToken, conn: DbConn, nt: Not #[post("/users//enable", format = "application/json")] async fn enable_user(user_id: UserId, _token: AdminToken, conn: DbConn) -> EmptyResult { - let (mut user, _) = get_user_or_404(&user_id, &conn).await?; + let mut user = get_user_or_404(&user_id, &conn).await?; user.enabled = true; user.save(&conn).await @@ -497,7 +502,7 @@ async fn enable_user(user_id: UserId, _token: AdminToken, conn: DbConn) -> Empty #[post("/users//remove-2fa", format = "application/json")] async fn remove_2fa(user_id: UserId, token: AdminToken, conn: DbConn) -> EmptyResult { - let (mut user, _) = get_user_or_404(&user_id, &conn).await?; + let mut user = get_user_or_404(&user_id, &conn).await?; TwoFactor::delete_all_by_user(&user.uuid, &conn).await?; two_factor::enforce_2fa_policy(&user, &ACTING_ADMIN_USER.into(), 14, &token.ip.ip, &conn).await?; user.totp_recover = None; From a407ea54b0b78e6c9fef4d1052f9cc453db4796e Mon Sep 17 00:00:00 2001 From: Kyattsukuro Date: Wed, 4 Feb 2026 22:46:25 +0100 Subject: [PATCH 06/10] removed get_sso_user --- src/api/admin.rs | 6 +----- src/db/models/user.rs | 10 ++++------ 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/api/admin.rs b/src/api/admin.rs index 487861c4..809b50d3 100644 --- a/src/api/admin.rs +++ b/src/api/admin.rs @@ -304,10 +304,6 @@ async fn get_user_or_404(user_id: &UserId, conn: &DbConn) -> ApiResult { } } -async fn get_sso_user(user_id: &UserId, conn: &DbConn) -> Option { - SsoUser::find_by_uuid(user_id, conn).await.and_then(|user_and_sso| user_and_sso.1) -} - #[post("/invite", format = "application/json", data = "")] async fn invite_user(data: Json, _token: AdminToken, conn: DbConn) -> JsonResult { async fn generate_invite(user: &User, conn: &DbConn) -> EmptyResult { @@ -404,7 +400,7 @@ async fn get_user_by_mail_json(mail: &str, _token: AdminToken, conn: DbConn) -> #[get("/users/")] async fn get_user_json(user_id: UserId, _token: AdminToken, conn: DbConn) -> JsonResult { let user = get_user_or_404(&user_id, &conn).await?; - let sso_user = get_sso_user(&user_id, &conn).await; + let sso_user = SsoUser::find_by_uuid(&user_id, &conn).await; let user_json = get_users_property(vec![(user, sso_user)], &conn).await[0].clone(); Ok(Json(user_json)) diff --git a/src/db/models/user.rs b/src/db/models/user.rs index 35a41dfc..5cf5c622 100644 --- a/src/db/models/user.rs +++ b/src/db/models/user.rs @@ -552,13 +552,11 @@ impl SsoUser { .await } - pub async fn find_by_uuid(uuid: &UserId, conn: &DbConn) -> Option<(User, Option)> { + pub async fn find_by_uuid(uuid: &UserId, conn: &DbConn) -> Option { db_run! { conn: { - users::table - .left_join(sso_users::table) - .select(<(User, Option)>::as_select()) - .filter(users::uuid.eq(uuid)) - .first::<(User, Option)>(conn) + sso_users::table + .filter(sso_users::user_uuid.eq(uuid)) + .first::(conn) .ok() }} } From 59ecb5ae7e049bbcd650b00eadfb0845f198b138 Mon Sep 17 00:00:00 2001 From: Kyattsukuro Date: Wed, 4 Feb 2026 22:56:49 +0100 Subject: [PATCH 07/10] in enrich_users_json, moved never active notice to template --- src/api/admin.rs | 12 ++++++------ src/static/templates/admin/users.hbs | 8 +++++++- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/api/admin.rs b/src/api/admin.rs index 809b50d3..ea2f3ca5 100644 --- a/src/api/admin.rs +++ b/src/api/admin.rs @@ -351,7 +351,7 @@ fn logout(cookies: &CookieJar<'_>) -> Redirect { Redirect::to(admin_path()) } -async fn get_users_property(users: Vec<(User, Option)>, conn: &DbConn) -> Vec { +async fn enrich_users_json(users: Vec<(User, Option)>, conn: &DbConn) -> Vec { let mut users_json = Vec::with_capacity(users.len()); for (u, sso_u) in users { let mut usr = u.to_json(conn).await; @@ -362,7 +362,7 @@ async fn get_users_property(users: Vec<(User, Option)>, conn: &DbConn) usr["createdAt"] = json!(format_naive_datetime_local(&u.created_at, DT_FMT)); usr["lastActive"] = match u.last_active(conn).await { Some(dt) => json!(format_naive_datetime_local(&dt, DT_FMT)), - None => json!("Never"), + None => json!(None::), }; usr["sso_identifier"] = json!(sso_u.map_or(String::new(), |u| u.identifier.to_string())); @@ -375,14 +375,14 @@ async fn get_users_property(users: Vec<(User, Option)>, conn: &DbConn) #[get("/users")] async fn get_users_json(_token: AdminToken, conn: DbConn) -> Json { let users = User::get_all(&conn).await; - let users_json = get_users_property(users, &conn).await; + let users_json = enrich_users_json(users, &conn).await; Json(Value::Array(users_json)) } #[get("/users/overview")] async fn users_overview(_token: AdminToken, conn: DbConn) -> ApiResult> { let users = User::get_all(&conn).await; - let users_json = get_users_property(users, &conn).await; + let users_json = enrich_users_json(users, &conn).await; let text = AdminTemplateData::new("admin/users", json!(users_json)).render()?; Ok(Html(text)) } @@ -390,7 +390,7 @@ async fn users_overview(_token: AdminToken, conn: DbConn) -> ApiResult")] async fn get_user_by_mail_json(mail: &str, _token: AdminToken, conn: DbConn) -> JsonResult { if let Some((u, sso)) = SsoUser::find_by_mail(mail, &conn).await { - let user_json = get_users_property(vec![(u, sso)], &conn).await[0].clone(); + let user_json = enrich_users_json(vec![(u, sso)], &conn).await[0].clone(); Ok(Json(user_json)) } else { err_code!("User doesn't exist", Status::NotFound.code); @@ -401,7 +401,7 @@ async fn get_user_by_mail_json(mail: &str, _token: AdminToken, conn: DbConn) -> async fn get_user_json(user_id: UserId, _token: AdminToken, conn: DbConn) -> JsonResult { let user = get_user_or_404(&user_id, &conn).await?; let sso_user = SsoUser::find_by_uuid(&user_id, &conn).await; - let user_json = get_users_property(vec![(user, sso_user)], &conn).await[0].clone(); + let user_json = enrich_users_json(vec![(user, sso_user)], &conn).await[0].clone(); Ok(Json(user_json)) } diff --git a/src/static/templates/admin/users.hbs b/src/static/templates/admin/users.hbs index efbf7241..5229cbf6 100644 --- a/src/static/templates/admin/users.hbs +++ b/src/static/templates/admin/users.hbs @@ -50,7 +50,13 @@ {{createdAt}} - {{lastActive}} + + {{#if lastActive}} + {{lastActive}} + {{else}} + Never + {{/if}} + {{cipherCount}} From 2de70aa59c4f77dc46210c433827476096fe4318 Mon Sep 17 00:00:00 2001 From: Kyattsukuro Date: Sun, 5 Apr 2026 13:55:56 +0200 Subject: [PATCH 08/10] use camel case for attachmentCount, use safer way to get user JSON --- src/api/admin.rs | 18 ++++++++++++++---- src/static/templates/admin/organizations.hbs | 4 ++-- src/static/templates/admin/users.hbs | 2 +- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/api/admin.rs b/src/api/admin.rs index ea2f3ca5..c19e0d77 100644 --- a/src/api/admin.rs +++ b/src/api/admin.rs @@ -389,8 +389,13 @@ async fn users_overview(_token: AdminToken, conn: DbConn) -> ApiResult")] async fn get_user_by_mail_json(mail: &str, _token: AdminToken, conn: DbConn) -> JsonResult { - if let Some((u, sso)) = SsoUser::find_by_mail(mail, &conn).await { - let user_json = enrich_users_json(vec![(u, sso)], &conn).await[0].clone(); + if let Some((user, sso_user)) = SsoUser::find_by_mail(mail, &conn).await { + let user_json = enrich_users_json(vec![(user, sso_user)], &conn) + .await + .into_iter() + .next() + .clone() + .ok_or(Error::new("Could not build user JSON", "").with_code(Status::InternalServerError.code))?; Ok(Json(user_json)) } else { err_code!("User doesn't exist", Status::NotFound.code); @@ -401,7 +406,12 @@ async fn get_user_by_mail_json(mail: &str, _token: AdminToken, conn: DbConn) -> async fn get_user_json(user_id: UserId, _token: AdminToken, conn: DbConn) -> JsonResult { let user = get_user_or_404(&user_id, &conn).await?; let sso_user = SsoUser::find_by_uuid(&user_id, &conn).await; - let user_json = enrich_users_json(vec![(user, sso_user)], &conn).await[0].clone(); + let user_json = enrich_users_json(vec![(user, sso_user)], &conn) + .await + .into_iter() + .next() + .clone() + .ok_or(Error::new("Could not build user JSON", "").with_code(Status::InternalServerError.code))?; Ok(Json(user_json)) } @@ -592,7 +602,7 @@ async fn organizations_overview(_token: AdminToken, conn: DbConn) -> ApiResult{{cipher_count}}
- Amount: {{attachment_count}} - {{#if attachment_count}} + Amount: {{attachmentCount}} + {{#if attachmentCount}} Size: {{attachment_size}} {{/if}} diff --git a/src/static/templates/admin/users.hbs b/src/static/templates/admin/users.hbs index 5229cbf6..1eaf546f 100644 --- a/src/static/templates/admin/users.hbs +++ b/src/static/templates/admin/users.hbs @@ -63,7 +63,7 @@ Amount: {{attachmentCount}} - {{#if attachment_count}} + {{#if attachmentCount}} Size: {{attachmentSize}} {{/if}} From 44cc4426efc85d82716eab82dc40508758f65e8c Mon Sep 17 00:00:00 2001 From: Kyattsukuro Date: Fri, 8 May 2026 13:40:30 +0200 Subject: [PATCH 09/10] revertet changing names of organisation attachment_count --- src/api/admin.rs | 2 +- src/static/templates/admin/organizations.hbs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/api/admin.rs b/src/api/admin.rs index c19e0d77..d7b31027 100644 --- a/src/api/admin.rs +++ b/src/api/admin.rs @@ -602,7 +602,7 @@ async fn organizations_overview(_token: AdminToken, conn: DbConn) -> ApiResult{{cipher_count}} - Amount: {{attachmentCount}} - {{#if attachmentCount}} + Amount: {{attachment_count}} + {{#if attachment_count}} Size: {{attachment_size}} {{/if}} From d79de9559cc22ea0ed7abe442c6e3c406d15726e Mon Sep 17 00:00:00 2001 From: Kyattsukuro Date: Mon, 22 Jun 2026 15:51:01 +0200 Subject: [PATCH 10/10] clippy fixes --- src/api/admin.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/api/admin.rs b/src/api/admin.rs index d7b31027..b108e80e 100644 --- a/src/api/admin.rs +++ b/src/api/admin.rs @@ -394,7 +394,6 @@ async fn get_user_by_mail_json(mail: &str, _token: AdminToken, conn: DbConn) -> .await .into_iter() .next() - .clone() .ok_or(Error::new("Could not build user JSON", "").with_code(Status::InternalServerError.code))?; Ok(Json(user_json)) } else { @@ -410,7 +409,6 @@ async fn get_user_json(user_id: UserId, _token: AdminToken, conn: DbConn) -> Jso .await .into_iter() .next() - .clone() .ok_or(Error::new("Could not build user JSON", "").with_code(Status::InternalServerError.code))?; Ok(Json(user_json))