backbone_bucket/domain/computed/
mod.rs1use std::collections::HashMap;
9use serde::{Deserialize, Serialize};
10#[allow(unused_imports)]
11use chrono::{DateTime, Utc, NaiveDate};
12
13use crate::domain::entity::*;
14
15pub trait HasComputedFields {
17 fn computed_fields(&self) -> ComputedFieldValues;
19
20 fn computed_field(&self, name: &str) -> Option<serde_json::Value>;
22}
23
24#[derive(Debug, Clone, Default, Serialize, Deserialize)]
26pub struct ComputedFieldValues {
27 pub fields: HashMap<String, serde_json::Value>,
28}
29
30impl ComputedFieldValues {
31 pub fn new() -> Self {
32 Self { fields: HashMap::new() }
33 }
34
35 pub fn set(&mut self, name: impl Into<String>, value: serde_json::Value) {
36 self.fields.insert(name.into(), value);
37 }
38
39 pub fn get(&self, name: &str) -> Option<&serde_json::Value> {
40 self.fields.get(name)
41 }
42
43 pub fn get_bool(&self, name: &str) -> Option<bool> {
44 self.fields.get(name).and_then(|v| v.as_bool())
45 }
46
47 pub fn get_str(&self, name: &str) -> Option<&str> {
48 self.fields.get(name).and_then(|v| v.as_str())
49 }
50
51 pub fn get_i64(&self, name: &str) -> Option<i64> {
52 self.fields.get(name).and_then(|v| v.as_i64())
53 }
54}
55
56impl HasComputedFields for Bucket {
58 fn computed_fields(&self) -> ComputedFieldValues {
59 let mut fields = ComputedFieldValues::new();
60
61 fields.set("is_empty", serde_json::json!(self.file_count == 0));
63
64 fields.set("usage_bytes", serde_json::json!(self.total_size_bytes));
66
67 fields.set("average_file_size", serde_json::json!(self.file_count > 0));
69
70 fields.set("is_accessible", serde_json::json!(self.status == BucketStatus::Active || self.status == BucketStatus::Readonly));
72
73 fields.set("accepts_uploads", serde_json::json!(self.status == BucketStatus::Active));
75
76 fields
77 }
78
79 fn computed_field(&self, name: &str) -> Option<serde_json::Value> {
80 self.computed_fields().fields.get(name).cloned()
81 }
82}
83
84impl HasComputedFields for ConversionJob {
86 fn computed_fields(&self) -> ComputedFieldValues {
87 let mut fields = ComputedFieldValues::new();
88
89 fields.set("is_active", serde_json::json!(self.status));
91
92 fields.set("is_terminal", serde_json::json!(self.status));
94
95 fields.set("can_retry", serde_json::json!(null));
100
101 fields.set("progress_percentage", serde_json::json!(self.progress));
103
104 fields.set("duration_seconds", serde_json::json!(self.started_at.is_some() && self.completed_at.is_some()));
106
107 fields
108 }
109
110 fn computed_field(&self, name: &str) -> Option<serde_json::Value> {
111 self.computed_fields().fields.get(name).cloned()
112 }
113}
114
115impl HasComputedFields for FileComment {
117 fn computed_fields(&self) -> ComputedFieldValues {
118 let mut fields = ComputedFieldValues::new();
119
120 fields.set("is_reply", serde_json::json!(self.parent_id.is_some()));
122
123 fields.set("is_resolved", serde_json::json!(self.status == CommentStatus::Resolved));
125
126 fields.set("is_deleted", serde_json::json!(self.status == CommentStatus::Deleted));
128
129 fields.set("is_editable", serde_json::json!(null));
133
134 fields.set("has_annotation", serde_json::json!(self.annotation_region.is_some()));
136
137 fields.set("has_mentions", serde_json::json!(null));
141
142 fields.set("reply_count", serde_json::json!(null));
147
148 fields
149 }
150
151 fn computed_field(&self, name: &str) -> Option<serde_json::Value> {
152 self.computed_fields().fields.get(name).cloned()
153 }
154}
155
156impl HasComputedFields for FileLock {
158 fn computed_fields(&self) -> ComputedFieldValues {
159 let mut fields = ComputedFieldValues::new();
160
161 fields.set("is_active", serde_json::json!(self.status == LockStatus::Active && self.expires_at > chrono::Utc::now()));
163
164 fields.set("is_expired", serde_json::json!(self.status == LockStatus::Expired || self.expires_at <= chrono::Utc::now()));
166
167 fields.set("time_remaining_minutes", serde_json::json!(self.status == LockStatus::Active));
169
170 fields.set("can_refresh", serde_json::json!(null));
175
176 fields
177 }
178
179 fn computed_field(&self, name: &str) -> Option<serde_json::Value> {
180 self.computed_fields().fields.get(name).cloned()
181 }
182}
183
184impl HasComputedFields for FileShare {
186 fn computed_fields(&self) -> ComputedFieldValues {
187 let mut fields = ComputedFieldValues::new();
188
189 fields.set("is_valid", serde_json::json!(self.status == ShareStatus::Active && !self.is_expired() && self.has_downloads_remaining()));
191
192 fields.set("share_url", serde_json::json!(format!("{}{}", "/share/", &self.token)));
194
195 fields.set("days_until_expiry", serde_json::json!(null));
199
200 fields.set("downloads_remaining", serde_json::json!(self.max_downloads.is_some()));
202
203 fields.set("is_password_protected", serde_json::json!(self.share_type == ShareType::Password && self.password_hash.is_some()));
205
206 fields.set("is_expired", serde_json::json!(self.expires_at.is_some() && self.expires_at <= Some(chrono::Utc::now())));
208
209 fields.set("has_downloads_remaining", serde_json::json!(self.max_downloads.is_none() || Some(self.download_count) < self.max_downloads));
211
212 fields
213 }
214
215 fn computed_field(&self, name: &str) -> Option<serde_json::Value> {
216 self.computed_fields().fields.get(name).cloned()
217 }
218}
219
220impl HasComputedFields for ProcessingJob {
222 fn computed_fields(&self) -> ComputedFieldValues {
223 let mut fields = ComputedFieldValues::new();
224
225 fields.set("is_active", serde_json::json!(self.status));
227
228 fields.set("is_terminal", serde_json::json!(self.status));
230
231 fields.set("can_retry", serde_json::json!(self.status == JobStatus::Failed && self.retry_count < self.max_retries));
233
234 fields.set("duration_seconds", serde_json::json!(self.started_at.is_some() && self.completed_at.is_some()));
236
237 fields.set("wait_time_seconds", serde_json::json!(self.started_at.is_some()));
239
240 fields
241 }
242
243 fn computed_field(&self, name: &str) -> Option<serde_json::Value> {
244 self.computed_fields().fields.get(name).cloned()
245 }
246}
247
248impl HasComputedFields for StoredFile {
250 fn computed_fields(&self) -> ComputedFieldValues {
251 let mut fields = ComputedFieldValues::new();
252
253 fields.set("compression_ratio", serde_json::json!(self.original_size.is_some()));
255
256 fields.set("is_image", serde_json::json!(self.mime_type.starts_with("image/")));
258
259 fields.set("is_video", serde_json::json!(self.mime_type.starts_with("video/")));
261
262 fields.set("is_document", serde_json::json!(self.mime_type.starts_with("application/pdf") || self.mime_type.contains("document")));
264
265 fields.set("full_path", serde_json::json!(null));
270
271 fields.set("age_days", serde_json::json!(null));
275
276 fields.set("is_accessible", serde_json::json!(self.status == FileStatus::Active));
278
279 fields.set("is_safe", serde_json::json!(self.is_scanned && self.threat_level.is_none() || self.threat_level == Some(ThreatLevel::Safe)));
281
282 fields
283 }
284
285 fn computed_field(&self, name: &str) -> Option<serde_json::Value> {
286 self.computed_fields().fields.get(name).cloned()
287 }
288}
289
290impl HasComputedFields for UploadSession {
292 fn computed_fields(&self) -> ComputedFieldValues {
293 let mut fields = ComputedFieldValues::new();
294
295 fields.set("progress_percent", serde_json::json!(self.total_chunks > 0));
297
298 fields.set("is_resumable", serde_json::json!(self.status == UploadStatus::Uploading && self.expires_at > chrono::Utc::now() && self.uploaded_chunks < self.total_chunks));
300
301 fields.set("is_complete", serde_json::json!(self.uploaded_chunks == self.total_chunks));
303
304 fields.set("remaining_chunks", serde_json::json!(self.total_chunks - self.uploaded_chunks));
306
307 fields.set("is_expired", serde_json::json!(true && self.expires_at <= chrono::Utc::now()));
309
310 fields
311 }
312
313 fn computed_field(&self, name: &str) -> Option<serde_json::Value> {
314 self.computed_fields().fields.get(name).cloned()
315 }
316}
317
318impl HasComputedFields for UserQuota {
320 fn computed_fields(&self) -> ComputedFieldValues {
321 let mut fields = ComputedFieldValues::new();
322
323 fields.set("usage_percent", serde_json::json!(null));
328
329 fields.set("storage_remaining_bytes", serde_json::json!(null));
334
335 fields.set("files_remaining", serde_json::json!(null));
340
341 fields.set("buckets_remaining", serde_json::json!(null));
346
347 fields.set("used_storage_formatted", serde_json::json!(null));
352
353 fields.set("max_storage_formatted", serde_json::json!(null));
358
359 fields.set("is_near_limit", serde_json::json!(null));
364
365 fields.set("is_at_file_limit", serde_json::json!(null));
370
371 fields.set("is_at_bucket_limit", serde_json::json!(null));
376
377 fields.set("is_expired", serde_json::json!(null));
382
383 fields.set("days_until_expiry", serde_json::json!(null));
388
389 fields.set("daily_bandwidth_remaining", serde_json::json!(null));
394
395 fields.set("monthly_bandwidth_remaining", serde_json::json!(null));
400
401 fields.set("can_upload", serde_json::json!(null));
406
407 fields.set("can_create_bucket", serde_json::json!(null));
412
413 fields.set("can_share", serde_json::json!(null));
418
419 fields.set("can_public_share", serde_json::json!(null));
424
425 fields.set("can_version", serde_json::json!(null));
430
431 fields.set("tier_label", serde_json::json!(self.tier == "free"));
433
434 fields
435 }
436
437 fn computed_field(&self, name: &str) -> Option<serde_json::Value> {
438 self.computed_fields().fields.get(name).cloned()
439 }
440}
441
442#[cfg(test)]
443mod tests {
444 #[allow(unused_imports)]
445 use super::*;
446
447 #[test]
448 fn test_bucket_computed_fields() {
449 }
451
452 #[test]
453 fn test_conversion_job_computed_fields() {
454 }
456
457 #[test]
458 fn test_file_comment_computed_fields() {
459 }
461
462 #[test]
463 fn test_file_lock_computed_fields() {
464 }
466
467 #[test]
468 fn test_file_share_computed_fields() {
469 }
471
472 #[test]
473 fn test_processing_job_computed_fields() {
474 }
476
477 #[test]
478 fn test_stored_file_computed_fields() {
479 }
481
482 #[test]
483 fn test_upload_session_computed_fields() {
484 }
486
487 #[test]
488 fn test_user_quota_computed_fields() {
489 }
491
492}