Skip to main content

yggdrasil/api/posts/
tags.rs

1//! 标签列表接口。
2//!
3//! 返回所有标签及其关联的已发布文章数量,用于标签云与侧边栏。
4//! Dioxus server function,注册在 `/api` 路径下。
5//! 仅在 `feature = "server"` 启用的服务端构建中查询数据库。
6
7use dioxus::prelude::*;
8
9use super::types::TagListResponse;
10#[cfg(feature = "server")]
11use crate::api::error::AppError;
12#[cfg(feature = "server")]
13use crate::db::pool::get_conn;
14#[cfg(feature = "server")]
15use crate::models::post::Tag;
16
17/// 获取全部标签列表。
18///
19/// 优先命中缓存;未命中时聚合每个标签关联的已发布文章数量,并按标签名升序排列。
20#[server(ListTags, "/api")]
21pub async fn list_tags() -> Result<TagListResponse, ServerFnError> {
22    #[cfg(feature = "server")]
23    {
24        if let Some(cached) = crate::cache::get_tag_list().await {
25            return Ok(TagListResponse { tags: cached });
26        }
27
28        let client = get_conn().await.map_err(AppError::db_conn)?;
29
30        // 聚合标签对应的已发布、未删除文章数量。
31        let rows = client
32            .query(
33                "SELECT t.id, t.name, COUNT(p.id) as post_count
34                 FROM tags t
35                 LEFT JOIN post_tags pt ON t.id = pt.tag_id
36                 LEFT JOIN posts p ON pt.post_id = p.id AND p.deleted_at IS NULL AND p.status = 'published'
37                 GROUP BY t.id, t.name
38                 ORDER BY t.name",
39                &[],
40            )
41            .await
42            .map_err(AppError::query)?;
43
44        let tags: Vec<Tag> = rows
45            .iter()
46            .map(|r| Tag {
47                id: r.get("id"),
48                name: r.get("name"),
49                post_count: r.get("post_count"),
50            })
51            .collect();
52
53        crate::cache::set_tag_list(tags.clone()).await;
54        Ok(TagListResponse { tags })
55    }
56
57    #[cfg(not(feature = "server"))]
58    {
59        Ok(TagListResponse { tags: Vec::new() })
60    }
61}