Skip to main content

communitas_core/
entity_service.rs

1// Copyright (c) 2025 Saorsa Labs Limited
2//
3// Dual-licensed under the AGPL-3.0-or-later and a commercial license.
4// You may use this file under the terms of the GNU Affero General Public License v3.0 or later.
5// For commercial licensing, contact: saorsalabs@gmail.com
6//
7// See the LICENSE-AGPL-3.0 and LICENSE-COMMERCIAL.md files for details.
8
9//! Entity Service - Unified entity and member management
10//!
11//! This service provides a unified interface for managing entities (groups, channels,
12//! projects, organizations) and their members. It consolidates functionality from
13//! both desktop (CRDT-based) and TUI (JSON file-based) implementations.
14//!
15//! **Key Features:**
16//! - CRDT-based member management for offline-first collaboration
17//! - Entity creation, listing, and metadata management
18//! - Member addition/removal with tombstone support
19//! - Unified API for desktop and TUI applications
20
21use crate::CrdtManager;
22use crate::crdt::EntityType;
23use serde::{Deserialize, Serialize};
24use std::sync::Arc;
25use uuid::Uuid;
26use yrs::{Map, Transact, WriteTxn};
27
28/// Get current Unix timestamp in seconds (never panics, falls back to 0)
29fn 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
36/// Get current Unix timestamp in seconds, returning an error on failure
37fn 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/// Entity information
45#[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>, // Four-word addresses of members
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub parent_org_id: Option<String>, // Links child entities (channel/group/project) to parent organization
56    /// Network four-word identity if this entity is linked to the P2P network
57    #[serde(skip_serializing_if = "Option::is_none")]
58    pub network_four_words: Option<String>,
59    /// True if this is a local-only placeholder (no network identity yet)
60    #[serde(default)]
61    pub is_local_only: bool,
62    /// Timestamp when entity was linked to a network identity
63    #[serde(skip_serializing_if = "Option::is_none")]
64    pub linked_at: Option<i64>,
65    /// Timestamp of last successful sync with network peer
66    #[serde(skip_serializing_if = "Option::is_none")]
67    pub last_sync_at: Option<i64>,
68}
69
70impl Entity {
71    /// Create a new local-only entity (placeholder without network identity)
72    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    /// Check if this entity is linked to a network identity
97    pub fn is_linked(&self) -> bool {
98        self.network_four_words.is_some() && !self.is_local_only
99    }
100
101    /// Link this entity to a network identity
102    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    /// Update the last sync timestamp
109    pub fn mark_synced(&mut self) {
110        self.last_sync_at = Some(unix_timestamp());
111    }
112}
113
114/// Member information
115#[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/// Entity service errors
124#[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
148/// Result type for entity service operations
149pub type EntityServiceResult<T> = Result<T, EntityServiceError>;
150
151/// Result of cascading member removal operation
152#[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
159/// Unified entity and member management service
160pub struct EntityService {
161    crdt_manager: Arc<CrdtManager>,
162}
163
164impl EntityService {
165    /// Create a new entity service
166    pub fn new(crdt_manager: Arc<CrdtManager>) -> Self {
167        Self { crdt_manager }
168    }
169
170    /// Create a new entity
171    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            // Default to network-linked for backward compatibility
192            network_four_words: None,
193            is_local_only: false,
194            linked_at: None,
195            last_sync_at: None,
196        };
197
198        // Save entity metadata
199        self.save_entity(&entity).await?;
200
201        // Add initial members (including creator)
202        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            // Creator gets "owner" role, others get "member"
209            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        // Update entity with actual members list (including creator)
219        let entity_with_members = Entity {
220            members: all_members,
221            ..entity
222        };
223
224        Ok(entity_with_members)
225    }
226
227    /// Get entity by ID
228    pub async fn get_entity(&self, entity_id: &str) -> EntityServiceResult<Entity> {
229        // Load entity metadata from CRDT
230        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        // Extract entity data from CRDT document
238        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        // Read new local-only/network-linked fields
265        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        // Get members list
273        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    /// List all entities
298    pub async fn list_entities(&self) -> EntityServiceResult<Vec<Entity>> {
299        use std::fs;
300
301        // Scan the entity directory for all metadata files
302        let entity_dir = self
303            .crdt_manager
304            .get_storage_dir()
305            .join("crdt")
306            .join("entity");
307
308        // Perform blocking filesystem scan in dedicated blocking thread pool
309        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                // Read all .meta files in the entity directory
318                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                    // Only process .meta files
330                    if path.extension().and_then(|s| s.to_str()) != Some("meta") {
331                        continue;
332                    }
333
334                    // Check if this is an entity metadata file (contains "entity:" in the hex-decoded filename)
335                    if let Some(filename) = path.file_stem().and_then(|s| s.to_str()) {
336                        // Hex-decode the filename to get the doc_id
337                        if let Ok(decoded_bytes) = hex::decode(filename)
338                            && let Ok(doc_id) = String::from_utf8(decoded_bytes)
339                        {
340                            // Check if it's an entity metadata document (format: "entity:{id}:metadata")
341                            if doc_id.starts_with("entity:") && doc_id.ends_with(":metadata") {
342                                // Extract entity ID from "entity:{id}:metadata"
343                                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        // Load entities using async operations (outside blocking section)
365        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                    // Log error but continue processing other entities
371                    tracing::warn!("Failed to load entity {}: {}", entity_id, e);
372                }
373            }
374        }
375
376        Ok(entities)
377    }
378
379    /// Add member to entity
380    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        // Get or create the document
392        let doc = match self.crdt_manager.load_document(&doc_id).await {
393            Ok(doc) => doc,
394            Err(_) => {
395                // Document doesn't exist, create new one
396                Doc::new()
397            }
398        };
399
400        // Check if member already exists and is active
401        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        // Add new member
417        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        // Save document
438        self.crdt_manager
439            .save_document(&doc_id, entity_type.as_str(), entity_id, &doc)
440            .await?;
441
442        Ok(())
443    }
444
445    /// Remove member from entity
446    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        // Load document
456        let doc = self
457            .crdt_manager
458            .load_document(&doc_id)
459            .await
460            .map_err(|_| EntityServiceError::NotFound(entity_id.to_string()))?;
461
462        // Get maps before transactions
463        let members_map = doc.get_or_insert_map("members");
464        let active_members_map = doc.get_or_insert_map("active_members");
465
466        // Check if member exists
467        {
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        // Mark member as deleted (tombstone)
476        {
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        // Save document
495        self.crdt_manager
496            .save_document(&doc_id, entity_type.as_str(), entity_id, &doc)
497            .await?;
498
499        Ok(())
500    }
501
502    /// List members of entity
503    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        // Load document
511        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        // Get map before transaction
520        let members_map = doc.get_or_insert_map("members");
521
522        // Read all members
523        // Add protection against excessive member counts that could cause memory issues
524        {
525            let txn = doc.transact();
526
527            // Limit member count to prevent memory exhaustion (1000 members max)
528            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                    // Skip deleted members - they should not appear in the active member list
550                    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    /// Set parent organization for a child entity
578    pub async fn set_parent_organization(
579        &self,
580        entity_id: &str,
581        parent_org_id: &str,
582    ) -> EntityServiceResult<()> {
583        // Load entity
584        let mut entity = self.get_entity(entity_id).await?;
585
586        // Update parent_org_id
587        entity.parent_org_id = Some(parent_org_id.to_string());
588
589        // Save entity
590        self.save_entity(&entity).await?;
591
592        Ok(())
593    }
594
595    /// List all child entities of an organization
596    async fn list_child_entities_of_org(
597        &self,
598        org_id: &str,
599    ) -> EntityServiceResult<Vec<(EntityType, String)>> {
600        // Scan all entities and find those with parent_org_id == org_id
601        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    /// Remove member from organization and all child entities (channels, groups, projects)
617    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        // 1. Remove from organization itself
630        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        // 2. Find all child entities
648        let children = self.list_child_entities_of_org(org_id).await?;
649
650        // 3. Remove from each child entity
651        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    /// Save entity metadata to CRDT
668    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            // Save local-only/network-linked fields
703            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    /// Create a new local-only entity (no network identity)
736    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        // Save entity metadata
746        self.save_entity(&entity).await?;
747
748        Ok(entity)
749    }
750
751    /// Link an existing entity to a network identity
752    pub async fn link_entity_to_network(
753        &self,
754        entity_id: &str,
755        four_words: &str,
756    ) -> EntityServiceResult<Entity> {
757        // Load existing entity
758        let mut entity = self.get_entity(entity_id).await?;
759
760        // Link to network
761        entity.link_to_network(four_words.to_string());
762
763        // Save updated entity
764        self.save_entity(&entity).await?;
765
766        Ok(entity)
767    }
768
769    /// Update entity sync timestamp
770    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    /// Import/join an existing entity by ID (for multi-node sync)
807    ///
808    /// This allows a node to join an entity that was created on another node.
809    /// The caller provides the entity metadata including the original ID.
810    #[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        // Check if entity already exists
823        if let Ok(existing) = self.get_entity(&id).await {
824            return Ok(existing);
825        }
826
827        // Create entity with the provided ID
828        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        // Save entity metadata
844        self.save_entity(&entity).await?;
845
846        // Add the joining member with their role
847        self.add_member(entity_type, &id, &joiner_four_words, &role)
848            .await?;
849
850        Ok(entity)
851    }
852
853    // ========================================================================
854    // Permission Methods (Phase 3b: CRDT Persistence)
855    // ========================================================================
856
857    /// Set a permission override for a member
858    ///
859    /// Stores the override in the entity's CRDT document under the member's
860    /// permission_overrides map. This override takes precedence over role defaults.
861    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        // Load document
872        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        // Check if member exists
881        {
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        // Set the override
889        {
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        // Save document
903        self.crdt_manager
904            .save_document(&doc_id, entity_type.as_str(), entity_id, &doc)
905            .await?;
906
907        Ok(())
908    }
909
910    /// Remove a permission override for a member
911    ///
912    /// Removes the override, reverting to the member's role default for that resource.
913    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        // Load document
923        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        // Check if member exists
932        {
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        // Remove the override
940        {
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        // Save document
952        self.crdt_manager
953            .save_document(&doc_id, entity_type.as_str(), entity_id, &doc)
954            .await?;
955
956        Ok(())
957    }
958
959    /// Get all permission overrides for a member
960    ///
961    /// Returns a map of resource_type -> access_level for all overrides.
962    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        // Load document
971        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        // Check if member exists
982        let member_data = CrdtManager::get_nested_map(&members_map, &txn, member_id)
983            .ok_or_else(|| EntityServiceError::MemberNotFound(member_id.to_string()))?;
984
985        // Get overrides map
986        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    /// Update a member's role
1004    ///
1005    /// Changes the member's role in the CRDT document. Note that this does not
1006    /// clear existing permission overrides - they continue to take precedence.
1007    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        // Load document
1017        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        // Check if member exists
1026        {
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        // Update the role
1034        {
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        // Save document
1043        self.crdt_manager
1044            .save_document(&doc_id, entity_type.as_str(), entity_id, &doc)
1045            .await?;
1046
1047        Ok(())
1048    }
1049
1050    /// Get a member's role from the CRDT document
1051    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        // Load document
1060        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        // Get member data
1070        let member_data = CrdtManager::get_nested_map(&members_map, &txn, member_id)
1071            .ok_or_else(|| EntityServiceError::MemberNotFound(member_id.to_string()))?;
1072
1073        // Get role (default to "member" if not set)
1074        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); // creator + new member
1177        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        // Member should no longer be in the list (list_members filters deleted)
1236        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); // creator + 2 members
1285        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    // ========================================================================
1291    // Permission Method Tests
1292    // ========================================================================
1293
1294    #[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        // Set permission override
1310        service
1311            .set_permission_override(EntityType::Group, &entity.id, "member1", "messages", "edit")
1312            .await
1313            .expect("Failed to set permission override");
1314
1315        // Verify the override was saved
1316        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        // Set multiple overrides
1345        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        // Verify all overrides
1373        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        // Set override
1440        service
1441            .set_permission_override(EntityType::Group, &entity.id, "member1", "messages", "edit")
1442            .await
1443            .expect("Failed to set override");
1444
1445        // Verify it exists
1446        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        // Remove override
1453        service
1454            .remove_permission_override(EntityType::Group, &entity.id, "member1", "messages")
1455            .await
1456            .expect("Failed to remove override");
1457
1458        // Verify it's gone
1459        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        // Get overrides (should be empty)
1504        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        // Change role
1550        service
1551            .set_member_role(EntityType::Group, &entity.id, "member1", "admin")
1552            .await
1553            .expect("Failed to set role");
1554
1555        // Verify the role was changed
1556        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        // Get role (should default to "member")
1602        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        // Set initial override
1648        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        // Update override
1660        service
1661            .set_permission_override(EntityType::Group, &entity.id, "member1", "messages", "edit")
1662            .await
1663            .expect("Failed to update override");
1664
1665        // Verify only one override exists with updated value
1666        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        // Set permission override
1695        service
1696            .set_permission_override(EntityType::Group, &entity.id, "member1", "messages", "edit")
1697            .await
1698            .expect("Failed to set override");
1699
1700        // Change role (should not clear overrides)
1701        service
1702            .set_member_role(EntityType::Group, &entity.id, "member1", "viewer")
1703            .await
1704            .expect("Failed to set role");
1705
1706        // Verify override still exists
1707        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        // Verify role was changed
1720        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        // Create Project entity
1733        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        // Create Channel entity
1745        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        // Set different overrides for each
1757        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        // Verify each entity has its own overrides
1780        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}