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