Skip to main content

yggdrasil/api/posts/
tags.rs

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