1use crate::identity::enhanced::{DepartmentId, EnhancedIdentity, OrganizationId, TeamId};
25use crate::quantum_crypto::types::GroupId;
26use crate::storage::{FileChunker, FileMetadata, StorageManager, keys, ttl};
27use crate::threshold::ThresholdSignature;
28use blake3::Hasher;
29use serde::{Deserialize, Serialize};
30use std::collections::HashMap;
31use std::time::SystemTime;
32use thiserror::Error;
33use uuid::Uuid;
34
35#[derive(Debug, Error)]
40pub enum ProjectsError {
41 #[error("Storage error: {0}")]
43 StorageError(#[from] crate::storage::StorageError),
44
45 #[error("Project not found: {0}")]
47 ProjectNotFound(String),
48
49 #[error("Document not found: {0}")]
51 DocumentNotFound(String),
52
53 #[error("Permission denied: {0}")]
55 PermissionDenied(String),
56
57 #[error("Invalid operation: {0}")]
59 InvalidOperation(String),
60
61 #[error("Workflow error: {0}")]
63 WorkflowError(String),
64}
65
66type Result<T> = std::result::Result<T, ProjectsError>;
68
69#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
74pub struct ProjectId(pub String);
75
76impl Default for ProjectId {
77 fn default() -> Self {
78 Self::new()
79 }
80}
81
82impl ProjectId {
83 pub fn new() -> Self {
88 Self(Uuid::new_v4().to_string())
89 }
90}
91
92#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
97pub struct DocumentId(pub String);
98
99impl Default for DocumentId {
100 fn default() -> Self {
101 Self::new()
102 }
103}
104
105impl DocumentId {
106 pub fn new() -> Self {
111 Self(Uuid::new_v4().to_string())
112 }
113}
114
115#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
120pub struct FolderId(pub String);
121
122impl Default for FolderId {
123 fn default() -> Self {
124 Self::new()
125 }
126}
127
128impl FolderId {
129 pub fn new() -> Self {
134 Self(Uuid::new_v4().to_string())
135 }
136}
137
138pub type UserId = String;
143
144pub type Blake3Hash = [u8; 32];
149
150#[derive(Debug, Clone, Serialize, Deserialize)]
156pub struct Project {
157 pub id: ProjectId,
159 pub name: String,
161 pub description: String,
163 pub organization_id: OrganizationId,
165 pub department_id: Option<DepartmentId>,
167 pub team_id: Option<TeamId>,
169 pub owner_group: GroupId,
171 pub access_groups: Vec<AccessGroup>,
173 pub root_folder: FolderId,
175 pub settings: ProjectSettings,
177 pub metadata: ProjectMetadata,
179 pub created_at: SystemTime,
181 pub created_by: UserId,
183}
184
185#[derive(Debug, Clone, Serialize, Deserialize)]
187pub struct AccessGroup {
188 pub group_id: GroupId,
189 pub permissions: Vec<ProjectPermission>,
190 pub name: String,
191}
192
193#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
195pub enum ProjectPermission {
196 Read,
197 Write,
198 Delete,
199 Share,
200 ManageMembers,
201 ManageWorkflows,
202 ApproveDocuments,
203 ViewAnalytics,
204}
205
206#[derive(Debug, Clone, Serialize, Deserialize)]
208pub struct ProjectSettings {
209 pub require_approval: bool,
210 pub approval_threshold: u16,
211 pub version_control: bool,
212 pub max_file_size_mb: u64,
213 pub allowed_file_types: Vec<String>,
214 pub retention_days: Option<u32>,
215 pub enable_watermarks: bool,
216 pub enable_analytics: bool,
217}
218
219impl Default for ProjectSettings {
220 fn default() -> Self {
221 Self {
222 require_approval: false,
223 approval_threshold: 1,
224 version_control: true,
225 max_file_size_mb: 1024, allowed_file_types: vec![],
227 retention_days: None,
228 enable_watermarks: false,
229 enable_analytics: true,
230 }
231 }
232}
233
234#[derive(Debug, Clone, Serialize, Deserialize)]
236pub struct ProjectMetadata {
237 pub total_documents: u64,
238 pub total_size_bytes: u64,
239 pub last_activity: SystemTime,
240 pub active_users: u32,
241 pub custom_fields: HashMap<String, String>,
242}
243
244#[derive(Debug, Clone, Serialize, Deserialize)]
246pub struct Folder {
247 pub id: FolderId,
248 pub name: String,
249 pub parent_id: Option<FolderId>,
250 pub project_id: ProjectId,
251 pub subfolders: Vec<FolderId>,
252 pub documents: Vec<DocumentId>,
253 pub created_at: SystemTime,
254 pub created_by: UserId,
255}
256
257#[derive(Debug, Clone, Serialize, Deserialize)]
259pub struct Document {
260 pub id: DocumentId,
261 pub name: String,
262 pub description: String,
263 pub folder_id: FolderId,
264 pub project_id: ProjectId,
265 pub document_type: DocumentType,
266 pub current_version: DocumentVersion,
267 pub versions: Vec<DocumentVersion>,
268 pub access_log: Vec<AccessLogEntry>,
269 pub workflow_state: Option<WorkflowState>,
270 pub tags: Vec<String>,
271 pub created_at: SystemTime,
272 pub created_by: UserId,
273}
274
275#[derive(Debug, Clone, Serialize, Deserialize)]
277pub enum DocumentType {
278 Text { format: String },
279 Spreadsheet,
280 Presentation,
281 Image { width: u32, height: u32 },
282 Video { duration_seconds: u64 },
283 Audio { duration_seconds: u64 },
284 PDF { pages: u32 },
285 Archive,
286 Other { mime_type: String },
287}
288
289#[derive(Debug, Clone, Serialize, Deserialize)]
291pub struct DocumentVersion {
292 pub version_number: u64,
293 pub content_hash: Blake3Hash,
294 pub encryption_key: EncryptedKey,
295 pub size_bytes: u64,
296 pub author: UserId,
297 pub signatures: Vec<VersionSignature>,
298 pub comment: String,
299 pub created_at: SystemTime,
300 pub is_approved: bool,
301}
302
303#[derive(Debug, Clone, Serialize, Deserialize)]
305pub struct EncryptedKey {
306 pub ciphertext: Vec<u8>,
307 pub nonce: Vec<u8>,
308}
309
310#[derive(Debug, Clone, Serialize, Deserialize)]
312pub struct VersionSignature {
313 pub signer_id: UserId,
314 pub signature: ThresholdSignature,
315 pub signed_at: SystemTime,
316 pub comment: Option<String>,
317}
318
319#[derive(Debug, Clone, Serialize, Deserialize)]
321pub struct AccessLogEntry {
322 pub user_id: UserId,
323 pub action: AccessAction,
324 pub timestamp: SystemTime,
325 pub ip_address: Option<String>,
326 pub device_id: Option<String>,
327}
328
329#[derive(Debug, Clone, Serialize, Deserialize)]
331pub enum AccessAction {
332 View,
333 Download,
334 Edit,
335 Share,
336 Delete,
337 Approve,
338 Reject,
339}
340
341#[derive(Debug, Clone, Serialize, Deserialize)]
343pub struct WorkflowState {
344 pub workflow_id: String,
345 pub current_stage: WorkflowStage,
346 pub approvers: Vec<UserId>,
347 pub approvals: Vec<Approval>,
348 pub required_approvals: u16,
349 pub deadline: Option<SystemTime>,
350 pub created_at: SystemTime,
351}
352
353#[derive(Debug, Clone, Serialize, Deserialize)]
355pub enum WorkflowStage {
356 Draft,
357 UnderReview,
358 Approved,
359 Rejected,
360 Published,
361}
362
363#[derive(Debug, Clone, Serialize, Deserialize)]
365pub struct Approval {
366 pub approver_id: UserId,
367 pub decision: ApprovalDecision,
368 pub signature: ThresholdSignature,
369 pub comment: Option<String>,
370 pub approved_at: SystemTime,
371}
372
373#[derive(Debug, Clone, Serialize, Deserialize)]
375pub enum ApprovalDecision {
376 Approve,
377 Reject,
378 RequestChanges,
379}
380
381#[derive(Debug, Clone, Serialize, Deserialize, Default)]
383pub struct ProjectAnalytics {
384 pub total_views: u64,
385 pub total_downloads: u64,
386 pub unique_viewers: u64,
387 pub average_time_spent_seconds: u64,
388 pub most_active_users: Vec<(UserId, u64)>,
389 pub popular_documents: Vec<(DocumentId, u64)>,
390 pub storage_trend: Vec<(SystemTime, u64)>,
391 pub activity_heatmap: HashMap<u32, u64>, }
393
394pub struct ProjectsManager {
396 storage: StorageManager,
397 identity: EnhancedIdentity,
398 file_chunker: FileChunker,
399}
400
401impl ProjectsManager {
402 pub fn new(storage: StorageManager, identity: EnhancedIdentity) -> Self {
404 Self {
405 storage,
406 identity,
407 file_chunker: FileChunker::new(1024 * 1024), }
409 }
410
411 pub async fn create_project(
413 &mut self,
414 name: String,
415 description: String,
416 organization_id: OrganizationId,
417 department_id: Option<DepartmentId>,
418 team_id: Option<TeamId>,
419 owner_group: GroupId,
420 ) -> Result<Project> {
421 let root_folder = Folder {
423 id: FolderId::new(),
424 name: "Root".to_string(),
425 parent_id: None,
426 project_id: ProjectId::new(), subfolders: vec![],
428 documents: vec![],
429 created_at: SystemTime::now(),
430 created_by: self.identity.base_identity.user_id.clone(),
431 };
432
433 let project = Project {
434 id: ProjectId::new(),
435 name,
436 description,
437 organization_id,
438 department_id,
439 team_id,
440 owner_group,
441 access_groups: vec![],
442 root_folder: root_folder.id.clone(),
443 settings: ProjectSettings::default(),
444 metadata: ProjectMetadata {
445 total_documents: 0,
446 total_size_bytes: 0,
447 last_activity: SystemTime::now(),
448 active_users: 1,
449 custom_fields: HashMap::new(),
450 },
451 created_at: SystemTime::now(),
452 created_by: self.identity.base_identity.user_id.clone(),
453 };
454
455 let key = keys::project(&project.id.0);
457 self.storage
458 .store_encrypted(&key, &project, ttl::PROFILE, None)
459 .await?;
460
461 let folder_key = format!("project:folder:{}", root_folder.id.0);
463 self.storage
464 .store_encrypted(&folder_key, &root_folder, ttl::PROFILE, None)
465 .await?;
466
467 Ok(project)
468 }
469
470 pub async fn upload_document(
472 &mut self,
473 project_id: ProjectId,
474 folder_id: FolderId,
475 name: String,
476 description: String,
477 content: &[u8],
478 document_type: DocumentType,
479 ) -> Result<Document> {
480 let project = self.get_project(&project_id).await?;
482 self.check_project_permission(&project, ProjectPermission::Write)?;
483
484 let size_mb = content.len() / (1024 * 1024);
486 if size_mb as u64 > project.settings.max_file_size_mb {
487 return Err(ProjectsError::InvalidOperation(format!(
488 "File too large: {}MB (max: {}MB).into()",
489 size_mb, project.settings.max_file_size_mb
490 )));
491 }
492
493 let mut hasher = Hasher::new();
495 hasher.update(content);
496 let content_hash = hasher.finalize().into();
497
498 let doc_key = rand::random::<[u8; 32]>();
500
501 let encrypted_content = self.encrypt_content(content, &doc_key)?;
503
504 let encrypted_key = EncryptedKey {
506 ciphertext: doc_key.to_vec(), nonce: vec![0; 12],
508 };
509
510 let document = Document {
512 id: DocumentId::new(),
513 name,
514 description,
515 folder_id,
516 project_id: project_id.clone(),
517 document_type,
518 current_version: DocumentVersion {
519 version_number: 1,
520 content_hash,
521 encryption_key: encrypted_key.clone(),
522 size_bytes: content.len() as u64,
523 author: self.identity.base_identity.user_id.clone(),
524 signatures: vec![],
525 comment: "Initial upload".to_string(),
526 created_at: SystemTime::now(),
527 is_approved: !project.settings.require_approval,
528 },
529 versions: vec![],
530 access_log: vec![AccessLogEntry {
531 user_id: self.identity.base_identity.user_id.clone(),
532 action: AccessAction::Edit,
533 timestamp: SystemTime::now(),
534 ip_address: None,
535 device_id: None,
536 }],
537 workflow_state: if project.settings.require_approval {
538 Some(WorkflowState {
539 workflow_id: Uuid::new_v4().to_string(),
540 current_stage: WorkflowStage::Draft,
541 approvers: vec![],
542 approvals: vec![],
543 required_approvals: project.settings.approval_threshold,
544 deadline: None,
545 created_at: SystemTime::now(),
546 })
547 } else {
548 None
549 },
550 tags: vec![],
551 created_at: SystemTime::now(),
552 created_by: self.identity.base_identity.user_id.clone(),
553 };
554
555 let doc_key = keys::document_meta(&document.id.0);
557 self.storage
558 .store_encrypted(&doc_key, &document, ttl::PROFILE, None)
559 .await?;
560
561 let file_metadata = FileMetadata {
563 file_id: document.id.0.clone(),
564 name: document.name.clone(),
565 size: content.len() as u64,
566 mime_type: match &document.document_type {
567 DocumentType::Other { mime_type } => mime_type.clone(),
568 _ => "application/octet-stream".to_string(),
569 },
570 hash: content_hash.to_vec(),
571 total_chunks: 0, created_at: SystemTime::now(),
573 created_by: self.identity.base_identity.user_id.clone(),
574 };
575
576 self.file_chunker
577 .store_file(
578 &mut self.storage,
579 &document.id.0,
580 &encrypted_content,
581 file_metadata,
582 )
583 .await?;
584
585 self.update_project_metadata(&project_id, 1, content.len() as i64)
587 .await?;
588
589 Ok(document)
590 }
591
592 pub async fn download_document(&mut self, document_id: &DocumentId) -> Result<Vec<u8>> {
594 let document = self.get_document(document_id).await?;
596
597 let project = self.get_project(&document.project_id).await?;
599 self.check_project_permission(&project, ProjectPermission::Read)?;
600
601 self.log_document_access(document_id, AccessAction::Download)
603 .await?;
604
605 let encrypted_content = self
607 .file_chunker
608 .get_file(&self.storage, &document_id.0)
609 .await?;
610
611 let doc_key = &document.current_version.encryption_key.ciphertext;
613 let content = self.decrypt_content(&encrypted_content, doc_key)?;
614
615 Ok(content)
616 }
617
618 pub async fn create_document_version(
620 &mut self,
621 document_id: &DocumentId,
622 content: &[u8],
623 comment: String,
624 ) -> Result<DocumentVersion> {
625 let mut document = self.get_document(document_id).await?;
626
627 let project = self.get_project(&document.project_id).await?;
629 self.check_project_permission(&project, ProjectPermission::Write)?;
630
631 document.versions.push(document.current_version.clone());
633
634 let mut hasher = Hasher::new();
636 hasher.update(content);
637 let content_hash = hasher.finalize().into();
638
639 let new_version = DocumentVersion {
640 version_number: document.current_version.version_number + 1,
641 content_hash,
642 encryption_key: document.current_version.encryption_key.clone(), size_bytes: content.len() as u64,
644 author: self.identity.base_identity.user_id.clone(),
645 signatures: vec![],
646 comment,
647 created_at: SystemTime::now(),
648 is_approved: !project.settings.require_approval,
649 };
650
651 document.current_version = new_version.clone();
652
653 if project.settings.require_approval {
655 document.workflow_state = Some(WorkflowState {
656 workflow_id: Uuid::new_v4().to_string(),
657 current_stage: WorkflowStage::UnderReview,
658 approvers: vec![],
659 approvals: vec![],
660 required_approvals: project.settings.approval_threshold,
661 deadline: None,
662 created_at: SystemTime::now(),
663 });
664 }
665
666 let doc_key = keys::document_meta(&document_id.0);
668 self.storage
669 .store_encrypted(&doc_key, &document, ttl::PROFILE, None)
670 .await?;
671
672 let encrypted_content =
674 self.encrypt_content(content, &document.current_version.encryption_key.ciphertext)?;
675 let file_metadata = FileMetadata {
676 file_id: format!("{}_v{}", document_id.0, new_version.version_number),
677 name: document.name.clone(),
678 size: content.len() as u64,
679 mime_type: "application/octet-stream".to_string(),
680 hash: content_hash.to_vec(),
681 total_chunks: 0,
682 created_at: SystemTime::now(),
683 created_by: self.identity.base_identity.user_id.clone(),
684 };
685
686 self.file_chunker
687 .store_file(
688 &mut self.storage,
689 &file_metadata.file_id,
690 &encrypted_content,
691 file_metadata.clone(),
692 )
693 .await?;
694
695 Ok(new_version)
696 }
697
698 pub async fn approve_document(
700 &mut self,
701 document_id: &DocumentId,
702 comment: Option<String>,
703 ) -> Result<()> {
704 let mut document = self.get_document(document_id).await?;
705
706 let project = self.get_project(&document.project_id).await?;
708 self.check_project_permission(&project, ProjectPermission::ApproveDocuments)?;
709
710 if let Some(ref mut workflow) = document.workflow_state {
712 workflow.approvals.push(Approval {
714 approver_id: self.identity.base_identity.user_id.clone(),
715 decision: ApprovalDecision::Approve,
716 signature: vec![0; 64], comment,
718 approved_at: SystemTime::now(),
719 });
720
721 if workflow.approvals.len() >= workflow.required_approvals as usize {
723 workflow.current_stage = WorkflowStage::Approved;
724 document.current_version.is_approved = true;
725 }
726 }
727
728 let doc_key = keys::document_meta(&document_id.0);
730 self.storage
731 .store_encrypted(&doc_key, &document, ttl::PROFILE, None)
732 .await?;
733
734 Ok(())
735 }
736
737 async fn get_project(&self, project_id: &ProjectId) -> Result<Project> {
739 let key = keys::project(&project_id.0);
740 self.storage
741 .get_encrypted(&key)
742 .await
743 .map_err(|_| ProjectsError::ProjectNotFound(project_id.0.clone()))
744 }
745
746 async fn get_document(&self, document_id: &DocumentId) -> Result<Document> {
748 let key = keys::document_meta(&document_id.0);
749 self.storage
750 .get_encrypted(&key)
751 .await
752 .map_err(|_| ProjectsError::DocumentNotFound(document_id.0.clone()))
753 }
754
755 fn check_project_permission(
757 &self,
758 _project: &Project,
759 _permission: ProjectPermission,
760 ) -> Result<()> {
761 Ok(())
764 }
765
766 async fn log_document_access(
768 &mut self,
769 document_id: &DocumentId,
770 action: AccessAction,
771 ) -> Result<()> {
772 let mut document = self.get_document(document_id).await?;
773
774 document.access_log.push(AccessLogEntry {
775 user_id: self.identity.base_identity.user_id.clone(),
776 action,
777 timestamp: SystemTime::now(),
778 ip_address: None,
779 device_id: None,
780 });
781
782 if document.access_log.len() > 1000 {
784 document.access_log.drain(0..100);
785 }
786
787 let doc_key = keys::document_meta(&document_id.0);
788 self.storage
789 .store_encrypted(&doc_key, &document, ttl::PROFILE, None)
790 .await?;
791
792 Ok(())
793 }
794
795 async fn update_project_metadata(
797 &mut self,
798 project_id: &ProjectId,
799 doc_delta: i64,
800 size_delta: i64,
801 ) -> Result<()> {
802 let mut project = self.get_project(project_id).await?;
803
804 project.metadata.total_documents =
805 (project.metadata.total_documents as i64 + doc_delta) as u64;
806 project.metadata.total_size_bytes =
807 (project.metadata.total_size_bytes as i64 + size_delta) as u64;
808 project.metadata.last_activity = SystemTime::now();
809
810 let key = keys::project(&project_id.0);
811 self.storage
812 .store_encrypted(&key, &project, ttl::PROFILE, None)
813 .await?;
814
815 Ok(())
816 }
817
818 fn encrypt_content(&self, content: &[u8], key: &[u8]) -> Result<Vec<u8>> {
820 use aes_gcm::{AeadInPlace, Aes256Gcm, KeyInit, Nonce};
821 use rand::RngCore;
822
823 if key.len() != 32 {
827 return Err(ProjectsError::InvalidOperation(
828 "Invalid encryption key length - must be 32 bytes".to_string(),
829 ));
830 }
831
832 let cipher_key = aes_gcm::Key::<Aes256Gcm>::from_slice(key);
833 let cipher = Aes256Gcm::new(cipher_key);
834
835 let mut nonce_bytes = [0u8; 12];
837 rand::thread_rng().fill_bytes(&mut nonce_bytes);
838 let nonce = Nonce::from_slice(&nonce_bytes);
839
840 let mut ciphertext = content.to_vec();
842 let tag = cipher
843 .encrypt_in_place_detached(nonce, b"", &mut ciphertext)
844 .map_err(|e| ProjectsError::InvalidOperation(format!("Encryption failed: {e}")))?;
845
846 let mut result = Vec::with_capacity(12 + ciphertext.len() + 16);
848 result.extend_from_slice(&nonce_bytes);
849 result.extend_from_slice(&ciphertext);
850 result.extend_from_slice(&tag);
851
852 Ok(result)
853 }
854
855 fn decrypt_content(&self, encrypted: &[u8], key: &[u8]) -> Result<Vec<u8>> {
857 use aes_gcm::{AeadInPlace, Aes256Gcm, KeyInit, Nonce};
858 if key.len() != 32 {
862 return Err(ProjectsError::InvalidOperation(
863 "Invalid decryption key length - must be 32 bytes".to_string(),
864 ));
865 }
866
867 if encrypted.len() < 28 {
869 return Err(ProjectsError::InvalidOperation(
870 "Invalid encrypted data - too short".to_string(),
871 ));
872 }
873
874 let cipher_key = aes_gcm::Key::<Aes256Gcm>::from_slice(key);
875 let cipher = Aes256Gcm::new(cipher_key);
876
877 let nonce = Nonce::from_slice(&encrypted[0..12]);
879
880 let tag_start = encrypted.len() - 16;
882 let tag = &encrypted[tag_start..];
883
884 let mut plaintext = encrypted[12..tag_start].to_vec();
886
887 cipher
889 .decrypt_in_place_detached(nonce, b"", &mut plaintext, tag.into())
890 .map_err(|e| ProjectsError::InvalidOperation(format!("Decryption failed: {e}")))?;
891
892 Ok(plaintext)
893 }
894}