Skip to main content

yggdrasil/api/posts/
delete.rs

1//! 删除文章接口。
2//!
3//! 采用软删除方式,将 posts.deleted_at 设置为当前时间,
4//! 并按影响范围失效相关缓存。
5//! Dioxus server function,注册在 `/api` 路径下。
6//! 仅在 `feature = "server"` 启用的服务端构建中执行删除与缓存失效。
7
8use dioxus::prelude::*;
9
10#[cfg(feature = "server")]
11use super::helpers::get_current_admin_user;
12use super::types::CreatePostResponse;
13#[cfg(feature = "server")]
14use crate::api::error::AppError;
15#[cfg(feature = "server")]
16use crate::db::pool::get_conn;
17
18/// 删除指定文章。
19///
20/// 仅 admin 可调用;通过设置 deleted_at 实现软删除,
21/// 成功后按影响范围失效文章列表、标签云、统计、slug 及相关标签文章缓存。
22#[server(DeletePost, "/api")]
23pub async fn delete_post(post_id: i32) -> Result<CreatePostResponse, ServerFnError> {
24    let _user = get_current_admin_user().await?;
25
26    #[cfg(feature = "server")]
27    {
28        let mut client = get_conn().await.map_err(AppError::db_conn)?;
29        let tx = client.transaction().await.map_err(AppError::tx)?;
30
31        // 在事务内锁定行并读取 slug,避免并发更新导致缓存失效目标过期。
32        let slug_row = tx
33            .query_opt(
34                "SELECT slug FROM posts WHERE id = $1 AND deleted_at IS NULL FOR UPDATE",
35                &[&post_id],
36            )
37            .await
38            .map_err(AppError::query)?;
39
40        let Some(slug_row) = slug_row else {
41            return Ok(CreatePostResponse::err("文章不存在".to_string()));
42        };
43        let slug: String = slug_row.get(0);
44
45        let tags = super::helpers::fetch_post_tags(&tx, post_id).await?;
46
47        // 软删除:仅影响未被删除的文章。
48        let result = tx
49            .execute(
50                "UPDATE posts SET deleted_at = NOW() WHERE id = $1 AND deleted_at IS NULL",
51                &[&post_id],
52            )
53            .await
54            .map_err(AppError::tx)?;
55
56        if result == 0 {
57            return Ok(CreatePostResponse::err("文章不存在".to_string()));
58        }
59
60        tx.commit().await.map_err(AppError::tx)?;
61
62        // 删除后按影响范围精准失效缓存(moka + SSR)。
63        crate::cache::invalidate_for_post_write(std::slice::from_ref(&slug), &tags).await;
64
65        Ok(CreatePostResponse::ok(
66            "删除成功".to_string(),
67            post_id,
68            slug,
69        ))
70    }
71
72    #[cfg(not(feature = "server"))]
73    {
74        Ok(CreatePostResponse::err("server only".to_string()))
75    }
76}