use crate::error::{OptimError, Result};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CollaborativeWorkspace {
pub id: String,
pub name: String,
pub members: Vec<ProjectMember>,
pub documents: Vec<SharedDocument>,
pub channels: Vec<CommunicationChannel>,
pub tasks: Vec<Task>,
pub version_control: VersionControl,
pub settings: WorkspaceSettings,
pub access_control: AccessControl,
pub activity_log: Vec<Activity>,
pub created_at: DateTime<Utc>,
pub modified_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectMember {
pub id: String,
pub user: UserInfo,
pub role: MemberRole,
pub permissions: Vec<Permission>,
pub joined_at: DateTime<Utc>,
pub last_active: DateTime<Utc>,
pub status: MemberStatus,
pub contributions: ContributionStats,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserInfo {
pub name: String,
pub email: String,
pub institution: String,
pub avatar_url: Option<String>,
pub timezone: String,
pub language: String,
pub research_interests: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum MemberRole {
Owner,
Admin,
PrincipalInvestigator,
SeniorResearcher,
Researcher,
PhDStudent,
MastersStudent,
ResearchAssistant,
Collaborator,
Guest,
Observer,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum Permission {
Read,
Write,
Delete,
ManageMembers,
ManagePermissions,
ManageSettings,
CreateExperiments,
RunExperiments,
PublishResults,
AccessSensitiveData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum MemberStatus {
Active,
Inactive,
OnLeave,
Suspended,
Former,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContributionStats {
pub experiments_created: usize,
pub experiments_run: usize,
pub lines_of_code: usize,
pub documents_authored: usize,
pub comments_posted: usize,
pub reviews_conducted: usize,
pub contribution_score: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SharedDocument {
pub id: String,
pub name: String,
pub document_type: DocumentType,
pub content: String,
pub owner_id: String,
pub collaborators: Vec<String>,
pub version: u32,
pub version_history: Vec<DocumentVersion>,
pub access_permissions: DocumentPermissions,
pub metadata: DocumentMetadata,
pub created_at: DateTime<Utc>,
pub modified_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum DocumentType {
Manuscript,
ExperimentNotes,
MeetingNotes,
LiteratureReview,
ResearchProposal,
DataAnalysis,
CodeDocumentation,
Presentation,
Other(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DocumentVersion {
pub version: u32,
pub author_id: String,
pub content: String,
pub change_summary: String,
pub timestamp: DateTime<Utc>,
pub content_hash: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DocumentPermissions {
pub public: bool,
pub read_access: Vec<String>,
pub write_access: Vec<String>,
pub admin_access: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DocumentMetadata {
pub tags: Vec<String>,
pub word_count: usize,
pub character_count: usize,
pub collaborator_count: usize,
pub version_count: usize,
pub last_editor_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommunicationChannel {
pub id: String,
pub name: String,
pub description: String,
pub channel_type: ChannelType,
pub members: Vec<String>,
pub messages: Vec<Message>,
pub settings: ChannelSettings,
pub created_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum ChannelType {
General,
Experiments,
PaperWriting,
CodeReview,
Announcements,
Random,
Private,
DirectMessage,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Message {
pub id: String,
pub author_id: String,
pub content: String,
pub message_type: MessageType,
pub attachments: Vec<Attachment>,
pub replies: Vec<Message>,
pub reactions: Vec<Reaction>,
pub mentions: Vec<String>,
pub thread_id: Option<String>,
pub timestamp: DateTime<Utc>,
pub edit_history: Vec<MessageEdit>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum MessageType {
Text,
Code,
File,
System,
ExperimentResult,
TaskAssignment,
MeetingInvitation,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Attachment {
pub filename: String,
pub size: usize,
pub mime_type: String,
pub file_path: String,
pub file_hash: String,
pub uploaded_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Reaction {
pub emoji: String,
pub users: Vec<String>,
pub count: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MessageEdit {
pub original_content: String,
pub edited_at: DateTime<Utc>,
pub edit_reason: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChannelSettings {
pub notifications: bool,
pub auto_archive: bool,
pub archive_after_days: u32,
pub allow_external_invites: bool,
pub moderation: ModerationSettings,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModerationSettings {
pub require_approval: bool,
pub auto_delete_inappropriate: bool,
pub spam_filtering: bool,
pub moderators: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Task {
pub id: String,
pub title: String,
pub description: String,
pub task_type: TaskType,
pub status: TaskStatus,
pub priority: TaskPriority,
pub assigned_to: Vec<String>,
pub created_by: String,
pub due_date: Option<DateTime<Utc>>,
pub estimated_hours: Option<f64>,
pub actual_hours: Option<f64>,
pub dependencies: Vec<String>,
pub subtasks: Vec<Task>,
pub comments: Vec<TaskComment>,
pub attachments: Vec<Attachment>,
pub labels: Vec<String>,
pub created_at: DateTime<Utc>,
pub completed_at: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum TaskType {
ExperimentDesign,
DataCollection,
DataAnalysis,
CodeDevelopment,
Documentation,
LiteratureReview,
PaperWriting,
Review,
Meeting,
Administrative,
Other(String),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum TaskStatus {
NotStarted,
InProgress,
OnHold,
Completed,
Cancelled,
NeedsReview,
Approved,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum TaskPriority {
Critical,
High,
Medium,
Low,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TaskComment {
pub id: String,
pub author_id: String,
pub content: String,
pub timestamp: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VersionControl {
pub repository_url: Option<String>,
pub current_branch: String,
pub branches: Vec<String>,
pub commits: Vec<Commit>,
pub merge_requests: Vec<MergeRequest>,
pub settings: VersionControlSettings,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Commit {
pub hash: String,
pub author: String,
pub message: String,
pub timestamp: DateTime<Utc>,
pub modified_files: Vec<String>,
pub parents: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MergeRequest {
pub id: String,
pub title: String,
pub description: String,
pub source_branch: String,
pub target_branch: String,
pub author: String,
pub reviewers: Vec<String>,
pub status: MergeRequestStatus,
pub created_at: DateTime<Utc>,
pub merged_at: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum MergeRequestStatus {
Open,
UnderReview,
Approved,
Merged,
Closed,
Draft,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VersionControlSettings {
pub auto_commit: bool,
pub auto_commit_frequency: u32,
pub require_review: bool,
pub protected_branches: Vec<String>,
pub automatic_backups: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkspaceSettings {
pub timezone: String,
pub default_language: String,
pub collaboration: CollaborationSettings,
pub notifications: NotificationSettings,
pub integrations: IntegrationSettings,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CollaborationSettings {
pub real_time_editing: bool,
pub auto_save_frequency: u32,
pub conflict_resolution: ConflictResolution,
pub max_simultaneous_editors: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum ConflictResolution {
Manual,
LastWriterWins,
FirstWriterWins,
Merge,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NotificationSettings {
pub email: bool,
pub in_app: bool,
pub desktop: bool,
pub mobile_push: bool,
pub frequency: NotificationFrequency,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum NotificationFrequency {
Immediate,
Hourly,
Daily,
Weekly,
None,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct IntegrationSettings {
pub slack: Option<SlackIntegration>,
pub email: Option<EmailIntegration>,
pub calendar: Option<CalendarIntegration>,
pub cloud_storage: Option<CloudStorageIntegration>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SlackIntegration {
pub webhook_url: String,
pub default_channel: String,
pub experiment_notifications: bool,
pub task_notifications: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmailIntegration {
pub smtp_server: String,
pub smtp_port: u16,
pub email_address: String,
pub auth_credentials: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CalendarIntegration {
pub provider: CalendarProvider,
pub calendar_id: String,
pub sync_meetings: bool,
pub sync_deadlines: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum CalendarProvider {
Google,
Outlook,
Apple,
CalDAV,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CloudStorageIntegration {
pub provider: CloudStorageProvider,
pub storage_path: String,
pub auto_sync: bool,
pub sync_frequency: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum CloudStorageProvider {
GoogleDrive,
Dropbox,
OneDrive,
AmazonS3,
Custom(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccessControl {
pub acls: Vec<AccessControlEntry>,
pub default_permissions: Vec<Permission>,
pub guest_access: bool,
pub public_visibility: bool,
pub invitation_settings: InvitationSettings,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccessControlEntry {
pub principal: Principal,
pub permissions: Vec<Permission>,
pub expires_at: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Principal {
User(String),
Group(String),
Role(MemberRole),
Everyone,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InvitationSettings {
pub require_approval: bool,
pub allow_external: bool,
pub expiration_days: u32,
pub max_invitations_per_user: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Activity {
pub id: String,
pub user_id: String,
pub activity_type: ActivityType,
pub description: String,
pub resources: Vec<String>,
pub metadata: HashMap<String, serde_json::Value>,
pub timestamp: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum ActivityType {
UserJoined,
UserLeft,
DocumentCreated,
DocumentEdited,
DocumentDeleted,
ExperimentCreated,
ExperimentStarted,
ExperimentCompleted,
TaskCreated,
TaskAssigned,
TaskCompleted,
MessagePosted,
FileUploaded,
MergeRequestCreated,
SettingsChanged,
}
#[derive(Debug)]
pub struct CollaborationManager {
workspaces: HashMap<String, CollaborativeWorkspace>,
storage_dir: PathBuf,
settings: CollaborationManagerSettings,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CollaborationManagerSettings {
pub max_workspaces_per_user: u32,
pub default_workspace_settings: WorkspaceSettings,
pub backup_settings: BackupSettings,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BackupSettings {
pub enabled: bool,
pub frequency_hours: u32,
pub retention_days: u32,
pub backup_location: PathBuf,
}
impl CollaborativeWorkspace {
pub fn new(name: &str, owner: UserInfo) -> Self {
let now = Utc::now();
let workspace_id = uuid::Uuid::new_v4().to_string();
let owner_id = uuid::Uuid::new_v4().to_string();
let owner_member = ProjectMember {
id: owner_id.clone(),
user: owner,
role: MemberRole::Owner,
permissions: vec![
Permission::Read,
Permission::Write,
Permission::Delete,
Permission::ManageMembers,
Permission::ManagePermissions,
Permission::ManageSettings,
Permission::CreateExperiments,
Permission::RunExperiments,
Permission::PublishResults,
Permission::AccessSensitiveData,
],
joined_at: now,
last_active: now,
status: MemberStatus::Active,
contributions: ContributionStats::default(),
};
Self {
id: workspace_id,
name: name.to_string(),
members: vec![owner_member],
documents: Vec::new(),
channels: Vec::new(),
tasks: Vec::new(),
version_control: VersionControl::default(),
settings: WorkspaceSettings::default(),
access_control: AccessControl::default(),
activity_log: Vec::new(),
created_at: now,
modified_at: now,
}
}
pub fn add_member(&mut self, user: UserInfo, role: MemberRole) -> Result<()> {
let member_id = uuid::Uuid::new_v4().to_string();
let permissions = self.get_default_permissions_for_role(&role);
let member = ProjectMember {
id: member_id.clone(),
user,
role,
permissions,
joined_at: Utc::now(),
last_active: Utc::now(),
status: MemberStatus::Active,
contributions: ContributionStats::default(),
};
self.members.push(member);
self.log_activity(
&member_id,
ActivityType::UserJoined,
"User joined the workspace".to_string(),
vec![],
);
Ok(())
}
pub fn create_document(
&mut self,
name: &str,
document_type: DocumentType,
ownerid: &str,
) -> Result<String> {
if !self.has_permission(ownerid, &Permission::Write) {
return Err(OptimError::InvalidConfig(
"Insufficient permissions to create document".to_string(),
));
}
let document_id = uuid::Uuid::new_v4().to_string();
let document = SharedDocument {
id: document_id.clone(),
name: name.to_string(),
document_type,
content: String::new(),
owner_id: ownerid.to_string(),
collaborators: Vec::new(),
version: 1,
version_history: Vec::new(),
access_permissions: DocumentPermissions {
public: false,
read_access: self.members.iter().map(|m| m.id.clone()).collect(),
write_access: vec![ownerid.to_string()],
admin_access: vec![ownerid.to_string()],
},
metadata: DocumentMetadata {
tags: Vec::new(),
word_count: 0,
character_count: 0,
collaborator_count: 1,
version_count: 1,
last_editor_id: ownerid.to_string(),
},
created_at: Utc::now(),
modified_at: Utc::now(),
};
self.documents.push(document);
self.log_activity(
ownerid,
ActivityType::DocumentCreated,
format!("Created document: {name}"),
vec![document_id.clone()],
);
Ok(document_id)
}
pub fn create_channel(
&mut self,
name: &str,
channel_type: ChannelType,
creatorid: &str,
) -> Result<String> {
let channel_id = uuid::Uuid::new_v4().to_string();
let channel = CommunicationChannel {
id: channel_id.clone(),
name: name.to_string(),
description: String::new(),
channel_type,
members: self.members.iter().map(|m| m.id.clone()).collect(),
messages: Vec::new(),
settings: ChannelSettings::default(),
created_at: Utc::now(),
};
self.channels.push(channel);
Ok(channel_id)
}
pub fn create_task(
&mut self,
title: &str,
task_type: TaskType,
creatorid: &str,
) -> Result<String> {
let task_id = uuid::Uuid::new_v4().to_string();
let task = Task {
id: task_id.clone(),
title: title.to_string(),
description: String::new(),
task_type,
status: TaskStatus::NotStarted,
priority: TaskPriority::Medium,
assigned_to: Vec::new(),
created_by: creatorid.to_string(),
due_date: None,
estimated_hours: None,
actual_hours: None,
dependencies: Vec::new(),
subtasks: Vec::new(),
comments: Vec::new(),
attachments: Vec::new(),
labels: Vec::new(),
created_at: Utc::now(),
completed_at: None,
};
self.tasks.push(task);
self.log_activity(
creatorid,
ActivityType::TaskCreated,
format!("Created task: {title}"),
vec![task_id.clone()],
);
Ok(task_id)
}
pub fn has_permission(&self, userid: &str, permission: &Permission) -> bool {
if let Some(member) = self.members.iter().find(|m| m.id == userid) {
member.permissions.contains(permission)
} else {
false
}
}
pub fn log_activity(
&mut self,
user_id: &str,
activitytype: ActivityType,
description: String,
resources: Vec<String>,
) {
let activity = Activity {
id: uuid::Uuid::new_v4().to_string(),
user_id: user_id.to_string(),
activity_type: activitytype,
description,
resources,
metadata: HashMap::new(),
timestamp: Utc::now(),
};
self.activity_log.push(activity);
self.modified_at = Utc::now();
}
fn get_default_permissions_for_role(&self, role: &MemberRole) -> Vec<Permission> {
match role {
MemberRole::Owner | MemberRole::Admin => vec![
Permission::Read,
Permission::Write,
Permission::Delete,
Permission::ManageMembers,
Permission::ManagePermissions,
Permission::ManageSettings,
Permission::CreateExperiments,
Permission::RunExperiments,
Permission::PublishResults,
Permission::AccessSensitiveData,
],
MemberRole::PrincipalInvestigator | MemberRole::SeniorResearcher => vec![
Permission::Read,
Permission::Write,
Permission::CreateExperiments,
Permission::RunExperiments,
Permission::PublishResults,
Permission::AccessSensitiveData,
],
MemberRole::Researcher | MemberRole::PhDStudent => vec![
Permission::Read,
Permission::Write,
Permission::CreateExperiments,
Permission::RunExperiments,
],
MemberRole::MastersStudent | MemberRole::ResearchAssistant => vec![
Permission::Read,
Permission::Write,
Permission::CreateExperiments,
],
MemberRole::Collaborator => vec![Permission::Read, Permission::Write],
MemberRole::Guest | MemberRole::Observer => vec![Permission::Read],
}
}
pub fn generate_statistics(&self) -> WorkspaceStatistics {
let active_members = self
.members
.iter()
.filter(|m| m.status == MemberStatus::Active)
.count();
let total_documents = self.documents.len();
let total_tasks = self.tasks.len();
let completed_tasks = self
.tasks
.iter()
.filter(|t| t.status == TaskStatus::Completed)
.count();
let total_messages = self.channels.iter().map(|c| c.messages.len()).sum();
let activity_last_30_days = self
.activity_log
.iter()
.filter(|a| {
let thirty_days_ago = Utc::now() - chrono::Duration::days(30);
a.timestamp > thirty_days_ago
})
.count();
WorkspaceStatistics {
total_members: self.members.len(),
active_members,
total_documents,
total_tasks,
completed_tasks,
task_completion_rate: if total_tasks > 0 {
completed_tasks as f64 / total_tasks as f64
} else {
0.0
},
total_messages,
total_channels: self.channels.len(),
activity_last_30_days,
creation_date: self.created_at,
last_activity: self.modified_at,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkspaceStatistics {
pub total_members: usize,
pub active_members: usize,
pub total_documents: usize,
pub total_tasks: usize,
pub completed_tasks: usize,
pub task_completion_rate: f64,
pub total_messages: usize,
pub total_channels: usize,
pub activity_last_30_days: usize,
pub creation_date: DateTime<Utc>,
pub last_activity: DateTime<Utc>,
}
impl Default for ContributionStats {
fn default() -> Self {
Self {
experiments_created: 0,
experiments_run: 0,
lines_of_code: 0,
documents_authored: 0,
comments_posted: 0,
reviews_conducted: 0,
contribution_score: 0.0,
}
}
}
impl Default for VersionControl {
fn default() -> Self {
Self {
repository_url: None,
current_branch: "main".to_string(),
branches: vec!["main".to_string()],
commits: Vec::new(),
merge_requests: Vec::new(),
settings: VersionControlSettings::default(),
}
}
}
impl Default for VersionControlSettings {
fn default() -> Self {
Self {
auto_commit: false,
auto_commit_frequency: 60, require_review: true,
protected_branches: vec!["main".to_string(), "master".to_string()],
automatic_backups: true,
}
}
}
impl Default for WorkspaceSettings {
fn default() -> Self {
Self {
timezone: "UTC".to_string(),
default_language: "en".to_string(),
collaboration: CollaborationSettings::default(),
notifications: NotificationSettings::default(),
integrations: IntegrationSettings::default(),
}
}
}
impl Default for CollaborationSettings {
fn default() -> Self {
Self {
real_time_editing: true,
auto_save_frequency: 30, conflict_resolution: ConflictResolution::Manual,
max_simultaneous_editors: 10,
}
}
}
impl Default for NotificationSettings {
fn default() -> Self {
Self {
email: true,
in_app: true,
desktop: false,
mobile_push: false,
frequency: NotificationFrequency::Daily,
}
}
}
impl Default for AccessControl {
fn default() -> Self {
Self {
acls: Vec::new(),
default_permissions: vec![Permission::Read],
guest_access: false,
public_visibility: false,
invitation_settings: InvitationSettings::default(),
}
}
}
impl Default for InvitationSettings {
fn default() -> Self {
Self {
require_approval: true,
allow_external: false,
expiration_days: 7,
max_invitations_per_user: 10,
}
}
}
impl Default for ChannelSettings {
fn default() -> Self {
Self {
notifications: true,
auto_archive: false,
archive_after_days: 365,
allow_external_invites: false,
moderation: ModerationSettings::default(),
}
}
}
impl Default for ModerationSettings {
fn default() -> Self {
Self {
require_approval: false,
auto_delete_inappropriate: false,
spam_filtering: true,
moderators: Vec::new(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_workspace_creation() {
let owner = UserInfo {
name: "Dr. Test".to_string(),
email: "test@example.com".to_string(),
institution: "Test University".to_string(),
avatar_url: None,
timezone: "UTC".to_string(),
language: "en".to_string(),
research_interests: vec!["machine learning".to_string()],
};
let workspace = CollaborativeWorkspace::new("Test Workspace", owner);
assert_eq!(workspace.name, "Test Workspace");
assert_eq!(workspace.members.len(), 1);
assert_eq!(workspace.members[0].role, MemberRole::Owner);
assert!(workspace.members[0]
.permissions
.contains(&Permission::ManageMembers));
}
#[test]
fn test_document_creation() {
let owner = UserInfo {
name: "Dr. Test".to_string(),
email: "test@example.com".to_string(),
institution: "Test University".to_string(),
avatar_url: None,
timezone: "UTC".to_string(),
language: "en".to_string(),
research_interests: vec![],
};
let mut workspace = CollaborativeWorkspace::new("Test Workspace", owner);
let owner_id = workspace.members[0].id.clone();
let doc_id = workspace
.create_document("Test Document", DocumentType::Manuscript, &owner_id)
.expect("unwrap failed");
assert_eq!(workspace.documents.len(), 1);
assert_eq!(workspace.documents[0].name, "Test Document");
assert_eq!(
workspace.documents[0].document_type,
DocumentType::Manuscript
);
assert_eq!(workspace.activity_log.len(), 1);
assert_eq!(
workspace.activity_log[0].activity_type,
ActivityType::DocumentCreated
);
}
#[test]
fn test_permission_checking() {
let owner = UserInfo {
name: "Dr. Test".to_string(),
email: "test@example.com".to_string(),
institution: "Test University".to_string(),
avatar_url: None,
timezone: "UTC".to_string(),
language: "en".to_string(),
research_interests: vec![],
};
let workspace = CollaborativeWorkspace::new("Test Workspace", owner);
let owner_id = &workspace.members[0].id;
assert!(workspace.has_permission(owner_id, &Permission::Read));
assert!(workspace.has_permission(owner_id, &Permission::Write));
assert!(workspace.has_permission(owner_id, &Permission::ManageMembers));
assert!(!workspace.has_permission("non-existent", &Permission::Read));
}
}