systemprompt_cli/commands/core/content/
popular.rs1use 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}