use crate::legion::{LegionError, LegionResult};
use phalanx_crypto::PublicKey;
use std::collections::{HashMap, HashSet};
use std::time::{SystemTime, Duration};
use tokio::sync::RwLock;
use serde::{Serialize, Deserialize};
const MAX_MEMBERS_PER_CHANNEL: usize = 1000;
const MEMBER_INACTIVITY_TIMEOUT: Duration = Duration::from_secs(7 * 24 * 60 * 60);
#[derive(Debug)]
pub struct MemberManager {
channel_members: RwLock<HashMap<String, ChannelMembership>>,
member_registry: RwLock<HashMap<String, MemberInfo>>,
permission_policies: RwLock<HashMap<String, PermissionPolicy>>,
invitations: RwLock<HashMap<String, Vec<Invitation>>>,
}
#[derive(Debug, Clone)]
struct ChannelMembership {
channel_name: String,
owner: String,
members: HashMap<String, ChannelMember>,
created_at: SystemTime,
settings: ChannelSettings,
pending_invitations: HashSet<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct ChannelMember {
member_id: String,
role: MemberRole,
joined_at: SystemTime,
last_activity: SystemTime,
custom_permissions: Option<HashSet<Permission>>,
public_key: PublicKey,
}
#[derive(Debug, Clone)]
struct MemberInfo {
member_id: String,
identity: phalanx_crypto::Identity,
channels: HashSet<String>,
registered_at: SystemTime,
last_seen: SystemTime,
metadata: HashMap<String, String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum MemberRole {
Owner,
Admin,
Moderator,
Member,
Readonly,
Muted,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Permission {
SendMessages,
EditMessages,
DeleteMessages,
SendFiles,
SendReactions,
ManageChannel,
ManageMembers,
ManageRoles,
ManageKeys,
ViewAuditLog,
InviteMembers,
KickMembers,
BanMembers,
MuteMembers,
ManageFederation,
AccessBackups,
ViewMetrics,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PermissionPolicy {
pub channel_name: String,
pub default_permissions: HashSet<Permission>,
pub role_permissions: HashMap<MemberRole, HashSet<Permission>>,
pub inherit_server_permissions: bool,
pub permission_overrides: HashMap<String, HashSet<Permission>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct ChannelSettings {
max_members: usize,
invite_only: bool,
require_approval: bool,
message_retention: Duration,
allow_external_invites: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Invitation {
pub id: String,
pub channel: String,
pub inviter: String,
pub invitee: String,
pub message: Option<String>,
pub expires_at: SystemTime,
pub used: bool,
}
impl Default for ChannelSettings {
fn default() -> Self {
Self {
max_members: MAX_MEMBERS_PER_CHANNEL,
invite_only: true,
require_approval: false,
message_retention: Duration::from_secs(30 * 24 * 60 * 60), allow_external_invites: false,
}
}
}
impl Default for PermissionPolicy {
fn default() -> Self {
let mut role_permissions = HashMap::new();
role_permissions.insert(MemberRole::Owner, Permission::all());
role_permissions.insert(MemberRole::Admin, Permission::admin_set());
role_permissions.insert(MemberRole::Moderator, Permission::moderator_set());
role_permissions.insert(MemberRole::Member, Permission::member_set());
role_permissions.insert(MemberRole::Readonly, HashSet::new());
role_permissions.insert(MemberRole::Muted, HashSet::new());
Self {
channel_name: String::new(),
default_permissions: Permission::member_set(),
role_permissions,
inherit_server_permissions: true,
permission_overrides: HashMap::new(),
}
}
}
impl Permission {
pub fn all() -> HashSet<Permission> {
use Permission::*;
[
SendMessages, EditMessages, DeleteMessages, SendFiles, SendReactions,
ManageChannel, ManageMembers, ManageRoles, ManageKeys, ViewAuditLog,
InviteMembers, KickMembers, BanMembers, MuteMembers,
ManageFederation, AccessBackups, ViewMetrics
].into_iter().collect()
}
pub fn admin_set() -> HashSet<Permission> {
use Permission::*;
[
SendMessages, EditMessages, DeleteMessages, SendFiles, SendReactions,
ManageChannel, ManageMembers, ManageRoles, ManageKeys, ViewAuditLog,
InviteMembers, KickMembers, BanMembers, MuteMembers,
ViewMetrics
].into_iter().collect()
}
pub fn moderator_set() -> HashSet<Permission> {
use Permission::*;
[
SendMessages, EditMessages, SendFiles, SendReactions,
InviteMembers, KickMembers, MuteMembers
].into_iter().collect()
}
pub fn member_set() -> HashSet<Permission> {
use Permission::*;
[
SendMessages, SendFiles, SendReactions
].into_iter().collect()
}
}
impl MemberManager {
pub async fn new() -> LegionResult<Self> {
Ok(Self {
channel_members: RwLock::new(HashMap::new()),
member_registry: RwLock::new(HashMap::new()),
permission_policies: RwLock::new(HashMap::new()),
invitations: RwLock::new(HashMap::new()),
})
}
pub async fn register_member(&self, member_id: String, identity: phalanx_crypto::Identity) -> LegionResult<()> {
let mut registry = self.member_registry.write().await;
let member_info = MemberInfo {
member_id: member_id.clone(),
identity,
channels: HashSet::new(),
registered_at: SystemTime::now(),
last_seen: SystemTime::now(),
metadata: HashMap::new(),
};
registry.insert(member_id.clone(), member_info);
tracing::info!("Registered new member: {}", member_id);
Ok(())
}
pub async fn create_channel(&self, channel_name: String, owner_id: String, owner_identity: phalanx_crypto::Identity) -> LegionResult<()> {
if !self.is_member_registered(&owner_id).await {
self.register_member(owner_id.clone(), owner_identity.clone()).await?;
}
let owner_member = ChannelMember {
member_id: owner_id.clone(),
role: MemberRole::Owner,
joined_at: SystemTime::now(),
last_activity: SystemTime::now(),
custom_permissions: None,
public_key: owner_identity.public_key(),
};
let mut members = HashMap::new();
members.insert(owner_id.clone(), owner_member);
let membership = ChannelMembership {
channel_name: channel_name.clone(),
owner: owner_id.clone(),
members,
created_at: SystemTime::now(),
settings: ChannelSettings::default(),
pending_invitations: HashSet::new(),
};
let mut channel_members = self.channel_members.write().await;
channel_members.insert(channel_name.clone(), membership);
{
let mut registry = self.member_registry.write().await;
if let Some(member) = registry.get_mut(&owner_id) {
member.channels.insert(channel_name.clone());
}
}
self.set_permission_policy(channel_name.clone(), PermissionPolicy::default()).await?;
tracing::info!("Created channel: {} with owner: {}", channel_name, owner_id);
Ok(())
}
pub async fn add_channel_member(&self, channel_name: &str, member_id: &str, role: MemberRole) -> LegionResult<()> {
let mut channel_members = self.channel_members.write().await;
let channel = channel_members.get_mut(channel_name)
.ok_or_else(|| LegionError::Channel(format!("Channel not found: {}", channel_name)))?;
if channel.members.len() >= channel.settings.max_members {
return Err(LegionError::Member(format!("Channel {} is at maximum capacity", channel_name)));
}
let registry = self.member_registry.read().await;
let member_info = registry.get(member_id)
.ok_or_else(|| LegionError::Member(format!("Member not registered: {}", member_id)))?;
let channel_member = ChannelMember {
member_id: member_id.to_string(),
role: role.clone(),
joined_at: SystemTime::now(),
last_activity: SystemTime::now(),
custom_permissions: None,
public_key: member_info.identity.public_key(),
};
channel.members.insert(member_id.to_string(), channel_member);
drop(registry);
drop(channel_members);
{
let mut registry = self.member_registry.write().await;
if let Some(member) = registry.get_mut(member_id) {
member.channels.insert(channel_name.to_string());
member.last_seen = SystemTime::now();
}
}
tracing::info!("Added member {} to channel: {} with role: {:?}", member_id, channel_name, role);
Ok(())
}
pub async fn remove_channel_member(&self, channel_name: &str, member_id: &str) -> LegionResult<()> {
let mut channel_members = self.channel_members.write().await;
let channel = channel_members.get_mut(channel_name)
.ok_or_else(|| LegionError::Channel(format!("Channel not found: {}", channel_name)))?;
if channel.owner == member_id {
return Err(LegionError::Member("Cannot remove channel owner".to_string()));
}
channel.members.remove(member_id);
drop(channel_members);
{
let mut registry = self.member_registry.write().await;
if let Some(member) = registry.get_mut(member_id) {
member.channels.remove(channel_name);
member.last_seen = SystemTime::now();
}
}
tracing::info!("Removed member {} from channel: {}", member_id, channel_name);
Ok(())
}
pub async fn can_join_channel(&self, channel_name: &str, member_id: &str) -> LegionResult<bool> {
let channel_members = self.channel_members.read().await;
let channel = channel_members.get(channel_name)
.ok_or_else(|| LegionError::Channel(format!("Channel not found: {}", channel_name)))?;
if channel.members.contains_key(member_id) {
return Ok(true);
}
if channel.members.len() >= channel.settings.max_members {
return Ok(false);
}
if channel.settings.invite_only {
let invitations = self.invitations.read().await;
if let Some(channel_invitations) = invitations.get(channel_name) {
let has_valid_invite = channel_invitations.iter().any(|inv| {
inv.invitee == member_id &&
!inv.used &&
SystemTime::now() < inv.expires_at
});
return Ok(has_valid_invite);
}
return Ok(false);
}
Ok(true)
}
pub async fn is_channel_member(&self, channel_name: &str, member_id: &str) -> LegionResult<bool> {
let channel_members = self.channel_members.read().await;
if let Some(channel) = channel_members.get(channel_name) {
Ok(channel.members.contains_key(member_id))
} else {
Ok(false)
}
}
pub async fn is_channel_admin(&self, channel_name: &str, member_id: &str) -> LegionResult<bool> {
let channel_members = self.channel_members.read().await;
if let Some(channel) = channel_members.get(channel_name) {
if let Some(member) = channel.members.get(member_id) {
Ok(matches!(member.role, MemberRole::Owner | MemberRole::Admin))
} else {
Ok(false)
}
} else {
Ok(false)
}
}
pub async fn has_permission(&self, channel_name: &str, member_id: &str, permission: Permission) -> LegionResult<bool> {
let channel_members = self.channel_members.read().await;
let channel = channel_members.get(channel_name)
.ok_or_else(|| LegionError::Channel(format!("Channel not found: {}", channel_name)))?;
let member = channel.members.get(member_id)
.ok_or_else(|| LegionError::Member(format!("Member not in channel: {}", member_id)))?;
if let Some(custom_perms) = &member.custom_permissions {
return Ok(custom_perms.contains(&permission));
}
let policies = self.permission_policies.read().await;
let policy = policies.get(channel_name).cloned().unwrap_or_default();
if let Some(role_perms) = policy.role_permissions.get(&member.role) {
return Ok(role_perms.contains(&permission));
}
Ok(policy.default_permissions.contains(&permission))
}
pub async fn set_permission_policy(&self, channel_name: String, mut policy: PermissionPolicy) -> LegionResult<()> {
policy.channel_name = channel_name.clone();
let mut policies = self.permission_policies.write().await;
policies.insert(channel_name.clone(), policy);
tracing::info!("Set permission policy for channel: {}", channel_name);
Ok(())
}
pub async fn create_invitation(&self, channel_name: String, inviter_id: String, invitee_id: String, expires_in: Duration) -> LegionResult<String> {
if !self.has_permission(&channel_name, &inviter_id, Permission::InviteMembers).await? {
return Err(LegionError::Member("Insufficient permissions to invite".to_string()));
}
let invitation_id = format!("inv_{}_{}", channel_name, uuid::Uuid::new_v4());
let expires_at = SystemTime::now() + expires_in;
let invitation = Invitation {
id: invitation_id.clone(),
channel: channel_name.clone(),
inviter: inviter_id,
invitee: invitee_id,
message: None,
expires_at,
used: false,
};
let mut invitations = self.invitations.write().await;
invitations.entry(channel_name.clone())
.or_insert_with(Vec::new)
.push(invitation);
tracing::info!("Created invitation {} for channel: {}", invitation_id, channel_name);
Ok(invitation_id)
}
pub async fn use_invitation(&self, invitation_id: &str) -> LegionResult<(String, String)> {
let mut invitations = self.invitations.write().await;
for (channel_name, channel_invitations) in invitations.iter_mut() {
if let Some(invitation) = channel_invitations.iter_mut().find(|inv| inv.id == invitation_id) {
if invitation.used {
return Err(LegionError::Member("Invitation already used".to_string()));
}
if SystemTime::now() >= invitation.expires_at {
return Err(LegionError::Member("Invitation expired".to_string()));
}
invitation.used = true;
return Ok((channel_name.clone(), invitation.invitee.clone()));
}
}
Err(LegionError::Member("Invitation not found".to_string()))
}
pub async fn is_member_registered(&self, member_id: &str) -> bool {
let registry = self.member_registry.read().await;
registry.contains_key(member_id)
}
pub async fn channel_member_count(&self, channel_name: &str) -> usize {
let channel_members = self.channel_members.read().await;
channel_members.get(channel_name)
.map(|channel| channel.members.len())
.unwrap_or(0)
}
pub async fn channel_stats(&self, channel_name: &str) -> LegionResult<ChannelStats> {
let channel_members = self.channel_members.read().await;
let channel = channel_members.get(channel_name)
.ok_or_else(|| LegionError::Channel(format!("Channel not found: {}", channel_name)))?;
let now = SystemTime::now();
let active_members = channel.members.values()
.filter(|member| {
now.duration_since(member.last_activity).unwrap_or(Duration::MAX) < MEMBER_INACTIVITY_TIMEOUT
})
.count();
let role_counts = channel.members.values().fold(HashMap::new(), |mut acc, member| {
*acc.entry(member.role.clone()).or_insert(0) += 1;
acc
});
Ok(ChannelStats {
channel_name: channel_name.to_string(),
owner: channel.owner.clone(),
total_members: channel.members.len(),
active_members,
created_at: channel.created_at,
role_counts,
settings: channel.settings.clone(),
})
}
pub async fn cleanup(&self) -> LegionResult<()> {
let mut cleaned_invitations = 0;
{
let mut invitations = self.invitations.write().await;
let now = SystemTime::now();
for channel_invitations in invitations.values_mut() {
let initial_len = channel_invitations.len();
channel_invitations.retain(|inv| !inv.used && now < inv.expires_at);
cleaned_invitations += initial_len - channel_invitations.len();
}
}
tracing::info!("Cleaned up {} expired invitations", cleaned_invitations);
Ok(())
}
pub async fn get_user_channels(&self, user_id: &str) -> LegionResult<Vec<String>> {
let registry = self.member_registry.read().await;
if let Some(member) = registry.get(user_id) {
Ok(member.channels.iter().cloned().collect())
} else {
Ok(Vec::new())
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct ChannelStats {
pub channel_name: String,
pub owner: String,
pub total_members: usize,
pub active_members: usize,
pub created_at: SystemTime,
pub role_counts: HashMap<MemberRole, usize>,
pub settings: ChannelSettings,
}
#[cfg(test)]
mod tests {
use super::*;
use phalanx_crypto::Identity;
#[tokio::test]
async fn test_member_manager_creation() {
let manager = MemberManager::new().await.unwrap();
assert!(!manager.is_member_registered("test_user").await);
}
#[tokio::test]
async fn test_channel_creation_and_membership() {
let manager = MemberManager::new().await.unwrap();
let owner_id = "owner".to_string();
let channel_name = "!test".to_string();
let identity = Identity::generate();
manager.create_channel(channel_name.clone(), owner_id.clone(), identity).await.unwrap();
assert!(manager.is_channel_member(&channel_name, &owner_id).await.unwrap());
assert!(manager.is_channel_admin(&channel_name, &owner_id).await.unwrap());
assert_eq!(manager.channel_member_count(&channel_name).await, 1);
}
#[tokio::test]
async fn test_permissions() {
let manager = MemberManager::new().await.unwrap();
let owner_id = "owner".to_string();
let channel_name = "!test".to_string();
let identity = Identity::generate();
manager.create_channel(channel_name.clone(), owner_id.clone(), identity).await.unwrap();
assert!(manager.has_permission(&channel_name, &owner_id, Permission::ManageChannel).await.unwrap());
assert!(manager.has_permission(&channel_name, &owner_id, Permission::SendMessages).await.unwrap());
}
}