yggdrasil/api/posts/
stats.rs1use dioxus::prelude::*;
8
9#[cfg(feature = "server")]
10use super::helpers::get_current_admin_user;
11use super::types::PostStatsResponse;
12#[cfg(feature = "server")]
13use crate::api::error::AppError;
14#[cfg(feature = "server")]
15use crate::db::pool::get_conn;
16#[cfg(feature = "server")]
17use crate::models::post::PostStats;
18
19#[server(GetPostStats, "/api")]
24pub async fn get_post_stats() -> Result<PostStatsResponse, ServerFnError> {
25 let _user = get_current_admin_user().await?;
26
27 #[cfg(feature = "server")]
28 {
29 if let Some(cached) = crate::cache::get_post_stats().await {
30 return Ok(PostStatsResponse { stats: cached });
31 }
32
33 let client = get_conn().await.map_err(AppError::db_conn)?;
34
35 let row = client
37 .query_one(
38 "SELECT
39 COUNT(*) FILTER (WHERE deleted_at IS NULL) AS total,
40 COUNT(*) FILTER (WHERE deleted_at IS NULL AND status = 'draft') AS drafts,
41 COUNT(*) FILTER (WHERE deleted_at IS NULL AND status = 'published') AS published,
42 COUNT(*) FILTER (WHERE deleted_at IS NOT NULL) AS trash,
43 COUNT(*) FILTER (WHERE deleted_at IS NULL AND created_at > now() - interval '30 days') AS recent_30d
44 FROM posts",
45 &[],
46 )
47 .await
48 .map_err(AppError::query)?;
49
50 let stats = PostStats {
51 total: row.get("total"),
52 drafts: row.get("drafts"),
53 published: row.get("published"),
54 trash: row.get("trash"),
55 recent_30d: row.get("recent_30d"),
56 activity_30d: Vec::new(),
57 };
58
59 let activity_rows = client
62 .query(
63 "SELECT gs.day::date AS day, COUNT(p.id) AS cnt
64 FROM generate_series(
65 (now() - interval '29 days')::date,
66 now()::date,
67 interval '1 day'
68 ) AS gs(day)
69 LEFT JOIN posts p
70 ON p.created_at::date = gs.day::date
71 AND p.deleted_at IS NULL
72 GROUP BY gs.day
73 ORDER BY gs.day",
74 &[],
75 )
76 .await
77 .map_err(AppError::query)?;
78 let activity_30d = activity_rows
79 .iter()
80 .map(|r| r.get::<_, i64>("cnt"))
81 .collect();
82
83 let stats = PostStats {
84 activity_30d,
85 ..stats
86 };
87 crate::cache::set_post_stats(stats.clone()).await;
88 Ok(PostStatsResponse { stats })
89 }
90
91 #[cfg(not(feature = "server"))]
92 {
93 Ok(PostStatsResponse {
94 stats: PostStats {
95 total: 0,
96 drafts: 0,
97 published: 0,
98 trash: 0,
99 recent_30d: 0,
100 activity_30d: Vec::new(),
101 },
102 })
103 }
104}