Skip to main content

max / makenotwork

9.9 KB · 368 lines History Blame Raw
1 //! Async data loading functions and the publish flow.
2
3 use tokio::sync::mpsc;
4
5 use crate::api::{CreatorStats, MnwApiClient};
6 use crate::staging;
7
8 use super::{AppEvent, DataPayload};
9
10 pub(super) async fn load_home_data(
11 api: &MnwApiClient,
12 user_id: &str,
13 tx: &mpsc::Sender<AppEvent>,
14 ) {
15 let projects = api.get_projects(user_id).await.unwrap_or_else(|e| {
16 tracing::warn!(error = %e, "failed to load projects");
17 Vec::new()
18 });
19 let stats = api
20 .get_stats(user_id, "30d")
21 .await
22 .unwrap_or_else(|e| {
23 tracing::warn!(error = %e, "failed to load stats");
24 CreatorStats {
25 current_revenue_cents: 0,
26 previous_revenue_cents: 0,
27 current_sales: 0,
28 previous_sales: 0,
29 current_followers: 0,
30 previous_followers: 0,
31 total_projects: 0,
32 total_items: 0,
33 }
34 });
35
36 let _ = tx
37 .send(AppEvent::DataLoaded(DataPayload::Home { projects, stats }))
38 .await;
39 }
40
41 pub(super) async fn load_project_items(
42 api: &MnwApiClient,
43 project_id: &str,
44 user_id: &str,
45 tx: &mpsc::Sender<AppEvent>,
46 ) {
47 let items = api
48 .get_project_items(project_id, user_id)
49 .await
50 .unwrap_or_else(|e| {
51 tracing::warn!(error = %e, %project_id, "failed to load project items");
52 Vec::new()
53 });
54
55 let _ = tx
56 .send(AppEvent::DataLoaded(DataPayload::ProjectItems { items }))
57 .await;
58 }
59
60 pub(super) async fn load_staged_files(
61 staging_dir: &std::path::Path,
62 api: &MnwApiClient,
63 user_id: &str,
64 tx: &mpsc::Sender<AppEvent>,
65 ) {
66 let files = staging::list_staged_files(staging_dir).await;
67 let storage = match api.get_storage_info(user_id).await {
68 Ok(s) => Some(s),
69 Err(e) => {
70 tracing::warn!(error = %e, "failed to load storage info");
71 None
72 }
73 };
74
75 let _ = tx
76 .send(AppEvent::DataLoaded(DataPayload::StagedFiles {
77 files,
78 storage,
79 }))
80 .await;
81 }
82
83 pub(super) async fn load_item_detail(
84 api: &MnwApiClient,
85 user_id: &str,
86 item_id: &str,
87 tx: &mpsc::Sender<AppEvent>,
88 ) {
89 let detail = api.get_item_detail(user_id, item_id).await;
90 let versions = api.get_item_versions(user_id, item_id).await;
91 let tags = api.list_item_tags(user_id, item_id).await.unwrap_or_default();
92
93 match detail {
94 Ok(detail) => {
95 let versions = versions.unwrap_or_default();
96 let _ = tx
97 .send(AppEvent::DataLoaded(DataPayload::ItemDetail {
98 detail,
99 versions,
100 }))
101 .await;
102 let _ = tx.send(AppEvent::DataLoaded(DataPayload::ItemTags { tags })).await;
103 }
104 Err(e) => {
105 let _ = tx
106 .send(AppEvent::DataLoaded(DataPayload::ItemActionError {
107 error: e.to_string(),
108 }))
109 .await;
110 }
111 }
112 }
113
114 pub(super) async fn load_collections(
115 api: &MnwApiClient,
116 user_id: &str,
117 tx: &mpsc::Sender<AppEvent>,
118 ) {
119 match api.list_collections(user_id).await {
120 Ok(collections) => {
121 let _ = tx.send(AppEvent::DataLoaded(DataPayload::CollectionsList { collections })).await;
122 }
123 Err(e) => {
124 let _ = tx.send(AppEvent::DataLoaded(DataPayload::GenericError { error: e.to_string() })).await;
125 }
126 }
127 }
128
129 pub(super) async fn load_tiers(
130 api: &MnwApiClient,
131 user_id: &str,
132 project_id: &str,
133 tx: &mpsc::Sender<AppEvent>,
134 ) {
135 match api.list_tiers(user_id, project_id).await {
136 Ok(tiers) => {
137 let _ = tx.send(AppEvent::DataLoaded(DataPayload::TiersList { tiers })).await;
138 }
139 Err(e) => {
140 let _ = tx.send(AppEvent::DataLoaded(DataPayload::GenericError { error: e.to_string() })).await;
141 }
142 }
143 }
144
145 pub(super) async fn search_tags(
146 api: &MnwApiClient,
147 query: &str,
148 tx: &mpsc::Sender<AppEvent>,
149 ) {
150 match api.search_tags(query).await {
151 Ok(results) => {
152 let _ = tx.send(AppEvent::DataLoaded(DataPayload::TagSearchResults { results })).await;
153 }
154 Err(_) => {
155 let _ = tx.send(AppEvent::DataLoaded(DataPayload::TagSearchResults { results: vec![] })).await;
156 }
157 }
158 }
159
160 pub(super) async fn load_blog_posts(
161 api: &MnwApiClient,
162 user_id: &str,
163 project_id: &str,
164 tx: &mpsc::Sender<AppEvent>,
165 ) {
166 let posts = api
167 .list_blog_posts(user_id, project_id)
168 .await
169 .unwrap_or_else(|e| {
170 tracing::warn!(error = %e, %project_id, "failed to load blog posts");
171 Vec::new()
172 });
173 let _ = tx
174 .send(AppEvent::DataLoaded(DataPayload::BlogPosts { posts }))
175 .await;
176 }
177
178 pub(super) async fn load_promo_codes(
179 api: &MnwApiClient,
180 user_id: &str,
181 tx: &mpsc::Sender<AppEvent>,
182 ) {
183 let codes = api
184 .list_promo_codes(user_id)
185 .await
186 .unwrap_or_else(|e| {
187 tracing::warn!(error = %e, "failed to load promo codes");
188 Vec::new()
189 });
190 let _ = tx
191 .send(AppEvent::DataLoaded(DataPayload::PromoCodes { codes }))
192 .await;
193 }
194
195 pub(super) async fn load_license_keys(
196 api: &MnwApiClient,
197 user_id: &str,
198 item_id: &str,
199 tx: &mpsc::Sender<AppEvent>,
200 ) {
201 let keys = api
202 .list_license_keys(user_id, item_id)
203 .await
204 .unwrap_or_else(|e| {
205 tracing::warn!(error = %e, %item_id, "failed to load license keys");
206 Vec::new()
207 });
208 let _ = tx
209 .send(AppEvent::DataLoaded(DataPayload::LicenseKeys { keys }))
210 .await;
211 }
212
213 pub(super) async fn load_analytics(
214 api: &MnwApiClient,
215 user_id: &str,
216 range: &str,
217 tx: &mpsc::Sender<AppEvent>,
218 ) {
219 match api.get_analytics(user_id, range).await {
220 Ok(data) => {
221 let _ = tx
222 .send(AppEvent::DataLoaded(DataPayload::Analytics { data }))
223 .await;
224 }
225 Err(e) => {
226 let _ = tx
227 .send(AppEvent::DataLoaded(DataPayload::GenericError {
228 error: e.to_string(),
229 }))
230 .await;
231 }
232 }
233 }
234
235 pub(super) async fn load_transactions(
236 api: &MnwApiClient,
237 user_id: &str,
238 tx: &mpsc::Sender<AppEvent>,
239 ) {
240 let txs = api.get_transactions(user_id).await.unwrap_or_else(|e| {
241 tracing::warn!(error = %e, "failed to load transactions");
242 Vec::new()
243 });
244 let _ = tx
245 .send(AppEvent::DataLoaded(DataPayload::Transactions { txs }))
246 .await;
247 }
248
249 pub(super) async fn load_settings(
250 api: &MnwApiClient,
251 user_id: &str,
252 tx: &mpsc::Sender<AppEvent>,
253 ) {
254 let keys = api.list_ssh_keys(user_id).await.unwrap_or_else(|e| {
255 tracing::warn!(error = %e, "failed to load SSH keys");
256 Vec::new()
257 });
258 let storage = match api.get_storage_info(user_id).await {
259 Ok(s) => Some(s),
260 Err(e) => {
261 tracing::warn!(error = %e, "failed to load storage info for settings");
262 None
263 }
264 };
265 let _ = tx
266 .send(AppEvent::DataLoaded(DataPayload::Settings { keys, storage }))
267 .await;
268 }
269
270 /// Full publish flow: create item -> presign -> upload to S3 -> confirm -> delete staging file.
271 #[allow(clippy::too_many_arguments)]
272 pub(super) async fn publish_file(
273 api: &MnwApiClient,
274 user_id: &str,
275 project_id: &str,
276 title: &str,
277 item_type: &str,
278 file_type: &str,
279 filename: &str,
280 content_type: &str,
281 price_cents: i32,
282 file_path: &std::path::Path,
283 ) -> anyhow::Result<()> {
284 // Step 1: Create item
285 let item = api
286 .create_item(user_id, project_id, title, item_type, price_cents)
287 .await?;
288
289 // Step 2: Get presigned URL
290 let presign = api
291 .presign_upload(user_id, &item.item_id, file_type, filename, content_type)
292 .await?;
293
294 // Step 3: Upload to S3
295 api.upload_to_s3(
296 &presign.upload_url,
297 file_path,
298 content_type,
299 presign.cache_control.as_deref(),
300 )
301 .await?;
302
303 // Step 4: Confirm upload
304 api.confirm_upload(user_id, &item.item_id, file_type, &presign.s3_key)
305 .await?;
306
307 // Step 5: Delete staging file
308 if let Err(e) = tokio::fs::remove_file(file_path).await {
309 tracing::warn!(error = %e, path = %file_path.display(), "failed to delete staging file after publish");
310 }
311
312 Ok(())
313 }
314
315 /// Bulk publish items.
316 pub(super) async fn bulk_publish(
317 api: &MnwApiClient,
318 user_id: &str,
319 item_ids: Vec<String>,
320 tx: &mpsc::Sender<AppEvent>,
321 ) {
322 let total = item_ids.len();
323 let mut ok = 0;
324 for id in &item_ids {
325 if api.publish_item(user_id, id).await.is_ok() {
326 ok += 1;
327 }
328 }
329 let msg = format!("Published {}/{} items", ok, total);
330 let _ = tx.send(AppEvent::DataLoaded(DataPayload::BulkActionComplete { message: msg })).await;
331 }
332
333 /// Bulk unpublish items.
334 pub(super) async fn bulk_unpublish(
335 api: &MnwApiClient,
336 user_id: &str,
337 item_ids: Vec<String>,
338 tx: &mpsc::Sender<AppEvent>,
339 ) {
340 let total = item_ids.len();
341 let mut ok = 0;
342 for id in &item_ids {
343 if api.unpublish_item(user_id, id).await.is_ok() {
344 ok += 1;
345 }
346 }
347 let msg = format!("Unpublished {}/{} items", ok, total);
348 let _ = tx.send(AppEvent::DataLoaded(DataPayload::BulkActionComplete { message: msg })).await;
349 }
350
351 /// Bulk delete items.
352 pub(super) async fn bulk_delete(
353 api: &MnwApiClient,
354 user_id: &str,
355 item_ids: Vec<String>,
356 tx: &mpsc::Sender<AppEvent>,
357 ) {
358 let total = item_ids.len();
359 let mut ok = 0;
360 for id in &item_ids {
361 if api.delete_item(user_id, id).await.is_ok() {
362 ok += 1;
363 }
364 }
365 let msg = format!("Deleted {}/{} items", ok, total);
366 let _ = tx.send(AppEvent::DataLoaded(DataPayload::BulkActionComplete { message: msg })).await;
367 }
368