envelope_cli/storage/
targets.rs1use 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}