1use crate::error::{OptimError, Result};
7use chrono::{DateTime, Utc};
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10use std::path::PathBuf;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct CollaborativeWorkspace {
15 pub id: String,
17 pub name: String,
19 pub members: Vec<ProjectMember>,
21 pub documents: Vec<SharedDocument>,
23 pub channels: Vec<CommunicationChannel>,
25 pub tasks: Vec<Task>,
27 pub version_control: VersionControl,
29 pub settings: WorkspaceSettings,
31 pub access_control: AccessControl,
33 pub activity_log: Vec<Activity>,
35 pub created_at: DateTime<Utc>,
37 pub modified_at: DateTime<Utc>,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct ProjectMember {
44 pub id: String,
46 pub user: UserInfo,
48 pub role: MemberRole,
50 pub permissions: Vec<Permission>,
52 pub joined_at: DateTime<Utc>,
54 pub last_active: DateTime<Utc>,
56 pub status: MemberStatus,
58 pub contributions: ContributionStats,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct UserInfo {
65 pub name: String,
67 pub email: String,
69 pub institution: String,
71 pub avatar_url: Option<String>,
73 pub timezone: String,
75 pub language: String,
77 pub research_interests: Vec<String>,
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
83pub enum MemberRole {
84 Owner,
86 Admin,
88 PrincipalInvestigator,
90 SeniorResearcher,
92 Researcher,
94 PhDStudent,
96 MastersStudent,
98 ResearchAssistant,
100 Collaborator,
102 Guest,
104 Observer,
106}
107
108#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
110pub enum Permission {
111 Read,
113 Write,
115 Delete,
117 ManageMembers,
119 ManagePermissions,
121 ManageSettings,
123 CreateExperiments,
125 RunExperiments,
127 PublishResults,
129 AccessSensitiveData,
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
135pub enum MemberStatus {
136 Active,
138 Inactive,
140 OnLeave,
142 Suspended,
144 Former,
146}
147
148#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct ContributionStats {
151 pub experiments_created: usize,
153 pub experiments_run: usize,
155 pub lines_of_code: usize,
157 pub documents_authored: usize,
159 pub comments_posted: usize,
161 pub reviews_conducted: usize,
163 pub contribution_score: f64,
165}
166
167#[derive(Debug, Clone, Serialize, Deserialize)]
169pub struct SharedDocument {
170 pub id: String,
172 pub name: String,
174 pub document_type: DocumentType,
176 pub content: String,
178 pub owner_id: String,
180 pub collaborators: Vec<String>,
182 pub version: u32,
184 pub version_history: Vec<DocumentVersion>,
186 pub access_permissions: DocumentPermissions,
188 pub metadata: DocumentMetadata,
190 pub created_at: DateTime<Utc>,
192 pub modified_at: DateTime<Utc>,
194}
195
196#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
198pub enum DocumentType {
199 Manuscript,
201 ExperimentNotes,
203 MeetingNotes,
205 LiteratureReview,
207 ResearchProposal,
209 DataAnalysis,
211 CodeDocumentation,
213 Presentation,
215 Other(String),
217}
218
219#[derive(Debug, Clone, Serialize, Deserialize)]
221pub struct DocumentVersion {
222 pub version: u32,
224 pub author_id: String,
226 pub content: String,
228 pub change_summary: String,
230 pub timestamp: DateTime<Utc>,
232 pub content_hash: String,
234}
235
236#[derive(Debug, Clone, Serialize, Deserialize)]
238pub struct DocumentPermissions {
239 pub public: bool,
241 pub read_access: Vec<String>,
243 pub write_access: Vec<String>,
245 pub admin_access: Vec<String>,
247}
248
249#[derive(Debug, Clone, Serialize, Deserialize)]
251pub struct DocumentMetadata {
252 pub tags: Vec<String>,
254 pub word_count: usize,
256 pub character_count: usize,
258 pub collaborator_count: usize,
260 pub version_count: usize,
262 pub last_editor_id: String,
264}
265
266#[derive(Debug, Clone, Serialize, Deserialize)]
268pub struct CommunicationChannel {
269 pub id: String,
271 pub name: String,
273 pub description: String,
275 pub channel_type: ChannelType,
277 pub members: Vec<String>,
279 pub messages: Vec<Message>,
281 pub settings: ChannelSettings,
283 pub created_at: DateTime<Utc>,
285}
286
287#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
289pub enum ChannelType {
290 General,
292 Experiments,
294 PaperWriting,
296 CodeReview,
298 Announcements,
300 Random,
302 Private,
304 DirectMessage,
306}
307
308#[derive(Debug, Clone, Serialize, Deserialize)]
310pub struct Message {
311 pub id: String,
313 pub author_id: String,
315 pub content: String,
317 pub message_type: MessageType,
319 pub attachments: Vec<Attachment>,
321 pub replies: Vec<Message>,
323 pub reactions: Vec<Reaction>,
325 pub mentions: Vec<String>,
327 pub thread_id: Option<String>,
329 pub timestamp: DateTime<Utc>,
331 pub edit_history: Vec<MessageEdit>,
333}
334
335#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
337pub enum MessageType {
338 Text,
340 Code,
342 File,
344 System,
346 ExperimentResult,
348 TaskAssignment,
350 MeetingInvitation,
352}
353
354#[derive(Debug, Clone, Serialize, Deserialize)]
356pub struct Attachment {
357 pub filename: String,
359 pub size: usize,
361 pub mime_type: String,
363 pub file_path: String,
365 pub file_hash: String,
367 pub uploaded_at: DateTime<Utc>,
369}
370
371#[derive(Debug, Clone, Serialize, Deserialize)]
373pub struct Reaction {
374 pub emoji: String,
376 pub users: Vec<String>,
378 pub count: usize,
380}
381
382#[derive(Debug, Clone, Serialize, Deserialize)]
384pub struct MessageEdit {
385 pub original_content: String,
387 pub edited_at: DateTime<Utc>,
389 pub edit_reason: Option<String>,
391}
392
393#[derive(Debug, Clone, Serialize, Deserialize)]
395pub struct ChannelSettings {
396 pub notifications: bool,
398 pub auto_archive: bool,
400 pub archive_after_days: u32,
402 pub allow_external_invites: bool,
404 pub moderation: ModerationSettings,
406}
407
408#[derive(Debug, Clone, Serialize, Deserialize)]
410pub struct ModerationSettings {
411 pub require_approval: bool,
413 pub auto_delete_inappropriate: bool,
415 pub spam_filtering: bool,
417 pub moderators: Vec<String>,
419}
420
421#[derive(Debug, Clone, Serialize, Deserialize)]
423pub struct Task {
424 pub id: String,
426 pub title: String,
428 pub description: String,
430 pub task_type: TaskType,
432 pub status: TaskStatus,
434 pub priority: TaskPriority,
436 pub assigned_to: Vec<String>,
438 pub created_by: String,
440 pub due_date: Option<DateTime<Utc>>,
442 pub estimated_hours: Option<f64>,
444 pub actual_hours: Option<f64>,
446 pub dependencies: Vec<String>,
448 pub subtasks: Vec<Task>,
450 pub comments: Vec<TaskComment>,
452 pub attachments: Vec<Attachment>,
454 pub labels: Vec<String>,
456 pub created_at: DateTime<Utc>,
458 pub completed_at: Option<DateTime<Utc>>,
460}
461
462#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
464pub enum TaskType {
465 ExperimentDesign,
467 DataCollection,
469 DataAnalysis,
471 CodeDevelopment,
473 Documentation,
475 LiteratureReview,
477 PaperWriting,
479 Review,
481 Meeting,
483 Administrative,
485 Other(String),
487}
488
489#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
491pub enum TaskStatus {
492 NotStarted,
494 InProgress,
496 OnHold,
498 Completed,
500 Cancelled,
502 NeedsReview,
504 Approved,
506}
507
508#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
510pub enum TaskPriority {
511 Critical,
513 High,
515 Medium,
517 Low,
519}
520
521#[derive(Debug, Clone, Serialize, Deserialize)]
523pub struct TaskComment {
524 pub id: String,
526 pub author_id: String,
528 pub content: String,
530 pub timestamp: DateTime<Utc>,
532}
533
534#[derive(Debug, Clone, Serialize, Deserialize)]
536pub struct VersionControl {
537 pub repository_url: Option<String>,
539 pub current_branch: String,
541 pub branches: Vec<String>,
543 pub commits: Vec<Commit>,
545 pub merge_requests: Vec<MergeRequest>,
547 pub settings: VersionControlSettings,
549}
550
551#[derive(Debug, Clone, Serialize, Deserialize)]
553pub struct Commit {
554 pub hash: String,
556 pub author: String,
558 pub message: String,
560 pub timestamp: DateTime<Utc>,
562 pub modified_files: Vec<String>,
564 pub parents: Vec<String>,
566}
567
568#[derive(Debug, Clone, Serialize, Deserialize)]
570pub struct MergeRequest {
571 pub id: String,
573 pub title: String,
575 pub description: String,
577 pub source_branch: String,
579 pub target_branch: String,
581 pub author: String,
583 pub reviewers: Vec<String>,
585 pub status: MergeRequestStatus,
587 pub created_at: DateTime<Utc>,
589 pub merged_at: Option<DateTime<Utc>>,
591}
592
593#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
595pub enum MergeRequestStatus {
596 Open,
598 UnderReview,
600 Approved,
602 Merged,
604 Closed,
606 Draft,
608}
609
610#[derive(Debug, Clone, Serialize, Deserialize)]
612pub struct VersionControlSettings {
613 pub auto_commit: bool,
615 pub auto_commit_frequency: u32,
617 pub require_review: bool,
619 pub protected_branches: Vec<String>,
621 pub automatic_backups: bool,
623}
624
625#[derive(Debug, Clone, Serialize, Deserialize)]
627pub struct WorkspaceSettings {
628 pub timezone: String,
630 pub default_language: String,
632 pub collaboration: CollaborationSettings,
634 pub notifications: NotificationSettings,
636 pub integrations: IntegrationSettings,
638}
639
640#[derive(Debug, Clone, Serialize, Deserialize)]
642pub struct CollaborationSettings {
643 pub real_time_editing: bool,
645 pub auto_save_frequency: u32,
647 pub conflict_resolution: ConflictResolution,
649 pub max_simultaneous_editors: u32,
651}
652
653#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
655pub enum ConflictResolution {
656 Manual,
658 LastWriterWins,
660 FirstWriterWins,
662 Merge,
664}
665
666#[derive(Debug, Clone, Serialize, Deserialize)]
668pub struct NotificationSettings {
669 pub email: bool,
671 pub in_app: bool,
673 pub desktop: bool,
675 pub mobile_push: bool,
677 pub frequency: NotificationFrequency,
679}
680
681#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
683pub enum NotificationFrequency {
684 Immediate,
686 Hourly,
688 Daily,
690 Weekly,
692 None,
694}
695
696#[derive(Debug, Clone, Serialize, Deserialize, Default)]
698pub struct IntegrationSettings {
699 pub slack: Option<SlackIntegration>,
701 pub email: Option<EmailIntegration>,
703 pub calendar: Option<CalendarIntegration>,
705 pub cloud_storage: Option<CloudStorageIntegration>,
707}
708
709#[derive(Debug, Clone, Serialize, Deserialize)]
711pub struct SlackIntegration {
712 pub webhook_url: String,
714 pub default_channel: String,
716 pub experiment_notifications: bool,
718 pub task_notifications: bool,
720}
721
722#[derive(Debug, Clone, Serialize, Deserialize)]
724pub struct EmailIntegration {
725 pub smtp_server: String,
727 pub smtp_port: u16,
729 pub email_address: String,
731 pub auth_credentials: Option<String>,
733}
734
735#[derive(Debug, Clone, Serialize, Deserialize)]
737pub struct CalendarIntegration {
738 pub provider: CalendarProvider,
740 pub calendar_id: String,
742 pub sync_meetings: bool,
744 pub sync_deadlines: bool,
746}
747
748#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
750pub enum CalendarProvider {
751 Google,
753 Outlook,
755 Apple,
757 CalDAV,
759}
760
761#[derive(Debug, Clone, Serialize, Deserialize)]
763pub struct CloudStorageIntegration {
764 pub provider: CloudStorageProvider,
766 pub storage_path: String,
768 pub auto_sync: bool,
770 pub sync_frequency: u32,
772}
773
774#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
776pub enum CloudStorageProvider {
777 GoogleDrive,
779 Dropbox,
781 OneDrive,
783 AmazonS3,
785 Custom(String),
787}
788
789#[derive(Debug, Clone, Serialize, Deserialize)]
791pub struct AccessControl {
792 pub acls: Vec<AccessControlEntry>,
794 pub default_permissions: Vec<Permission>,
796 pub guest_access: bool,
798 pub public_visibility: bool,
800 pub invitation_settings: InvitationSettings,
802}
803
804#[derive(Debug, Clone, Serialize, Deserialize)]
806pub struct AccessControlEntry {
807 pub principal: Principal,
809 pub permissions: Vec<Permission>,
811 pub expires_at: Option<DateTime<Utc>>,
813}
814
815#[derive(Debug, Clone, Serialize, Deserialize)]
817pub enum Principal {
818 User(String),
820 Group(String),
822 Role(MemberRole),
824 Everyone,
826}
827
828#[derive(Debug, Clone, Serialize, Deserialize)]
830pub struct InvitationSettings {
831 pub require_approval: bool,
833 pub allow_external: bool,
835 pub expiration_days: u32,
837 pub max_invitations_per_user: u32,
839}
840
841#[derive(Debug, Clone, Serialize, Deserialize)]
843pub struct Activity {
844 pub id: String,
846 pub user_id: String,
848 pub activity_type: ActivityType,
850 pub description: String,
852 pub resources: Vec<String>,
854 pub metadata: HashMap<String, serde_json::Value>,
856 pub timestamp: DateTime<Utc>,
858}
859
860#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
862pub enum ActivityType {
863 UserJoined,
865 UserLeft,
867 DocumentCreated,
869 DocumentEdited,
871 DocumentDeleted,
873 ExperimentCreated,
875 ExperimentStarted,
877 ExperimentCompleted,
879 TaskCreated,
881 TaskAssigned,
883 TaskCompleted,
885 MessagePosted,
887 FileUploaded,
889 MergeRequestCreated,
891 SettingsChanged,
893}
894
895#[derive(Debug)]
897pub struct CollaborationManager {
898 workspaces: HashMap<String, CollaborativeWorkspace>,
900 storage_dir: PathBuf,
902 settings: CollaborationManagerSettings,
904}
905
906#[derive(Debug, Clone, Serialize, Deserialize)]
908pub struct CollaborationManagerSettings {
909 pub max_workspaces_per_user: u32,
911 pub default_workspace_settings: WorkspaceSettings,
913 pub backup_settings: BackupSettings,
915}
916
917#[derive(Debug, Clone, Serialize, Deserialize)]
919pub struct BackupSettings {
920 pub enabled: bool,
922 pub frequency_hours: u32,
924 pub retention_days: u32,
926 pub backup_location: PathBuf,
928}
929
930impl CollaborativeWorkspace {
931 pub fn new(name: &str, owner: UserInfo) -> Self {
933 let now = Utc::now();
934 let workspace_id = uuid::Uuid::new_v4().to_string();
935 let owner_id = uuid::Uuid::new_v4().to_string();
936
937 let owner_member = ProjectMember {
938 id: owner_id.clone(),
939 user: owner,
940 role: MemberRole::Owner,
941 permissions: vec![
942 Permission::Read,
943 Permission::Write,
944 Permission::Delete,
945 Permission::ManageMembers,
946 Permission::ManagePermissions,
947 Permission::ManageSettings,
948 Permission::CreateExperiments,
949 Permission::RunExperiments,
950 Permission::PublishResults,
951 Permission::AccessSensitiveData,
952 ],
953 joined_at: now,
954 last_active: now,
955 status: MemberStatus::Active,
956 contributions: ContributionStats::default(),
957 };
958
959 Self {
960 id: workspace_id,
961 name: name.to_string(),
962 members: vec![owner_member],
963 documents: Vec::new(),
964 channels: Vec::new(),
965 tasks: Vec::new(),
966 version_control: VersionControl::default(),
967 settings: WorkspaceSettings::default(),
968 access_control: AccessControl::default(),
969 activity_log: Vec::new(),
970 created_at: now,
971 modified_at: now,
972 }
973 }
974
975 pub fn add_member(&mut self, user: UserInfo, role: MemberRole) -> Result<()> {
977 let member_id = uuid::Uuid::new_v4().to_string();
978 let permissions = self.get_default_permissions_for_role(&role);
979
980 let member = ProjectMember {
981 id: member_id.clone(),
982 user,
983 role,
984 permissions,
985 joined_at: Utc::now(),
986 last_active: Utc::now(),
987 status: MemberStatus::Active,
988 contributions: ContributionStats::default(),
989 };
990
991 self.members.push(member);
992 self.log_activity(
993 &member_id,
994 ActivityType::UserJoined,
995 "User joined the workspace".to_string(),
996 vec![],
997 );
998
999 Ok(())
1000 }
1001
1002 pub fn create_document(
1004 &mut self,
1005 name: &str,
1006 document_type: DocumentType,
1007 ownerid: &str,
1008 ) -> Result<String> {
1009 if !self.has_permission(ownerid, &Permission::Write) {
1010 return Err(OptimError::InvalidConfig(
1011 "Insufficient permissions to create document".to_string(),
1012 ));
1013 }
1014
1015 let document_id = uuid::Uuid::new_v4().to_string();
1016 let document = SharedDocument {
1017 id: document_id.clone(),
1018 name: name.to_string(),
1019 document_type,
1020 content: String::new(),
1021 owner_id: ownerid.to_string(),
1022 collaborators: Vec::new(),
1023 version: 1,
1024 version_history: Vec::new(),
1025 access_permissions: DocumentPermissions {
1026 public: false,
1027 read_access: self.members.iter().map(|m| m.id.clone()).collect(),
1028 write_access: vec![ownerid.to_string()],
1029 admin_access: vec![ownerid.to_string()],
1030 },
1031 metadata: DocumentMetadata {
1032 tags: Vec::new(),
1033 word_count: 0,
1034 character_count: 0,
1035 collaborator_count: 1,
1036 version_count: 1,
1037 last_editor_id: ownerid.to_string(),
1038 },
1039 created_at: Utc::now(),
1040 modified_at: Utc::now(),
1041 };
1042
1043 self.documents.push(document);
1044 self.log_activity(
1045 ownerid,
1046 ActivityType::DocumentCreated,
1047 format!("Created document: {name}"),
1048 vec![document_id.clone()],
1049 );
1050
1051 Ok(document_id)
1052 }
1053
1054 pub fn create_channel(
1056 &mut self,
1057 name: &str,
1058 channel_type: ChannelType,
1059 creatorid: &str,
1060 ) -> Result<String> {
1061 let channel_id = uuid::Uuid::new_v4().to_string();
1062 let channel = CommunicationChannel {
1063 id: channel_id.clone(),
1064 name: name.to_string(),
1065 description: String::new(),
1066 channel_type,
1067 members: self.members.iter().map(|m| m.id.clone()).collect(),
1068 messages: Vec::new(),
1069 settings: ChannelSettings::default(),
1070 created_at: Utc::now(),
1071 };
1072
1073 self.channels.push(channel);
1074 Ok(channel_id)
1075 }
1076
1077 pub fn create_task(
1079 &mut self,
1080 title: &str,
1081 task_type: TaskType,
1082 creatorid: &str,
1083 ) -> Result<String> {
1084 let task_id = uuid::Uuid::new_v4().to_string();
1085 let task = Task {
1086 id: task_id.clone(),
1087 title: title.to_string(),
1088 description: String::new(),
1089 task_type,
1090 status: TaskStatus::NotStarted,
1091 priority: TaskPriority::Medium,
1092 assigned_to: Vec::new(),
1093 created_by: creatorid.to_string(),
1094 due_date: None,
1095 estimated_hours: None,
1096 actual_hours: None,
1097 dependencies: Vec::new(),
1098 subtasks: Vec::new(),
1099 comments: Vec::new(),
1100 attachments: Vec::new(),
1101 labels: Vec::new(),
1102 created_at: Utc::now(),
1103 completed_at: None,
1104 };
1105
1106 self.tasks.push(task);
1107 self.log_activity(
1108 creatorid,
1109 ActivityType::TaskCreated,
1110 format!("Created task: {title}"),
1111 vec![task_id.clone()],
1112 );
1113
1114 Ok(task_id)
1115 }
1116
1117 pub fn has_permission(&self, userid: &str, permission: &Permission) -> bool {
1119 if let Some(member) = self.members.iter().find(|m| m.id == userid) {
1120 member.permissions.contains(permission)
1121 } else {
1122 false
1123 }
1124 }
1125
1126 pub fn log_activity(
1128 &mut self,
1129 user_id: &str,
1130 activitytype: ActivityType,
1131 description: String,
1132 resources: Vec<String>,
1133 ) {
1134 let activity = Activity {
1135 id: uuid::Uuid::new_v4().to_string(),
1136 user_id: user_id.to_string(),
1137 activity_type: activitytype,
1138 description,
1139 resources,
1140 metadata: HashMap::new(),
1141 timestamp: Utc::now(),
1142 };
1143
1144 self.activity_log.push(activity);
1145 self.modified_at = Utc::now();
1146 }
1147
1148 fn get_default_permissions_for_role(&self, role: &MemberRole) -> Vec<Permission> {
1149 match role {
1150 MemberRole::Owner | MemberRole::Admin => vec![
1151 Permission::Read,
1152 Permission::Write,
1153 Permission::Delete,
1154 Permission::ManageMembers,
1155 Permission::ManagePermissions,
1156 Permission::ManageSettings,
1157 Permission::CreateExperiments,
1158 Permission::RunExperiments,
1159 Permission::PublishResults,
1160 Permission::AccessSensitiveData,
1161 ],
1162 MemberRole::PrincipalInvestigator | MemberRole::SeniorResearcher => vec![
1163 Permission::Read,
1164 Permission::Write,
1165 Permission::CreateExperiments,
1166 Permission::RunExperiments,
1167 Permission::PublishResults,
1168 Permission::AccessSensitiveData,
1169 ],
1170 MemberRole::Researcher | MemberRole::PhDStudent => vec![
1171 Permission::Read,
1172 Permission::Write,
1173 Permission::CreateExperiments,
1174 Permission::RunExperiments,
1175 ],
1176 MemberRole::MastersStudent | MemberRole::ResearchAssistant => vec![
1177 Permission::Read,
1178 Permission::Write,
1179 Permission::CreateExperiments,
1180 ],
1181 MemberRole::Collaborator => vec![Permission::Read, Permission::Write],
1182 MemberRole::Guest | MemberRole::Observer => vec![Permission::Read],
1183 }
1184 }
1185
1186 pub fn generate_statistics(&self) -> WorkspaceStatistics {
1188 let active_members = self
1189 .members
1190 .iter()
1191 .filter(|m| m.status == MemberStatus::Active)
1192 .count();
1193
1194 let total_documents = self.documents.len();
1195 let total_tasks = self.tasks.len();
1196 let completed_tasks = self
1197 .tasks
1198 .iter()
1199 .filter(|t| t.status == TaskStatus::Completed)
1200 .count();
1201
1202 let total_messages = self.channels.iter().map(|c| c.messages.len()).sum();
1203
1204 let activity_last_30_days = self
1205 .activity_log
1206 .iter()
1207 .filter(|a| {
1208 let thirty_days_ago = Utc::now() - chrono::Duration::days(30);
1209 a.timestamp > thirty_days_ago
1210 })
1211 .count();
1212
1213 WorkspaceStatistics {
1214 total_members: self.members.len(),
1215 active_members,
1216 total_documents,
1217 total_tasks,
1218 completed_tasks,
1219 task_completion_rate: if total_tasks > 0 {
1220 completed_tasks as f64 / total_tasks as f64
1221 } else {
1222 0.0
1223 },
1224 total_messages,
1225 total_channels: self.channels.len(),
1226 activity_last_30_days,
1227 creation_date: self.created_at,
1228 last_activity: self.modified_at,
1229 }
1230 }
1231}
1232
1233#[derive(Debug, Clone, Serialize, Deserialize)]
1235pub struct WorkspaceStatistics {
1236 pub total_members: usize,
1238 pub active_members: usize,
1240 pub total_documents: usize,
1242 pub total_tasks: usize,
1244 pub completed_tasks: usize,
1246 pub task_completion_rate: f64,
1248 pub total_messages: usize,
1250 pub total_channels: usize,
1252 pub activity_last_30_days: usize,
1254 pub creation_date: DateTime<Utc>,
1256 pub last_activity: DateTime<Utc>,
1258}
1259
1260impl Default for ContributionStats {
1261 fn default() -> Self {
1262 Self {
1263 experiments_created: 0,
1264 experiments_run: 0,
1265 lines_of_code: 0,
1266 documents_authored: 0,
1267 comments_posted: 0,
1268 reviews_conducted: 0,
1269 contribution_score: 0.0,
1270 }
1271 }
1272}
1273
1274impl Default for VersionControl {
1275 fn default() -> Self {
1276 Self {
1277 repository_url: None,
1278 current_branch: "main".to_string(),
1279 branches: vec!["main".to_string()],
1280 commits: Vec::new(),
1281 merge_requests: Vec::new(),
1282 settings: VersionControlSettings::default(),
1283 }
1284 }
1285}
1286
1287impl Default for VersionControlSettings {
1288 fn default() -> Self {
1289 Self {
1290 auto_commit: false,
1291 auto_commit_frequency: 60, require_review: true,
1293 protected_branches: vec!["main".to_string(), "master".to_string()],
1294 automatic_backups: true,
1295 }
1296 }
1297}
1298
1299impl Default for WorkspaceSettings {
1300 fn default() -> Self {
1301 Self {
1302 timezone: "UTC".to_string(),
1303 default_language: "en".to_string(),
1304 collaboration: CollaborationSettings::default(),
1305 notifications: NotificationSettings::default(),
1306 integrations: IntegrationSettings::default(),
1307 }
1308 }
1309}
1310
1311impl Default for CollaborationSettings {
1312 fn default() -> Self {
1313 Self {
1314 real_time_editing: true,
1315 auto_save_frequency: 30, conflict_resolution: ConflictResolution::Manual,
1317 max_simultaneous_editors: 10,
1318 }
1319 }
1320}
1321
1322impl Default for NotificationSettings {
1323 fn default() -> Self {
1324 Self {
1325 email: true,
1326 in_app: true,
1327 desktop: false,
1328 mobile_push: false,
1329 frequency: NotificationFrequency::Daily,
1330 }
1331 }
1332}
1333
1334impl Default for AccessControl {
1335 fn default() -> Self {
1336 Self {
1337 acls: Vec::new(),
1338 default_permissions: vec![Permission::Read],
1339 guest_access: false,
1340 public_visibility: false,
1341 invitation_settings: InvitationSettings::default(),
1342 }
1343 }
1344}
1345
1346impl Default for InvitationSettings {
1347 fn default() -> Self {
1348 Self {
1349 require_approval: true,
1350 allow_external: false,
1351 expiration_days: 7,
1352 max_invitations_per_user: 10,
1353 }
1354 }
1355}
1356
1357impl Default for ChannelSettings {
1358 fn default() -> Self {
1359 Self {
1360 notifications: true,
1361 auto_archive: false,
1362 archive_after_days: 365,
1363 allow_external_invites: false,
1364 moderation: ModerationSettings::default(),
1365 }
1366 }
1367}
1368
1369impl Default for ModerationSettings {
1370 fn default() -> Self {
1371 Self {
1372 require_approval: false,
1373 auto_delete_inappropriate: false,
1374 spam_filtering: true,
1375 moderators: Vec::new(),
1376 }
1377 }
1378}
1379
1380#[cfg(test)]
1381mod tests {
1382 use super::*;
1383
1384 #[test]
1385 fn test_workspace_creation() {
1386 let owner = UserInfo {
1387 name: "Dr. Test".to_string(),
1388 email: "test@example.com".to_string(),
1389 institution: "Test University".to_string(),
1390 avatar_url: None,
1391 timezone: "UTC".to_string(),
1392 language: "en".to_string(),
1393 research_interests: vec!["machine learning".to_string()],
1394 };
1395
1396 let workspace = CollaborativeWorkspace::new("Test Workspace", owner);
1397
1398 assert_eq!(workspace.name, "Test Workspace");
1399 assert_eq!(workspace.members.len(), 1);
1400 assert_eq!(workspace.members[0].role, MemberRole::Owner);
1401 assert!(workspace.members[0]
1402 .permissions
1403 .contains(&Permission::ManageMembers));
1404 }
1405
1406 #[test]
1407 fn test_document_creation() {
1408 let owner = UserInfo {
1409 name: "Dr. Test".to_string(),
1410 email: "test@example.com".to_string(),
1411 institution: "Test University".to_string(),
1412 avatar_url: None,
1413 timezone: "UTC".to_string(),
1414 language: "en".to_string(),
1415 research_interests: vec![],
1416 };
1417
1418 let mut workspace = CollaborativeWorkspace::new("Test Workspace", owner);
1419 let owner_id = workspace.members[0].id.clone();
1420
1421 let doc_id = workspace
1422 .create_document("Test Document", DocumentType::Manuscript, &owner_id)
1423 .unwrap();
1424
1425 assert_eq!(workspace.documents.len(), 1);
1426 assert_eq!(workspace.documents[0].name, "Test Document");
1427 assert_eq!(
1428 workspace.documents[0].document_type,
1429 DocumentType::Manuscript
1430 );
1431 assert_eq!(workspace.activity_log.len(), 1);
1432 assert_eq!(
1433 workspace.activity_log[0].activity_type,
1434 ActivityType::DocumentCreated
1435 );
1436 }
1437
1438 #[test]
1439 fn test_permission_checking() {
1440 let owner = UserInfo {
1441 name: "Dr. Test".to_string(),
1442 email: "test@example.com".to_string(),
1443 institution: "Test University".to_string(),
1444 avatar_url: None,
1445 timezone: "UTC".to_string(),
1446 language: "en".to_string(),
1447 research_interests: vec![],
1448 };
1449
1450 let workspace = CollaborativeWorkspace::new("Test Workspace", owner);
1451 let owner_id = &workspace.members[0].id;
1452
1453 assert!(workspace.has_permission(owner_id, &Permission::Read));
1454 assert!(workspace.has_permission(owner_id, &Permission::Write));
1455 assert!(workspace.has_permission(owner_id, &Permission::ManageMembers));
1456
1457 assert!(!workspace.has_permission("non-existent", &Permission::Read));
1459 }
1460}