Skip to main content

max / makenotwork

3.7 KB · 122 lines History Blame Raw
1 //! Analytics: the timeseries and period comparison behind the dashboard, the
2 //! transaction ledger, and the CSV export of it.
3
4 use super::{MnwApiClient, json_response, revenue_of};
5 use crate::currency::{Currency, RevenueByCurrency};
6 use serde::{Deserialize, Serialize};
7 use std::collections::BTreeMap;
8
9 /// A revenue bucket for analytics timeseries.
10 #[derive(Debug, Clone, Deserialize, Serialize)]
11 pub(crate) struct AnalyticsBucket {
12 pub label: String,
13 pub revenue_cents: i64,
14 pub sales_count: i64,
15 }
16
17 /// Per-project revenue summary.
18 #[derive(Debug, Clone, Deserialize, Serialize)]
19 pub(crate) struct ProjectRevenue {
20 pub id: String,
21 pub title: String,
22 /// Revenue in `currency`. See [`Project::revenue_cents`].
23 pub revenue_cents: i64,
24 #[serde(default)]
25 pub currency: Currency,
26 #[serde(default)]
27 pub revenue_cents_by_currency: BTreeMap<String, i64>,
28 }
29
30 impl ProjectRevenue {
31 /// Revenue across every currency it was earned in.
32 pub(crate) fn revenue(&self) -> RevenueByCurrency {
33 revenue_of(
34 self.revenue_cents,
35 self.currency,
36 &self.revenue_cents_by_currency,
37 )
38 }
39 }
40
41 /// Analytics response with timeseries, comparison, and top projects.
42 #[derive(Debug, Clone, Deserialize, Serialize)]
43 pub(crate) struct AnalyticsData {
44 pub buckets: Vec<AnalyticsBucket>,
45 pub current_revenue_cents: i64,
46 pub previous_revenue_cents: i64,
47 pub current_sales: i64,
48 pub previous_sales: i64,
49 pub current_followers: i64,
50 pub previous_followers: i64,
51 pub top_projects: Vec<ProjectRevenue>,
52 }
53
54 /// A seller transaction.
55 #[derive(Debug, Clone, Deserialize, Serialize)]
56 pub(crate) struct Transaction {
57 pub id: String,
58 pub item_title: Option<String>,
59 pub amount_cents: i32,
60 pub status: String,
61 pub created_at: String,
62 pub completed_at: Option<String>,
63 }
64
65 /// CSV export result.
66 #[derive(Debug, Clone, Deserialize, Serialize)]
67 pub(crate) struct ExportResult {
68 pub csv: String,
69 pub row_count: usize,
70 }
71
72 impl MnwApiClient {
73 /// Get analytics data (timeseries, period comparison, top projects).
74 pub(crate) async fn get_analytics(
75 &self,
76 user_id: &str,
77 range: &str,
78 ) -> anyhow::Result<AnalyticsData> {
79 let url = format!("{}/api/internal/creator/analytics", self.base_url);
80 let resp = self
81 .http
82 .get(&url)
83 .bearer_auth(&self.service_token)
84 .header("X-MNW-Actor", self.actor_header())
85 .query(&[("user_id", user_id), ("range", range)])
86 .send()
87 .await?;
88
89 json_response(resp, "get_analytics").await
90 }
91
92 /// Get recent seller transactions.
93 pub(crate) async fn get_transactions(&self, user_id: &str) -> anyhow::Result<Vec<Transaction>> {
94 let url = format!("{}/api/internal/creator/transactions", self.base_url);
95 let resp = self
96 .http
97 .get(&url)
98 .bearer_auth(&self.service_token)
99 .header("X-MNW-Actor", self.actor_header())
100 .query(&[("user_id", user_id)])
101 .send()
102 .await?;
103
104 json_response(resp, "get_transactions").await
105 }
106
107 /// Export sales as CSV string.
108 pub(crate) async fn export_sales_csv(&self, user_id: &str) -> anyhow::Result<ExportResult> {
109 let url = format!("{}/api/internal/creator/export/sales", self.base_url);
110 let resp = self
111 .http
112 .get(&url)
113 .bearer_auth(&self.service_token)
114 .header("X-MNW-Actor", self.actor_header())
115 .query(&[("user_id", user_id)])
116 .send()
117 .await?;
118
119 json_response(resp, "export_sales_csv").await
120 }
121 }
122