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
165#[cfg(test)]
166mod tests {
167 use super::*;
168 use crate::dal::Database;
169
170 fn setup_service() -> DatabaseService {
171 let db = Database::new(":memory:").expect("Failed to create test database");
172 DatabaseService::new(db.into_repository())
173 }
174
175 fn create_test_document() -> NewDocument {
176 NewDocument {
177 filepath: "/test/doc.md".to_string(),
178 id: "test-doc-1".to_string(),
179 title: "Test Document".to_string(),
180 document_type: "vision".to_string(),
181 created_at: 1609459200.0,
182 updated_at: 1609459200.0,
183 archived: false,
184 exit_criteria_met: false,
185 file_hash: "abc123".to_string(),
186 frontmatter_json: "{}".to_string(),
187 content: Some("Test content".to_string()),
188 phase: "draft".to_string(),
189 strategy_id: None,
190 initiative_id: None,
191 short_code: "TEST-V-0601".to_string(),
192 }
193 }
194
195 fn create_test_document_with_lineage(
196 id: &str,
197 doc_type: &str,
198 filepath: &str,
199 strategy_id: Option<String>,
200 initiative_id: Option<String>,
201 ) -> NewDocument {
202 NewDocument {
203 filepath: filepath.to_string(),
204 id: id.to_string(),
205 title: format!("Test {}", doc_type),
206 document_type: doc_type.to_string(),
207 created_at: 1609459200.0,
208 updated_at: 1609459200.0,
209 archived: false,
210 exit_criteria_met: false,
211 file_hash: "abc123".to_string(),
212 frontmatter_json: "{}".to_string(),
213 content: Some("Test content".to_string()),
214 phase: "draft".to_string(),
215 strategy_id,
216 initiative_id,
217 short_code: format!(
218 "TEST-{}-{:04}",
219 doc_type.chars().next().unwrap().to_uppercase(),
220 match doc_type {
221 "vision" => 701,
222 "strategy" => 702,
223 "initiative" => 703,
224 "task" => 704,
225 "adr" => 705,
226 _ => 799,
227 }
228 ),
229 }
230 }
231
232 #[test]
233 fn test_database_service_crud() {
234 let mut service = setup_service();
235
236 let new_doc = create_test_document();
238 let created = service.create_document(new_doc).expect("Failed to create");
239 assert_eq!(created.id, "test-doc-1");
240
241 let found = service
243 .find_by_id("test-doc-1")
244 .expect("Failed to find")
245 .expect("Document not found");
246 assert_eq!(found.filepath, "/test/doc.md");
247
248 let mut updated_doc = found.clone();
250 updated_doc.title = "Updated Title".to_string();
251 let updated = service
252 .update_document("/test/doc.md", &updated_doc)
253 .expect("Failed to update");
254 assert_eq!(updated.title, "Updated Title");
255
256 let deleted = service
258 .delete_document("/test/doc.md")
259 .expect("Failed to delete");
260 assert!(deleted);
261
262 assert!(!service
264 .document_exists("/test/doc.md")
265 .expect("Failed to check existence"));
266 }
267
268 #[test]
269 fn test_database_service_relationships() {
270 let mut service = setup_service();
271
272 let parent = NewDocument {
274 id: "parent-1".to_string(),
275 filepath: "/parent.md".to_string(),
276 document_type: "strategy".to_string(),
277 short_code: "TEST-S-0601".to_string(),
278 ..create_test_document()
279 };
280
281 let child = NewDocument {
282 id: "child-1".to_string(),
283 filepath: "/child.md".to_string(),
284 document_type: "initiative".to_string(),
285 short_code: "TEST-I-0601".to_string(),
286 ..create_test_document()
287 };
288
289 service
290 .create_document(parent)
291 .expect("Failed to create parent");
292 service
293 .create_document(child)
294 .expect("Failed to create child");
295
296 service
298 .create_relationship("parent-1", "child-1", "/parent.md", "/child.md")
299 .expect("Failed to create relationship");
300
301 let children = service
303 .find_children("parent-1")
304 .expect("Failed to find children");
305 assert_eq!(children.len(), 1);
306 assert_eq!(children[0].id, "child-1");
307
308 let parent = service
310 .find_parent("child-1")
311 .expect("Failed to find parent")
312 .expect("Parent not found");
313 assert_eq!(parent.id, "parent-1");
314 }
315
316 #[test]
317 fn test_lineage_queries() {
318 let mut service = setup_service();
319
320 let strategy = create_test_document_with_lineage(
322 "strategy-1",
323 "strategy",
324 "/strategies/strategy-1/strategy.md",
325 None,
326 None,
327 );
328 service
329 .create_document(strategy)
330 .expect("Failed to create strategy");
331
332 let initiative = create_test_document_with_lineage(
334 "initiative-1",
335 "initiative",
336 "/strategies/strategy-1/initiatives/initiative-1/initiative.md",
337 Some("strategy-1".to_string()),
338 None,
339 );
340 service
341 .create_document(initiative)
342 .expect("Failed to create initiative");
343
344 let mut task1 = create_test_document_with_lineage(
346 "task-1",
347 "task",
348 "/strategies/strategy-1/initiatives/initiative-1/tasks/task-1.md",
349 Some("strategy-1".to_string()),
350 Some("initiative-1".to_string()),
351 );
352 task1.short_code = "TEST-T-0704".to_string();
353
354 let mut task2 = create_test_document_with_lineage(
355 "task-2",
356 "task",
357 "/strategies/strategy-1/initiatives/initiative-1/tasks/task-2.md",
358 Some("strategy-1".to_string()),
359 Some("initiative-1".to_string()),
360 );
361 task2.short_code = "TEST-T-0705".to_string();
362 service
363 .create_document(task1)
364 .expect("Failed to create task1");
365 service
366 .create_document(task2)
367 .expect("Failed to create task2");
368
369 let strategy_docs = service
371 .find_by_strategy_id("strategy-1")
372 .expect("Failed to find by strategy");
373 assert_eq!(strategy_docs.len(), 3); let initiative_docs = service
377 .find_by_initiative_id("initiative-1")
378 .expect("Failed to find by initiative");
379 assert_eq!(initiative_docs.len(), 2); let strategy_hierarchy = service
383 .find_strategy_hierarchy("strategy-1")
384 .expect("Failed to find strategy hierarchy");
385 assert_eq!(strategy_hierarchy.len(), 4); let initiative_hierarchy = service
389 .find_initiative_hierarchy("initiative-1")
390 .expect("Failed to find initiative hierarchy");
391 assert_eq!(initiative_hierarchy.len(), 3); let doc_types: Vec<&str> = strategy_hierarchy
395 .iter()
396 .map(|d| d.document_type.as_str())
397 .collect();
398 assert!(doc_types.contains(&"strategy"));
399 assert!(doc_types.contains(&"initiative"));
400 assert!(doc_types.iter().filter(|&&t| t == "task").count() == 2);
401 }
402}