1use anyhow::Result;
2use metis_core::{
3 application::services::document::{
4 creation::DocumentCreationConfig, DeletionService, DocumentCreationService,
5 },
6 dal::Database,
7 domain::documents::types::DocumentType,
8};
9use std::path::PathBuf;
10use std::str::FromStr;
11
12pub struct DocumentService {
14 workspace_dir: PathBuf,
15}
16
17impl DocumentService {
18 pub fn new(workspace_dir: PathBuf) -> Self {
19 Self { workspace_dir }
20 }
21
22 pub async fn create_document(
23 &self,
24 document_type: DocumentType,
25 title: String,
26 description: Option<String>,
27 parent_id: Option<String>,
28 ) -> Result<String> {
29 let creation_service = DocumentCreationService::new(&self.workspace_dir);
30
31 let config = DocumentCreationConfig {
32 title,
33 description,
34 parent_id: parent_id
35 .as_ref()
36 .map(|id| metis_core::domain::documents::types::DocumentId::from(id.clone())),
37 tags: vec![],
38 phase: None,
39 complexity: None,
40 risk_level: None,
41 };
42
43 let result = match document_type {
44 DocumentType::Vision => creation_service.create_vision(config).await?,
45 DocumentType::Strategy => creation_service.create_strategy(config).await?,
46 DocumentType::Initiative => {
47 if let Some(parent_id) = &parent_id {
48 creation_service
49 .create_initiative(config, parent_id)
50 .await?
51 } else {
52 creation_service
55 .create_initiative_with_config(
56 config,
57 "NULL",
58 &metis_core::domain::configuration::FlightLevelConfig::streamlined(),
59 )
60 .await?
61 }
62 }
63 DocumentType::Task => {
64 if let Some(initiative_id) = &parent_id {
65 match creation_service
68 .create_task(config, initiative_id, initiative_id)
69 .await
70 {
71 Ok(result) => result,
72 Err(e) => return Err(anyhow::anyhow!("Failed to create task: {}", e)),
73 }
74 } else {
75 creation_service.create_backlog_item(config).await?
77 }
78 }
79 DocumentType::Adr => creation_service.create_adr(config).await?,
80 };
81
82 Ok(result.document_id.to_string())
83 }
84
85 pub async fn delete_document(&self, file_path: &str) -> Result<()> {
86 let deletion_service = DeletionService::new();
87 deletion_service
88 .delete_document_recursive(file_path)
89 .await?;
90 Ok(())
91 }
92
93 pub async fn load_documents_from_database(
94 &self,
95 ) -> Result<Vec<crate::models::DatabaseDocument>> {
96 let db_path = self.workspace_dir.join("metis.db");
97 let db = Database::new(&db_path.to_string_lossy())
98 .map_err(|e| anyhow::anyhow!("Database error: {}", e))?;
99
100 let mut repository = db.into_repository();
101 let mut documents = Vec::new();
102
103 for doc_type in ["vision", "strategy", "initiative", "task", "adr"] {
105 if let Ok(mut docs) = repository.find_by_type(doc_type) {
106 documents.append(&mut docs);
107 }
108 }
109
110 Ok(documents
111 .into_iter()
112 .filter(|doc| !doc.archived) .map(|doc| crate::models::DatabaseDocument {
114 id: doc.id,
115 title: doc.title,
116 document_type: DocumentType::from_str(&doc.document_type).unwrap(),
117 filepath: doc.filepath,
118 archived: doc.archived,
119 })
120 .collect())
121 }
122
123 pub async fn save_document_content(&self, file_path: &str, new_content: &str) -> Result<()> {
124 use metis_core::{Adr, Document, Initiative, Strategy, Task};
125
126 let path = std::path::Path::new(file_path);
128
129 if file_path.contains("/strategies/") && file_path.ends_with("/strategy.md") {
131 let mut strategy = Strategy::from_file(path)
132 .await
133 .map_err(|e| anyhow::anyhow!("Failed to load strategy: {}", e))?;
134 strategy
135 .update_content_body(new_content.to_string())
136 .map_err(|e| anyhow::anyhow!("Failed to update strategy content: {}", e))?;
137 strategy
138 .to_file(path)
139 .await
140 .map_err(|e| anyhow::anyhow!("Failed to save strategy: {}", e))?;
141 } else if file_path.contains("/initiatives/") && file_path.ends_with("/initiative.md") {
142 let mut initiative = Initiative::from_file(path)
143 .await
144 .map_err(|e| anyhow::anyhow!("Failed to load initiative: {}", e))?;
145 initiative
146 .update_content_body(new_content.to_string())
147 .map_err(|e| anyhow::anyhow!("Failed to update initiative content: {}", e))?;
148 initiative
149 .to_file(path)
150 .await
151 .map_err(|e| anyhow::anyhow!("Failed to save initiative: {}", e))?;
152 } else if file_path.contains("/tasks/") {
153 let mut task = Task::from_file(path)
154 .await
155 .map_err(|e| anyhow::anyhow!("Failed to load task: {}", e))?;
156 task.update_content_body(new_content.to_string())
157 .map_err(|e| anyhow::anyhow!("Failed to update task content: {}", e))?;
158 task.to_file(path)
159 .await
160 .map_err(|e| anyhow::anyhow!("Failed to save task: {}", e))?;
161 } else if file_path.contains("/adrs/") {
162 let mut adr = Adr::from_file(path)
163 .await
164 .map_err(|e| anyhow::anyhow!("Failed to load adr: {}", e))?;
165 adr.update_content_body(new_content.to_string())
166 .map_err(|e| anyhow::anyhow!("Failed to update adr content: {}", e))?;
167 adr.to_file(path)
168 .await
169 .map_err(|e| anyhow::anyhow!("Failed to save adr: {}", e))?;
170 } else {
171 return Err(anyhow::anyhow!(
172 "Unable to determine document type from path: {}",
173 file_path
174 ));
175 }
176
177 Ok(())
178 }
179
180 pub async fn create_child_task(
181 &self,
182 title: String,
183 strategy_id: String,
184 initiative_id: String,
185 ) -> Result<String> {
186 let creation_service = DocumentCreationService::new(&self.workspace_dir);
187
188 let config = DocumentCreationConfig {
189 title,
190 description: None,
191 parent_id: Some(metis_core::domain::documents::types::DocumentId::from(
192 initiative_id.clone(),
193 )),
194 tags: vec![],
195 phase: None,
196 complexity: None,
197 risk_level: None,
198 };
199
200 let flight_config = if strategy_id == "NULL" {
202 metis_core::domain::configuration::FlightLevelConfig::streamlined()
203 } else {
204 metis_core::domain::configuration::FlightLevelConfig::full()
205 };
206
207 let result = creation_service
208 .create_task_with_config(config, &strategy_id, &initiative_id, &flight_config)
209 .await?;
210 Ok(result.document_id.to_string())
211 }
212
213 pub async fn create_adr(&self, title: String, context: Option<String>) -> Result<PathBuf> {
214 let creation_service = DocumentCreationService::new(&self.workspace_dir);
215
216 let config = DocumentCreationConfig {
217 title,
218 description: context,
219 parent_id: None, tags: vec![],
221 phase: None, complexity: None,
223 risk_level: None,
224 };
225
226 let result = creation_service.create_adr(config).await?;
227 Ok(result.file_path)
228 }
229}