use crate::acl::to_effect_s1::MemberRow;
use bytes::Bytes;
use helix_core::effect::{DomainEventBytes, Effect};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RenderMember {
pub member_id: String,
pub nickname: String,
pub admin: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MemberProjectionMode {
Snapshot,
Incremental,
}
impl MemberProjectionMode {
fn as_str(self) -> &'static str {
match self {
Self::Snapshot => "snapshot",
Self::Incremental => "incremental",
}
}
}
pub fn role_is_admin(role: &str) -> bool {
matches!(role, "CREATOR" | "OWNER" | "ADMIN" | "MANAGER" | "MANGER")
}
pub fn members_from_rows(rows: Vec<MemberRow>) -> Vec<RenderMember> {
rows.into_iter()
.map(|m| RenderMember {
admin: role_is_admin(&m.role),
member_id: m.user_id,
nickname: m.nick_name,
})
.collect()
}
pub fn members_from_byids_body(body: &serde_json::Value) -> Vec<(String, Vec<RenderMember>)> {
let Some(data) = body.get("data").and_then(serde_json::Value::as_object) else {
return Vec::new();
};
let mut out: Vec<(String, Vec<RenderMember>)> = Vec::new();
for (channel_id, arr) in data {
let Some(arr) = arr.as_array() else { continue };
let mut members: Vec<RenderMember> = Vec::new();
let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();
for item in arr {
let Some(obj) = item.as_object() else {
continue;
};
let Some(id) = obj
.get("id")
.and_then(serde_json::Value::as_str)
.filter(|s| !s.is_empty())
else {
continue;
};
if !seen.insert(id.to_string()) {
continue;
}
let nickname = obj
.get("nickName")
.and_then(serde_json::Value::as_str)
.unwrap_or("")
.to_string();
let role = obj
.get("role")
.and_then(serde_json::Value::as_str)
.unwrap_or("");
members.push(RenderMember {
member_id: id.to_string(),
nickname,
admin: role_is_admin(role),
});
}
out.push((channel_id.clone(), members));
}
out
}
pub fn emit_channel_members(
channel_id: &str,
members: Vec<RenderMember>,
leaves: Vec<String>,
projection_mode: MemberProjectionMode,
) -> Effect {
let member_count = members.len();
let members_json: Vec<serde_json::Value> = members
.into_iter()
.map(|m| {
serde_json::json!({
"memberId": m.member_id,
"nickname": m.nickname,
"admin": m.admin,
})
})
.collect();
let payload = serde_json::json!({
"event": "im:channel:members",
"data": {
"channelId": channel_id,
"memberCount": member_count,
"projectionMode": projection_mode.as_str(),
"members": members_json,
"leaves": leaves
},
});
let bytes = Bytes::from(
serde_json::to_vec(&payload)
.expect("emit_channel_members: static JSON shape must serialize"),
);
Effect::Emit {
event: DomainEventBytes(bytes),
}
}
#[cfg(test)]
#[path = "members_tests.rs"]
mod tests;