metis_core/application/services/
database.rs

1use crate::dal::database::{models::*, repository::DocumentRepository};
2use crate::domain::documents::types::DocumentType;
3use crate::Result;
4
5/// Database service - handles all database CRUD operations
6pub struct DatabaseService {
7    repository: DocumentRepository,
8}
9
10impl DatabaseService {
11    pub fn new(repository: DocumentRepository) -> Self {
12        Self { repository }
13    }
14
15    /// Create a new document in the database
16    pub fn create_document(&mut self, document: NewDocument) -> Result<Document> {
17        self.repository.create_document(document)
18    }
19
20    /// Find a document by filepath
21    pub fn find_by_filepath(&mut self, filepath: &str) -> Result<Option<Document>> {
22        self.repository.find_by_filepath(filepath)
23    }
24
25    /// Find a document by ID
26    pub fn find_by_id(&mut self, id: &str) -> Result<Option<Document>> {
27        self.repository.find_by_id(id)
28    }
29
30    /// Update an existing document
31    pub fn update_document(&mut self, filepath: &str, document: &Document) -> Result<Document> {
32        self.repository.update_document(filepath, document)
33    }
34
35    /// Delete a document from the database
36    pub fn delete_document(&mut self, filepath: &str) -> Result<bool> {
37        self.repository.delete_document(filepath)
38    }
39
40    /// Search documents using full-text search
41    pub fn search_documents(&mut self, query: &str) -> Result<Vec<Document>> {
42        self.repository.search_documents(query)
43    }
44
45    /// Get all documents of a specific type
46    pub fn find_by_type(&mut self, doc_type: DocumentType) -> Result<Vec<Document>> {
47        let type_str = doc_type.to_string();
48        self.repository.find_by_type(&type_str)
49    }
50
51    /// Get documents with a specific tag
52    pub fn find_by_tag(&mut self, tag: &str) -> Result<Vec<Document>> {
53        self.repository.find_by_tag(tag)
54    }
55
56    /// Get all children of a document
57    pub fn find_children(&mut self, parent_id: &str) -> Result<Vec<Document>> {
58        self.repository.find_children(parent_id)
59    }
60
61    /// Get the parent of a document
62    pub fn find_parent(&mut self, child_id: &str) -> Result<Option<Document>> {
63        self.repository.find_parent(child_id)
64    }
65
66    /// Create a parent-child relationship
67    pub fn create_relationship(
68        &mut self,
69        parent_id: &str,
70        child_id: &str,
71        parent_filepath: &str,
72        child_filepath: &str,
73    ) -> Result<()> {
74        let relationship = DocumentRelationship {
75            parent_id: parent_id.to_string(),
76            child_id: child_id.to_string(),
77            parent_filepath: parent_filepath.to_string(),
78            child_filepath: child_filepath.to_string(),
79        };
80        self.repository.create_relationship(relationship)
81    }
82
83    /// Check if a document exists by filepath
84    pub fn document_exists(&mut self, filepath: &str) -> Result<bool> {
85        Ok(self.repository.find_by_filepath(filepath)?.is_some())
86    }
87
88    /// Get document count by type
89    pub fn count_by_type(&mut self, doc_type: DocumentType) -> Result<usize> {
90        let docs = self.repository.find_by_type(&doc_type.to_string())?;
91        Ok(docs.len())
92    }
93
94    /// Get all document IDs and their filepaths (useful for validation)
95    pub fn get_all_id_filepath_pairs(&mut self) -> Result<Vec<(String, String)>> {
96        // This would need a custom query in the repository
97        // For now, we'll use find_by_type for each type
98        let mut pairs = Vec::new();
99
100        for doc_type in [
101            DocumentType::Vision,
102            DocumentType::Strategy,
103            DocumentType::Initiative,
104            DocumentType::Task,
105            DocumentType::Adr,
106        ] {
107            let docs = self.repository.find_by_type(&doc_type.to_string())?;
108            for doc in docs {
109                pairs.push((doc.id, doc.filepath));
110            }
111        }
112
113        Ok(pairs)
114    }
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120    use crate::dal::Database;
121
122    fn setup_service() -> DatabaseService {
123        let db = Database::new(":memory:").expect("Failed to create test database");
124        DatabaseService::new(db.into_repository())
125    }
126
127    fn create_test_document() -> NewDocument {
128        NewDocument {
129            filepath: "/test/doc.md".to_string(),
130            id: "test-doc-1".to_string(),
131            title: "Test Document".to_string(),
132            document_type: "vision".to_string(),
133            created_at: 1609459200.0,
134            updated_at: 1609459200.0,
135            archived: false,
136            exit_criteria_met: false,
137            file_hash: "abc123".to_string(),
138            frontmatter_json: "{}".to_string(),
139            content: Some("Test content".to_string()),
140            phase: "draft".to_string(),
141        }
142    }
143
144    #[test]
145    fn test_database_service_crud() {
146        let mut service = setup_service();
147
148        // Create
149        let new_doc = create_test_document();
150        let created = service.create_document(new_doc).expect("Failed to create");
151        assert_eq!(created.id, "test-doc-1");
152
153        // Read
154        let found = service
155            .find_by_id("test-doc-1")
156            .expect("Failed to find")
157            .expect("Document not found");
158        assert_eq!(found.filepath, "/test/doc.md");
159
160        // Update
161        let mut updated_doc = found.clone();
162        updated_doc.title = "Updated Title".to_string();
163        let updated = service
164            .update_document("/test/doc.md", &updated_doc)
165            .expect("Failed to update");
166        assert_eq!(updated.title, "Updated Title");
167
168        // Delete
169        let deleted = service
170            .delete_document("/test/doc.md")
171            .expect("Failed to delete");
172        assert!(deleted);
173
174        // Verify deleted
175        assert!(!service
176            .document_exists("/test/doc.md")
177            .expect("Failed to check existence"));
178    }
179
180    #[test]
181    fn test_database_service_relationships() {
182        let mut service = setup_service();
183
184        // Create parent and child documents
185        let parent = NewDocument {
186            id: "parent-1".to_string(),
187            filepath: "/parent.md".to_string(),
188            document_type: "strategy".to_string(),
189            ..create_test_document()
190        };
191
192        let child = NewDocument {
193            id: "child-1".to_string(),
194            filepath: "/child.md".to_string(),
195            document_type: "initiative".to_string(),
196            ..create_test_document()
197        };
198
199        service
200            .create_document(parent)
201            .expect("Failed to create parent");
202        service
203            .create_document(child)
204            .expect("Failed to create child");
205
206        // Create relationship
207        service
208            .create_relationship("parent-1", "child-1", "/parent.md", "/child.md")
209            .expect("Failed to create relationship");
210
211        // Test find children
212        let children = service
213            .find_children("parent-1")
214            .expect("Failed to find children");
215        assert_eq!(children.len(), 1);
216        assert_eq!(children[0].id, "child-1");
217
218        // Test find parent
219        let parent = service
220            .find_parent("child-1")
221            .expect("Failed to find parent")
222            .expect("Parent not found");
223        assert_eq!(parent.id, "parent-1");
224    }
225}