107 lines
3.2 KiB
Rust
107 lines
3.2 KiB
Rust
use crate::controllers::HttpResult;
|
|
use crate::extractors::auth_extractor::AuthExtractor;
|
|
use crate::models::accounts::Account;
|
|
use crate::services::{accounts_service, files_service, movements_service};
|
|
use crate::utils::time_utils::time;
|
|
use actix_web::{HttpResponse, web};
|
|
use std::collections::HashMap;
|
|
|
|
#[derive(serde::Serialize)]
|
|
struct GlobalStats {
|
|
global_balance: f32,
|
|
number_movements: usize,
|
|
number_files: usize,
|
|
total_files_size: u64,
|
|
}
|
|
|
|
/// Get global statistics
|
|
pub async fn global(auth: AuthExtractor) -> HttpResult {
|
|
let movements = movements_service::get_all_movements_user(auth.user.id()).await?;
|
|
let files = files_service::get_all_files_user(auth.user.id()).await?;
|
|
|
|
Ok(HttpResponse::Ok().json(GlobalStats {
|
|
global_balance: movements.iter().fold(0.0, |sum, m| sum + m.amount),
|
|
number_movements: movements.len(),
|
|
number_files: files.len(),
|
|
total_files_size: files.iter().fold(0, |sum, m| sum + m.file_size as u64),
|
|
}))
|
|
}
|
|
|
|
#[derive(serde::Deserialize)]
|
|
pub struct BalanceVariationQuery {
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
interval: Option<u64>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
start: Option<i64>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
end: Option<i64>,
|
|
}
|
|
|
|
/// Statistic dataset entry
|
|
#[derive(serde::Serialize, Debug, Clone)]
|
|
struct StatEntry {
|
|
time: i64,
|
|
/// Account balances. Note: due to JSON limitation, we had to turn account id into strings
|
|
#[serde(flatten)]
|
|
balances: HashMap<String, f32>,
|
|
}
|
|
|
|
impl StatEntry {
|
|
fn init_first(time: i64, accounts: &[Account]) -> Self {
|
|
Self {
|
|
time,
|
|
balances: accounts
|
|
.iter()
|
|
.map(|a| (a.id().0.to_string(), 0.0))
|
|
.collect(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Accounts balance variation
|
|
pub async fn balance_variation(
|
|
auth: AuthExtractor,
|
|
query: web::Query<BalanceVariationQuery>,
|
|
) -> HttpResult {
|
|
let start = query.start.unwrap_or((time() - 3600 * 24 * 30) as i64);
|
|
let end = query.end.unwrap_or(time() as i64);
|
|
let interval = query.interval.unwrap_or(3600 * 24);
|
|
|
|
let accounts = accounts_service::get_list_user(auth.user.id()).await?;
|
|
let mut movements = movements_service::get_all_movements_user(auth.user.id()).await?;
|
|
movements.sort_by(|a, b| a.time.cmp(&b.time));
|
|
|
|
let mut dataset = vec![];
|
|
let mut stat_entry = StatEntry::init_first(start, &accounts);
|
|
|
|
// Iter movements
|
|
for m in movements {
|
|
if m.time > end {
|
|
break;
|
|
}
|
|
|
|
// Check if it is time to go the the next entry
|
|
while m.time > stat_entry.time {
|
|
dataset.push(stat_entry.clone());
|
|
stat_entry.time += interval as i64;
|
|
}
|
|
|
|
let target_account_id = m.account_id().0.to_string();
|
|
let old_amount = *stat_entry.balances.get(&target_account_id).unwrap_or(&0.0);
|
|
stat_entry
|
|
.balances
|
|
.insert(target_account_id, old_amount + m.amount);
|
|
}
|
|
|
|
// Add missing end entries, if necessary
|
|
while stat_entry.time < end {
|
|
dataset.push(stat_entry.clone());
|
|
stat_entry.time += interval as i64;
|
|
}
|
|
|
|
// Final push
|
|
dataset.push(stat_entry);
|
|
|
|
Ok(HttpResponse::Ok().json(dataset))
|
|
}
|