Skip to main content

max / makenotwork

3.1 KB · 118 lines History Blame Raw
1 //! Creator platform import system.
2 //!
3 //! Converts data from external platforms (Patreon, Ko-fi, Gumroad, Bandcamp,
4 //! Substack, Ghost, Lemon Squeezy) into a common intermediate format, then
5 //! feeds it through a generic pipeline that creates MNW entities.
6
7 pub mod csv_converter;
8 pub mod pipeline;
9
10 use chrono::{DateTime, Utc};
11 use serde::Deserialize;
12
13 // Re-export enums from db layer (where impl_str_enum macro lives).
14 pub use crate::db::{ImportJobStatus, ImportSource};
15
16 // ── Common Intermediate Format ──
17
18 #[derive(Debug, Clone, Default)]
19 pub struct ImportPayload {
20 pub subscribers: Vec<ImportSubscriber>,
21 pub items: Vec<ImportItem>,
22 pub tiers: Vec<ImportTier>,
23 pub transactions: Vec<ImportTransaction>,
24 pub tags: Vec<String>,
25 }
26
27 impl ImportPayload {
28 /// Total number of entities across all categories.
29 pub fn total_rows(&self) -> usize {
30 self.subscribers.len() + self.items.len() + self.tiers.len() + self.transactions.len()
31 }
32 }
33
34 #[derive(Debug, Clone)]
35 pub struct ImportSubscriber {
36 pub email: String,
37 pub name: Option<String>,
38 pub tier_name: Option<String>,
39 pub status: Option<String>,
40 pub joined_at: Option<DateTime<Utc>>,
41 pub lifetime_amount_cents: Option<i64>,
42 pub stripe_customer_id: Option<String>,
43 }
44
45 #[derive(Debug, Clone)]
46 pub struct ImportItem {
47 pub title: String,
48 pub description: Option<String>,
49 pub price_cents: Option<i64>,
50 pub body_html: Option<String>,
51 pub tags: Vec<String>,
52 pub published_at: Option<DateTime<Utc>>,
53 pub is_public: bool,
54 }
55
56 #[derive(Debug, Clone)]
57 pub struct ImportTier {
58 pub name: String,
59 pub description: Option<String>,
60 pub price_cents: i64,
61 }
62
63 #[derive(Debug, Clone)]
64 pub struct ImportTransaction {
65 pub buyer_email: String,
66 pub buyer_name: Option<String>,
67 pub item_title: Option<String>,
68 pub amount_cents: i64,
69 pub currency: String,
70 pub date: DateTime<Utc>,
71 pub status: Option<String>,
72 }
73
74 // ── Column Mapping (CSV) ──
75
76 /// Maps CSV column indices to semantic fields.
77 #[derive(Debug, Clone, Default, Deserialize)]
78 pub struct ColumnMapping {
79 pub email: Option<usize>,
80 pub name: Option<usize>,
81 pub amount: Option<usize>,
82 pub date: Option<usize>,
83 pub item_title: Option<usize>,
84 pub tier: Option<usize>,
85 pub status: Option<usize>,
86 }
87
88 #[cfg(test)]
89 mod tests {
90 use super::*;
91
92 #[test]
93 fn payload_total_rows() {
94 let mut p = ImportPayload::default();
95 assert_eq!(p.total_rows(), 0);
96
97 p.subscribers.push(ImportSubscriber {
98 email: "a@b.com".into(),
99 name: None,
100 tier_name: None,
101 status: None,
102 joined_at: None,
103 lifetime_amount_cents: None,
104 stripe_customer_id: None,
105 });
106 p.transactions.push(ImportTransaction {
107 buyer_email: "a@b.com".into(),
108 buyer_name: None,
109 item_title: None,
110 amount_cents: 100,
111 currency: "USD".into(),
112 date: Utc::now(),
113 status: None,
114 });
115 assert_eq!(p.total_rows(), 2);
116 }
117 }
118