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};
15
16use crate::context::CommandContext;
17use crate::shared::render_result;
18
19#[derive(Debug, Subcommand)]
20pub enum ContentCommands {
21    #[command(about = "Content engagement statistics", alias = "list")]
22    Stats(stats::StatsArgs),
23
24    #[command(about = "Top performing content")]
25    Top(top::TopArgs),
26
27    #[command(about = "Top performing content", hide = true)]
28    Popular(top::TopArgs),
29
30    #[command(about = "Content trends over time")]
31    Trends(trends::TrendsArgs),
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
35pub struct ContentStatsOutput {
36    pub period: String,
37    pub total_views: i64,
38    pub unique_visitors: i64,
39    pub avg_time_on_page_seconds: i64,
40    pub avg_scroll_depth: f64,
41    pub total_clicks: i64,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
45pub struct TopContentRow {
46    #[serde(rename = "content_id")]
47    pub content: String,
48    pub slug: String,
49    pub title: String,
50    pub source: String,
51    pub views: i64,
52    pub unique_visitors: i64,
53    pub avg_time_seconds: i64,
54    pub trend: String,
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
58pub struct TopContentOutput {
59    pub period: String,
60    pub content: Vec<TopContentRow>,
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
64pub struct ContentTrendPoint {
65    pub timestamp: String,
66    pub views: i64,
67    pub unique_visitors: i64,
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
71pub struct ContentTrendsOutput {
72    pub period: String,
73    pub group_by: String,
74    pub points: Vec<ContentTrendPoint>,
75}
76
77pub async fn execute(command: ContentCommands, ctx: &CommandContext) -> Result<()> {
78    let db_ctx = ctx.database().await?;
79    match command {
80        ContentCommands::Stats(args) => {
81            let result = stats::execute_with_pool(args, &db_ctx, &ctx.cli).await?;
82            render_result(&result, &ctx.cli);
83            Ok(())
84        },
85        ContentCommands::Top(args) | ContentCommands::Popular(args) => {
86            let result = top::execute_with_pool(args, &db_ctx, &ctx.cli).await?;
87            render_result(&result, &ctx.cli);
88            Ok(())
89        },
90        ContentCommands::Trends(args) => {
91            let result = trends::execute_with_pool(args, &db_ctx, &ctx.cli).await?;
92            render_result(&result, &ctx.cli);
93            Ok(())
94        },
95    }
96}