1use crate::CrdtManager;
22use crate::crdt::EntityType;
23use serde::{Deserialize, Serialize};
24use std::sync::Arc;
25use uuid::Uuid;
26use yrs::{Map, Transact, WriteTxn};
27
28fn unix_timestamp() -> i64 {
30 std::time::SystemTime::now()
31 .duration_since(std::time::UNIX_EPOCH)
32 .map(|d| d.as_secs() as i64)
33 .unwrap_or(0)
34}
35
36fn unix_timestamp_result() -> EntityServiceResult<i64> {
38 std::time::SystemTime::now()
39 .duration_since(std::time::UNIX_EPOCH)
40 .map(|d| d.as_secs() as i64)
41 .map_err(|e| EntityServiceError::Io(std::io::Error::other(format!("Time error: {}", e))))
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
46pub struct Entity {
47 pub id: String,
48 pub name: String,
49 pub entity_type: EntityType,
50 pub description: Option<String>,
51 pub created_by: String,
52 pub created_at: i64,
53 pub members: Vec<String>, #[serde(skip_serializing_if = "Option::is_none")]
55 pub parent_org_id: Option<String>, #[serde(skip_serializing_if = "Option::is_none")]
58 pub network_four_words: Option<String>,
59 #[serde(default)]
61 pub is_local_only: bool,
62 #[serde(skip_serializing_if = "Option::is_none")]
64 pub linked_at: Option<i64>,
65 #[serde(skip_serializing_if = "Option::is_none")]
67 pub last_sync_at: Option<i64>,
68}
69
70impl Entity {
71 pub fn new_local(
73 name: String,
74 entity_type: EntityType,
75 description: Option<String>,
76 created_by: String,
77 ) -> Self {
78 let now = unix_timestamp();
79
80 Self {
81 id: uuid::Uuid::new_v4().to_string(),
82 name,
83 entity_type,
84 description,
85 created_by,
86 created_at: now,
87 members: vec![],
88 parent_org_id: None,
89 network_four_words: None,
90 is_local_only: true,
91 linked_at: None,
92 last_sync_at: None,
93 }
94 }
95
96 pub fn is_linked(&self) -> bool {
98 self.network_four_words.is_some() && !self.is_local_only
99 }
100
101 pub fn link_to_network(&mut self, four_words: String) {
103 self.network_four_words = Some(four_words);
104 self.is_local_only = false;
105 self.linked_at = Some(unix_timestamp());
106 }
107
108 pub fn mark_synced(&mut self) {
110 self.last_sync_at = Some(unix_timestamp());
111 }
112}
113
114#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
116pub struct MemberInfo {
117 pub member_id: String,
118 pub role: String,
119 pub joined_at: i64,
120 pub deleted: bool,
121}
122
123#[derive(Debug, thiserror::Error)]
125pub enum EntityServiceError {
126 #[error("Entity not found: {0}")]
127 NotFound(String),
128
129 #[error("Entity already exists: {0}")]
130 AlreadyExists(String),
131
132 #[error("Member not found: {0}")]
133 MemberNotFound(String),
134
135 #[error("Member already exists: {0}")]
136 MemberAlreadyExists(String),
137
138 #[error("CRDT error: {0}")]
139 Crdt(#[from] crate::crdt_manager::CrdtError),
140
141 #[error("Serialization error: {0}")]
142 Serialization(#[from] serde_json::Error),
143
144 #[error("IO error: {0}")]
145 Io(#[from] std::io::Error),
146}
147
148pub type EntityServiceResult<T> = Result<T, EntityServiceError>;
150
151#[derive(Debug, Clone)]
153pub struct CascadeRemovalResult {
154 pub removed_in: Vec<(EntityType, String)>,
155 pub skipped_not_member: Vec<(EntityType, String)>,
156 pub failed: Vec<(EntityType, String, String)>,
157}
158
159pub struct EntityService {
161 crdt_manager: Arc<CrdtManager>,
162}
163
164impl EntityService {
165 pub fn new(crdt_manager: Arc<CrdtManager>) -> Self {
167 Self { crdt_manager }
168 }
169
170 pub async fn create_entity(
172 &self,
173 name: String,
174 entity_type: EntityType,
175 description: Option<String>,
176 created_by: String,
177 initial_members: Vec<String>,
178 ) -> EntityServiceResult<Entity> {
179 let entity_id = Uuid::new_v4().to_string();
180 let now = unix_timestamp_result()?;
181
182 let entity = Entity {
183 id: entity_id.clone(),
184 name,
185 entity_type,
186 description,
187 created_by: created_by.clone(),
188 created_at: now,
189 members: initial_members.clone(),
190 parent_org_id: None,
191 network_four_words: None,
193 is_local_only: false,
194 linked_at: None,
195 last_sync_at: None,
196 };
197
198 self.save_entity(&entity).await?;
200
201 let mut all_members = initial_members;
203 if !all_members.contains(&created_by) {
204 all_members.push(created_by.clone());
205 }
206
207 for member_id in &all_members {
208 let role = if member_id == &created_by {
210 "owner"
211 } else {
212 "member"
213 };
214 self.add_member(entity_type, &entity_id, member_id, role)
215 .await?;
216 }
217
218 let entity_with_members = Entity {
220 members: all_members,
221 ..entity
222 };
223
224 Ok(entity_with_members)
225 }
226
227 pub async fn get_entity(&self, entity_id: &str) -> EntityServiceResult<Entity> {
229 let doc_id = format!("entity:{}:metadata", entity_id);
231 let doc = self
232 .crdt_manager
233 .load_document(&doc_id)
234 .await
235 .map_err(|_| EntityServiceError::NotFound(entity_id.to_string()))?;
236
237 let metadata_map = doc.get_or_insert_map("metadata");
239 let txn = doc.transact();
240
241 let name = CrdtManager::get_map_string(&metadata_map, &txn, "name")
242 .unwrap_or_else(|| "Unknown".to_string());
243
244 let entity_type_str = CrdtManager::get_map_string(&metadata_map, &txn, "entity_type")
245 .unwrap_or_else(|| "group".to_string());
246
247 let entity_type = match entity_type_str.as_str() {
248 "group" => EntityType::Group,
249 "channel" => EntityType::Channel,
250 "project" => EntityType::Project,
251 "organisation" => EntityType::Organisation,
252 _ => EntityType::Group,
253 };
254
255 let description = CrdtManager::get_map_string(&metadata_map, &txn, "description");
256
257 let created_by = CrdtManager::get_map_string(&metadata_map, &txn, "created_by")
258 .unwrap_or_else(|| "unknown".to_string());
259
260 let created_at = CrdtManager::get_map_i64(&metadata_map, &txn, "created_at").unwrap_or(0);
261
262 let parent_org_id = CrdtManager::get_map_string(&metadata_map, &txn, "parent_org_id");
263
264 let network_four_words =
266 CrdtManager::get_map_string(&metadata_map, &txn, "network_four_words");
267 let is_local_only =
268 CrdtManager::get_map_bool(&metadata_map, &txn, "is_local_only").unwrap_or(false);
269 let linked_at = CrdtManager::get_map_i64(&metadata_map, &txn, "linked_at");
270 let last_sync_at = CrdtManager::get_map_i64(&metadata_map, &txn, "last_sync_at");
271
272 let members = self
274 .list_members(entity_type, entity_id)
275 .await?
276 .into_iter()
277 .filter(|m| !m.deleted)
278 .map(|m| m.member_id)
279 .collect();
280
281 Ok(Entity {
282 id: entity_id.to_string(),
283 name,
284 entity_type,
285 description,
286 created_by,
287 created_at,
288 members,
289 parent_org_id,
290 network_four_words,
291 is_local_only,
292 linked_at,
293 last_sync_at,
294 })
295 }
296
297 pub async fn list_entities(&self) -> EntityServiceResult<Vec<Entity>> {
299 use std::fs;
300
301 let entity_dir = self
303 .crdt_manager
304 .get_storage_dir()
305 .join("crdt")
306 .join("entity");
307
308 let entity_ids =
310 tokio::task::spawn_blocking(move || -> Result<Vec<String>, EntityServiceError> {
311 if !entity_dir.exists() {
312 return Ok(vec![]);
313 }
314
315 let mut ids = Vec::new();
316
317 let entries = fs::read_dir(&entity_dir).map_err(|e| {
319 EntityServiceError::Io(std::io::Error::other(format!(
320 "Failed to read entity directory: {}",
321 e
322 )))
323 })?;
324
325 for entry in entries {
326 let entry = entry?;
327 let path = entry.path();
328
329 if path.extension().and_then(|s| s.to_str()) != Some("meta") {
331 continue;
332 }
333
334 if let Some(filename) = path.file_stem().and_then(|s| s.to_str()) {
336 if let Ok(decoded_bytes) = hex::decode(filename)
338 && let Ok(doc_id) = String::from_utf8(decoded_bytes)
339 {
340 if doc_id.starts_with("entity:") && doc_id.ends_with(":metadata") {
342 if let Some(entity_id) = doc_id
344 .strip_prefix("entity:")
345 .and_then(|s| s.strip_suffix(":metadata"))
346 {
347 ids.push(entity_id.to_string());
348 }
349 }
350 }
351 }
352 }
353
354 Ok(ids)
355 })
356 .await
357 .map_err(|e| {
358 EntityServiceError::Io(std::io::Error::other(format!(
359 "Blocking task failed: {}",
360 e
361 )))
362 })??;
363
364 let mut entities = Vec::new();
366 for entity_id in entity_ids {
367 match self.get_entity(&entity_id).await {
368 Ok(entity) => entities.push(entity),
369 Err(e) => {
370 tracing::warn!("Failed to load entity {}: {}", entity_id, e);
372 }
373 }
374 }
375
376 Ok(entities)
377 }
378
379 pub async fn add_member(
381 &self,
382 entity_type: EntityType,
383 entity_id: &str,
384 member_id: &str,
385 role: &str,
386 ) -> EntityServiceResult<()> {
387 use yrs::Doc;
388
389 let doc_id = format!("{}:{}:core", entity_type.as_str(), entity_id);
390
391 let doc = match self.crdt_manager.load_document(&doc_id).await {
393 Ok(doc) => doc,
394 Err(_) => {
395 Doc::new()
397 }
398 };
399
400 let members_map = doc.get_or_insert_map("members");
402 {
403 let txn = doc.transact();
404
405 if let Some(member_data) = CrdtManager::get_nested_map(&members_map, &txn, member_id) {
406 let is_deleted =
407 CrdtManager::get_map_bool(&member_data, &txn, "deleted").unwrap_or(false);
408 if !is_deleted {
409 return Err(EntityServiceError::MemberAlreadyExists(
410 member_id.to_string(),
411 ));
412 }
413 }
414 }
415
416 let active_members_map = doc.get_or_insert_map("active_members");
418 {
419 let mut txn = doc.transact_mut();
420
421 let member_data =
422 CrdtManager::get_or_create_nested_map(&members_map, &mut txn, member_id);
423
424 CrdtManager::set_map_string(&member_data, &mut txn, "member_id", member_id);
425 CrdtManager::set_map_string(&member_data, &mut txn, "role", role);
426 CrdtManager::set_map_i64(
427 &member_data,
428 &mut txn,
429 "joined_at",
430 unix_timestamp_result()?,
431 );
432 CrdtManager::set_map_bool(&member_data, &mut txn, "deleted", false);
433
434 CrdtManager::set_map_bool(&active_members_map, &mut txn, member_id, true);
435 }
436
437 self.crdt_manager
439 .save_document(&doc_id, entity_type.as_str(), entity_id, &doc)
440 .await?;
441
442 Ok(())
443 }
444
445 pub async fn remove_member(
447 &self,
448 entity_type: EntityType,
449 entity_id: &str,
450 member_id: &str,
451 deleted_by: &str,
452 ) -> EntityServiceResult<()> {
453 let doc_id = format!("{}:{}:core", entity_type.as_str(), entity_id);
454
455 let doc = self
457 .crdt_manager
458 .load_document(&doc_id)
459 .await
460 .map_err(|_| EntityServiceError::NotFound(entity_id.to_string()))?;
461
462 let members_map = doc.get_or_insert_map("members");
464 let active_members_map = doc.get_or_insert_map("active_members");
465
466 {
468 let txn = doc.transact();
469
470 if CrdtManager::get_nested_map(&members_map, &txn, member_id).is_none() {
471 return Err(EntityServiceError::MemberNotFound(member_id.to_string()));
472 }
473 }
474
475 {
477 let mut txn = doc.transact_mut();
478
479 let member_data =
480 CrdtManager::get_or_create_nested_map(&members_map, &mut txn, member_id);
481
482 CrdtManager::set_map_bool(&member_data, &mut txn, "deleted", true);
483 CrdtManager::set_map_i64(
484 &member_data,
485 &mut txn,
486 "deleted_at",
487 unix_timestamp_result()?,
488 );
489 CrdtManager::set_map_string(&member_data, &mut txn, "deleted_by", deleted_by);
490
491 active_members_map.remove(&mut txn, member_id);
492 }
493
494 self.crdt_manager
496 .save_document(&doc_id, entity_type.as_str(), entity_id, &doc)
497 .await?;
498
499 Ok(())
500 }
501
502 pub async fn list_members(
504 &self,
505 entity_type: EntityType,
506 entity_id: &str,
507 ) -> EntityServiceResult<Vec<MemberInfo>> {
508 let doc_id = format!("{}:{}:core", entity_type.as_str(), entity_id);
509
510 let doc = self
512 .crdt_manager
513 .load_document(&doc_id)
514 .await
515 .map_err(|_| EntityServiceError::NotFound(entity_id.to_string()))?;
516
517 let mut members = Vec::new();
518
519 let members_map = doc.get_or_insert_map("members");
521
522 {
525 let txn = doc.transact();
526
527 const MAX_MEMBERS: u32 = 1000;
529 let member_count = members_map.len(&txn);
530 if member_count > MAX_MEMBERS {
531 return Err(EntityServiceError::Io(std::io::Error::new(
532 std::io::ErrorKind::InvalidData,
533 format!(
534 "Too many members: {} (max: {}) for entity: {}",
535 member_count, MAX_MEMBERS, entity_id
536 ),
537 )));
538 }
539
540 for (member_id, _) in members_map.iter(&txn) {
541 let member_id_string = member_id.to_string();
542
543 if let Some(member_data) =
544 CrdtManager::get_nested_map(&members_map, &txn, &member_id_string)
545 {
546 let deleted =
547 CrdtManager::get_map_bool(&member_data, &txn, "deleted").unwrap_or(false);
548
549 if deleted {
551 continue;
552 }
553
554 let member_id_str =
555 CrdtManager::get_map_string(&member_data, &txn, "member_id")
556 .unwrap_or_else(|| member_id_string.clone());
557
558 let role = CrdtManager::get_map_string(&member_data, &txn, "role")
559 .unwrap_or_else(|| "member".to_string());
560
561 let joined_at =
562 CrdtManager::get_map_i64(&member_data, &txn, "joined_at").unwrap_or(0);
563
564 members.push(MemberInfo {
565 member_id: member_id_str,
566 role,
567 joined_at,
568 deleted,
569 });
570 }
571 }
572 }
573
574 Ok(members)
575 }
576
577 pub async fn set_parent_organization(
579 &self,
580 entity_id: &str,
581 parent_org_id: &str,
582 ) -> EntityServiceResult<()> {
583 let mut entity = self.get_entity(entity_id).await?;
585
586 entity.parent_org_id = Some(parent_org_id.to_string());
588
589 self.save_entity(&entity).await?;
591
592 Ok(())
593 }
594
595 async fn list_child_entities_of_org(
597 &self,
598 org_id: &str,
599 ) -> EntityServiceResult<Vec<(EntityType, String)>> {
600 let entities = self.list_entities().await?;
602 let children = entities
603 .into_iter()
604 .filter(|e| {
605 e.parent_org_id.as_deref() == Some(org_id)
606 && matches!(
607 e.entity_type,
608 EntityType::Channel | EntityType::Group | EntityType::Project
609 )
610 })
611 .map(|e| (e.entity_type, e.id))
612 .collect();
613 Ok(children)
614 }
615
616 pub async fn remove_organization_member(
618 &self,
619 org_id: &str,
620 member_id: &str,
621 deleted_by: &str,
622 ) -> EntityServiceResult<CascadeRemovalResult> {
623 let mut result = CascadeRemovalResult {
624 removed_in: vec![],
625 skipped_not_member: vec![],
626 failed: vec![],
627 };
628
629 match self
631 .remove_member(EntityType::Organisation, org_id, member_id, deleted_by)
632 .await
633 {
634 Ok(_) => result
635 .removed_in
636 .push((EntityType::Organisation, org_id.to_string())),
637 Err(EntityServiceError::MemberNotFound(_)) => result
638 .skipped_not_member
639 .push((EntityType::Organisation, org_id.to_string())),
640 Err(e) => {
641 result
642 .failed
643 .push((EntityType::Organisation, org_id.to_string(), e.to_string()))
644 }
645 }
646
647 let children = self.list_child_entities_of_org(org_id).await?;
649
650 for (entity_type, entity_id) in children {
652 match self
653 .remove_member(entity_type, &entity_id, member_id, deleted_by)
654 .await
655 {
656 Ok(_) => result.removed_in.push((entity_type, entity_id)),
657 Err(EntityServiceError::MemberNotFound(_)) => {
658 result.skipped_not_member.push((entity_type, entity_id))
659 }
660 Err(e) => result.failed.push((entity_type, entity_id, e.to_string())),
661 }
662 }
663
664 Ok(result)
665 }
666
667 async fn save_entity(&self, entity: &Entity) -> EntityServiceResult<()> {
669 use yrs::Doc;
670
671 let doc_id = format!("entity:{}:metadata", entity.id);
672 let doc = Doc::new();
673
674 {
675 let mut txn = doc.transact_mut();
676 let metadata_map = txn.get_or_insert_map("metadata");
677
678 CrdtManager::set_map_string(&metadata_map, &mut txn, "name", &entity.name);
679 CrdtManager::set_map_string(
680 &metadata_map,
681 &mut txn,
682 "entity_type",
683 entity.entity_type.as_str(),
684 );
685
686 if let Some(description) = &entity.description {
687 CrdtManager::set_map_string(&metadata_map, &mut txn, "description", description);
688 }
689
690 CrdtManager::set_map_string(&metadata_map, &mut txn, "created_by", &entity.created_by);
691 CrdtManager::set_map_i64(&metadata_map, &mut txn, "created_at", entity.created_at);
692
693 if let Some(parent_org_id) = &entity.parent_org_id {
694 CrdtManager::set_map_string(
695 &metadata_map,
696 &mut txn,
697 "parent_org_id",
698 parent_org_id,
699 );
700 }
701
702 if let Some(network_four_words) = &entity.network_four_words {
704 CrdtManager::set_map_string(
705 &metadata_map,
706 &mut txn,
707 "network_four_words",
708 network_four_words,
709 );
710 }
711
712 CrdtManager::set_map_bool(
713 &metadata_map,
714 &mut txn,
715 "is_local_only",
716 entity.is_local_only,
717 );
718
719 if let Some(linked_at) = entity.linked_at {
720 CrdtManager::set_map_i64(&metadata_map, &mut txn, "linked_at", linked_at);
721 }
722
723 if let Some(last_sync_at) = entity.last_sync_at {
724 CrdtManager::set_map_i64(&metadata_map, &mut txn, "last_sync_at", last_sync_at);
725 }
726 }
727
728 self.crdt_manager
729 .save_document(&doc_id, "entity", &entity.id, &doc)
730 .await?;
731
732 Ok(())
733 }
734
735 pub async fn create_local_entity(
737 &self,
738 name: String,
739 entity_type: EntityType,
740 description: Option<String>,
741 created_by: String,
742 ) -> EntityServiceResult<Entity> {
743 let entity = Entity::new_local(name, entity_type, description, created_by);
744
745 self.save_entity(&entity).await?;
747
748 Ok(entity)
749 }
750
751 pub async fn link_entity_to_network(
753 &self,
754 entity_id: &str,
755 four_words: &str,
756 ) -> EntityServiceResult<Entity> {
757 let mut entity = self.get_entity(entity_id).await?;
759
760 entity.link_to_network(four_words.to_string());
762
763 self.save_entity(&entity).await?;
765
766 Ok(entity)
767 }
768
769 pub async fn mark_entity_synced(&self, entity_id: &str) -> EntityServiceResult<Entity> {
771 let mut entity = self.get_entity(entity_id).await?;
772 entity.mark_synced();
773 self.save_entity(&entity).await?;
774 Ok(entity)
775 }
776
777 pub async fn update_entity(
778 &self,
779 entity_id: &str,
780 name: Option<String>,
781 description: Option<Option<String>>,
782 ) -> EntityServiceResult<Entity> {
783 let mut entity = self.get_entity(entity_id).await?;
784
785 if let Some(new_name) = name {
786 entity.name = new_name;
787 }
788
789 if let Some(new_description) = description {
790 entity.description = new_description;
791 }
792
793 self.save_entity(&entity).await?;
794 Ok(entity)
795 }
796
797 pub async fn delete_entity(&self, entity_id: &str) -> EntityServiceResult<()> {
798 let doc_id = format!("entity:{}:metadata", entity_id);
799 self.crdt_manager
800 .delete_document(&doc_id)
801 .await
802 .map_err(|_| EntityServiceError::NotFound(entity_id.to_string()))?;
803 Ok(())
804 }
805
806 #[allow(clippy::too_many_arguments)]
811 pub async fn import_entity(
812 &self,
813 id: String,
814 name: String,
815 entity_type: EntityType,
816 description: Option<String>,
817 created_by: String,
818 created_at: i64,
819 joiner_four_words: String,
820 role: String,
821 ) -> EntityServiceResult<Entity> {
822 if let Ok(existing) = self.get_entity(&id).await {
824 return Ok(existing);
825 }
826
827 let entity = Entity {
829 id: id.clone(),
830 name,
831 entity_type,
832 description,
833 created_by,
834 created_at,
835 members: vec![joiner_four_words.clone()],
836 parent_org_id: None,
837 network_four_words: None,
838 is_local_only: false,
839 linked_at: None,
840 last_sync_at: None,
841 };
842
843 self.save_entity(&entity).await?;
845
846 self.add_member(entity_type, &id, &joiner_four_words, &role)
848 .await?;
849
850 Ok(entity)
851 }
852
853 pub async fn set_permission_override(
862 &self,
863 entity_type: EntityType,
864 entity_id: &str,
865 member_id: &str,
866 resource_type: &str,
867 access_level: &str,
868 ) -> EntityServiceResult<()> {
869 let doc_id = format!("{}:{}:core", entity_type.as_str(), entity_id);
870
871 let doc = self
873 .crdt_manager
874 .load_document(&doc_id)
875 .await
876 .map_err(|_| EntityServiceError::NotFound(entity_id.to_string()))?;
877
878 let members_map = doc.get_or_insert_map("members");
879
880 {
882 let txn = doc.transact();
883 if CrdtManager::get_nested_map(&members_map, &txn, member_id).is_none() {
884 return Err(EntityServiceError::MemberNotFound(member_id.to_string()));
885 }
886 }
887
888 {
890 let mut txn = doc.transact_mut();
891 let member_data =
892 CrdtManager::get_or_create_nested_map(&members_map, &mut txn, member_id);
893 let overrides = CrdtManager::get_or_create_nested_map(
894 &member_data,
895 &mut txn,
896 "permission_overrides",
897 );
898
899 CrdtManager::set_map_string(&overrides, &mut txn, resource_type, access_level);
900 }
901
902 self.crdt_manager
904 .save_document(&doc_id, entity_type.as_str(), entity_id, &doc)
905 .await?;
906
907 Ok(())
908 }
909
910 pub async fn remove_permission_override(
914 &self,
915 entity_type: EntityType,
916 entity_id: &str,
917 member_id: &str,
918 resource_type: &str,
919 ) -> EntityServiceResult<()> {
920 let doc_id = format!("{}:{}:core", entity_type.as_str(), entity_id);
921
922 let doc = self
924 .crdt_manager
925 .load_document(&doc_id)
926 .await
927 .map_err(|_| EntityServiceError::NotFound(entity_id.to_string()))?;
928
929 let members_map = doc.get_or_insert_map("members");
930
931 {
933 let txn = doc.transact();
934 if CrdtManager::get_nested_map(&members_map, &txn, member_id).is_none() {
935 return Err(EntityServiceError::MemberNotFound(member_id.to_string()));
936 }
937 }
938
939 {
941 let mut txn = doc.transact_mut();
942
943 if let Some(member_data) = CrdtManager::get_nested_map(&members_map, &txn, member_id)
944 && let Some(overrides) =
945 CrdtManager::get_nested_map(&member_data, &txn, "permission_overrides")
946 {
947 overrides.remove(&mut txn, resource_type);
948 }
949 }
950
951 self.crdt_manager
953 .save_document(&doc_id, entity_type.as_str(), entity_id, &doc)
954 .await?;
955
956 Ok(())
957 }
958
959 pub async fn get_permission_overrides(
963 &self,
964 entity_type: EntityType,
965 entity_id: &str,
966 member_id: &str,
967 ) -> EntityServiceResult<Vec<(String, String)>> {
968 let doc_id = format!("{}:{}:core", entity_type.as_str(), entity_id);
969
970 let doc = self
972 .crdt_manager
973 .load_document(&doc_id)
974 .await
975 .map_err(|_| EntityServiceError::NotFound(entity_id.to_string()))?;
976
977 let members_map = doc.get_or_insert_map("members");
978
979 let txn = doc.transact();
980
981 let member_data = CrdtManager::get_nested_map(&members_map, &txn, member_id)
983 .ok_or_else(|| EntityServiceError::MemberNotFound(member_id.to_string()))?;
984
985 let mut overrides = Vec::new();
987 if let Some(overrides_map) =
988 CrdtManager::get_nested_map(&member_data, &txn, "permission_overrides")
989 {
990 for (key, _) in overrides_map.iter(&txn) {
991 let resource_type = key.to_string();
992 if let Some(access_level) =
993 CrdtManager::get_map_string(&overrides_map, &txn, &resource_type)
994 {
995 overrides.push((resource_type, access_level));
996 }
997 }
998 }
999
1000 Ok(overrides)
1001 }
1002
1003 pub async fn set_member_role(
1008 &self,
1009 entity_type: EntityType,
1010 entity_id: &str,
1011 member_id: &str,
1012 new_role: &str,
1013 ) -> EntityServiceResult<()> {
1014 let doc_id = format!("{}:{}:core", entity_type.as_str(), entity_id);
1015
1016 let doc = self
1018 .crdt_manager
1019 .load_document(&doc_id)
1020 .await
1021 .map_err(|_| EntityServiceError::NotFound(entity_id.to_string()))?;
1022
1023 let members_map = doc.get_or_insert_map("members");
1024
1025 {
1027 let txn = doc.transact();
1028 if CrdtManager::get_nested_map(&members_map, &txn, member_id).is_none() {
1029 return Err(EntityServiceError::MemberNotFound(member_id.to_string()));
1030 }
1031 }
1032
1033 {
1035 let mut txn = doc.transact_mut();
1036 let member_data =
1037 CrdtManager::get_or_create_nested_map(&members_map, &mut txn, member_id);
1038
1039 CrdtManager::set_map_string(&member_data, &mut txn, "role", new_role);
1040 }
1041
1042 self.crdt_manager
1044 .save_document(&doc_id, entity_type.as_str(), entity_id, &doc)
1045 .await?;
1046
1047 Ok(())
1048 }
1049
1050 pub async fn get_member_role(
1052 &self,
1053 entity_type: EntityType,
1054 entity_id: &str,
1055 member_id: &str,
1056 ) -> EntityServiceResult<String> {
1057 let doc_id = format!("{}:{}:core", entity_type.as_str(), entity_id);
1058
1059 let doc = self
1061 .crdt_manager
1062 .load_document(&doc_id)
1063 .await
1064 .map_err(|_| EntityServiceError::NotFound(entity_id.to_string()))?;
1065
1066 let members_map = doc.get_or_insert_map("members");
1067 let txn = doc.transact();
1068
1069 let member_data = CrdtManager::get_nested_map(&members_map, &txn, member_id)
1071 .ok_or_else(|| EntityServiceError::MemberNotFound(member_id.to_string()))?;
1072
1073 let role = CrdtManager::get_map_string(&member_data, &txn, "role")
1075 .unwrap_or_else(|| "member".to_string());
1076
1077 Ok(role)
1078 }
1079}
1080
1081#[cfg(test)]
1082mod tests {
1083 use super::*;
1084 use crate::crdt_manager::CrdtManager;
1085 use std::sync::Arc;
1086 use tempfile::tempdir;
1087
1088 async fn create_test_service() -> EntityService {
1089 let temp_dir = tempdir().unwrap();
1090 let db_path = temp_dir.path().join("test.db");
1091 let crdt_manager = Arc::new(CrdtManager::new(&db_path).await.unwrap());
1092 EntityService::new(crdt_manager)
1093 }
1094
1095 #[tokio::test]
1096 async fn test_create_entity() {
1097 let service = create_test_service().await;
1098
1099 let entity = service
1100 .create_entity(
1101 "Test Group".to_string(),
1102 EntityType::Group,
1103 Some("A test group".to_string()),
1104 "creator-id".to_string(),
1105 vec!["member1".to_string(), "member2".to_string()],
1106 )
1107 .await
1108 .expect("Failed to create entity");
1109
1110 assert_eq!(entity.name, "Test Group");
1111 assert_eq!(entity.entity_type, EntityType::Group);
1112 assert_eq!(entity.created_by, "creator-id");
1113 assert!(entity.members.contains(&"creator-id".to_string()));
1114 assert!(entity.members.contains(&"member1".to_string()));
1115 assert!(entity.members.contains(&"member2".to_string()));
1116 }
1117
1118 #[tokio::test]
1119 async fn test_get_entity() {
1120 let service = create_test_service().await;
1121
1122 let created_entity = service
1123 .create_entity(
1124 "Test Group".to_string(),
1125 EntityType::Group,
1126 Some("A test group".to_string()),
1127 "creator-id".to_string(),
1128 vec![],
1129 )
1130 .await
1131 .expect("Failed to create entity");
1132
1133 let retrieved_entity = service
1134 .get_entity(&created_entity.id)
1135 .await
1136 .expect("Failed to get entity");
1137
1138 assert_eq!(retrieved_entity.id, created_entity.id);
1139 assert_eq!(retrieved_entity.name, "Test Group");
1140 assert_eq!(retrieved_entity.entity_type, EntityType::Group);
1141 }
1142
1143 #[tokio::test]
1144 async fn test_get_nonexistent_entity() {
1145 let service = create_test_service().await;
1146
1147 let result = service.get_entity("nonexistent").await;
1148 assert!(matches!(result, Err(EntityServiceError::NotFound(_))));
1149 }
1150
1151 #[tokio::test]
1152 async fn test_add_member() {
1153 let service = create_test_service().await;
1154
1155 let entity = service
1156 .create_entity(
1157 "Test Group".to_string(),
1158 EntityType::Group,
1159 None,
1160 "creator-id".to_string(),
1161 vec![],
1162 )
1163 .await
1164 .expect("Failed to create entity");
1165
1166 service
1167 .add_member(EntityType::Group, &entity.id, "new-member", "admin")
1168 .await
1169 .expect("Failed to add member");
1170
1171 let members = service
1172 .list_members(EntityType::Group, &entity.id)
1173 .await
1174 .expect("Failed to list members");
1175
1176 assert_eq!(members.len(), 2); let new_member = members
1178 .iter()
1179 .find(|m| m.member_id == "new-member")
1180 .unwrap();
1181 assert_eq!(new_member.role, "admin");
1182 assert!(!new_member.deleted);
1183 }
1184
1185 #[tokio::test]
1186 async fn test_add_duplicate_member() {
1187 let service = create_test_service().await;
1188
1189 let entity = service
1190 .create_entity(
1191 "Test Group".to_string(),
1192 EntityType::Group,
1193 None,
1194 "creator-id".to_string(),
1195 vec!["member1".to_string()],
1196 )
1197 .await
1198 .expect("Failed to create entity");
1199
1200 let result = service
1201 .add_member(EntityType::Group, &entity.id, "member1", "member")
1202 .await;
1203
1204 assert!(matches!(
1205 result,
1206 Err(EntityServiceError::MemberAlreadyExists(_))
1207 ));
1208 }
1209
1210 #[tokio::test]
1211 async fn test_remove_member() {
1212 let service = create_test_service().await;
1213
1214 let entity = service
1215 .create_entity(
1216 "Test Group".to_string(),
1217 EntityType::Group,
1218 None,
1219 "creator-id".to_string(),
1220 vec!["member1".to_string()],
1221 )
1222 .await
1223 .expect("Failed to create entity");
1224
1225 service
1226 .remove_member(EntityType::Group, &entity.id, "member1", "creator-id")
1227 .await
1228 .expect("Failed to remove member");
1229
1230 let members = service
1231 .list_members(EntityType::Group, &entity.id)
1232 .await
1233 .expect("Failed to list members");
1234
1235 assert!(
1237 !members.iter().any(|m| m.member_id == "member1"),
1238 "Deleted member should not appear in list_members"
1239 );
1240 }
1241
1242 #[tokio::test]
1243 async fn test_remove_nonexistent_member() {
1244 let service = create_test_service().await;
1245
1246 let entity = service
1247 .create_entity(
1248 "Test Group".to_string(),
1249 EntityType::Group,
1250 None,
1251 "creator-id".to_string(),
1252 vec![],
1253 )
1254 .await
1255 .expect("Failed to create entity");
1256
1257 let result = service
1258 .remove_member(EntityType::Group, &entity.id, "nonexistent", "creator-id")
1259 .await;
1260
1261 assert!(matches!(result, Err(EntityServiceError::MemberNotFound(_))));
1262 }
1263
1264 #[tokio::test]
1265 async fn test_list_members() {
1266 let service = create_test_service().await;
1267
1268 let entity = service
1269 .create_entity(
1270 "Test Group".to_string(),
1271 EntityType::Group,
1272 None,
1273 "creator-id".to_string(),
1274 vec!["member1".to_string(), "member2".to_string()],
1275 )
1276 .await
1277 .expect("Failed to create entity");
1278
1279 let members = service
1280 .list_members(EntityType::Group, &entity.id)
1281 .await
1282 .expect("Failed to list members");
1283
1284 assert_eq!(members.len(), 3); assert!(members.iter().any(|m| m.member_id == "creator-id"));
1286 assert!(members.iter().any(|m| m.member_id == "member1"));
1287 assert!(members.iter().any(|m| m.member_id == "member2"));
1288 }
1289
1290 #[tokio::test]
1295 async fn test_set_permission_override() {
1296 let service = create_test_service().await;
1297
1298 let entity = service
1299 .create_entity(
1300 "Test Group".to_string(),
1301 EntityType::Group,
1302 None,
1303 "creator-id".to_string(),
1304 vec!["member1".to_string()],
1305 )
1306 .await
1307 .expect("Failed to create entity");
1308
1309 service
1311 .set_permission_override(EntityType::Group, &entity.id, "member1", "messages", "edit")
1312 .await
1313 .expect("Failed to set permission override");
1314
1315 let overrides = service
1317 .get_permission_overrides(EntityType::Group, &entity.id, "member1")
1318 .await
1319 .expect("Failed to get overrides");
1320
1321 assert_eq!(overrides.len(), 1);
1322 assert!(
1323 overrides
1324 .iter()
1325 .any(|(k, v)| k == "messages" && v == "edit")
1326 );
1327 }
1328
1329 #[tokio::test]
1330 async fn test_set_multiple_permission_overrides() {
1331 let service = create_test_service().await;
1332
1333 let entity = service
1334 .create_entity(
1335 "Test Group".to_string(),
1336 EntityType::Group,
1337 None,
1338 "creator-id".to_string(),
1339 vec!["member1".to_string()],
1340 )
1341 .await
1342 .expect("Failed to create entity");
1343
1344 service
1346 .set_permission_override(EntityType::Group, &entity.id, "member1", "messages", "edit")
1347 .await
1348 .expect("Failed to set override 1");
1349
1350 service
1351 .set_permission_override(
1352 EntityType::Group,
1353 &entity.id,
1354 "member1",
1355 "documents",
1356 "read_only",
1357 )
1358 .await
1359 .expect("Failed to set override 2");
1360
1361 service
1362 .set_permission_override(
1363 EntityType::Group,
1364 &entity.id,
1365 "member1",
1366 "settings",
1367 "not_visible",
1368 )
1369 .await
1370 .expect("Failed to set override 3");
1371
1372 let overrides = service
1374 .get_permission_overrides(EntityType::Group, &entity.id, "member1")
1375 .await
1376 .expect("Failed to get overrides");
1377
1378 assert_eq!(overrides.len(), 3);
1379 assert!(
1380 overrides
1381 .iter()
1382 .any(|(k, v)| k == "messages" && v == "edit")
1383 );
1384 assert!(
1385 overrides
1386 .iter()
1387 .any(|(k, v)| k == "documents" && v == "read_only")
1388 );
1389 assert!(
1390 overrides
1391 .iter()
1392 .any(|(k, v)| k == "settings" && v == "not_visible")
1393 );
1394 }
1395
1396 #[tokio::test]
1397 async fn test_set_permission_override_nonexistent_member() {
1398 let service = create_test_service().await;
1399
1400 let entity = service
1401 .create_entity(
1402 "Test Group".to_string(),
1403 EntityType::Group,
1404 None,
1405 "creator-id".to_string(),
1406 vec![],
1407 )
1408 .await
1409 .expect("Failed to create entity");
1410
1411 let result = service
1412 .set_permission_override(
1413 EntityType::Group,
1414 &entity.id,
1415 "nonexistent",
1416 "messages",
1417 "edit",
1418 )
1419 .await;
1420
1421 assert!(matches!(result, Err(EntityServiceError::MemberNotFound(_))));
1422 }
1423
1424 #[tokio::test]
1425 async fn test_remove_permission_override() {
1426 let service = create_test_service().await;
1427
1428 let entity = service
1429 .create_entity(
1430 "Test Group".to_string(),
1431 EntityType::Group,
1432 None,
1433 "creator-id".to_string(),
1434 vec!["member1".to_string()],
1435 )
1436 .await
1437 .expect("Failed to create entity");
1438
1439 service
1441 .set_permission_override(EntityType::Group, &entity.id, "member1", "messages", "edit")
1442 .await
1443 .expect("Failed to set override");
1444
1445 let overrides = service
1447 .get_permission_overrides(EntityType::Group, &entity.id, "member1")
1448 .await
1449 .expect("Failed to get overrides");
1450 assert_eq!(overrides.len(), 1);
1451
1452 service
1454 .remove_permission_override(EntityType::Group, &entity.id, "member1", "messages")
1455 .await
1456 .expect("Failed to remove override");
1457
1458 let overrides = service
1460 .get_permission_overrides(EntityType::Group, &entity.id, "member1")
1461 .await
1462 .expect("Failed to get overrides");
1463 assert!(overrides.is_empty());
1464 }
1465
1466 #[tokio::test]
1467 async fn test_remove_permission_override_nonexistent_member() {
1468 let service = create_test_service().await;
1469
1470 let entity = service
1471 .create_entity(
1472 "Test Group".to_string(),
1473 EntityType::Group,
1474 None,
1475 "creator-id".to_string(),
1476 vec![],
1477 )
1478 .await
1479 .expect("Failed to create entity");
1480
1481 let result = service
1482 .remove_permission_override(EntityType::Group, &entity.id, "nonexistent", "messages")
1483 .await;
1484
1485 assert!(matches!(result, Err(EntityServiceError::MemberNotFound(_))));
1486 }
1487
1488 #[tokio::test]
1489 async fn test_get_permission_overrides_empty() {
1490 let service = create_test_service().await;
1491
1492 let entity = service
1493 .create_entity(
1494 "Test Group".to_string(),
1495 EntityType::Group,
1496 None,
1497 "creator-id".to_string(),
1498 vec!["member1".to_string()],
1499 )
1500 .await
1501 .expect("Failed to create entity");
1502
1503 let overrides = service
1505 .get_permission_overrides(EntityType::Group, &entity.id, "member1")
1506 .await
1507 .expect("Failed to get overrides");
1508
1509 assert!(overrides.is_empty());
1510 }
1511
1512 #[tokio::test]
1513 async fn test_get_permission_overrides_nonexistent_member() {
1514 let service = create_test_service().await;
1515
1516 let entity = service
1517 .create_entity(
1518 "Test Group".to_string(),
1519 EntityType::Group,
1520 None,
1521 "creator-id".to_string(),
1522 vec![],
1523 )
1524 .await
1525 .expect("Failed to create entity");
1526
1527 let result = service
1528 .get_permission_overrides(EntityType::Group, &entity.id, "nonexistent")
1529 .await;
1530
1531 assert!(matches!(result, Err(EntityServiceError::MemberNotFound(_))));
1532 }
1533
1534 #[tokio::test]
1535 async fn test_set_member_role() {
1536 let service = create_test_service().await;
1537
1538 let entity = service
1539 .create_entity(
1540 "Test Group".to_string(),
1541 EntityType::Group,
1542 None,
1543 "creator-id".to_string(),
1544 vec!["member1".to_string()],
1545 )
1546 .await
1547 .expect("Failed to create entity");
1548
1549 service
1551 .set_member_role(EntityType::Group, &entity.id, "member1", "admin")
1552 .await
1553 .expect("Failed to set role");
1554
1555 let role = service
1557 .get_member_role(EntityType::Group, &entity.id, "member1")
1558 .await
1559 .expect("Failed to get role");
1560
1561 assert_eq!(role, "admin");
1562 }
1563
1564 #[tokio::test]
1565 async fn test_set_member_role_nonexistent_member() {
1566 let service = create_test_service().await;
1567
1568 let entity = service
1569 .create_entity(
1570 "Test Group".to_string(),
1571 EntityType::Group,
1572 None,
1573 "creator-id".to_string(),
1574 vec![],
1575 )
1576 .await
1577 .expect("Failed to create entity");
1578
1579 let result = service
1580 .set_member_role(EntityType::Group, &entity.id, "nonexistent", "admin")
1581 .await;
1582
1583 assert!(matches!(result, Err(EntityServiceError::MemberNotFound(_))));
1584 }
1585
1586 #[tokio::test]
1587 async fn test_get_member_role_default() {
1588 let service = create_test_service().await;
1589
1590 let entity = service
1591 .create_entity(
1592 "Test Group".to_string(),
1593 EntityType::Group,
1594 None,
1595 "creator-id".to_string(),
1596 vec!["member1".to_string()],
1597 )
1598 .await
1599 .expect("Failed to create entity");
1600
1601 let role = service
1603 .get_member_role(EntityType::Group, &entity.id, "member1")
1604 .await
1605 .expect("Failed to get role");
1606
1607 assert_eq!(role, "member");
1608 }
1609
1610 #[tokio::test]
1611 async fn test_get_member_role_nonexistent_member() {
1612 let service = create_test_service().await;
1613
1614 let entity = service
1615 .create_entity(
1616 "Test Group".to_string(),
1617 EntityType::Group,
1618 None,
1619 "creator-id".to_string(),
1620 vec![],
1621 )
1622 .await
1623 .expect("Failed to create entity");
1624
1625 let result = service
1626 .get_member_role(EntityType::Group, &entity.id, "nonexistent")
1627 .await;
1628
1629 assert!(matches!(result, Err(EntityServiceError::MemberNotFound(_))));
1630 }
1631
1632 #[tokio::test]
1633 async fn test_permission_override_update() {
1634 let service = create_test_service().await;
1635
1636 let entity = service
1637 .create_entity(
1638 "Test Group".to_string(),
1639 EntityType::Group,
1640 None,
1641 "creator-id".to_string(),
1642 vec!["member1".to_string()],
1643 )
1644 .await
1645 .expect("Failed to create entity");
1646
1647 service
1649 .set_permission_override(
1650 EntityType::Group,
1651 &entity.id,
1652 "member1",
1653 "messages",
1654 "read_only",
1655 )
1656 .await
1657 .expect("Failed to set override");
1658
1659 service
1661 .set_permission_override(EntityType::Group, &entity.id, "member1", "messages", "edit")
1662 .await
1663 .expect("Failed to update override");
1664
1665 let overrides = service
1667 .get_permission_overrides(EntityType::Group, &entity.id, "member1")
1668 .await
1669 .expect("Failed to get overrides");
1670
1671 assert_eq!(overrides.len(), 1);
1672 assert!(
1673 overrides
1674 .iter()
1675 .any(|(k, v)| k == "messages" && v == "edit")
1676 );
1677 }
1678
1679 #[tokio::test]
1680 async fn test_permission_persistence_across_operations() {
1681 let service = create_test_service().await;
1682
1683 let entity = service
1684 .create_entity(
1685 "Test Group".to_string(),
1686 EntityType::Group,
1687 None,
1688 "creator-id".to_string(),
1689 vec!["member1".to_string()],
1690 )
1691 .await
1692 .expect("Failed to create entity");
1693
1694 service
1696 .set_permission_override(EntityType::Group, &entity.id, "member1", "messages", "edit")
1697 .await
1698 .expect("Failed to set override");
1699
1700 service
1702 .set_member_role(EntityType::Group, &entity.id, "member1", "viewer")
1703 .await
1704 .expect("Failed to set role");
1705
1706 let overrides = service
1708 .get_permission_overrides(EntityType::Group, &entity.id, "member1")
1709 .await
1710 .expect("Failed to get overrides");
1711
1712 assert_eq!(overrides.len(), 1);
1713 assert!(
1714 overrides
1715 .iter()
1716 .any(|(k, v)| k == "messages" && v == "edit")
1717 );
1718
1719 let role = service
1721 .get_member_role(EntityType::Group, &entity.id, "member1")
1722 .await
1723 .expect("Failed to get role");
1724
1725 assert_eq!(role, "viewer");
1726 }
1727
1728 #[tokio::test]
1729 async fn test_different_entity_types() {
1730 let service = create_test_service().await;
1731
1732 let project = service
1734 .create_entity(
1735 "Test Project".to_string(),
1736 EntityType::Project,
1737 None,
1738 "creator-id".to_string(),
1739 vec!["member1".to_string()],
1740 )
1741 .await
1742 .expect("Failed to create project");
1743
1744 let channel = service
1746 .create_entity(
1747 "Test Channel".to_string(),
1748 EntityType::Channel,
1749 None,
1750 "creator-id".to_string(),
1751 vec!["member1".to_string()],
1752 )
1753 .await
1754 .expect("Failed to create channel");
1755
1756 service
1758 .set_permission_override(
1759 EntityType::Project,
1760 &project.id,
1761 "member1",
1762 "kanban_boards",
1763 "edit",
1764 )
1765 .await
1766 .expect("Failed to set project override");
1767
1768 service
1769 .set_permission_override(
1770 EntityType::Channel,
1771 &channel.id,
1772 "member1",
1773 "messages",
1774 "read_only",
1775 )
1776 .await
1777 .expect("Failed to set channel override");
1778
1779 let project_overrides = service
1781 .get_permission_overrides(EntityType::Project, &project.id, "member1")
1782 .await
1783 .expect("Failed to get project overrides");
1784
1785 let channel_overrides = service
1786 .get_permission_overrides(EntityType::Channel, &channel.id, "member1")
1787 .await
1788 .expect("Failed to get channel overrides");
1789
1790 assert_eq!(project_overrides.len(), 1);
1791 assert!(
1792 project_overrides
1793 .iter()
1794 .any(|(k, v)| k == "kanban_boards" && v == "edit")
1795 );
1796
1797 assert_eq!(channel_overrides.len(), 1);
1798 assert!(
1799 channel_overrides
1800 .iter()
1801 .any(|(k, v)| k == "messages" && v == "read_only")
1802 );
1803 }
1804}