yggdrasil/src/api/posts/stats.rs
xfy 9c3f2242ba feat(stats): add trash count to PostStats for posts page badge
为合并回收站到 /admin/posts 的 tab 设计铺路:PostStats 新增 trash 字段,
统计 deleted_at IS NOT NULL 的文章数,供文章列表页「回收站」tab 角标使用。

- stats.rs: 条件聚合查询增加 trash 列,server/非 server 构造同步补字段
- models/post.rs: PostStats 加 pub trash: i64
- cache.rs: post_stats_cache_roundtrip 测试 fixture 补 trash 字段
2026-07-08 16:40:32 +08:00

71 lines
2.4 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! 文章统计接口。
//!
//! 返回文章总数、草稿数、已发布数与回收站(软删除)数量,供管理后台仪表盘与
//! 文章列表页使用结果缓存。Dioxus server function注册在 `/api` 路径下。
//! 仅在 `feature = "server"` 启用的服务端构建中查询数据库。
use dioxus::prelude::*;
#[cfg(feature = "server")]
use super::helpers::get_current_admin_user;
use super::types::PostStatsResponse;
#[cfg(feature = "server")]
use crate::api::error::AppError;
#[cfg(feature = "server")]
use crate::db::pool::get_conn;
#[cfg(feature = "server")]
use crate::models::post::PostStats;
/// 获取文章统计信息。
///
/// 需要 admin 权限;优先命中缓存,未命中时通过单次条件聚合查询同时统计
/// 未删除文章总数、草稿数、已发布数与回收站(软删除)数量。
#[server(GetPostStats, "/api")]
pub async fn get_post_stats() -> Result<PostStatsResponse, ServerFnError> {
let _user = get_current_admin_user().await?;
#[cfg(feature = "server")]
{
if let Some(cached) = crate::cache::get_post_stats().await {
return Ok(PostStatsResponse { stats: cached });
}
let client = get_conn().await.map_err(AppError::db_conn)?;
// 通过单次条件聚合查询同时统计总数、草稿数、已发布数与回收站数量。
let row = client
.query_one(
"SELECT
COUNT(*) FILTER (WHERE deleted_at IS NULL) AS total,
COUNT(*) FILTER (WHERE deleted_at IS NULL AND status = 'draft') AS drafts,
COUNT(*) FILTER (WHERE deleted_at IS NULL AND status = 'published') AS published,
COUNT(*) FILTER (WHERE deleted_at IS NOT NULL) AS trash
FROM posts",
&[],
)
.await
.map_err(AppError::query)?;
let stats = PostStats {
total: row.get("total"),
drafts: row.get("drafts"),
published: row.get("published"),
trash: row.get("trash"),
};
crate::cache::set_post_stats(stats.clone()).await;
Ok(PostStatsResponse { stats })
}
#[cfg(not(feature = "server"))]
{
Ok(PostStatsResponse {
stats: PostStats {
total: 0,
drafts: 0,
published: 0,
trash: 0,
},
})
}
}