//! Analytics: the timeseries and period comparison behind the dashboard, the //! transaction ledger, and the CSV export of it. use super::{MnwApiClient, json_response, revenue_of}; use crate::currency::{Currency, RevenueByCurrency}; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; /// A revenue bucket for analytics timeseries. #[derive(Debug, Clone, Deserialize, Serialize)] pub(crate) struct AnalyticsBucket { pub label: String, pub revenue_cents: i64, pub sales_count: i64, } /// Per-project revenue summary. #[derive(Debug, Clone, Deserialize, Serialize)] pub(crate) struct ProjectRevenue { pub id: String, pub title: String, /// Revenue in `currency`. See [`Project::revenue_cents`]. pub revenue_cents: i64, #[serde(default)] pub currency: Currency, #[serde(default)] pub revenue_cents_by_currency: BTreeMap, } impl ProjectRevenue { /// Revenue across every currency it was earned in. pub(crate) fn revenue(&self) -> RevenueByCurrency { revenue_of( self.revenue_cents, self.currency, &self.revenue_cents_by_currency, ) } } /// Analytics response with timeseries, comparison, and top projects. #[derive(Debug, Clone, Deserialize, Serialize)] pub(crate) struct AnalyticsData { pub buckets: Vec, pub current_revenue_cents: i64, pub previous_revenue_cents: i64, pub current_sales: i64, pub previous_sales: i64, pub current_followers: i64, pub previous_followers: i64, pub top_projects: Vec, } /// A seller transaction. #[derive(Debug, Clone, Deserialize, Serialize)] pub(crate) struct Transaction { pub id: String, pub item_title: Option, pub amount_cents: i32, pub status: String, pub created_at: String, pub completed_at: Option, } /// CSV export result. #[derive(Debug, Clone, Deserialize, Serialize)] pub(crate) struct ExportResult { pub csv: String, pub row_count: usize, } impl MnwApiClient { /// Get analytics data (timeseries, period comparison, top projects). pub(crate) async fn get_analytics( &self, user_id: &str, range: &str, ) -> anyhow::Result { let url = format!("{}/api/internal/creator/analytics", self.base_url); let resp = self .http .get(&url) .bearer_auth(&self.service_token) .header("X-MNW-Actor", self.actor_header()) .query(&[("user_id", user_id), ("range", range)]) .send() .await?; json_response(resp, "get_analytics").await } /// Get recent seller transactions. pub(crate) async fn get_transactions(&self, user_id: &str) -> anyhow::Result> { let url = format!("{}/api/internal/creator/transactions", self.base_url); let resp = self .http .get(&url) .bearer_auth(&self.service_token) .header("X-MNW-Actor", self.actor_header()) .query(&[("user_id", user_id)]) .send() .await?; json_response(resp, "get_transactions").await } /// Export sales as CSV string. pub(crate) async fn export_sales_csv(&self, user_id: &str) -> anyhow::Result { let url = format!("{}/api/internal/creator/export/sales", self.base_url); let resp = self .http .get(&url) .bearer_auth(&self.service_token) .header("X-MNW-Actor", self.actor_header()) .query(&[("user_id", user_id)]) .send() .await?; json_response(resp, "export_sales_csv").await } }