metis_core/application/services/
database.rs1use crate::dal::database::{models::*, repository::DocumentRepository};
2use crate::domain::documents::types::DocumentType;
3use crate::Result;
4
5pub struct DatabaseService {
7 repository: DocumentRepository,
8}
9
10impl DatabaseService {
11 pub fn new(repository: DocumentRepository) -> Self {
12 Self { repository }
13 }
14
15 pub fn create_document(&mut self, document: NewDocument) -> Result<Document> {
17 self.repository.create_document(document)
18 }
19
20 pub fn find_by_filepath(&mut self, filepath: &str) -> Result<Option<Document>> {
22 self.repository.find_by_filepath(filepath)
23 }
24
25 pub fn find_by_id(&mut self, id: &str) -> Result<Option<Document>> {
27 self.repository.find_by_id(id)
28 }
29
30 pub fn find_by_short_code(&mut self, short_code: &str) -> Result<Option<Document>> {
32 self.repository.find_by_short_code(short_code)
33 }
34
35 pub fn update_document(&mut self, filepath: &str, document: &Document) -> Result<Document> {
37 self.repository.update_document(filepath, document)
38 }
39
40 pub fn delete_document(&mut self, filepath: &str) -> Result<bool> {
42 self.repository.delete_document(filepath)
43 }
44
45 pub fn search_documents(&mut self, query: &str) -> Result<Vec<Document>> {
47 self.repository.search_documents(query)
48 }
49
50 pub fn find_by_type(&mut self, doc_type: DocumentType) -> Result<Vec<Document>> {
52 let type_str = doc_type.to_string();
53 self.repository.find_by_type(&type_str)
54 }
55
56 pub fn find_by_tag(&mut self, tag: &str) -> Result<Vec<Document>> {
58 self.repository.find_by_tag(tag)
59 }
60
61 pub fn get_tags_for_document(&mut self, doc_filepath: &str) -> Result<Vec<String>> {
63 self.repository.get_tags_for_document(doc_filepath)
64 }
65
66 pub fn find_children(&mut self, parent_id: &str) -> Result<Vec<Document>> {
68 self.repository.find_children(parent_id)
69 }
70
71 pub fn find_parent(&mut self, child_id: &str) -> Result<Option<Document>> {
73 self.repository.find_parent(child_id)
74 }
75
76 pub fn create_relationship(
78 &mut self,
79 parent_id: &str,
80 child_id: &str,
81 parent_filepath: &str,
82 child_filepath: &str,
83 ) -> Result<()> {
84 let relationship = DocumentRelationship {
85 parent_id: parent_id.to_string(),
86 child_id: child_id.to_string(),
87 parent_filepath: parent_filepath.to_string(),
88 child_filepath: child_filepath.to_string(),
89 };
90 self.repository.create_relationship(relationship)
91 }
92
93 pub fn document_exists(&mut self, filepath: &str) -> Result<bool> {
95 Ok(self.repository.find_by_filepath(filepath)?.is_some())
96 }
97
98 pub fn count_by_type(&mut self, doc_type: DocumentType) -> Result<usize> {
100 let docs = self.repository.find_by_type(&doc_type.to_string())?;
101 Ok(docs.len())
102 }
103
104 pub fn get_all_id_filepath_pairs(&mut self) -> Result<Vec<(String, String)>> {
106 let mut pairs = Vec::new();
109
110 for doc_type in [
111 DocumentType::Vision,
112 DocumentType::Strategy,
113 DocumentType::Initiative,
114 DocumentType::Task,
115 DocumentType::Adr,
116 ] {
117 let docs = self.repository.find_by_type(&doc_type.to_string())?;
118 for doc in docs {
119 pairs.push((doc.id, doc.filepath));
120 }
121 }
122
123 Ok(pairs)
124 }
125
126 pub fn find_by_strategy_id(&mut self, strategy_id: &str) -> Result<Vec<Document>> {
128 self.repository.find_by_strategy_id(strategy_id)
129 }
130
131 pub fn find_by_initiative_id(&mut self, initiative_id: &str) -> Result<Vec<Document>> {
133 self.repository.find_by_initiative_id(initiative_id)
134 }
135
136 pub fn find_strategy_hierarchy(&mut self, strategy_id: &str) -> Result<Vec<Document>> {
138 self.repository.find_strategy_hierarchy(strategy_id)
139 }
140
141 pub fn find_strategy_hierarchy_by_short_code(
143 &mut self,
144 strategy_short_code: &str,
145 ) -> Result<Vec<Document>> {
146 self.repository
147 .find_strategy_hierarchy_by_short_code(strategy_short_code)
148 }
149
150 pub fn find_initiative_hierarchy(&mut self, initiative_id: &str) -> Result<Vec<Document>> {
152 self.repository.find_initiative_hierarchy(initiative_id)
153 }
154
155 pub fn find_initiative_hierarchy_by_short_code(
157 &mut self,
158 initiative_short_code: &str,
159 ) -> Result<Vec<Document>> {
160 self.repository
161 .find_initiative_hierarchy_by_short_code(initiative_short_code)
162 }
163
164 pub fn generate_short_code(&mut self, doc_type: &str) -> Result<String> {
166 self.repository.generate_short_code(doc_type, ":memory:")
170 }
171
172 pub fn set_counter_if_lower(&mut self, _doc_type: &str, _min_value: u32) -> Result<bool> {
175 Ok(true)
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184 use crate::dal::Database;
185
186 fn setup_service() -> DatabaseService {
187 let db = Database::new(":memory:").expect("Failed to create test database");
188 DatabaseService::new(db.into_repository())
189 }
190
191 fn create_test_document() -> NewDocument {
192 NewDocument {
193 filepath: "/test/doc.md".to_string(),
194 id: "test-doc-1".to_string(),
195 title: "Test Document".to_string(),
196 document_type: "vision".to_string(),
197 created_at: 1609459200.0,
198 updated_at: 1609459200.0,
199 archived: false,
200 exit_criteria_met: false,
201 file_hash: "abc123".to_string(),
202 frontmatter_json: "{}".to_string(),
203 content: Some("Test content".to_string()),
204 phase: "draft".to_string(),
205 strategy_id: None,
206 initiative_id: None,
207 short_code: "TEST-V-0601".to_string(),
208 }
209 }
210
211 fn create_test_document_with_lineage(
212 id: &str,
213 doc_type: &str,
214 filepath: &str,
215 strategy_id: Option<String>,
216 initiative_id: Option<String>,
217 ) -> NewDocument {
218 NewDocument {
219 filepath: filepath.to_string(),
220 id: id.to_string(),
221 title: format!("Test {}", doc_type),
222 document_type: doc_type.to_string(),
223 created_at: 1609459200.0,
224 updated_at: 1609459200.0,
225 archived: false,
226 exit_criteria_met: false,
227 file_hash: "abc123".to_string(),
228 frontmatter_json: "{}".to_string(),
229 content: Some("Test content".to_string()),
230 phase: "draft".to_string(),
231 strategy_id,
232 initiative_id,
233 short_code: format!(
234 "TEST-{}-{:04}",
235 doc_type.chars().next().unwrap().to_uppercase(),
236 match doc_type {
237 "vision" => 701,
238 "strategy" => 702,
239 "initiative" => 703,
240 "task" => 704,
241 "adr" => 705,
242 _ => 799,
243 }
244 ),
245 }
246 }
247
248 #[test]
249 fn test_database_service_crud() {
250 let mut service = setup_service();
251
252 let new_doc = create_test_document();
254 let created = service.create_document(new_doc).expect("Failed to create");
255 assert_eq!(created.id, "test-doc-1");
256
257 let found = service
259 .find_by_id("test-doc-1")
260 .expect("Failed to find")
261 .expect("Document not found");
262 assert_eq!(found.filepath, "/test/doc.md");
263
264 let mut updated_doc = found.clone();
266 updated_doc.title = "Updated Title".to_string();
267 let updated = service
268 .update_document("/test/doc.md", &updated_doc)
269 .expect("Failed to update");
270 assert_eq!(updated.title, "Updated Title");
271
272 let deleted = service
274 .delete_document("/test/doc.md")
275 .expect("Failed to delete");
276 assert!(deleted);
277
278 assert!(!service
280 .document_exists("/test/doc.md")
281 .expect("Failed to check existence"));
282 }
283
284 #[test]
285 fn test_database_service_relationships() {
286 let mut service = setup_service();
287
288 let parent = NewDocument {
290 id: "parent-1".to_string(),
291 filepath: "/parent.md".to_string(),
292 document_type: "strategy".to_string(),
293 short_code: "TEST-S-0601".to_string(),
294 ..create_test_document()
295 };
296
297 let child = NewDocument {
298 id: "child-1".to_string(),
299 filepath: "/child.md".to_string(),
300 document_type: "initiative".to_string(),
301 short_code: "TEST-I-0601".to_string(),
302 ..create_test_document()
303 };
304
305 service
306 .create_document(parent)
307 .expect("Failed to create parent");
308 service
309 .create_document(child)
310 .expect("Failed to create child");
311
312 service
314 .create_relationship("parent-1", "child-1", "/parent.md", "/child.md")
315 .expect("Failed to create relationship");
316
317 let children = service
319 .find_children("parent-1")
320 .expect("Failed to find children");
321 assert_eq!(children.len(), 1);
322 assert_eq!(children[0].id, "child-1");
323
324 let parent = service
326 .find_parent("child-1")
327 .expect("Failed to find parent")
328 .expect("Parent not found");
329 assert_eq!(parent.id, "parent-1");
330 }
331
332 #[test]
333 fn test_lineage_queries() {
334 let mut service = setup_service();
335
336 let strategy = create_test_document_with_lineage(
338 "strategy-1",
339 "strategy",
340 "/strategies/strategy-1/strategy.md",
341 None,
342 None,
343 );
344 service
345 .create_document(strategy)
346 .expect("Failed to create strategy");
347
348 let initiative = create_test_document_with_lineage(
350 "initiative-1",
351 "initiative",
352 "/strategies/strategy-1/initiatives/initiative-1/initiative.md",
353 Some("strategy-1".to_string()),
354 None,
355 );
356 service
357 .create_document(initiative)
358 .expect("Failed to create initiative");
359
360 let mut task1 = create_test_document_with_lineage(
362 "task-1",
363 "task",
364 "/strategies/strategy-1/initiatives/initiative-1/tasks/task-1.md",
365 Some("strategy-1".to_string()),
366 Some("initiative-1".to_string()),
367 );
368 task1.short_code = "TEST-T-0704".to_string();
369
370 let mut task2 = create_test_document_with_lineage(
371 "task-2",
372 "task",
373 "/strategies/strategy-1/initiatives/initiative-1/tasks/task-2.md",
374 Some("strategy-1".to_string()),
375 Some("initiative-1".to_string()),
376 );
377 task2.short_code = "TEST-T-0705".to_string();
378 service
379 .create_document(task1)
380 .expect("Failed to create task1");
381 service
382 .create_document(task2)
383 .expect("Failed to create task2");
384
385 let strategy_docs = service
387 .find_by_strategy_id("strategy-1")
388 .expect("Failed to find by strategy");
389 assert_eq!(strategy_docs.len(), 3); let initiative_docs = service
393 .find_by_initiative_id("initiative-1")
394 .expect("Failed to find by initiative");
395 assert_eq!(initiative_docs.len(), 2); let strategy_hierarchy = service
399 .find_strategy_hierarchy("strategy-1")
400 .expect("Failed to find strategy hierarchy");
401 assert_eq!(strategy_hierarchy.len(), 4); let initiative_hierarchy = service
405 .find_initiative_hierarchy("initiative-1")
406 .expect("Failed to find initiative hierarchy");
407 assert_eq!(initiative_hierarchy.len(), 3); let doc_types: Vec<&str> = strategy_hierarchy
411 .iter()
412 .map(|d| d.document_type.as_str())
413 .collect();
414 assert!(doc_types.contains(&"strategy"));
415 assert!(doc_types.contains(&"initiative"));
416 assert!(doc_types.iter().filter(|&&t| t == "task").count() == 2);
417 }
418}