systemprompt_users/repository/user/
stats.rs1use chrono::{DateTime, Utc};
7
8use crate::error::Result;
9use crate::models::{UserStats, UserStatus};
10use crate::repository::UserRepository;
11
12#[derive(sqlx::FromRow)]
13struct StatusCount {
14 status: Option<String>,
15 cnt: Option<i64>,
16}
17
18#[derive(sqlx::FromRow)]
19struct RoleCount {
20 role: Option<String>,
21 cnt: Option<i64>,
22}
23
24#[derive(sqlx::FromRow)]
25struct StatsRow {
26 total: Option<i64>,
27 created_24h: Option<i64>,
28 created_7d: Option<i64>,
29 created_30d: Option<i64>,
30 active: Option<i64>,
31 suspended: Option<i64>,
32 admins: Option<i64>,
33 anonymous: Option<i64>,
34 bots: Option<i64>,
35 oldest_user: Option<DateTime<Utc>>,
36 newest_user: Option<DateTime<Utc>>,
37}
38
39impl UserRepository {
40 pub async fn count_by_status(&self) -> Result<Vec<(String, i64)>> {
41 let deleted_status = UserStatus::Deleted.as_str();
42 let rows = sqlx::query_as!(
43 StatusCount,
44 r#"
45 SELECT status, COUNT(*)::bigint as cnt
46 FROM users
47 WHERE status != $1
48 GROUP BY status
49 ORDER BY cnt DESC
50 "#,
51 deleted_status
52 )
53 .fetch_all(&*self.pool)
54 .await?;
55
56 Ok(rows
57 .into_iter()
58 .filter_map(|r| Some((r.status?, r.cnt?)))
59 .collect())
60 }
61
62 pub async fn count_by_role(&self) -> Result<Vec<(String, i64)>> {
63 let deleted_status = UserStatus::Deleted.as_str();
64 let rows = sqlx::query_as!(
65 RoleCount,
66 r#"
67 SELECT role, COUNT(*)::bigint as cnt
68 FROM users, UNNEST(roles) as role
69 WHERE status != $1
70 GROUP BY role
71 ORDER BY cnt DESC
72 "#,
73 deleted_status
74 )
75 .fetch_all(&*self.pool)
76 .await?;
77
78 Ok(rows
79 .into_iter()
80 .filter_map(|r| Some((r.role?, r.cnt?)))
81 .collect())
82 }
83
84 pub async fn get_stats(&self) -> Result<UserStats> {
85 let deleted_status = UserStatus::Deleted.as_str();
86 let row = sqlx::query_as!(
87 StatsRow,
88 r#"
89 SELECT
90 COUNT(*)::bigint as total,
91 COUNT(*) FILTER (WHERE created_at >= NOW() - INTERVAL '24 hours')::bigint as created_24h,
92 COUNT(*) FILTER (WHERE created_at >= NOW() - INTERVAL '7 days')::bigint as created_7d,
93 COUNT(*) FILTER (WHERE created_at >= NOW() - INTERVAL '30 days')::bigint as created_30d,
94 COUNT(*) FILTER (WHERE status = 'active')::bigint as active,
95 COUNT(*) FILTER (WHERE status = 'suspended')::bigint as suspended,
96 COUNT(*) FILTER (WHERE 'admin' = ANY(roles))::bigint as admins,
97 COUNT(*) FILTER (WHERE 'anonymous' = ANY(roles))::bigint as anonymous,
98 COUNT(*) FILTER (WHERE is_bot = true)::bigint as bots,
99 MIN(created_at) as oldest_user,
100 MAX(created_at) as newest_user
101 FROM users
102 WHERE status != $1
103 "#,
104 deleted_status
105 )
106 .fetch_one(&*self.pool)
107 .await?;
108
109 Ok(UserStats {
110 total: row.total.unwrap_or(0),
111 created_24h: row.created_24h.unwrap_or(0),
112 created_7d: row.created_7d.unwrap_or(0),
113 created_30d: row.created_30d.unwrap_or(0),
114 active: row.active.unwrap_or(0),
115 suspended: row.suspended.unwrap_or(0),
116 admins: row.admins.unwrap_or(0),
117 anonymous: row.anonymous.unwrap_or(0),
118 bots: row.bots.unwrap_or(0),
119 oldest_user: row.oldest_user,
120 newest_user: row.newest_user,
121 })
122 }
123}