1use crate::core::models::team::{Team, TeamMember, TeamRole, TeamStatus};
6use crate::utils::error::gateway_error::{GatewayError, Result};
7use async_trait::async_trait;
8use std::collections::HashMap;
9use std::sync::RwLock;
10use uuid::Uuid;
11
12#[async_trait]
14pub trait TeamRepository: Send + Sync {
15 async fn create(&self, team: Team) -> Result<Team>;
17
18 async fn get(&self, id: Uuid) -> Result<Option<Team>>;
20
21 async fn get_by_name(&self, name: &str) -> Result<Option<Team>>;
23
24 async fn update(&self, team: Team) -> Result<Team>;
26
27 async fn delete(&self, id: Uuid) -> Result<()>;
29
30 async fn list(&self, offset: u32, limit: u32) -> Result<(Vec<Team>, u64)>;
32
33 async fn count(&self) -> Result<u64>;
35
36 async fn add_member(&self, member: TeamMember) -> Result<TeamMember>;
38
39 async fn get_member(&self, team_id: Uuid, user_id: Uuid) -> Result<Option<TeamMember>>;
41
42 async fn update_member_role(
44 &self,
45 team_id: Uuid,
46 user_id: Uuid,
47 role: TeamRole,
48 ) -> Result<TeamMember>;
49
50 async fn remove_member(&self, team_id: Uuid, user_id: Uuid) -> Result<()>;
52
53 async fn list_members(&self, team_id: Uuid) -> Result<Vec<TeamMember>>;
55
56 async fn get_user_teams(&self, user_id: Uuid) -> Result<Vec<Team>>;
58}
59
60pub struct InMemoryTeamRepository {
62 teams: RwLock<HashMap<Uuid, Team>>,
63 members: RwLock<HashMap<(Uuid, Uuid), TeamMember>>,
64}
65
66impl InMemoryTeamRepository {
67 pub fn new() -> Self {
69 Self {
70 teams: RwLock::new(HashMap::new()),
71 members: RwLock::new(HashMap::new()),
72 }
73 }
74}
75
76impl Default for InMemoryTeamRepository {
77 fn default() -> Self {
78 Self::new()
79 }
80}
81
82#[async_trait]
83impl TeamRepository for InMemoryTeamRepository {
84 async fn create(&self, team: Team) -> Result<Team> {
85 let mut teams = self
86 .teams
87 .write()
88 .map_err(|_| GatewayError::Internal("Lock poisoned".to_string()))?;
89
90 if teams.values().any(|t| t.name == team.name) {
92 return Err(GatewayError::Conflict(format!(
93 "Team with name '{}' already exists",
94 team.name
95 )));
96 }
97
98 let id = team.id();
99 teams.insert(id, team.clone());
100 Ok(team)
101 }
102
103 async fn get(&self, id: Uuid) -> Result<Option<Team>> {
104 let teams = self
105 .teams
106 .read()
107 .map_err(|_| GatewayError::Internal("Lock poisoned".to_string()))?;
108 Ok(teams.get(&id).cloned())
109 }
110
111 async fn get_by_name(&self, name: &str) -> Result<Option<Team>> {
112 let teams = self
113 .teams
114 .read()
115 .map_err(|_| GatewayError::Internal("Lock poisoned".to_string()))?;
116 Ok(teams.values().find(|t| t.name == name).cloned())
117 }
118
119 async fn update(&self, mut team: Team) -> Result<Team> {
120 let mut teams = self
121 .teams
122 .write()
123 .map_err(|_| GatewayError::Internal("Lock poisoned".to_string()))?;
124
125 let id = team.id();
126 if !teams.contains_key(&id) {
127 return Err(GatewayError::NotFound(format!("Team {} not found", id)));
128 }
129
130 if teams.values().any(|t| t.name == team.name && t.id() != id) {
132 return Err(GatewayError::Conflict(format!(
133 "Team with name '{}' already exists",
134 team.name
135 )));
136 }
137
138 team.metadata.touch();
139 teams.insert(id, team.clone());
140 Ok(team)
141 }
142
143 async fn delete(&self, id: Uuid) -> Result<()> {
144 let mut teams = self
145 .teams
146 .write()
147 .map_err(|_| GatewayError::Internal("Lock poisoned".to_string()))?;
148
149 if teams.remove(&id).is_none() {
150 return Err(GatewayError::NotFound(format!("Team {} not found", id)));
151 }
152
153 let mut members = self
155 .members
156 .write()
157 .map_err(|_| GatewayError::Internal("Lock poisoned".to_string()))?;
158 members.retain(|(team_id, _), _| *team_id != id);
159
160 Ok(())
161 }
162
163 async fn list(&self, offset: u32, limit: u32) -> Result<(Vec<Team>, u64)> {
164 let teams = self
165 .teams
166 .read()
167 .map_err(|_| GatewayError::Internal("Lock poisoned".to_string()))?;
168
169 let total = teams.len() as u64;
170 let mut team_list: Vec<Team> = teams
171 .values()
172 .filter(|t| !matches!(t.status, TeamStatus::Deleted))
173 .cloned()
174 .collect();
175
176 team_list.sort_by_key(|b| std::cmp::Reverse(b.metadata.created_at));
178
179 let paginated: Vec<Team> = team_list
180 .into_iter()
181 .skip(offset as usize)
182 .take(limit as usize)
183 .collect();
184
185 Ok((paginated, total))
186 }
187
188 async fn count(&self) -> Result<u64> {
189 let teams = self
190 .teams
191 .read()
192 .map_err(|_| GatewayError::Internal("Lock poisoned".to_string()))?;
193 Ok(teams
194 .values()
195 .filter(|t| !matches!(t.status, TeamStatus::Deleted))
196 .count() as u64)
197 }
198
199 async fn add_member(&self, member: TeamMember) -> Result<TeamMember> {
200 {
202 let teams = self
203 .teams
204 .read()
205 .map_err(|_| GatewayError::Internal("Lock poisoned".to_string()))?;
206 if !teams.contains_key(&member.team_id) {
207 return Err(GatewayError::NotFound(format!(
208 "Team {} not found",
209 member.team_id
210 )));
211 }
212 }
213
214 let mut members = self
215 .members
216 .write()
217 .map_err(|_| GatewayError::Internal("Lock poisoned".to_string()))?;
218
219 let key = (member.team_id, member.user_id);
220 if members.contains_key(&key) {
221 return Err(GatewayError::Conflict(format!(
222 "User {} is already a member of team {}",
223 member.user_id, member.team_id
224 )));
225 }
226
227 members.insert(key, member.clone());
228 Ok(member)
229 }
230
231 async fn get_member(&self, team_id: Uuid, user_id: Uuid) -> Result<Option<TeamMember>> {
232 let members = self
233 .members
234 .read()
235 .map_err(|_| GatewayError::Internal("Lock poisoned".to_string()))?;
236 Ok(members.get(&(team_id, user_id)).cloned())
237 }
238
239 async fn update_member_role(
240 &self,
241 team_id: Uuid,
242 user_id: Uuid,
243 role: TeamRole,
244 ) -> Result<TeamMember> {
245 let mut members = self
246 .members
247 .write()
248 .map_err(|_| GatewayError::Internal("Lock poisoned".to_string()))?;
249
250 let key = (team_id, user_id);
251 let member = members.get_mut(&key).ok_or_else(|| {
252 GatewayError::NotFound(format!("Member {} not found in team {}", user_id, team_id))
253 })?;
254
255 member.role = role;
256 member.metadata.touch();
257 Ok(member.clone())
258 }
259
260 async fn remove_member(&self, team_id: Uuid, user_id: Uuid) -> Result<()> {
261 let mut members = self
262 .members
263 .write()
264 .map_err(|_| GatewayError::Internal("Lock poisoned".to_string()))?;
265
266 let key = (team_id, user_id);
267 if members.remove(&key).is_none() {
268 return Err(GatewayError::NotFound(format!(
269 "Member {} not found in team {}",
270 user_id, team_id
271 )));
272 }
273
274 Ok(())
275 }
276
277 async fn list_members(&self, team_id: Uuid) -> Result<Vec<TeamMember>> {
278 let members = self
279 .members
280 .read()
281 .map_err(|_| GatewayError::Internal("Lock poisoned".to_string()))?;
282
283 let team_members: Vec<TeamMember> = members
284 .iter()
285 .filter(|((tid, _), _)| *tid == team_id)
286 .map(|(_, m)| m.clone())
287 .collect();
288
289 Ok(team_members)
290 }
291
292 async fn get_user_teams(&self, user_id: Uuid) -> Result<Vec<Team>> {
293 let members = self
294 .members
295 .read()
296 .map_err(|_| GatewayError::Internal("Lock poisoned".to_string()))?;
297
298 let team_ids: Vec<Uuid> = members
299 .iter()
300 .filter(|((_, uid), _)| *uid == user_id)
301 .map(|((tid, _), _)| *tid)
302 .collect();
303
304 let teams = self
305 .teams
306 .read()
307 .map_err(|_| GatewayError::Internal("Lock poisoned".to_string()))?;
308
309 let user_teams: Vec<Team> = team_ids
310 .into_iter()
311 .filter_map(|tid| teams.get(&tid).cloned())
312 .collect();
313
314 Ok(user_teams)
315 }
316}
317
318#[cfg(feature = "postgres")]
319pub mod postgres {
320 use super::*;
325 use crate::storage::database::Database;
326 use std::sync::Arc;
327
328 pub struct PostgresTeamRepository {
330 _db: Arc<Database>,
331 }
332
333 impl PostgresTeamRepository {
334 pub fn new(db: Arc<Database>) -> Self {
336 Self { _db: db }
337 }
338 }
339
340 #[async_trait]
341 impl TeamRepository for PostgresTeamRepository {
342 async fn create(&self, _team: Team) -> Result<Team> {
343 Err(GatewayError::NotImplemented(
346 "PostgreSQL team storage not yet implemented".to_string(),
347 ))
348 }
349
350 async fn get(&self, _id: Uuid) -> Result<Option<Team>> {
351 Err(GatewayError::NotImplemented(
352 "PostgreSQL team storage not yet implemented".to_string(),
353 ))
354 }
355
356 async fn get_by_name(&self, _name: &str) -> Result<Option<Team>> {
357 Err(GatewayError::NotImplemented(
358 "PostgreSQL team storage not yet implemented".to_string(),
359 ))
360 }
361
362 async fn update(&self, _team: Team) -> Result<Team> {
363 Err(GatewayError::NotImplemented(
364 "PostgreSQL team storage not yet implemented".to_string(),
365 ))
366 }
367
368 async fn delete(&self, _id: Uuid) -> Result<()> {
369 Err(GatewayError::NotImplemented(
370 "PostgreSQL team storage not yet implemented".to_string(),
371 ))
372 }
373
374 async fn list(&self, _offset: u32, _limit: u32) -> Result<(Vec<Team>, u64)> {
375 Err(GatewayError::NotImplemented(
376 "PostgreSQL team storage not yet implemented".to_string(),
377 ))
378 }
379
380 async fn count(&self) -> Result<u64> {
381 Err(GatewayError::NotImplemented(
382 "PostgreSQL team storage not yet implemented".to_string(),
383 ))
384 }
385
386 async fn add_member(&self, _member: TeamMember) -> Result<TeamMember> {
387 Err(GatewayError::NotImplemented(
388 "PostgreSQL team storage not yet implemented".to_string(),
389 ))
390 }
391
392 async fn get_member(&self, _team_id: Uuid, _user_id: Uuid) -> Result<Option<TeamMember>> {
393 Err(GatewayError::NotImplemented(
394 "PostgreSQL team storage not yet implemented".to_string(),
395 ))
396 }
397
398 async fn update_member_role(
399 &self,
400 _team_id: Uuid,
401 _user_id: Uuid,
402 _role: TeamRole,
403 ) -> Result<TeamMember> {
404 Err(GatewayError::NotImplemented(
405 "PostgreSQL team storage not yet implemented".to_string(),
406 ))
407 }
408
409 async fn remove_member(&self, _team_id: Uuid, _user_id: Uuid) -> Result<()> {
410 Err(GatewayError::NotImplemented(
411 "PostgreSQL team storage not yet implemented".to_string(),
412 ))
413 }
414
415 async fn list_members(&self, _team_id: Uuid) -> Result<Vec<TeamMember>> {
416 Err(GatewayError::NotImplemented(
417 "PostgreSQL team storage not yet implemented".to_string(),
418 ))
419 }
420
421 async fn get_user_teams(&self, _user_id: Uuid) -> Result<Vec<Team>> {
422 Err(GatewayError::NotImplemented(
423 "PostgreSQL team storage not yet implemented".to_string(),
424 ))
425 }
426 }
427}
428
429#[cfg(test)]
430mod tests {
431 use super::*;
432
433 #[tokio::test]
434 async fn test_create_team() {
435 let repo = InMemoryTeamRepository::new();
436 let team = Team::new("test-team".to_string(), Some("Test Team".to_string()));
437
438 let created = repo.create(team.clone()).await.unwrap();
439 assert_eq!(created.name, "test-team");
440 }
441
442 #[tokio::test]
443 async fn test_get_team() {
444 let repo = InMemoryTeamRepository::new();
445 let team = Team::new("test-team".to_string(), None);
446 let id = team.id();
447
448 repo.create(team).await.unwrap();
449 let fetched = repo.get(id).await.unwrap();
450
451 assert!(fetched.is_some());
452 assert_eq!(fetched.unwrap().name, "test-team");
453 }
454
455 #[tokio::test]
456 async fn test_get_team_not_found() {
457 let repo = InMemoryTeamRepository::new();
458 let result = repo.get(Uuid::new_v4()).await.unwrap();
459 assert!(result.is_none());
460 }
461
462 #[tokio::test]
463 async fn test_get_by_name() {
464 let repo = InMemoryTeamRepository::new();
465 let team = Team::new("unique-name".to_string(), None);
466 repo.create(team).await.unwrap();
467
468 let fetched = repo.get_by_name("unique-name").await.unwrap();
469 assert!(fetched.is_some());
470 assert_eq!(fetched.unwrap().name, "unique-name");
471 }
472
473 #[tokio::test]
474 async fn test_update_team() {
475 let repo = InMemoryTeamRepository::new();
476 let team = Team::new("original".to_string(), None);
477 let id = team.id();
478
479 repo.create(team).await.unwrap();
480
481 let mut updated_team = repo.get(id).await.unwrap().unwrap();
482 updated_team.description = Some("Updated description".to_string());
483
484 let result = repo.update(updated_team).await.unwrap();
485 assert_eq!(result.description, Some("Updated description".to_string()));
486 }
487
488 #[tokio::test]
489 async fn test_delete_team() {
490 let repo = InMemoryTeamRepository::new();
491 let team = Team::new("to-delete".to_string(), None);
492 let id = team.id();
493
494 repo.create(team).await.unwrap();
495 repo.delete(id).await.unwrap();
496
497 let fetched = repo.get(id).await.unwrap();
498 assert!(fetched.is_none());
499 }
500
501 #[tokio::test]
502 async fn test_list_teams() {
503 let repo = InMemoryTeamRepository::new();
504
505 for i in 0..5 {
506 let team = Team::new(format!("team-{}", i), None);
507 repo.create(team).await.unwrap();
508 }
509
510 let (teams, total) = repo.list(0, 10).await.unwrap();
511 assert_eq!(teams.len(), 5);
512 assert_eq!(total, 5);
513 }
514
515 #[tokio::test]
516 async fn test_list_teams_pagination() {
517 let repo = InMemoryTeamRepository::new();
518
519 for i in 0..10 {
520 let team = Team::new(format!("team-{}", i), None);
521 repo.create(team).await.unwrap();
522 }
523
524 let (teams, total) = repo.list(0, 5).await.unwrap();
525 assert_eq!(teams.len(), 5);
526 assert_eq!(total, 10);
527
528 let (teams2, _) = repo.list(5, 5).await.unwrap();
529 assert_eq!(teams2.len(), 5);
530 }
531
532 #[tokio::test]
533 async fn test_duplicate_team_name() {
534 let repo = InMemoryTeamRepository::new();
535 let team1 = Team::new("duplicate".to_string(), None);
536 let team2 = Team::new("duplicate".to_string(), None);
537
538 repo.create(team1).await.unwrap();
539 let result = repo.create(team2).await;
540
541 assert!(result.is_err());
542 }
543
544 #[tokio::test]
545 async fn test_add_member() {
546 let repo = InMemoryTeamRepository::new();
547 let team = Team::new("team".to_string(), None);
548 let team_id = team.id();
549 repo.create(team).await.unwrap();
550
551 let user_id = Uuid::new_v4();
552 let member = TeamMember::new(team_id, user_id, TeamRole::Member, None);
553
554 let created = repo.add_member(member).await.unwrap();
555 assert_eq!(created.user_id, user_id);
556 assert!(matches!(created.role, TeamRole::Member));
557 }
558
559 #[tokio::test]
560 async fn test_get_member() {
561 let repo = InMemoryTeamRepository::new();
562 let team = Team::new("team".to_string(), None);
563 let team_id = team.id();
564 repo.create(team).await.unwrap();
565
566 let user_id = Uuid::new_v4();
567 let member = TeamMember::new(team_id, user_id, TeamRole::Admin, None);
568 repo.add_member(member).await.unwrap();
569
570 let fetched = repo.get_member(team_id, user_id).await.unwrap();
571 assert!(fetched.is_some());
572 assert!(matches!(fetched.unwrap().role, TeamRole::Admin));
573 }
574
575 #[tokio::test]
576 async fn test_update_member_role() {
577 let repo = InMemoryTeamRepository::new();
578 let team = Team::new("team".to_string(), None);
579 let team_id = team.id();
580 repo.create(team).await.unwrap();
581
582 let user_id = Uuid::new_v4();
583 let member = TeamMember::new(team_id, user_id, TeamRole::Member, None);
584 repo.add_member(member).await.unwrap();
585
586 let updated = repo
587 .update_member_role(team_id, user_id, TeamRole::Admin)
588 .await
589 .unwrap();
590 assert!(matches!(updated.role, TeamRole::Admin));
591 }
592
593 #[tokio::test]
594 async fn test_remove_member() {
595 let repo = InMemoryTeamRepository::new();
596 let team = Team::new("team".to_string(), None);
597 let team_id = team.id();
598 repo.create(team).await.unwrap();
599
600 let user_id = Uuid::new_v4();
601 let member = TeamMember::new(team_id, user_id, TeamRole::Member, None);
602 repo.add_member(member).await.unwrap();
603
604 repo.remove_member(team_id, user_id).await.unwrap();
605
606 let fetched = repo.get_member(team_id, user_id).await.unwrap();
607 assert!(fetched.is_none());
608 }
609
610 #[tokio::test]
611 async fn test_list_members() {
612 let repo = InMemoryTeamRepository::new();
613 let team = Team::new("team".to_string(), None);
614 let team_id = team.id();
615 repo.create(team).await.unwrap();
616
617 for _ in 0..3 {
618 let member = TeamMember::new(team_id, Uuid::new_v4(), TeamRole::Member, None);
619 repo.add_member(member).await.unwrap();
620 }
621
622 let members = repo.list_members(team_id).await.unwrap();
623 assert_eq!(members.len(), 3);
624 }
625
626 #[tokio::test]
627 async fn test_get_user_teams() {
628 let repo = InMemoryTeamRepository::new();
629 let user_id = Uuid::new_v4();
630
631 for i in 0..3 {
632 let team = Team::new(format!("team-{}", i), None);
633 let team_id = team.id();
634 repo.create(team).await.unwrap();
635
636 let member = TeamMember::new(team_id, user_id, TeamRole::Member, None);
637 repo.add_member(member).await.unwrap();
638 }
639
640 let teams = repo.get_user_teams(user_id).await.unwrap();
641 assert_eq!(teams.len(), 3);
642 }
643
644 #[tokio::test]
645 async fn test_add_member_to_nonexistent_team() {
646 let repo = InMemoryTeamRepository::new();
647 let member = TeamMember::new(Uuid::new_v4(), Uuid::new_v4(), TeamRole::Member, None);
648
649 let result = repo.add_member(member).await;
650 assert!(result.is_err());
651 }
652
653 #[tokio::test]
654 async fn test_duplicate_member() {
655 let repo = InMemoryTeamRepository::new();
656 let team = Team::new("team".to_string(), None);
657 let team_id = team.id();
658 repo.create(team).await.unwrap();
659
660 let user_id = Uuid::new_v4();
661 let member1 = TeamMember::new(team_id, user_id, TeamRole::Member, None);
662 let member2 = TeamMember::new(team_id, user_id, TeamRole::Admin, None);
663
664 repo.add_member(member1).await.unwrap();
665 let result = repo.add_member(member2).await;
666
667 assert!(result.is_err());
668 }
669}