Skip to main content

systemprompt_cli/commands/core/content/
popular.rs

1use super::types::{ContentSummary, PopularOutput};
2use crate::cli_settings::CliConfig;
3use crate::shared::CommandResult;
4use anyhow::{anyhow, Result};
5use clap::Args;
6use systemprompt_content::ContentRepository;
7use systemprompt_database::DbPool;
8use systemprompt_identifiers::SourceId;
9use systemprompt_runtime::AppContext;
10
11fn parse_duration(s: &str) -> Result<i64> {
12    let s = s.trim().to_lowercase();
13    if s.ends_with('d') {
14        s[..s.len() - 1]
15            .parse::<i64>()
16            .map_err(|_| anyhow!("Invalid duration format: {}", s))
17    } else if s.ends_with('w') {
18        s[..s.len() - 1]
19            .parse::<i64>()
20            .map(|w| w * 7)
21            .map_err(|_| anyhow!("Invalid duration format: {}", s))
22    } else {
23        s.parse::<i64>().map_err(|_| {
24            anyhow!(
25                "Invalid duration format: {}. Use '7d', '30d', '1w', etc.",
26                s
27            )
28        })
29    }
30}
31
32#[derive(Debug, Args)]
33pub struct PopularArgs {
34    #[arg(help = "Source ID")]
35    pub source: String,
36
37    #[arg(long, default_value = "30d", help = "Time period (e.g., 7d, 30d, 1w)")]
38    pub since: String,
39
40    #[arg(long, default_value = "10")]
41    pub limit: i64,
42}
43
44pub async fn execute(
45    args: PopularArgs,
46    config: &CliConfig,
47) -> Result<CommandResult<PopularOutput>> {
48    let ctx = AppContext::new().await?;
49    execute_with_pool(args, ctx.db_pool(), config).await
50}
51
52pub async fn execute_with_pool(
53    args: PopularArgs,
54    pool: &DbPool,
55    _config: &CliConfig,
56) -> Result<CommandResult<PopularOutput>> {
57    let repo = ContentRepository::new(pool)?;
58
59    let source = SourceId::new(args.source.clone());
60    let days_i64 = parse_duration(&args.since)?;
61    let days = i32::try_from(days_i64).map_err(|_| anyhow!("Duration too large"))?;
62
63    let content_ids = repo
64        .get_popular_content_ids(&source, days, args.limit)
65        .await?;
66
67    let mut items = Vec::with_capacity(content_ids.len());
68    for id in content_ids {
69        if let Some(content) = repo.get_by_id(&id).await? {
70            items.push(ContentSummary {
71                id: content.id,
72                slug: content.slug,
73                title: content.title,
74                kind: content.kind,
75                source_id: content.source_id,
76                category_id: content.category_id,
77                published_at: Some(content.published_at),
78            });
79        }
80    }
81
82    let output = PopularOutput {
83        items,
84        source_id: source,
85        days: days_i64,
86    };
87
88    Ok(CommandResult::table(output)
89        .with_title("Popular Content")
90        .with_columns(vec![
91            "id".to_string(),
92            "title".to_string(),
93            "kind".to_string(),
94            "published_at".to_string(),
95        ]))
96}