oxios_kernel/kernel_handle/
project_api.rs1use std::path::PathBuf;
9use std::sync::Arc;
10
11use anyhow::{Context, Result};
12use chrono::Utc;
13use serde::{Deserialize, Serialize};
14use uuid::Uuid;
15
16use crate::project::{Project, ProjectManager, ProjectSource};
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
20#[allow(missing_docs)]
21pub struct ProjectInfo {
22 pub id: String,
23 pub name: String,
24 pub description: String,
25 pub source: String,
26 pub paths: Vec<String>,
27 pub tags: Vec<String>,
28 pub emoji: String,
29 pub memory_visible: bool,
30 pub mount_ids: Vec<String>,
32 pub instructions: String,
34 pub created_at: String,
35 pub updated_at: String,
36 pub last_active_at: String,
37}
38
39impl From<&Project> for ProjectInfo {
40 fn from(project: &Project) -> Self {
41 Self {
42 id: project.id.to_string(),
43 name: project.name.clone(),
44 description: project.description.clone(),
45 source: project.source.to_string(),
46 paths: project
47 .paths
48 .iter()
49 .map(|p| p.to_string_lossy().to_string())
50 .collect(),
51 tags: project.tags.clone(),
52 emoji: project.emoji.clone(),
53 memory_visible: project.memory_visible,
54 mount_ids: project.mount_ids.iter().map(|m| m.to_string()).collect(),
55 instructions: project.instructions.clone(),
56 created_at: project.created_at.to_rfc3339(),
57 updated_at: project.updated_at.to_rfc3339(),
58 last_active_at: project.last_active_at.to_rfc3339(),
59 }
60 }
61}
62
63#[allow(dead_code)]
68pub struct ProjectApi {
69 pub(crate) project_manager: Arc<ProjectManager>,
71}
72
73impl ProjectApi {
74 pub fn new(project_manager: Arc<ProjectManager>) -> Self {
76 Self { project_manager }
77 }
78
79 pub fn list_projects(&self) -> Vec<ProjectInfo> {
81 self.project_manager
82 .list_projects()
83 .iter()
84 .map(ProjectInfo::from)
85 .collect()
86 }
87
88 pub fn get_project(&self, id: &str) -> Option<ProjectInfo> {
90 let project_id = Uuid::parse_str(id).ok()?;
91 self.project_manager
92 .get_project(project_id)
93 .as_ref()
94 .map(ProjectInfo::from)
95 }
96
97 pub fn create_project(
99 &self,
100 name: String,
101 paths: Vec<String>,
102 tags: Vec<String>,
103 emoji: Option<String>,
104 description: Option<String>,
105 ) -> Result<ProjectInfo> {
106 let paths: Vec<PathBuf> = paths.into_iter().map(PathBuf::from).collect();
107 let project = self.project_manager.create_project(
108 name,
109 paths,
110 tags,
111 emoji,
112 description,
113 ProjectSource::Manual,
114 )?;
115 Ok(ProjectInfo::from(&project))
116 }
117
118 #[allow(clippy::too_many_arguments)]
120 pub fn update_project(
121 &self,
122 id: &str,
123 name: Option<String>,
124 paths: Option<Vec<String>>,
125 tags: Option<Vec<String>>,
126 emoji: Option<String>,
127 description: Option<String>,
128 memory_visible: Option<bool>,
129 ) -> Result<ProjectInfo> {
130 let project_id = Uuid::parse_str(id).context("Invalid project ID")?;
131 let paths = paths.map(|v| v.into_iter().map(PathBuf::from).collect());
132
133 let mut project = self.project_manager.update_project(
134 project_id,
135 name,
136 paths,
137 tags,
138 emoji,
139 description,
140 )?;
141
142 if let Some(visible) = memory_visible {
144 project.memory_visible = visible;
145 project.updated_at = Utc::now();
146 self.project_manager.save_project(&project)?;
147 }
148
149 Ok(ProjectInfo::from(&project))
150 }
151
152 pub fn remove_project(&self, id: &str) -> Result<()> {
154 let project_id = Uuid::parse_str(id).context("Invalid project ID")?;
155 self.project_manager.remove_project(project_id)
156 }
157
158 pub fn link_memory(&self, project_id: &str, memory_id: &str) -> Result<()> {
160 let pid = Uuid::parse_str(project_id).context("Invalid project ID")?;
161 self.project_manager.link_memory(pid, memory_id)
162 }
163
164 pub fn unlink_memory(&self, project_id: &str, memory_id: &str) -> Result<()> {
166 let pid = Uuid::parse_str(project_id).context("Invalid project ID")?;
167 self.project_manager.unlink_memory(pid, memory_id)
168 }
169
170 pub fn get_project_memory_ids(&self, project_id: &str) -> Result<Vec<String>> {
172 let pid = Uuid::parse_str(project_id).context("Invalid project ID")?;
173 self.project_manager.get_project_memory_ids(pid)
174 }
175
176 pub fn update_project_bundle(
178 &self,
179 id: &str,
180 mount_ids: Option<Vec<String>>,
181 instructions: Option<String>,
182 ) -> Result<ProjectInfo> {
183 let pid = Uuid::parse_str(id).context("Invalid project ID")?;
184 let mount_ids = match mount_ids {
187 Some(ids) => {
188 let mut parsed = Vec::with_capacity(ids.len());
189 let mut bad = Vec::new();
190 for s in ids {
191 match uuid::Uuid::parse_str(&s) {
192 Ok(u) => parsed.push(u),
193 Err(_) => bad.push(s),
194 }
195 }
196 if !bad.is_empty() {
197 anyhow::bail!("Invalid mount ID(s): {}", bad.join(", "));
198 }
199 Some(parsed)
200 }
201 None => None,
202 };
203 let project = self
204 .project_manager
205 .update_project_bundle(pid, mount_ids, instructions)?;
206 Ok(ProjectInfo::from(&project))
207 }
208}