1use 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#[derive(Debug, Error)]
29pub enum ProjectsError {
30 #[error("Storage error: {0}")]
32 StorageError(#[from] crate::storage::StorageError),
33
34 #[error("Project not found: {0}")]
36 ProjectNotFound(String),
37
38 #[error("Document not found: {0}")]
40 DocumentNotFound(String),
41
42 #[error("Permission denied: {0}")]
44 PermissionDenied(String),
45
46 #[error("Invalid operation: {0}")]
48 InvalidOperation(String),
49
50 #[error("Workflow error: {0}")]
52 WorkflowError(String),
53}
54
55type Result<T> = std::result::Result<T, ProjectsError>;
57
58#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
63pub struct ProjectId(pub String);
64
65impl ProjectId {
66 pub fn new() -> Self {
71 Self(Uuid::new_v4().to_string())
72 }
73}
74
75#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
80pub struct DocumentId(pub String);
81
82impl DocumentId {
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 FolderId(pub String);
98
99impl FolderId {
100 pub fn new() -> Self {
105 Self(Uuid::new_v4().to_string())
106 }
107}
108
109pub type UserId = String;
114
115pub type Blake3Hash = [u8; 32];
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct Project {
128 pub id: ProjectId,
130 pub name: String,
132 pub description: String,
134 pub organization_id: OrganizationId,
136 pub department_id: Option<DepartmentId>,
138 pub team_id: Option<TeamId>,
140 pub owner_group: GroupId,
142 pub access_groups: Vec<AccessGroup>,
144 pub root_folder: FolderId,
146 pub settings: ProjectSettings,
148 pub metadata: ProjectMetadata,
150 pub created_at: SystemTime,
152 pub created_by: UserId,
154}
155
156#[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#[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#[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, allowed_file_types: vec![],
198 retention_days: None,
199 enable_watermarks: false,
200 enable_analytics: true,
201 }
202 }
203}
204
205#[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#[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#[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#[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#[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#[derive(Debug, Clone, Serialize, Deserialize)]
276pub struct EncryptedKey {
277 pub ciphertext: Vec<u8>,
278 pub nonce: Vec<u8>,
279}
280
281#[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#[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#[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#[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#[derive(Debug, Clone, Serialize, Deserialize)]
326pub enum WorkflowStage {
327 Draft,
328 UnderReview,
329 Approved,
330 Rejected,
331 Published,
332}
333
334#[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#[derive(Debug, Clone, Serialize, Deserialize)]
346pub enum ApprovalDecision {
347 Approve,
348 Reject,
349 RequestChanges,
350}
351
352#[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>, }
364
365pub struct ProjectsManager {
367 storage: StorageManager,
368 identity: EnhancedIdentity,
369 file_chunker: FileChunker,
370}
371
372impl ProjectsManager {
373 pub fn new(storage: StorageManager, identity: EnhancedIdentity) -> Self {
375 Self {
376 storage,
377 identity,
378 file_chunker: FileChunker::new(1024 * 1024), }
380 }
381
382 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 let root_folder = Folder {
394 id: FolderId::new(),
395 name: "Root".to_string(),
396 parent_id: None,
397 project_id: ProjectId::new(), 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 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 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 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 let project = self.get_project(&project_id).await?;
459 self.check_project_permission(&project, ProjectPermission::Write)?;
460
461 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 let mut hasher = Hasher::new();
471 hasher.update(content);
472 let content_hash = hasher.finalize().into();
473
474 let doc_key = rand::random::<[u8; 32]>();
476
477 let encrypted_content = self.encrypt_content(content, &doc_key)?;
479
480 let encrypted_key = EncryptedKey {
482 ciphertext: doc_key.to_vec(), nonce: vec![0; 12],
484 };
485
486 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 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 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, 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 self.update_project_metadata(&project_id, 1, content.len() as i64).await?;
564
565 Ok(document)
566 }
567
568 pub async fn download_document(
570 &mut self,
571 document_id: &DocumentId,
572 ) -> Result<Vec<u8>> {
573 let document = self.get_document(document_id).await?;
575
576 let project = self.get_project(&document.project_id).await?;
578 self.check_project_permission(&project, ProjectPermission::Read)?;
579
580 self.log_document_access(document_id, AccessAction::Download).await?;
582
583 let encrypted_content = self.file_chunker.get_file(
585 &self.storage,
586 &document_id.0,
587 ).await?;
588
589 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 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 let project = self.get_project(&document.project_id).await?;
607 self.check_project_permission(&project, ProjectPermission::Write)?;
608
609 document.versions.push(document.current_version.clone());
611
612 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(), 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 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 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 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 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 let project = self.get_project(&document.project_id).await?;
686 self.check_project_permission(&project, ProjectPermission::ApproveDocuments)?;
687
688 if let Some(ref mut workflow) = document.workflow_state {
690 workflow.approvals.push(Approval {
692 approver_id: self.identity.base_identity.user_id.clone(),
693 decision: ApprovalDecision::Approve,
694 signature: vec![0; 64], comment,
696 approved_at: SystemTime::now(),
697 });
698
699 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 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 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 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 fn check_project_permission(
734 &self,
735 project: &Project,
736 permission: ProjectPermission,
737 ) -> Result<()> {
738 Ok(())
741 }
742
743 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 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 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 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 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 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 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 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 fn decrypt_content(&self, encrypted: &[u8], key: &[u8]) -> Result<Vec<u8>> {
837 use aes_gcm::{Aes256Gcm, Key, Nonce, AeadInPlace, KeyInit};
838 if key.len() != 32 {
842 return Err(ProjectsError::InvalidOperation("Invalid decryption key length - must be 32 bytes".to_string()));
843 }
844
845 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 let nonce = Nonce::from_slice(&encrypted[0..12]);
855
856 let tag_start = encrypted.len() - 16;
858 let tag = &encrypted[tag_start..];
859
860 let mut plaintext = encrypted[12..tag_start].to_vec();
862
863 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}