feat(homeserver): add info endpoint (#118)

* feat(homeserver): add info endpoint

* fix clippy
This commit is contained in:
SHAcollision
2025-05-05 09:47:07 +02:00
committed by GitHub
parent d3b9cf0f69
commit 64d003c235
3 changed files with 124 additions and 2 deletions

View File

@@ -2,8 +2,11 @@ use std::net::SocketAddr;
use std::path::PathBuf;
use std::time::Duration;
use super::routes::disable_users::{disable_user, enable_user};
use super::routes::{delete_entry, generate_signup_token, root};
use super::routes::{
delete_entry,
disable_users::{disable_user, enable_user},
generate_signup_token, info, root,
};
use super::trace::with_trace_layer;
use super::{app_state::AppState, auth_middleware::AdminAuthLayer};
use crate::app_context::AppContext;
@@ -21,6 +24,7 @@ fn create_protected_router(password: &str) -> Router<AppState> {
"/generate_signup_token",
get(generate_signup_token::generate_signup_token),
)
.route("/info", get(info::info))
.route(
"/webdav/{pubkey}/{*path}",
delete(delete_entry::delete_entry),

View File

@@ -0,0 +1,117 @@
// src/admin/routes/info.rs
use super::super::app_state::AppState;
use crate::persistence::lmdb::tables::signup_tokens::SignupToken;
use crate::shared::HttpResult;
use axum::{extract::State, http::StatusCode, Json};
use serde::Serialize;
#[derive(Serialize)]
pub(crate) struct InfoResponse {
num_users: u64,
num_disabled_users: u64,
total_disk_used_mb: f64,
num_signup_codes: u64,
num_unused_signup_codes: u64,
}
/// Return summary statistics about the homeserver.
pub async fn info(State(state): State<AppState>) -> HttpResult<(StatusCode, Json<InfoResponse>)> {
// Read-only transaction
let rtxn = state.db.env.read_txn()?;
// Count users, disabled flag, and accumulate usage
let mut num_users = 0;
let mut num_disabled_users = 0;
let mut total_bytes = 0u64;
let mut users_iter = state.db.tables.users.iter(&rtxn)?;
while let Some(Ok((_pk, user))) = users_iter.next() {
num_users += 1;
if user.disabled {
num_disabled_users += 1;
}
total_bytes = total_bytes.saturating_add(user.used_bytes);
}
// Count signup tokens and unused ones
let mut num_signup_codes = 0;
let mut num_unused_signup_codes = 0;
let mut tokens_iter = state.db.tables.signup_tokens.iter(&rtxn)?;
while let Some(Ok((_token_str, bytes))) = tokens_iter.next() {
num_signup_codes += 1;
let token: SignupToken = SignupToken::deserialize(bytes);
if !token.is_used() {
num_unused_signup_codes += 1;
}
}
// Build response
let body = InfoResponse {
num_users,
num_disabled_users,
total_disk_used_mb: (total_bytes as f64) / (1024.0 * 1024.0),
num_signup_codes,
num_unused_signup_codes,
};
Ok((StatusCode::OK, Json(body)))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::admin::app_state::AppState;
use crate::persistence::lmdb::LmDB;
use axum::extract::State;
use axum::http::StatusCode;
use pkarr::Keypair;
#[tokio::test]
async fn test_info_counts() {
// Setup test DB
let mut db = LmDB::test();
let key1 = Keypair::random().public_key();
let key2 = Keypair::random().public_key();
// 1) Create both users
{
let mut wtxn = db.env.write_txn().unwrap();
db.create_user(&key1, &mut wtxn).unwrap();
db.create_user(&key2, &mut wtxn).unwrap();
wtxn.commit().unwrap();
}
// 2) Modify usage and disabled flags
{
let mut wtxn = db.env.write_txn().unwrap();
// User1: enabled, 1 MB
let mut user1 = db.get_user(&key1, &mut db.env.read_txn().unwrap()).unwrap();
user1.used_bytes = 1024 * 1024;
db.tables.users.put(&mut wtxn, &key1, &user1).unwrap();
// User2: disabled, 0.5 MB
let mut user2 = db.get_user(&key2, &mut db.env.read_txn().unwrap()).unwrap();
user2.disabled = true;
user2.used_bytes = 512 * 1024;
db.tables.users.put(&mut wtxn, &key2, &user2).unwrap();
wtxn.commit().unwrap();
}
// 3) Create two signup tokens and consume one
let code1 = db.generate_signup_token().unwrap();
let _code2 = db.generate_signup_token().unwrap();
db.validate_and_consume_signup_token(&code1, &key1).unwrap();
// 4) Invoke handler
let state = AppState::new(db);
let (status, Json(info)) = info(State(state)).await.unwrap();
assert_eq!(status, StatusCode::OK);
assert_eq!(info.num_users, 2);
assert_eq!(info.num_disabled_users, 1);
// 1 MB + 0.5 MB = 1.5 MB
assert!((info.total_disk_used_mb - 1.5).abs() < 1e-6);
assert_eq!(info.num_signup_codes, 2);
assert_eq!(info.num_unused_signup_codes, 1);
}
}

View File

@@ -1,4 +1,5 @@
pub(crate) mod delete_entry;
pub(crate) mod disable_users;
pub(crate) mod generate_signup_token;
pub(crate) mod info;
pub(crate) mod root;