Skip to main content

backbone_bucket/domain/computed/
mod.rs

1//! Computed Fields Module
2//!
3//! Generated by metaphor-schema.
4//!
5//! Provides virtual/computed fields for entities based on their data and relationships.
6//! These fields are not stored in the database but calculated on-demand.
7
8use std::collections::HashMap;
9use serde::{Deserialize, Serialize};
10#[allow(unused_imports)]
11use chrono::{DateTime, Utc, NaiveDate};
12
13use crate::domain::entity::*;
14
15/// Trait for entities that have computed fields
16pub trait HasComputedFields {
17    /// Get all computed field values
18    fn computed_fields(&self) -> ComputedFieldValues;
19
20    /// Get a specific computed field value
21    fn computed_field(&self, name: &str) -> Option<serde_json::Value>;
22}
23
24/// Collection of computed field values
25#[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
56/// Computed fields for Bucket entity
57impl HasComputedFields for Bucket {
58    fn computed_fields(&self) -> ComputedFieldValues {
59        let mut fields = ComputedFieldValues::new();
60
61        // is_empty
62        fields.set("is_empty", serde_json::json!(self.file_count == 0));
63
64        // usage_bytes
65        fields.set("usage_bytes", serde_json::json!(self.total_size_bytes));
66
67        // average_file_size
68        fields.set("average_file_size", serde_json::json!(self.file_count > 0));
69
70        // is_accessible
71        fields.set("is_accessible", serde_json::json!(self.status == BucketStatus::Active || self.status == BucketStatus::Readonly));
72
73        // accepts_uploads
74        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
84/// Computed fields for ConversionJob entity
85impl HasComputedFields for ConversionJob {
86    fn computed_fields(&self) -> ComputedFieldValues {
87        let mut fields = ComputedFieldValues::new();
88
89        // is_active
90        fields.set("is_active", serde_json::json!(self.status));
91
92        // is_terminal
93        fields.set("is_terminal", serde_json::json!(self.status));
94
95        // can_retry
96        // WARNING: Expression references fields not on this entity: ["retry_count"]
97        // These may be on a related entity or junction table.
98        // Implement via AsyncComputedFields trait or repository query.
99        fields.set("can_retry", serde_json::json!(null));
100
101        // progress_percentage
102        fields.set("progress_percentage", serde_json::json!(self.progress));
103
104        // duration_seconds
105        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
115/// Computed fields for FileComment entity
116impl HasComputedFields for FileComment {
117    fn computed_fields(&self) -> ComputedFieldValues {
118        let mut fields = ComputedFieldValues::new();
119
120        // is_reply
121        fields.set("is_reply", serde_json::json!(self.parent_id.is_some()));
122
123        // is_resolved
124        fields.set("is_resolved", serde_json::json!(self.status == CommentStatus::Resolved));
125
126        // is_deleted
127        fields.set("is_deleted", serde_json::json!(self.status == CommentStatus::Deleted));
128
129        // is_editable
130        // NOTE: Expression requires runtime context ($actor) - requires repository access
131        // Implement via async computed field trait or cache
132        fields.set("is_editable", serde_json::json!(null));
133
134        // has_annotation
135        fields.set("has_annotation", serde_json::json!(self.annotation_region.is_some()));
136
137        // has_mentions
138        // NOTE: Contains repository-dependent function - requires repository access
139        // Implement via async computed field trait or cache
140        fields.set("has_mentions", serde_json::json!(null));
141
142        // reply_count
143        // WARNING: Expression references fields not on this entity: ["FileComment"]
144        // These may be on a related entity or junction table.
145        // Implement via AsyncComputedFields trait or repository query.
146        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
156/// Computed fields for FileLock entity
157impl HasComputedFields for FileLock {
158    fn computed_fields(&self) -> ComputedFieldValues {
159        let mut fields = ComputedFieldValues::new();
160
161        // is_active
162        fields.set("is_active", serde_json::json!(self.status == LockStatus::Active && self.expires_at > chrono::Utc::now()));
163
164        // is_expired
165        fields.set("is_expired", serde_json::json!(self.status == LockStatus::Expired || self.expires_at <= chrono::Utc::now()));
166
167        // time_remaining_minutes
168        fields.set("time_remaining_minutes", serde_json::json!(self.status == LockStatus::Active));
169
170        // can_refresh
171        // WARNING: Expression references fields not on this entity: ["INTERVAL"]
172        // These may be on a related entity or junction table.
173        // Implement via AsyncComputedFields trait or repository query.
174        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
184/// Computed fields for FileShare entity
185impl HasComputedFields for FileShare {
186    fn computed_fields(&self) -> ComputedFieldValues {
187        let mut fields = ComputedFieldValues::new();
188
189        // is_valid
190        fields.set("is_valid", serde_json::json!(self.status == ShareStatus::Active && !self.is_expired() && self.has_downloads_remaining()));
191
192        // share_url
193        fields.set("share_url", serde_json::json!(format!("{}{}", "/share/", &self.token)));
194
195        // days_until_expiry
196        // NOTE: Expression requires external data - requires repository access
197        // Implement via async computed field trait or cache
198        fields.set("days_until_expiry", serde_json::json!(null));
199
200        // downloads_remaining
201        fields.set("downloads_remaining", serde_json::json!(self.max_downloads.is_some()));
202
203        // is_password_protected
204        fields.set("is_password_protected", serde_json::json!(self.share_type == ShareType::Password && self.password_hash.is_some()));
205
206        // is_expired
207        fields.set("is_expired", serde_json::json!(self.expires_at.is_some() && self.expires_at <= Some(chrono::Utc::now())));
208
209        // has_downloads_remaining
210        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
220/// Computed fields for ProcessingJob entity
221impl HasComputedFields for ProcessingJob {
222    fn computed_fields(&self) -> ComputedFieldValues {
223        let mut fields = ComputedFieldValues::new();
224
225        // is_active
226        fields.set("is_active", serde_json::json!(self.status));
227
228        // is_terminal
229        fields.set("is_terminal", serde_json::json!(self.status));
230
231        // can_retry
232        fields.set("can_retry", serde_json::json!(self.status == JobStatus::Failed && self.retry_count < self.max_retries));
233
234        // duration_seconds
235        fields.set("duration_seconds", serde_json::json!(self.started_at.is_some() && self.completed_at.is_some()));
236
237        // wait_time_seconds
238        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
248/// Computed fields for StoredFile entity
249impl HasComputedFields for StoredFile {
250    fn computed_fields(&self) -> ComputedFieldValues {
251        let mut fields = ComputedFieldValues::new();
252
253        // compression_ratio
254        fields.set("compression_ratio", serde_json::json!(self.original_size.is_some()));
255
256        // is_image
257        fields.set("is_image", serde_json::json!(self.mime_type.starts_with("image/")));
258
259        // is_video
260        fields.set("is_video", serde_json::json!(self.mime_type.starts_with("video/")));
261
262        // is_document
263        fields.set("is_document", serde_json::json!(self.mime_type.starts_with("application/pdf") || self.mime_type.contains("document")));
264
265        // full_path
266        // WARNING: Expression references fields not on this entity: ["bucket"]
267        // These may be on a related entity or junction table.
268        // Implement via AsyncComputedFields trait or repository query.
269        fields.set("full_path", serde_json::json!(null));
270
271        // age_days
272        // NOTE: Expression requires JsonValue field access - requires repository access
273        // Implement via async computed field trait or cache
274        fields.set("age_days", serde_json::json!(null));
275
276        // is_accessible
277        fields.set("is_accessible", serde_json::json!(self.status == FileStatus::Active));
278
279        // is_safe
280        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
290/// Computed fields for UploadSession entity
291impl HasComputedFields for UploadSession {
292    fn computed_fields(&self) -> ComputedFieldValues {
293        let mut fields = ComputedFieldValues::new();
294
295        // progress_percent
296        fields.set("progress_percent", serde_json::json!(self.total_chunks > 0));
297
298        // is_resumable
299        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        // is_complete
302        fields.set("is_complete", serde_json::json!(self.uploaded_chunks == self.total_chunks));
303
304        // remaining_chunks
305        fields.set("remaining_chunks", serde_json::json!(self.total_chunks - self.uploaded_chunks));
306
307        // is_expired
308        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
318/// Computed fields for UserQuota entity
319impl HasComputedFields for UserQuota {
320    fn computed_fields(&self) -> ComputedFieldValues {
321        let mut fields = ComputedFieldValues::new();
322
323        // usage_percent
324        // WARNING: Expression references fields not on this entity: ["max_storage_bytes", "used_storage_bytes"]
325        // These may be on a related entity or junction table.
326        // Implement via AsyncComputedFields trait or repository query.
327        fields.set("usage_percent", serde_json::json!(null));
328
329        // storage_remaining_bytes
330        // WARNING: Expression references fields not on this entity: ["max_storage_bytes", "used_storage_bytes"]
331        // These may be on a related entity or junction table.
332        // Implement via AsyncComputedFields trait or repository query.
333        fields.set("storage_remaining_bytes", serde_json::json!(null));
334
335        // files_remaining
336        // WARNING: Expression references fields not on this entity: ["max_files"]
337        // These may be on a related entity or junction table.
338        // Implement via AsyncComputedFields trait or repository query.
339        fields.set("files_remaining", serde_json::json!(null));
340
341        // buckets_remaining
342        // WARNING: Expression references fields not on this entity: ["bucket_count", "max_buckets"]
343        // These may be on a related entity or junction table.
344        // Implement via AsyncComputedFields trait or repository query.
345        fields.set("buckets_remaining", serde_json::json!(null));
346
347        // used_storage_formatted
348        // WARNING: Expression references fields not on this entity: ["used_storage_bytes"]
349        // These may be on a related entity or junction table.
350        // Implement via AsyncComputedFields trait or repository query.
351        fields.set("used_storage_formatted", serde_json::json!(null));
352
353        // max_storage_formatted
354        // WARNING: Expression references fields not on this entity: ["max_storage_bytes"]
355        // These may be on a related entity or junction table.
356        // Implement via AsyncComputedFields trait or repository query.
357        fields.set("max_storage_formatted", serde_json::json!(null));
358
359        // is_near_limit
360        // WARNING: Expression references fields not on this entity: ["usage_percent"]
361        // These may be on a related entity or junction table.
362        // Implement via AsyncComputedFields trait or repository query.
363        fields.set("is_near_limit", serde_json::json!(null));
364
365        // is_at_file_limit
366        // WARNING: Expression references fields not on this entity: ["max_files"]
367        // These may be on a related entity or junction table.
368        // Implement via AsyncComputedFields trait or repository query.
369        fields.set("is_at_file_limit", serde_json::json!(null));
370
371        // is_at_bucket_limit
372        // WARNING: Expression references fields not on this entity: ["bucket_count", "max_buckets"]
373        // These may be on a related entity or junction table.
374        // Implement via AsyncComputedFields trait or repository query.
375        fields.set("is_at_bucket_limit", serde_json::json!(null));
376
377        // is_expired
378        // WARNING: Expression references fields not on this entity: ["expires_at"]
379        // These may be on a related entity or junction table.
380        // Implement via AsyncComputedFields trait or repository query.
381        fields.set("is_expired", serde_json::json!(null));
382
383        // days_until_expiry
384        // WARNING: Expression references fields not on this entity: ["expires_at"]
385        // These may be on a related entity or junction table.
386        // Implement via AsyncComputedFields trait or repository query.
387        fields.set("days_until_expiry", serde_json::json!(null));
388
389        // daily_bandwidth_remaining
390        // WARNING: Expression references fields not on this entity: ["daily_bandwidth_bytes"]
391        // These may be on a related entity or junction table.
392        // Implement via AsyncComputedFields trait or repository query.
393        fields.set("daily_bandwidth_remaining", serde_json::json!(null));
394
395        // monthly_bandwidth_remaining
396        // WARNING: Expression references fields not on this entity: ["monthly_bandwidth_bytes"]
397        // These may be on a related entity or junction table.
398        // Implement via AsyncComputedFields trait or repository query.
399        fields.set("monthly_bandwidth_remaining", serde_json::json!(null));
400
401        // can_upload
402        // WARNING: Expression references fields not on this entity: ["is_at_file_limit", "is_exceeded"]
403        // These may be on a related entity or junction table.
404        // Implement via AsyncComputedFields trait or repository query.
405        fields.set("can_upload", serde_json::json!(null));
406
407        // can_create_bucket
408        // WARNING: Expression references fields not on this entity: ["is_at_bucket_limit"]
409        // These may be on a related entity or junction table.
410        // Implement via AsyncComputedFields trait or repository query.
411        fields.set("can_create_bucket", serde_json::json!(null));
412
413        // can_share
414        // WARNING: Expression references fields not on this entity: ["sharing_enabled"]
415        // These may be on a related entity or junction table.
416        // Implement via AsyncComputedFields trait or repository query.
417        fields.set("can_share", serde_json::json!(null));
418
419        // can_public_share
420        // WARNING: Expression references fields not on this entity: ["public_sharing_enabled"]
421        // These may be on a related entity or junction table.
422        // Implement via AsyncComputedFields trait or repository query.
423        fields.set("can_public_share", serde_json::json!(null));
424
425        // can_version
426        // WARNING: Expression references fields not on this entity: ["versioning_enabled"]
427        // These may be on a related entity or junction table.
428        // Implement via AsyncComputedFields trait or repository query.
429        fields.set("can_version", serde_json::json!(null));
430
431        // tier_label
432        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        // TODO: Add test implementation for Bucket
450    }
451
452    #[test]
453    fn test_conversion_job_computed_fields() {
454        // TODO: Add test implementation for ConversionJob
455    }
456
457    #[test]
458    fn test_file_comment_computed_fields() {
459        // TODO: Add test implementation for FileComment
460    }
461
462    #[test]
463    fn test_file_lock_computed_fields() {
464        // TODO: Add test implementation for FileLock
465    }
466
467    #[test]
468    fn test_file_share_computed_fields() {
469        // TODO: Add test implementation for FileShare
470    }
471
472    #[test]
473    fn test_processing_job_computed_fields() {
474        // TODO: Add test implementation for ProcessingJob
475    }
476
477    #[test]
478    fn test_stored_file_computed_fields() {
479        // TODO: Add test implementation for StoredFile
480    }
481
482    #[test]
483    fn test_upload_session_computed_fields() {
484        // TODO: Add test implementation for UploadSession
485    }
486
487    #[test]
488    fn test_user_quota_computed_fields() {
489        // TODO: Add test implementation for UserQuota
490    }
491
492}