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,
}
pub async fn info(State(state): State<AppState>) -> HttpResult<(StatusCode, Json<InfoResponse>)> {
let rtxn = state.db.env.read_txn()?;
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);
}
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;
}
}
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::files::FileService;
use crate::AppContext;
use axum::extract::State;
use axum::http::StatusCode;
use pkarr::Keypair;
#[tokio::test]
async fn test_info_counts() {
let context = AppContext::test();
let mut db = context.db.clone();
let key1 = Keypair::random().public_key();
let key2 = Keypair::random().public_key();
{
db.create_user(&key1).unwrap();
db.create_user(&key2).unwrap();
}
{
let mut wtxn = db.env.write_txn().unwrap();
let mut user1 = db
.get_user(&key1, &db.env.read_txn().unwrap())
.unwrap()
.unwrap();
user1.used_bytes = 1024 * 1024;
db.tables.users.put(&mut wtxn, &key1, &user1).unwrap();
let mut user2 = db
.get_user(&key2, &db.env.read_txn().unwrap())
.unwrap()
.unwrap();
user2.disabled = true;
user2.used_bytes = 512 * 1024;
db.tables.users.put(&mut wtxn, &key2, &user2).unwrap();
wtxn.commit().unwrap();
}
let code1 = db.generate_signup_token().unwrap();
let _code2 = db.generate_signup_token().unwrap();
db.validate_and_consume_signup_token(&code1, &key1).unwrap();
let state = AppState::new(
db.clone(),
FileService::new_from_context(&context).unwrap(),
"",
);
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);
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);
}
}