Skip to main content

pebble_cms/cli/
serve.rs

1use crate::services::{content, search};
2use crate::{web, Config, Database};
3use anyhow::Result;
4use std::path::Path;
5use std::time::Duration;
6use tokio::task::JoinHandle;
7
8pub async fn run(config_path: &Path, host: &str, port: u16) -> Result<()> {
9    let config = Config::load(config_path)?;
10    let db = Database::open(&config.database.path)?;
11
12    db.migrate()?;
13
14    if let Ok(count) = search::rebuild_fts_index(&db) {
15        tracing::info!("Search index rebuilt: {} documents indexed", count);
16    }
17
18    let (shutdown_tx, shutdown_rx) = tokio::sync::watch::channel(false);
19    let mut bg_handles: Vec<JoinHandle<()>> = Vec::new();
20
21    let scheduler_db = db.clone();
22    let mut scheduler_rx = shutdown_rx.clone();
23    bg_handles.push(tokio::spawn(async move {
24        let mut interval = tokio::time::interval(Duration::from_secs(60));
25        loop {
26            tokio::select! {
27                _ = interval.tick() => {
28                    if let Ok(count) = content::publish_scheduled(&scheduler_db) {
29                        if count > 0 {
30                            tracing::info!("Scheduled publisher: {} post(s) published", count);
31                        }
32                    }
33                }
34                _ = scheduler_rx.changed() => {
35                    tracing::info!("Scheduled publisher stopping...");
36                    break;
37                }
38            }
39        }
40    }));
41
42    // Auto-backup scheduler
43    if config.backup.auto_enabled {
44        let backup_config = config.backup.clone();
45        let backup_site_config = config.clone();
46        let mut backup_rx = shutdown_rx.clone();
47        bg_handles.push(tokio::spawn(async move {
48            let interval_secs = backup_config.interval_hours.max(1) * 3600;
49            let mut interval = tokio::time::interval(Duration::from_secs(interval_secs));
50            // Skip the first immediate tick
51            interval.tick().await;
52            loop {
53                tokio::select! {
54                    _ = interval.tick() => {
55                        let backup_dir = std::path::Path::new(&backup_config.directory);
56                        match crate::cli::backup::create_backup(&backup_site_config, backup_dir) {
57                            Ok(()) => {
58                                tracing::info!("Auto-backup completed successfully");
59                                let _ = crate::cli::backup::enforce_retention(
60                                    backup_dir,
61                                    backup_config.retention_count,
62                                );
63                            }
64                            Err(e) => {
65                                tracing::error!("Auto-backup failed: {}", e);
66                            }
67                        }
68                    }
69                    _ = backup_rx.changed() => {
70                        tracing::info!("Auto-backup scheduler stopping...");
71                        break;
72                    }
73                }
74            }
75        }));
76        tracing::info!(
77            "Auto-backup enabled: every {} hours, keeping {} backups in {}",
78            config.backup.interval_hours,
79            config.backup.retention_count,
80            config.backup.directory
81        );
82    }
83
84    let addr = format!("{}:{}", host, port);
85    tracing::info!("Starting server at http://{}", addr);
86
87    web::serve(config, config_path.to_path_buf(), db, &addr, Some(shutdown_rx)).await?;
88
89    // Signal all background tasks to stop
90    let _ = shutdown_tx.send(true);
91    for handle in bg_handles {
92        handle.abort();
93    }
94
95    Ok(())
96}