Skip to main content

tuitbot_server/routes/
analytics.rs

1//! Analytics endpoints.
2
3use std::sync::Arc;
4
5use axum::extract::{Query, State};
6use axum::Json;
7use serde::Deserialize;
8use serde_json::{json, Value};
9use tuitbot_core::storage::analytics;
10
11use crate::error::ApiError;
12use crate::state::AppState;
13
14/// Query parameters for the followers endpoint.
15#[derive(Deserialize)]
16pub struct FollowersQuery {
17    /// Number of days of follower snapshots to return (default: 7).
18    #[serde(default = "default_days")]
19    pub days: u32,
20}
21
22fn default_days() -> u32 {
23    7
24}
25
26/// Query parameters for the topics endpoint.
27#[derive(Deserialize)]
28pub struct TopicsQuery {
29    /// Maximum number of topics to return (default: 20).
30    #[serde(default = "default_topic_limit")]
31    pub limit: u32,
32}
33
34fn default_topic_limit() -> u32 {
35    20
36}
37
38/// Query parameters for the recent-performance endpoint.
39#[derive(Deserialize)]
40pub struct RecentPerformanceQuery {
41    /// Maximum number of items to return (default: 20).
42    #[serde(default = "default_recent_limit")]
43    pub limit: u32,
44}
45
46fn default_recent_limit() -> u32 {
47    20
48}
49
50/// `GET /api/analytics/followers` — follower snapshots over time.
51pub async fn followers(
52    State(state): State<Arc<AppState>>,
53    Query(params): Query<FollowersQuery>,
54) -> Result<Json<Value>, ApiError> {
55    let snapshots = analytics::get_follower_snapshots(&state.db, params.days).await?;
56    Ok(Json(json!(snapshots)))
57}
58
59/// `GET /api/analytics/performance` — reply and tweet performance summaries.
60pub async fn performance(State(state): State<Arc<AppState>>) -> Result<Json<Value>, ApiError> {
61    let avg_reply = analytics::get_avg_reply_engagement(&state.db).await?;
62    let avg_tweet = analytics::get_avg_tweet_engagement(&state.db).await?;
63    let (reply_count, tweet_count) = analytics::get_performance_counts(&state.db).await?;
64
65    Ok(Json(json!({
66        "avg_reply_engagement": avg_reply,
67        "avg_tweet_engagement": avg_tweet,
68        "measured_replies": reply_count,
69        "measured_tweets": tweet_count,
70    })))
71}
72
73/// `GET /api/analytics/topics` — topic performance scores.
74pub async fn topics(
75    State(state): State<Arc<AppState>>,
76    Query(params): Query<TopicsQuery>,
77) -> Result<Json<Value>, ApiError> {
78    let scores = analytics::get_top_topics(&state.db, params.limit).await?;
79    Ok(Json(json!(scores)))
80}
81
82/// `GET /api/analytics/summary` — combined analytics dashboard summary.
83pub async fn summary(State(state): State<Arc<AppState>>) -> Result<Json<Value>, ApiError> {
84    let data = analytics::get_analytics_summary(&state.db).await?;
85    Ok(Json(json!(data)))
86}
87
88/// `GET /api/analytics/recent-performance` — recent content with performance metrics.
89pub async fn recent_performance(
90    State(state): State<Arc<AppState>>,
91    Query(params): Query<RecentPerformanceQuery>,
92) -> Result<Json<Value>, ApiError> {
93    let items = analytics::get_recent_performance_items(&state.db, params.limit).await?;
94    Ok(Json(json!(items)))
95}