systemprompt_cli/commands/core/content/
status.rs1use super::types::{ContentStatusRow, StatusOutput};
2use crate::cli_settings::CliConfig;
3use crate::shared::CommandResult;
4use anyhow::Result;
5use clap::Args;
6use std::path::PathBuf;
7use systemprompt_content::ContentRepository;
8use systemprompt_database::DbPool;
9use systemprompt_identifiers::SourceId;
10use systemprompt_runtime::AppContext;
11
12#[derive(Debug, Args)]
13pub struct StatusArgs {
14 #[arg(long, help = "Filter by source ID")]
15 pub source: String,
16
17 #[arg(long, help = "Web dist directory to check for prerendered HTML")]
18 pub web_dist: Option<PathBuf>,
19
20 #[arg(long, help = "URL pattern (e.g., /{source}/{slug})")]
21 pub url_pattern: Option<String>,
22
23 #[arg(long, default_value = "50")]
24 pub limit: i64,
25}
26
27pub async fn execute(args: StatusArgs, config: &CliConfig) -> Result<CommandResult<StatusOutput>> {
28 let ctx = AppContext::new().await?;
29 execute_with_pool(args, ctx.db_pool(), config).await
30}
31
32pub async fn execute_with_pool(
33 args: StatusArgs,
34 pool: &DbPool,
35 _config: &CliConfig,
36) -> Result<CommandResult<StatusOutput>> {
37 let repo = ContentRepository::new(pool)?;
38
39 let source = SourceId::new(args.source.clone());
40 let contents = repo.list_by_source(&source).await?;
41
42 let url_pattern = args
43 .url_pattern
44 .unwrap_or_else(|| format!("/{}/{{}}", args.source));
45
46 let mut items = Vec::with_capacity(contents.len().min(args.limit as usize));
47 let mut healthy = 0i64;
48 let mut issues = 0i64;
49
50 for content in contents.into_iter().take(args.limit as usize) {
51 let expected_url = url_pattern.replace("{slug}", &content.slug);
52 let expected_url = expected_url.replace("{}", &content.slug);
53
54 let prerendered = args.web_dist.as_ref().map(|dist_dir| {
55 let html_path = dist_dir.join(format!(
56 "{}/index.html",
57 expected_url.trim_start_matches('/')
58 ));
59 html_path.exists()
60 });
61
62 let is_healthy = content.public && prerendered.unwrap_or(true);
63 if is_healthy {
64 healthy += 1;
65 } else {
66 issues += 1;
67 }
68
69 items.push(ContentStatusRow {
70 slug: content.slug,
71 title: content.title,
72 in_database: true,
73 is_public: content.public,
74 prerendered,
75 http_status: None,
76 last_updated: content.updated_at,
77 });
78 }
79
80 let total = items.len() as i64;
81
82 let output = StatusOutput {
83 items,
84 source_id: source,
85 total,
86 healthy,
87 issues,
88 };
89
90 Ok(CommandResult::table(output)
91 .with_title("Content Status")
92 .with_columns(vec![
93 "slug".to_string(),
94 "title".to_string(),
95 "is_public".to_string(),
96 "prerendered".to_string(),
97 "last_updated".to_string(),
98 ]))
99}