systemprompt_cli/commands/core/content/
popular.rs1use 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;
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(|_e| 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(|_e| anyhow!("Invalid duration format: {}", s))
22 } else {
23 s.parse::<i64>().map_err(|_e| {
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(args: PopularArgs, config: &CliConfig) -> Result<CommandOutput> {
45 let ctx = AppContext::new().await?;
46 execute_with_pool(args, ctx.db_pool(), config).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}