Skip to main content

yggdrasil/tasks/
post_purge.rs

1//! 回收站自动清理后台任务。
2//!
3//! 仅在 `server` feature 启用时编译,每天运行一次。
4//! 每次执行前读取 settings 表:若自动清理关闭则跳过,否则按保留天数物理删除过期文章。
5
6use std::time::Duration;
7
8use tokio::time::interval;
9
10use crate::db::pool::get_conn;
11use crate::models::settings::TrashSettings;
12
13/// 启动回收站自动清理循环,每天触发一次。
14///
15/// 每次读取最新配置:若 `trash_auto_purge_enabled` 关闭则 no-op,
16/// 否则删除 `deleted_at < NOW() - retention_days` 的文章。
17/// 任何错误只记录日志,不中断循环。
18pub async fn run_purge() {
19    let mut ticker = interval(Duration::from_secs(86400));
20    loop {
21        match get_conn().await {
22            Ok(client) => match purge_expired(&client).await {
23                Ok(n) => {
24                    if n > 0 {
25                        tracing::info!("Post auto-purge: removed {} expired trashed posts", n);
26                    }
27                }
28                Err(e) => tracing::error!("Post auto-purge error: {:?}", e),
29            },
30            Err(e) => tracing::error!("Failed to get DB connection for post purge: {:?}", e),
31        }
32        ticker.tick().await;
33    }
34}
35
36/// 读取配置并删除过期回收站文章,返回删除行数。
37///
38/// 抽取为独立函数便于单元测试(虽需 DB,但逻辑集中)。
39async fn purge_expired(client: &tokio_postgres::Client) -> Result<u64, tokio_postgres::Error> {
40    // 读取配置,缺键时回退默认值。
41    let enabled: bool = client
42        .query_opt(
43            "SELECT value FROM settings WHERE key = 'trash_auto_purge_enabled'",
44            &[],
45        )
46        .await?
47        .and_then(|r| r.get::<_, String>("value").parse().ok())
48        .unwrap_or(false);
49
50    if !enabled {
51        return Ok(0);
52    }
53
54    let days: i32 = client
55        .query_opt(
56            "SELECT value FROM settings WHERE key = 'trash_retention_days'",
57            &[],
58        )
59        .await?
60        .and_then(|r| r.get::<_, String>("value").parse().ok())
61        .unwrap_or(30);
62
63    let days = TrashSettings::clamp_retention(days);
64
65    let n = client
66        .execute(
67            "DELETE FROM posts WHERE deleted_at IS NOT NULL AND deleted_at < NOW() - make_interval(days => $1)",
68            &[&days],
69        )
70        .await?;
71    Ok(n)
72}