Skip to main content

yggdrasil/api/comments/
list.rs

1//! 评论列表查询接口:后台管理用的评论列表与各状态条数。
2//!
3//! 所有接口均需管理员身份,Leptos server function 注册在 `/api` 路径下。
4//! 仅在 `feature = "server"` 启用的服务端构建中查询数据库。
5
6use crate::api::comments::types::*;
7use crate::api::error::ApiError;
8use leptos::prelude::*;
9
10/// 获取全部评论分页列表。
11///
12/// 支持按状态筛选(`trash` 为软删除的评论);未指定状态时返回所有未删除评论。
13/// 同时返回各状态条数。
14#[server(GetAllComments, endpoint = "get_all_comments", input = leptos::server_fn::codec::Json)]
15pub async fn get_all_comments(
16    page: i32,
17    status: Option<String>,
18) -> Result<AllCommentsResponse, ApiError> {
19    #[cfg(feature = "server")]
20    {
21        use crate::api::auth::get_current_admin_user;
22
23        let _admin = get_current_admin_user().await?;
24        Ok(list_admin_comments_impl(page, status).await?)
25    }
26    #[cfg(not(feature = "server"))]
27    unreachable!()
28}
29
30/// 后台评论分页列表与各状态条数。
31///
32/// 不做鉴权:调用方(web server fn 与 MCP 工具)各自鉴权。
33#[cfg(feature = "server")]
34pub(crate) async fn list_admin_comments_impl(
35    page: i32,
36    status: Option<String>,
37) -> Result<AllCommentsResponse, crate::api::error::AppError> {
38    use crate::api::comments::helpers::row_to_admin_comment;
39    use crate::api::error::AppError;
40    use crate::db::pool::get_conn;
41
42    let page = page.max(1);
43    let per_page: i64 = 20;
44    let offset: i64 = (page as i64 - 1) * per_page;
45
46    let client = get_conn().await.map_err(AppError::db_conn)?;
47
48    // 回收站按软删除筛选;其余状态只看未删除的评论;不传状态时为全部未删除评论。
49    let status = status.filter(|s| !s.is_empty());
50    let trash = status.as_deref() == Some("trash");
51    // 两种条件都引用 $1,参数个数保持一致;回收站传 NULL。
52    let where_clause = if trash {
53        "c.deleted_at IS NOT NULL AND ($1::text IS NULL OR c.status = $1)"
54    } else {
55        "c.deleted_at IS NULL AND ($1::text IS NULL OR c.status = $1)"
56    };
57    let status_param: Option<&str> = if trash { None } else { status.as_deref() };
58    let total: i64 = client
59        .query_one(
60            &format!("SELECT COUNT(*) FROM comments c WHERE {where_clause}"),
61            &[&status_param],
62        )
63        .await
64        .map_err(AppError::query)?
65        .get(0);
66    let rows = client
67        .query(
68            &format!(
69                "SELECT c.id, c.post_id, c.parent_id, c.depth, c.author_name, c.author_email, \
70                        c.author_url, c.content_md, c.status, c.created_at, \
71                        c.user_id, u.display_name AS user_display_name, u.avatar_url AS user_avatar, \
72                        COALESCE(pu.display_name, pc.author_name) AS parent_author, \
73                        p.title as post_title, p.slug as post_slug \
74                 FROM comments c JOIN posts p ON c.post_id = p.id \
75                 LEFT JOIN users u ON c.user_id = u.id \
76                 LEFT JOIN comments pc ON pc.id = c.parent_id \
77                 LEFT JOIN users pu ON pc.user_id = pu.id \
78                 WHERE {where_clause} \
79                 ORDER BY c.created_at DESC LIMIT $2 OFFSET $3"
80            ),
81            &[&status_param, &per_page, &offset],
82        )
83        .await
84        .map_err(AppError::query)?;
85    let counts = client
86        .query_one(
87            "SELECT COUNT(*) FILTER (WHERE deleted_at IS NULL AND status = 'pending'), \
88                    COUNT(*) FILTER (WHERE deleted_at IS NULL AND status = 'approved'), \
89                    COUNT(*) FILTER (WHERE deleted_at IS NULL AND status = 'spam'), \
90                    COUNT(*) FILTER (WHERE deleted_at IS NOT NULL) \
91             FROM comments",
92            &[],
93        )
94        .await
95        .map_err(AppError::query)?;
96    let counts = CommentCounts {
97        pending: counts.get(0),
98        approved: counts.get(1),
99        spam: counts.get(2),
100        trash: counts.get(3),
101    };
102
103    let comments = rows.iter().map(row_to_admin_comment).collect();
104
105    Ok(AllCommentsResponse {
106        comments,
107        total,
108        counts,
109    })
110}