saorsa_core/projects/
mod.rs

1//! Projects system with hierarchical organization structure
2//! 
3//! Features:
4//! - Hierarchical structure: Organizations → Departments → Teams → Projects
5//! - Document management with version control and threshold signatures
6//! - Media storage for videos, audio, and images
7//! - Granular permissions using threshold groups
8//! - Approval workflows with multi-signature requirements
9//! - Activity tracking and analytics
10
11use crate::identity::enhanced::{
12    EnhancedIdentity, OrganizationId, DepartmentId, TeamId
13};
14use crate::storage::{StorageManager, keys, ttl, FileChunker, FileMetadata};
15use crate::threshold::ThresholdSignature;
16use crate::quantum_crypto::types::GroupId;
17use serde::{Deserialize, Serialize};
18use std::collections::HashMap;
19use std::time::SystemTime;
20use thiserror::Error;
21use uuid::Uuid;
22use blake3::Hasher;
23
24/// Comprehensive error types for project operations
25/// 
26/// Covers all possible failure modes in project management including
27/// storage failures, permission denials, and workflow violations.
28#[derive(Debug, Error)]
29pub enum ProjectsError {
30    /// Underlying storage system error
31    #[error("Storage error: {0}")]
32    StorageError(#[from] crate::storage::StorageError),
33    
34    /// Project with specified ID does not exist
35    #[error("Project not found: {0}")]
36    ProjectNotFound(String),
37    
38    /// Document with specified ID does not exist
39    #[error("Document not found: {0}")]
40    DocumentNotFound(String),
41    
42    /// User lacks required permissions for operation
43    #[error("Permission denied: {0}")]
44    PermissionDenied(String),
45    
46    /// Operation is not valid in current context
47    #[error("Invalid operation: {0}")]
48    InvalidOperation(String),
49    
50    /// Workflow validation or execution error
51    #[error("Workflow error: {0}")]
52    WorkflowError(String),
53}
54
55/// Result type for project operations
56type Result<T> = std::result::Result<T, ProjectsError>;
57
58/// Unique identifier for projects in the system
59/// 
60/// Uses UUID v4 to ensure global uniqueness across all organizations
61/// and prevent ID collision in distributed environments.
62#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
63pub struct ProjectId(pub String);
64
65impl ProjectId {
66    /// Generate a new unique project identifier
67    /// 
68    /// # Returns
69    /// A new ProjectId with a randomly generated UUID
70    pub fn new() -> Self {
71        Self(Uuid::new_v4().to_string())
72    }
73}
74
75/// Unique identifier for documents within projects
76/// 
77/// Documents are the primary content units in projects and can represent
78/// text files, code, specifications, or any other structured content.
79#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
80pub struct DocumentId(pub String);
81
82impl DocumentId {
83    /// Generate a new unique document identifier
84    /// 
85    /// # Returns
86    /// A new DocumentId with a randomly generated UUID
87    pub fn new() -> Self {
88        Self(Uuid::new_v4().to_string())
89    }
90}
91
92/// Unique identifier for folders in project hierarchies
93/// 
94/// Folders organize documents and other folders in a hierarchical
95/// structure, supporting nested organization of project content.
96#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
97pub struct FolderId(pub String);
98
99impl FolderId {
100    /// Generate a new unique folder identifier
101    /// 
102    /// # Returns
103    /// A new FolderId with a randomly generated UUID
104    pub fn new() -> Self {
105        Self(Uuid::new_v4().to_string())
106    }
107}
108
109/// User identifier type for project member references
110/// 
111/// References users who participate in projects with various roles
112/// and permission levels.
113pub type UserId = String;
114
115/// Blake3 cryptographic hash for content integrity verification
116/// 
117/// Used for document versioning, deduplication, and integrity checks.
118/// Blake3 provides fast, secure hashing with excellent performance.
119pub type Blake3Hash = [u8; 32];
120
121/// Complete project structure with hierarchical organization
122/// 
123/// Projects are the primary organizational unit for collaborative work.
124/// They belong to organizations and can be assigned to departments and teams.
125/// Access control is managed through threshold groups and permissions.
126#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct Project {
128    /// Unique identifier for this project
129    pub id: ProjectId,
130    /// Human-readable project name
131    pub name: String,
132    /// Detailed description of project purpose and scope
133    pub description: String,
134    /// Organization this project belongs to
135    pub organization_id: OrganizationId,
136    /// Optional department assignment within organization
137    pub department_id: Option<DepartmentId>,
138    /// Optional team assignment within department
139    pub team_id: Option<TeamId>,
140    /// Threshold group that owns this project
141    pub owner_group: GroupId,
142    /// Additional access groups with specific permissions
143    pub access_groups: Vec<AccessGroup>,
144    /// Root folder containing all project content
145    pub root_folder: FolderId,
146    /// Project configuration and behavior settings
147    pub settings: ProjectSettings,
148    /// Metadata for analytics and tracking
149    pub metadata: ProjectMetadata,
150    /// Timestamp when project was created
151    pub created_at: SystemTime,
152    /// User ID of project creator
153    pub created_by: UserId,
154}
155
156/// Access group with permissions
157#[derive(Debug, Clone, Serialize, Deserialize)]
158pub struct AccessGroup {
159    pub group_id: GroupId,
160    pub permissions: Vec<ProjectPermission>,
161    pub name: String,
162}
163
164/// Project-specific permissions
165#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
166pub enum ProjectPermission {
167    Read,
168    Write,
169    Delete,
170    Share,
171    ManageMembers,
172    ManageWorkflows,
173    ApproveDocuments,
174    ViewAnalytics,
175}
176
177/// Project settings
178#[derive(Debug, Clone, Serialize, Deserialize)]
179pub struct ProjectSettings {
180    pub require_approval: bool,
181    pub approval_threshold: u16,
182    pub version_control: bool,
183    pub max_file_size_mb: u64,
184    pub allowed_file_types: Vec<String>,
185    pub retention_days: Option<u32>,
186    pub enable_watermarks: bool,
187    pub enable_analytics: bool,
188}
189
190impl Default for ProjectSettings {
191    fn default() -> Self {
192        Self {
193            require_approval: false,
194            approval_threshold: 1,
195            version_control: true,
196            max_file_size_mb: 1024, // 1GB
197            allowed_file_types: vec![],
198            retention_days: None,
199            enable_watermarks: false,
200            enable_analytics: true,
201        }
202    }
203}
204
205/// Project metadata
206#[derive(Debug, Clone, Serialize, Deserialize)]
207pub struct ProjectMetadata {
208    pub total_documents: u64,
209    pub total_size_bytes: u64,
210    pub last_activity: SystemTime,
211    pub active_users: u32,
212    pub custom_fields: HashMap<String, String>,
213}
214
215/// Folder structure
216#[derive(Debug, Clone, Serialize, Deserialize)]
217pub struct Folder {
218    pub id: FolderId,
219    pub name: String,
220    pub parent_id: Option<FolderId>,
221    pub project_id: ProjectId,
222    pub subfolders: Vec<FolderId>,
223    pub documents: Vec<DocumentId>,
224    pub created_at: SystemTime,
225    pub created_by: UserId,
226}
227
228/// Document with version control
229#[derive(Debug, Clone, Serialize, Deserialize)]
230pub struct Document {
231    pub id: DocumentId,
232    pub name: String,
233    pub description: String,
234    pub folder_id: FolderId,
235    pub project_id: ProjectId,
236    pub document_type: DocumentType,
237    pub current_version: DocumentVersion,
238    pub versions: Vec<DocumentVersion>,
239    pub access_log: Vec<AccessLogEntry>,
240    pub workflow_state: Option<WorkflowState>,
241    pub tags: Vec<String>,
242    pub created_at: SystemTime,
243    pub created_by: UserId,
244}
245
246/// Document type
247#[derive(Debug, Clone, Serialize, Deserialize)]
248pub enum DocumentType {
249    Text { format: String },
250    Spreadsheet,
251    Presentation,
252    Image { width: u32, height: u32 },
253    Video { duration_seconds: u64 },
254    Audio { duration_seconds: u64 },
255    PDF { pages: u32 },
256    Archive,
257    Other { mime_type: String },
258}
259
260/// Document version
261#[derive(Debug, Clone, Serialize, Deserialize)]
262pub struct DocumentVersion {
263    pub version_number: u64,
264    pub content_hash: Blake3Hash,
265    pub encryption_key: EncryptedKey,
266    pub size_bytes: u64,
267    pub author: UserId,
268    pub signatures: Vec<VersionSignature>,
269    pub comment: String,
270    pub created_at: SystemTime,
271    pub is_approved: bool,
272}
273
274/// Encrypted key (encrypted with project key)
275#[derive(Debug, Clone, Serialize, Deserialize)]
276pub struct EncryptedKey {
277    pub ciphertext: Vec<u8>,
278    pub nonce: Vec<u8>,
279}
280
281/// Version signature with threshold crypto
282#[derive(Debug, Clone, Serialize, Deserialize)]
283pub struct VersionSignature {
284    pub signer_id: UserId,
285    pub signature: ThresholdSignature,
286    pub signed_at: SystemTime,
287    pub comment: Option<String>,
288}
289
290/// Access log entry
291#[derive(Debug, Clone, Serialize, Deserialize)]
292pub struct AccessLogEntry {
293    pub user_id: UserId,
294    pub action: AccessAction,
295    pub timestamp: SystemTime,
296    pub ip_address: Option<String>,
297    pub device_id: Option<String>,
298}
299
300/// Access action
301#[derive(Debug, Clone, Serialize, Deserialize)]
302pub enum AccessAction {
303    View,
304    Download,
305    Edit,
306    Share,
307    Delete,
308    Approve,
309    Reject,
310}
311
312/// Workflow state for approvals
313#[derive(Debug, Clone, Serialize, Deserialize)]
314pub struct WorkflowState {
315    pub workflow_id: String,
316    pub current_stage: WorkflowStage,
317    pub approvers: Vec<UserId>,
318    pub approvals: Vec<Approval>,
319    pub required_approvals: u16,
320    pub deadline: Option<SystemTime>,
321    pub created_at: SystemTime,
322}
323
324/// Workflow stage
325#[derive(Debug, Clone, Serialize, Deserialize)]
326pub enum WorkflowStage {
327    Draft,
328    UnderReview,
329    Approved,
330    Rejected,
331    Published,
332}
333
334/// Approval record
335#[derive(Debug, Clone, Serialize, Deserialize)]
336pub struct Approval {
337    pub approver_id: UserId,
338    pub decision: ApprovalDecision,
339    pub signature: ThresholdSignature,
340    pub comment: Option<String>,
341    pub approved_at: SystemTime,
342}
343
344/// Approval decision
345#[derive(Debug, Clone, Serialize, Deserialize)]
346pub enum ApprovalDecision {
347    Approve,
348    Reject,
349    RequestChanges,
350}
351
352/// Project analytics
353#[derive(Debug, Clone, Serialize, Deserialize, Default)]
354pub struct ProjectAnalytics {
355    pub total_views: u64,
356    pub total_downloads: u64,
357    pub unique_viewers: u64,
358    pub average_time_spent_seconds: u64,
359    pub most_active_users: Vec<(UserId, u64)>,
360    pub popular_documents: Vec<(DocumentId, u64)>,
361    pub storage_trend: Vec<(SystemTime, u64)>,
362    pub activity_heatmap: HashMap<u32, u64>, // hour of day -> activity count
363}
364
365/// Projects manager
366pub struct ProjectsManager {
367    storage: StorageManager,
368    identity: EnhancedIdentity,
369    file_chunker: FileChunker,
370}
371
372impl ProjectsManager {
373    /// Create new projects manager
374    pub fn new(storage: StorageManager, identity: EnhancedIdentity) -> Self {
375        Self {
376            storage,
377            identity,
378            file_chunker: FileChunker::new(1024 * 1024), // 1MB chunks
379        }
380    }
381    
382    /// Create a new project
383    pub async fn create_project(
384        &mut self,
385        name: String,
386        description: String,
387        organization_id: OrganizationId,
388        department_id: Option<DepartmentId>,
389        team_id: Option<TeamId>,
390        owner_group: GroupId,
391    ) -> Result<Project> {
392        // Create root folder
393        let root_folder = Folder {
394            id: FolderId::new(),
395            name: "Root".to_string(),
396            parent_id: None,
397            project_id: ProjectId::new(), // Will be updated
398            subfolders: vec![],
399            documents: vec![],
400            created_at: SystemTime::now(),
401            created_by: self.identity.base_identity.user_id.clone(),
402        };
403        
404        let project = Project {
405            id: ProjectId::new(),
406            name,
407            description,
408            organization_id,
409            department_id,
410            team_id,
411            owner_group,
412            access_groups: vec![],
413            root_folder: root_folder.id.clone(),
414            settings: ProjectSettings::default(),
415            metadata: ProjectMetadata {
416                total_documents: 0,
417                total_size_bytes: 0,
418                last_activity: SystemTime::now(),
419                active_users: 1,
420                custom_fields: HashMap::new(),
421            },
422            created_at: SystemTime::now(),
423            created_by: self.identity.base_identity.user_id.clone(),
424        };
425        
426        // Store project
427        let key = keys::project(&project.id.0);
428        self.storage.store_encrypted(
429            &key,
430            &project,
431            ttl::PROFILE,
432            None,
433        ).await?;
434        
435        // Store root folder
436        let folder_key = format!("project:folder:{}", root_folder.id.0);
437        self.storage.store_encrypted(
438            &folder_key,
439            &root_folder,
440            ttl::PROFILE,
441            None,
442        ).await?;
443        
444        Ok(project)
445    }
446    
447    /// Upload a document
448    pub async fn upload_document(
449        &mut self,
450        project_id: ProjectId,
451        folder_id: FolderId,
452        name: String,
453        description: String,
454        content: &[u8],
455        document_type: DocumentType,
456    ) -> Result<Document> {
457        // Verify project access
458        let project = self.get_project(&project_id).await?;
459        self.check_project_permission(&project, ProjectPermission::Write)?;
460        
461        // Check file size
462        let size_mb = content.len() / (1024 * 1024);
463        if size_mb as u64 > project.settings.max_file_size_mb {
464            return Err(ProjectsError::InvalidOperation(
465                format!("File too large: {}MB (max: {}MB)", size_mb, project.settings.max_file_size_mb)
466            ));
467        }
468        
469        // Calculate content hash
470        let mut hasher = Hasher::new();
471        hasher.update(content);
472        let content_hash = hasher.finalize().into();
473        
474        // Generate encryption key for this document
475        let doc_key = rand::random::<[u8; 32]>();
476        
477        // Encrypt content with document key
478        let encrypted_content = self.encrypt_content(content, &doc_key)?;
479        
480        // Encrypt document key with project key (simplified)
481        let encrypted_key = EncryptedKey {
482            ciphertext: doc_key.to_vec(), // In practice, properly encrypt this
483            nonce: vec![0; 12],
484        };
485        
486        // Create document
487        let document = Document {
488            id: DocumentId::new(),
489            name,
490            description,
491            folder_id,
492            project_id: project_id.clone(),
493            document_type,
494            current_version: DocumentVersion {
495                version_number: 1,
496                content_hash,
497                encryption_key: encrypted_key.clone(),
498                size_bytes: content.len() as u64,
499                author: self.identity.base_identity.user_id.clone(),
500                signatures: vec![],
501                comment: "Initial upload".to_string(),
502                created_at: SystemTime::now(),
503                is_approved: !project.settings.require_approval,
504            },
505            versions: vec![],
506            access_log: vec![AccessLogEntry {
507                user_id: self.identity.base_identity.user_id.clone(),
508                action: AccessAction::Edit,
509                timestamp: SystemTime::now(),
510                ip_address: None,
511                device_id: None,
512            }],
513            workflow_state: if project.settings.require_approval {
514                Some(WorkflowState {
515                    workflow_id: Uuid::new_v4().to_string(),
516                    current_stage: WorkflowStage::Draft,
517                    approvers: vec![],
518                    approvals: vec![],
519                    required_approvals: project.settings.approval_threshold,
520                    deadline: None,
521                    created_at: SystemTime::now(),
522                })
523            } else {
524                None
525            },
526            tags: vec![],
527            created_at: SystemTime::now(),
528            created_by: self.identity.base_identity.user_id.clone(),
529        };
530        
531        // Store document metadata
532        let doc_key = keys::document_meta(&document.id.0);
533        self.storage.store_encrypted(
534            &doc_key,
535            &document,
536            ttl::PROFILE,
537            None,
538        ).await?;
539        
540        // Store document content using chunker
541        let file_metadata = FileMetadata {
542            file_id: document.id.0.clone(),
543            name: document.name.clone(),
544            size: content.len() as u64,
545            mime_type: match &document.document_type {
546                DocumentType::Other { mime_type } => mime_type.clone(),
547                _ => "application/octet-stream".to_string(),
548            },
549            hash: content_hash.to_vec(),
550            total_chunks: 0, // Will be set by chunker
551            created_at: SystemTime::now(),
552            created_by: self.identity.base_identity.user_id.clone(),
553        };
554        
555        self.file_chunker.store_file(
556            &mut self.storage,
557            &document.id.0,
558            &encrypted_content,
559            file_metadata,
560        ).await?;
561        
562        // Update project metadata
563        self.update_project_metadata(&project_id, 1, content.len() as i64).await?;
564        
565        Ok(document)
566    }
567    
568    /// Download a document
569    pub async fn download_document(
570        &mut self,
571        document_id: &DocumentId,
572    ) -> Result<Vec<u8>> {
573        // Get document metadata
574        let document = self.get_document(document_id).await?;
575        
576        // Check access
577        let project = self.get_project(&document.project_id).await?;
578        self.check_project_permission(&project, ProjectPermission::Read)?;
579        
580        // Log access
581        self.log_document_access(document_id, AccessAction::Download).await?;
582        
583        // Retrieve document content
584        let encrypted_content = self.file_chunker.get_file(
585            &self.storage,
586            &document_id.0,
587        ).await?;
588        
589        // Decrypt content (simplified - in practice, decrypt the doc key first)
590        let doc_key = &document.current_version.encryption_key.ciphertext;
591        let content = self.decrypt_content(&encrypted_content, doc_key)?;
592        
593        Ok(content)
594    }
595    
596    /// Create a new version of a document
597    pub async fn create_document_version(
598        &mut self,
599        document_id: &DocumentId,
600        content: &[u8],
601        comment: String,
602    ) -> Result<DocumentVersion> {
603        let mut document = self.get_document(document_id).await?;
604        
605        // Check write permission
606        let project = self.get_project(&document.project_id).await?;
607        self.check_project_permission(&project, ProjectPermission::Write)?;
608        
609        // Move current version to history
610        document.versions.push(document.current_version.clone());
611        
612        // Create new version
613        let mut hasher = Hasher::new();
614        hasher.update(content);
615        let content_hash = hasher.finalize().into();
616        
617        let new_version = DocumentVersion {
618            version_number: document.current_version.version_number + 1,
619            content_hash,
620            encryption_key: document.current_version.encryption_key.clone(), // Reuse key
621            size_bytes: content.len() as u64,
622            author: self.identity.base_identity.user_id.clone(),
623            signatures: vec![],
624            comment,
625            created_at: SystemTime::now(),
626            is_approved: !project.settings.require_approval,
627        };
628        
629        document.current_version = new_version.clone();
630        
631        // Update workflow state if needed
632        if project.settings.require_approval {
633            document.workflow_state = Some(WorkflowState {
634                workflow_id: Uuid::new_v4().to_string(),
635                current_stage: WorkflowStage::UnderReview,
636                approvers: vec![],
637                approvals: vec![],
638                required_approvals: project.settings.approval_threshold,
639                deadline: None,
640                created_at: SystemTime::now(),
641            });
642        }
643        
644        // Store updated document
645        let doc_key = keys::document_meta(&document_id.0);
646        self.storage.store_encrypted(
647            &doc_key,
648            &document,
649            ttl::PROFILE,
650            None,
651        ).await?;
652        
653        // Store new content
654        let encrypted_content = self.encrypt_content(content, &document.current_version.encryption_key.ciphertext)?;
655        let file_metadata = FileMetadata {
656            file_id: format!("{}_v{}", document_id.0, new_version.version_number),
657            name: document.name.clone(),
658            size: content.len() as u64,
659            mime_type: "application/octet-stream".to_string(),
660            hash: content_hash.to_vec(),
661            total_chunks: 0,
662            created_at: SystemTime::now(),
663            created_by: self.identity.base_identity.user_id.clone(),
664        };
665        
666        self.file_chunker.store_file(
667            &mut self.storage,
668            &file_metadata.file_id,
669            &encrypted_content,
670            file_metadata.clone(),
671        ).await?;
672        
673        Ok(new_version)
674    }
675    
676    /// Approve a document
677    pub async fn approve_document(
678        &mut self,
679        document_id: &DocumentId,
680        comment: Option<String>,
681    ) -> Result<()> {
682        let mut document = self.get_document(document_id).await?;
683        
684        // Check approval permission
685        let project = self.get_project(&document.project_id).await?;
686        self.check_project_permission(&project, ProjectPermission::ApproveDocuments)?;
687        
688        // Update workflow state
689        if let Some(ref mut workflow) = document.workflow_state {
690            // Add approval (simplified - would use threshold signature)
691            workflow.approvals.push(Approval {
692                approver_id: self.identity.base_identity.user_id.clone(),
693                decision: ApprovalDecision::Approve,
694                signature: vec![0; 64], // Placeholder
695                comment,
696                approved_at: SystemTime::now(),
697            });
698            
699            // Check if enough approvals
700            if workflow.approvals.len() >= workflow.required_approvals as usize {
701                workflow.current_stage = WorkflowStage::Approved;
702                document.current_version.is_approved = true;
703            }
704        }
705        
706        // Store updated document
707        let doc_key = keys::document_meta(&document_id.0);
708        self.storage.store_encrypted(
709            &doc_key,
710            &document,
711            ttl::PROFILE,
712            None,
713        ).await?;
714        
715        Ok(())
716    }
717    
718    /// Get project by ID
719    async fn get_project(&self, project_id: &ProjectId) -> Result<Project> {
720        let key = keys::project(&project_id.0);
721        self.storage.get_encrypted(&key).await
722            .map_err(|_| ProjectsError::ProjectNotFound(project_id.0.clone()))
723    }
724    
725    /// Get document by ID
726    async fn get_document(&self, document_id: &DocumentId) -> Result<Document> {
727        let key = keys::document_meta(&document_id.0);
728        self.storage.get_encrypted(&key).await
729            .map_err(|_| ProjectsError::DocumentNotFound(document_id.0.clone()))
730    }
731    
732    /// Check project permission
733    fn check_project_permission(
734        &self,
735        project: &Project,
736        permission: ProjectPermission,
737    ) -> Result<()> {
738        // Simplified permission check
739        // In practice, would check threshold groups and access groups
740        Ok(())
741    }
742    
743    /// Log document access
744    async fn log_document_access(
745        &mut self,
746        document_id: &DocumentId,
747        action: AccessAction,
748    ) -> Result<()> {
749        let mut document = self.get_document(document_id).await?;
750        
751        document.access_log.push(AccessLogEntry {
752            user_id: self.identity.base_identity.user_id.clone(),
753            action,
754            timestamp: SystemTime::now(),
755            ip_address: None,
756            device_id: None,
757        });
758        
759        // Keep log size reasonable
760        if document.access_log.len() > 1000 {
761            document.access_log.drain(0..100);
762        }
763        
764        let doc_key = keys::document_meta(&document_id.0);
765        self.storage.store_encrypted(
766            &doc_key,
767            &document,
768            ttl::PROFILE,
769            None,
770        ).await?;
771        
772        Ok(())
773    }
774    
775    /// Update project metadata
776    async fn update_project_metadata(
777        &mut self,
778        project_id: &ProjectId,
779        doc_delta: i64,
780        size_delta: i64,
781    ) -> Result<()> {
782        let mut project = self.get_project(project_id).await?;
783        
784        project.metadata.total_documents = 
785            (project.metadata.total_documents as i64 + doc_delta) as u64;
786        project.metadata.total_size_bytes = 
787            (project.metadata.total_size_bytes as i64 + size_delta) as u64;
788        project.metadata.last_activity = SystemTime::now();
789        
790        let key = keys::project(&project_id.0);
791        self.storage.store_encrypted(
792            &key,
793            &project,
794            ttl::PROFILE,
795            None,
796        ).await?;
797        
798        Ok(())
799    }
800    
801    /// Encrypt content using AES-GCM
802    fn encrypt_content(&self, content: &[u8], key: &[u8]) -> Result<Vec<u8>> {
803        use aes_gcm::{Aes256Gcm, Key, Nonce, AeadInPlace, KeyInit};
804        use rand::RngCore;
805        
806        // Removed unused P2PError import
807        
808        // Ensure key is exactly 32 bytes for AES-256
809        if key.len() != 32 {
810            return Err(ProjectsError::InvalidOperation("Invalid encryption key length - must be 32 bytes".to_string()));
811        }
812        
813        let cipher_key = aes_gcm::Key::<Aes256Gcm>::from_slice(key);
814        let cipher = Aes256Gcm::new(cipher_key);
815        
816        // Generate random 96-bit nonce
817        let mut nonce_bytes = [0u8; 12];
818        rand::thread_rng().fill_bytes(&mut nonce_bytes);
819        let nonce = Nonce::from_slice(&nonce_bytes);
820        
821        // Encrypt the data
822        let mut ciphertext = content.to_vec();
823        let tag = cipher.encrypt_in_place_detached(nonce, b"", &mut ciphertext)
824            .map_err(|e| ProjectsError::InvalidOperation(format!("Encryption failed: {}", e)))?;
825        
826        // Combine nonce + ciphertext + tag
827        let mut result = Vec::with_capacity(12 + ciphertext.len() + 16);
828        result.extend_from_slice(&nonce_bytes);
829        result.extend_from_slice(&ciphertext);
830        result.extend_from_slice(&tag);
831        
832        Ok(result)
833    }
834    
835    /// Decrypt content using AES-GCM
836    fn decrypt_content(&self, encrypted: &[u8], key: &[u8]) -> Result<Vec<u8>> {
837        use aes_gcm::{Aes256Gcm, Key, Nonce, AeadInPlace, KeyInit};
838        // Removed unused P2PError import
839        
840        // Ensure key is exactly 32 bytes for AES-256
841        if key.len() != 32 {
842            return Err(ProjectsError::InvalidOperation("Invalid decryption key length - must be 32 bytes".to_string()));
843        }
844        
845        // Minimum size: 12 (nonce) + 16 (tag) = 28 bytes
846        if encrypted.len() < 28 {
847            return Err(ProjectsError::InvalidOperation("Invalid encrypted data - too short".to_string()));
848        }
849        
850        let cipher_key = aes_gcm::Key::<Aes256Gcm>::from_slice(key);
851        let cipher = Aes256Gcm::new(cipher_key);
852        
853        // Extract nonce (first 12 bytes)
854        let nonce = Nonce::from_slice(&encrypted[0..12]);
855        
856        // Extract tag (last 16 bytes)
857        let tag_start = encrypted.len() - 16;
858        let tag = &encrypted[tag_start..];
859        
860        // Extract ciphertext (middle portion)
861        let mut plaintext = encrypted[12..tag_start].to_vec();
862        
863        // Decrypt the data
864        cipher.decrypt_in_place_detached(nonce, b"", &mut plaintext, tag.into())
865            .map_err(|e| ProjectsError::InvalidOperation(format!("Decryption failed: {}", e)))?;
866        
867        Ok(plaintext)
868    }
869}