Skip to main content

teamcraft/services/
stats.rs

1//! Figures for the dashboard.
2
3use serde::Serialize;
4use sqlx::PgPool;
5
6use crate::{auth::Actor, error::AppResult, services::access};
7
8#[derive(Clone, Debug, Serialize, sqlx::FromRow)]
9pub struct Stats {
10    pub items: i64,
11    pub revisions_in_work: i64,
12    pub revisions_in_review: i64,
13    pub revisions_released: i64,
14    pub open_changes: i64,
15    pub files: i64,
16    pub stored_bytes: i64,
17}
18
19/// Counts over what the actor may see (admins: the whole vault).
20pub async fn load(pool: &PgPool, actor: &Actor) -> AppResult<Stats> {
21    let item = access::item_visible("i", 1);
22    let change = access::change_visible("c", 2, 1);
23    Ok(sqlx::query_as::<_, Stats>(&format!(
24        "WITH vr AS (SELECT r.id, r.state FROM revisions r JOIN items i ON i.id = r.item_id
25                     WHERE {item}),
26              vf AS (SELECT f.sha256, f.size_bytes FROM files f JOIN vr ON vr.id = f.revision_id)
27         SELECT
28            (SELECT COUNT(*) FROM items i WHERE {item}) AS items,
29            (SELECT COUNT(*) FROM vr WHERE state = 'in_work') AS revisions_in_work,
30            (SELECT COUNT(*) FROM vr WHERE state = 'in_review') AS revisions_in_review,
31            (SELECT COUNT(*) FROM vr WHERE state = 'released') AS revisions_released,
32            (SELECT COUNT(*) FROM changes c
33             WHERE c.status IN ('draft', 'in_review', 'rejected') AND {change}) AS open_changes,
34            (SELECT COUNT(*) FROM vf) AS files,
35            -- byte-wise comparison: the hex digests sort several times
36            -- faster than with the database's linguistic collation
37            (SELECT COALESCE(SUM(size_bytes), 0)::bigint FROM
38                (SELECT DISTINCT sha256 COLLATE \"C\", size_bytes FROM vf) blobs) AS stored_bytes"
39    ))
40    .bind(&actor.hidden_projects)
41    .bind(actor.id)
42    .fetch_one(pool)
43    .await?)
44}