//! Creator platform import system. //! //! Converts data from external platforms (Patreon, Ko-fi, Gumroad, Bandcamp, //! Substack, Ghost, Lemon Squeezy) into a common intermediate format, then //! feeds it through a generic pipeline that creates MNW entities. pub mod csv_converter; pub mod pipeline; use chrono::{DateTime, Utc}; use serde::Deserialize; // Re-export enums from db layer (where impl_str_enum macro lives). pub use crate::db::{ImportJobStatus, ImportSource}; // ── Common Intermediate Format ── #[derive(Debug, Clone, Default)] pub struct ImportPayload { pub subscribers: Vec, pub items: Vec, pub tiers: Vec, pub transactions: Vec, pub tags: Vec, } impl ImportPayload { /// Total number of entities across all categories. pub fn total_rows(&self) -> usize { self.subscribers.len() + self.items.len() + self.tiers.len() + self.transactions.len() } } #[derive(Debug, Clone)] pub struct ImportSubscriber { pub email: String, pub name: Option, pub tier_name: Option, pub status: Option, pub joined_at: Option>, pub lifetime_amount_cents: Option, pub stripe_customer_id: Option, } #[derive(Debug, Clone)] pub struct ImportItem { pub title: String, pub description: Option, pub price_cents: Option, pub body_html: Option, pub tags: Vec, pub published_at: Option>, pub is_public: bool, } #[derive(Debug, Clone)] pub struct ImportTier { pub name: String, pub description: Option, pub price_cents: i64, } #[derive(Debug, Clone)] pub struct ImportTransaction { pub buyer_email: String, pub buyer_name: Option, pub item_title: Option, pub amount_cents: i64, pub currency: String, pub date: DateTime, pub status: Option, } // ── Column Mapping (CSV) ── /// Maps CSV column indices to semantic fields. #[derive(Debug, Clone, Default, Deserialize)] pub struct ColumnMapping { pub email: Option, pub name: Option, pub amount: Option, pub date: Option, pub item_title: Option, pub tier: Option, pub status: Option, } #[cfg(test)] mod tests { use super::*; #[test] fn payload_total_rows() { let mut p = ImportPayload::default(); assert_eq!(p.total_rows(), 0); p.subscribers.push(ImportSubscriber { email: "a@b.com".into(), name: None, tier_name: None, status: None, joined_at: None, lifetime_amount_cents: None, stripe_customer_id: None, }); p.transactions.push(ImportTransaction { buyer_email: "a@b.com".into(), buyer_name: None, item_title: None, amount_cents: 100, currency: "USD".into(), date: Utc::now(), status: None, }); assert_eq!(p.total_rows(), 2); } }