envelope_cli/storage/
targets.rs

1//! Budget target repository for JSON storage
2
3use std::collections::HashMap;
4use std::path::PathBuf;
5use std::sync::RwLock;
6
7use crate::error::EnvelopeError;
8use crate::models::{BudgetTarget, BudgetTargetId, CategoryId};
9
10use super::file_io::{read_json, write_json_atomic};
11
12#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
13struct TargetData {
14    #[serde(default)]
15    targets: Vec<BudgetTarget>,
16}
17
18pub struct TargetRepository {
19    path: PathBuf,
20    targets: RwLock<HashMap<BudgetTargetId, BudgetTarget>>,
21}
22
23impl TargetRepository {
24    pub fn new(path: PathBuf) -> Self {
25        Self {
26            path,
27            targets: RwLock::new(HashMap::new()),
28        }
29    }
30
31    pub fn load(&self) -> Result<(), EnvelopeError> {
32        let file_data: TargetData = read_json(&self.path)?;
33
34        let mut targets = self
35            .targets
36            .write()
37            .map_err(|e| EnvelopeError::Storage(format!("Failed to acquire write lock: {}", e)))?;
38
39        targets.clear();
40        for target in file_data.targets {
41            targets.insert(target.id, target);
42        }
43
44        Ok(())
45    }
46
47    pub fn save(&self) -> Result<(), EnvelopeError> {
48        let targets = self
49            .targets
50            .read()
51            .map_err(|e| EnvelopeError::Storage(format!("Failed to acquire read lock: {}", e)))?;
52
53        let mut target_list: Vec<_> = targets.values().cloned().collect();
54        target_list.sort_by(|a, b| a.created_at.cmp(&b.created_at));
55
56        let file_data = TargetData {
57            targets: target_list,
58        };
59
60        write_json_atomic(&self.path, &file_data)
61    }
62
63    pub fn get(&self, id: BudgetTargetId) -> Result<Option<BudgetTarget>, EnvelopeError> {
64        let targets = self
65            .targets
66            .read()
67            .map_err(|e| EnvelopeError::Storage(format!("Failed to acquire read lock: {}", e)))?;
68
69        Ok(targets.get(&id).cloned())
70    }
71
72    pub fn get_for_category(
73        &self,
74        category_id: CategoryId,
75    ) -> Result<Option<BudgetTarget>, EnvelopeError> {
76        let targets = self
77            .targets
78            .read()
79            .map_err(|e| EnvelopeError::Storage(format!("Failed to acquire read lock: {}", e)))?;
80
81        Ok(targets
82            .values()
83            .find(|t| t.category_id == category_id && t.active)
84            .cloned())
85    }
86
87    pub fn get_all_active(&self) -> Result<Vec<BudgetTarget>, EnvelopeError> {
88        let targets = self
89            .targets
90            .read()
91            .map_err(|e| EnvelopeError::Storage(format!("Failed to acquire read lock: {}", e)))?;
92
93        let mut list: Vec<_> = targets.values().filter(|t| t.active).cloned().collect();
94        list.sort_by(|a, b| a.created_at.cmp(&b.created_at));
95        Ok(list)
96    }
97
98    pub fn upsert(&self, target: BudgetTarget) -> Result<(), EnvelopeError> {
99        let mut targets = self
100            .targets
101            .write()
102            .map_err(|e| EnvelopeError::Storage(format!("Failed to acquire write lock: {}", e)))?;
103
104        targets.insert(target.id, target);
105        Ok(())
106    }
107
108    pub fn delete(&self, id: BudgetTargetId) -> Result<bool, EnvelopeError> {
109        let mut targets = self
110            .targets
111            .write()
112            .map_err(|e| EnvelopeError::Storage(format!("Failed to acquire write lock: {}", e)))?;
113
114        Ok(targets.remove(&id).is_some())
115    }
116}