1use crate::error::{CollabError, Result};
4use crate::models::{TeamWorkspace, UserRole, WorkspaceMember};
5use crate::permissions::{Permission, PermissionChecker};
6use chrono::Utc;
7use parking_lot::RwLock;
8use sqlx::{Pool, Sqlite};
9use std::collections::HashMap;
10use std::sync::Arc;
11use uuid::Uuid;
12
13pub struct WorkspaceService {
15 db: Pool<Sqlite>,
16 cache: Arc<RwLock<HashMap<Uuid, TeamWorkspace>>>,
17}
18
19impl WorkspaceService {
20 pub fn new(db: Pool<Sqlite>) -> Self {
22 Self {
23 db,
24 cache: Arc::new(RwLock::new(HashMap::new())),
25 }
26 }
27
28 pub async fn create_workspace(
30 &self,
31 name: String,
32 description: Option<String>,
33 owner_id: Uuid,
34 ) -> Result<TeamWorkspace> {
35 let mut workspace = TeamWorkspace::new(name, owner_id);
36 workspace.description = description;
37
38 sqlx::query!(
40 r#"
41 INSERT INTO workspaces (id, name, description, owner_id, config, version, created_at, updated_at, is_archived)
42 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
43 "#,
44 workspace.id,
45 workspace.name,
46 workspace.description,
47 workspace.owner_id,
48 workspace.config,
49 workspace.version,
50 workspace.created_at,
51 workspace.updated_at,
52 workspace.is_archived
53 )
54 .execute(&self.db)
55 .await?;
56
57 let member = WorkspaceMember::new(workspace.id, owner_id, UserRole::Admin);
59 sqlx::query!(
60 r#"
61 INSERT INTO workspace_members (id, workspace_id, user_id, role, joined_at, last_activity)
62 VALUES (?, ?, ?, ?, ?, ?)
63 "#,
64 member.id,
65 member.workspace_id,
66 member.user_id,
67 member.role,
68 member.joined_at,
69 member.last_activity
70 )
71 .execute(&self.db)
72 .await?;
73
74 self.cache.write().insert(workspace.id, workspace.clone());
76
77 Ok(workspace)
78 }
79
80 pub async fn get_workspace(&self, workspace_id: Uuid) -> Result<TeamWorkspace> {
82 if let Some(workspace) = self.cache.read().get(&workspace_id) {
84 return Ok(workspace.clone());
85 }
86
87 let workspace = sqlx::query_as!(
89 TeamWorkspace,
90 r#"
91 SELECT
92 id as "id: Uuid",
93 name,
94 description,
95 owner_id as "owner_id: Uuid",
96 config,
97 version,
98 created_at as "created_at: chrono::DateTime<chrono::Utc>",
99 updated_at as "updated_at: chrono::DateTime<chrono::Utc>",
100 is_archived as "is_archived: bool"
101 FROM workspaces
102 WHERE id = ?
103 "#,
104 workspace_id
105 )
106 .fetch_optional(&self.db)
107 .await?
108 .ok_or_else(|| CollabError::WorkspaceNotFound(workspace_id.to_string()))?;
109
110 self.cache.write().insert(workspace_id, workspace.clone());
112
113 Ok(workspace)
114 }
115
116 pub async fn update_workspace(
118 &self,
119 workspace_id: Uuid,
120 user_id: Uuid,
121 name: Option<String>,
122 description: Option<String>,
123 config: Option<serde_json::Value>,
124 ) -> Result<TeamWorkspace> {
125 let member = self.get_member(workspace_id, user_id).await?;
127 PermissionChecker::check(member.role, Permission::WorkspaceUpdate)?;
128
129 let mut workspace = self.get_workspace(workspace_id).await?;
130
131 if let Some(name) = name {
133 workspace.name = name;
134 }
135 if let Some(description) = description {
136 workspace.description = Some(description);
137 }
138 if let Some(config) = config {
139 workspace.config = config;
140 }
141 workspace.updated_at = Utc::now();
142 workspace.version += 1;
143
144 sqlx::query!(
146 r#"
147 UPDATE workspaces
148 SET name = ?, description = ?, config = ?, version = ?, updated_at = ?
149 WHERE id = ?
150 "#,
151 workspace.name,
152 workspace.description,
153 workspace.config,
154 workspace.version,
155 workspace.updated_at,
156 workspace.id
157 )
158 .execute(&self.db)
159 .await?;
160
161 self.cache.write().insert(workspace_id, workspace.clone());
163
164 Ok(workspace)
165 }
166
167 pub async fn delete_workspace(&self, workspace_id: Uuid, user_id: Uuid) -> Result<()> {
169 let member = self.get_member(workspace_id, user_id).await?;
171 PermissionChecker::check(member.role, Permission::WorkspaceDelete)?;
172
173 let now = Utc::now();
174 sqlx::query!(
175 r#"
176 UPDATE workspaces
177 SET is_archived = TRUE, updated_at = ?
178 WHERE id = ?
179 "#,
180 now,
181 workspace_id
182 )
183 .execute(&self.db)
184 .await?;
185
186 self.cache.write().remove(&workspace_id);
188
189 Ok(())
190 }
191
192 pub async fn add_member(
194 &self,
195 workspace_id: Uuid,
196 user_id: Uuid,
197 new_member_id: Uuid,
198 role: UserRole,
199 ) -> Result<WorkspaceMember> {
200 let member = self.get_member(workspace_id, user_id).await?;
202 PermissionChecker::check(member.role, Permission::InviteMembers)?;
203
204 let new_member = WorkspaceMember::new(workspace_id, new_member_id, role);
206
207 sqlx::query!(
208 r#"
209 INSERT INTO workspace_members (id, workspace_id, user_id, role, joined_at, last_activity)
210 VALUES (?, ?, ?, ?, ?, ?)
211 "#,
212 new_member.id,
213 new_member.workspace_id,
214 new_member.user_id,
215 new_member.role,
216 new_member.joined_at,
217 new_member.last_activity
218 )
219 .execute(&self.db)
220 .await?;
221
222 Ok(new_member)
223 }
224
225 pub async fn remove_member(
227 &self,
228 workspace_id: Uuid,
229 user_id: Uuid,
230 member_to_remove: Uuid,
231 ) -> Result<()> {
232 let member = self.get_member(workspace_id, user_id).await?;
234 PermissionChecker::check(member.role, Permission::RemoveMembers)?;
235
236 let workspace = self.get_workspace(workspace_id).await?;
238 if member_to_remove == workspace.owner_id {
239 return Err(CollabError::InvalidInput("Cannot remove workspace owner".to_string()));
240 }
241
242 sqlx::query!(
243 r#"
244 DELETE FROM workspace_members
245 WHERE workspace_id = ? AND user_id = ?
246 "#,
247 workspace_id,
248 member_to_remove
249 )
250 .execute(&self.db)
251 .await?;
252
253 Ok(())
254 }
255
256 pub async fn change_role(
258 &self,
259 workspace_id: Uuid,
260 user_id: Uuid,
261 member_id: Uuid,
262 new_role: UserRole,
263 ) -> Result<WorkspaceMember> {
264 let member = self.get_member(workspace_id, user_id).await?;
266 PermissionChecker::check(member.role, Permission::ChangeRoles)?;
267
268 let workspace = self.get_workspace(workspace_id).await?;
270 if member_id == workspace.owner_id {
271 return Err(CollabError::InvalidInput(
272 "Cannot change workspace owner's role".to_string(),
273 ));
274 }
275
276 sqlx::query!(
277 r#"
278 UPDATE workspace_members
279 SET role = ?
280 WHERE workspace_id = ? AND user_id = ?
281 "#,
282 new_role,
283 workspace_id,
284 member_id
285 )
286 .execute(&self.db)
287 .await?;
288
289 self.get_member(workspace_id, member_id).await
290 }
291
292 pub async fn get_member(&self, workspace_id: Uuid, user_id: Uuid) -> Result<WorkspaceMember> {
294 sqlx::query_as!(
295 WorkspaceMember,
296 r#"
297 SELECT
298 id as "id: Uuid",
299 workspace_id as "workspace_id: Uuid",
300 user_id as "user_id: Uuid",
301 role as "role: UserRole",
302 joined_at as "joined_at: chrono::DateTime<chrono::Utc>",
303 last_activity as "last_activity: chrono::DateTime<chrono::Utc>"
304 FROM workspace_members
305 WHERE workspace_id = ? AND user_id = ?
306 "#,
307 workspace_id,
308 user_id
309 )
310 .fetch_optional(&self.db)
311 .await?
312 .ok_or_else(|| CollabError::AuthorizationFailed("User is not a member".to_string()))
313 }
314
315 pub async fn list_members(&self, workspace_id: Uuid) -> Result<Vec<WorkspaceMember>> {
317 let members = sqlx::query_as!(
318 WorkspaceMember,
319 r#"
320 SELECT
321 id as "id: Uuid",
322 workspace_id as "workspace_id: Uuid",
323 user_id as "user_id: Uuid",
324 role as "role: UserRole",
325 joined_at as "joined_at: chrono::DateTime<chrono::Utc>",
326 last_activity as "last_activity: chrono::DateTime<chrono::Utc>"
327 FROM workspace_members
328 WHERE workspace_id = ?
329 ORDER BY joined_at
330 "#,
331 workspace_id
332 )
333 .fetch_all(&self.db)
334 .await?;
335
336 Ok(members)
337 }
338
339 pub async fn list_user_workspaces(&self, user_id: Uuid) -> Result<Vec<TeamWorkspace>> {
341 let workspaces = sqlx::query_as!(
342 TeamWorkspace,
343 r#"
344 SELECT
345 w.id as "id: Uuid",
346 w.name,
347 w.description,
348 w.owner_id as "owner_id: Uuid",
349 w.config,
350 w.version,
351 w.created_at as "created_at: chrono::DateTime<chrono::Utc>",
352 w.updated_at as "updated_at: chrono::DateTime<chrono::Utc>",
353 w.is_archived as "is_archived: bool"
354 FROM workspaces w
355 INNER JOIN workspace_members m ON w.id = m.workspace_id
356 WHERE m.user_id = ? AND w.is_archived = FALSE
357 ORDER BY w.updated_at DESC
358 "#,
359 user_id
360 )
361 .fetch_all(&self.db)
362 .await?;
363
364 Ok(workspaces)
365 }
366}
367
368pub struct WorkspaceManager {
370 service: Arc<WorkspaceService>,
371}
372
373impl WorkspaceManager {
374 pub fn new(service: Arc<WorkspaceService>) -> Self {
376 Self { service }
377 }
378
379 pub async fn create_workspace(
381 &self,
382 name: String,
383 description: Option<String>,
384 owner_id: Uuid,
385 ) -> Result<TeamWorkspace> {
386 self.service.create_workspace(name, description, owner_id).await
387 }
388
389 pub async fn get_workspace(&self, workspace_id: Uuid, user_id: Uuid) -> Result<TeamWorkspace> {
391 self.service.get_member(workspace_id, user_id).await?;
393 self.service.get_workspace(workspace_id).await
394 }
395}
396
397#[cfg(test)]
398mod tests {
399 use super::*;
400
401 }