Skip to main content

systemprompt_cli/commands/analytics/content/
mod.rs

1//! Content performance analytics: engagement stats, top content, and trends.
2//!
3//! Defines the [`ContentCommands`] subcommand tree and the typed output shapes
4//! ([`ContentStatsOutput`], [`TopContentOutput`], [`ContentTrendsOutput`])
5//! rendered by the `analytics content` commands.
6
7mod stats;
8mod top;
9mod trends;
10
11use anyhow::Result;
12use clap::Subcommand;
13use schemars::JsonSchema;
14use serde::{Deserialize, Serialize};
15use systemprompt_runtime::DatabaseContext;
16
17use crate::CliConfig;
18use crate::shared::render_result;
19
20#[derive(Debug, Subcommand)]
21pub enum ContentCommands {
22    #[command(about = "Content engagement statistics", alias = "list")]
23    Stats(stats::StatsArgs),
24
25    #[command(about = "Top performing content")]
26    Top(top::TopArgs),
27
28    #[command(about = "Top performing content", hide = true)]
29    Popular(top::TopArgs),
30
31    #[command(about = "Content trends over time")]
32    Trends(trends::TrendsArgs),
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
36pub struct ContentStatsOutput {
37    pub period: String,
38    pub total_views: i64,
39    pub unique_visitors: i64,
40    pub avg_time_on_page_seconds: i64,
41    pub avg_scroll_depth: f64,
42    pub total_clicks: i64,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
46pub struct TopContentRow {
47    #[serde(rename = "content_id")]
48    pub content: String,
49    pub slug: String,
50    pub title: String,
51    pub source: String,
52    pub views: i64,
53    pub unique_visitors: i64,
54    pub avg_time_seconds: i64,
55    pub trend: String,
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
59pub struct TopContentOutput {
60    pub period: String,
61    pub content: Vec<TopContentRow>,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
65pub struct ContentTrendPoint {
66    pub timestamp: String,
67    pub views: i64,
68    pub unique_visitors: i64,
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
72pub struct ContentTrendsOutput {
73    pub period: String,
74    pub group_by: String,
75    pub points: Vec<ContentTrendPoint>,
76}
77
78pub async fn execute(command: ContentCommands, config: &CliConfig) -> Result<()> {
79    match command {
80        ContentCommands::Stats(args) => {
81            let result = stats::execute(args, config).await?;
82            render_result(&result);
83            Ok(())
84        },
85        ContentCommands::Top(args) | ContentCommands::Popular(args) => {
86            let result = top::execute(args, config).await?;
87            render_result(&result);
88            Ok(())
89        },
90        ContentCommands::Trends(args) => {
91            let result = trends::execute(args, config).await?;
92            render_result(&result);
93            Ok(())
94        },
95    }
96}
97
98pub async fn execute_with_pool(
99    command: ContentCommands,
100    db_ctx: &DatabaseContext,
101    config: &CliConfig,
102) -> Result<()> {
103    match command {
104        ContentCommands::Stats(args) => {
105            let result = stats::execute_with_pool(args, db_ctx, config).await?;
106            render_result(&result);
107            Ok(())
108        },
109        ContentCommands::Top(args) | ContentCommands::Popular(args) => {
110            let result = top::execute_with_pool(args, db_ctx, config).await?;
111            render_result(&result);
112            Ok(())
113        },
114        ContentCommands::Trends(args) => {
115            let result = trends::execute_with_pool(args, db_ctx, config).await?;
116            render_result(&result);
117            Ok(())
118        },
119    }
120}