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