Skip to main content

systemprompt_cli/commands/core/content/
popular.rs

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