use crate::acl::to_effect_s1::MemberRow;
pub fn collect_members(data: &serde_json::Value) -> Vec<MemberRow> {
let mut out: Vec<MemberRow> = Vec::new();
collect_array(data.get("members"), "MEMBER", &mut out);
collect_array(data.get("adminUsers"), "ADMIN", &mut out);
collect_boss_array(data.get("boss"), &mut out);
collect_owner(data.get("owner"), &mut out);
collect_member_change_join(data, &mut out);
dedupe_keep_last(out)
}
fn collect_member_change_join(data: &serde_json::Value, out: &mut Vec<MemberRow>) {
let Some(join) = data
.get("memberChange")
.and_then(|m| m.get("join"))
.and_then(serde_json::Value::as_array)
else {
return;
};
let owner_id = data
.get("owner")
.and_then(|o| o.get("id").or_else(|| o.get("userId")))
.and_then(serde_json::Value::as_str)
.unwrap_or("");
for item in join {
let Some(row) = member_row_from_value(item, "MEMBER") else {
continue;
};
if row.user_id == owner_id && row.role == "MEMBER" {
continue;
}
out.push(row);
}
}
pub fn collect_member_leaves(data: &serde_json::Value) -> Vec<String> {
data.get("memberChange")
.and_then(|m| m.get("leave"))
.and_then(serde_json::Value::as_array)
.map(|arr| {
arr.iter()
.filter_map(|item| {
item.get("id")
.or_else(|| item.get("userId"))
.and_then(serde_json::Value::as_str)
.filter(|s| !s.is_empty())
.map(str::to_string)
})
.collect()
})
.unwrap_or_default()
}
fn collect_array(value: Option<&serde_json::Value>, default_role: &str, out: &mut Vec<MemberRow>) {
let Some(value) = value else { return };
let parsed;
let value = match value.as_str() {
Some(s) if s.trim().is_empty() => return,
Some(s) => {
parsed =
serde_json::from_str::<serde_json::Value>(s).unwrap_or(serde_json::Value::Null);
&parsed
}
None => value,
};
let Some(arr) = value.as_array() else { return };
for item in arr {
if let Some(row) = member_row_from_value(item, default_role) {
out.push(row);
}
}
}
fn collect_boss_array(value: Option<&serde_json::Value>, out: &mut Vec<MemberRow>) {
let Some(value) = value else { return };
let parsed;
let value = match value.as_str() {
Some(s) if s.trim().is_empty() => return,
Some(s) => {
parsed =
serde_json::from_str::<serde_json::Value>(s).unwrap_or(serde_json::Value::Null);
&parsed
}
None => value,
};
let Some(arr) = value.as_array() else { return };
for item in arr {
if item.get("option").and_then(serde_json::Value::as_str) == Some("leave") {
continue;
}
if let Some(row) = member_row_from_value(item, "BOSS") {
out.push(row);
}
}
}
fn collect_owner(value: Option<&serde_json::Value>, out: &mut Vec<MemberRow>) {
let Some(value) = value else { return };
let parsed;
let value = match value.as_str() {
Some(s) if s.trim().is_empty() => return,
Some(s) => {
parsed =
serde_json::from_str::<serde_json::Value>(s).unwrap_or(serde_json::Value::Null);
&parsed
}
None => value,
};
if value.is_object() {
if let Some(row) = member_row_from_value(value, "OWNER") {
out.push(row);
}
}
}
fn member_row_from_value(value: &serde_json::Value, default_role: &str) -> Option<MemberRow> {
let obj = value.as_object()?;
let user_id = obj
.get("userId")
.or_else(|| obj.get("id"))
.and_then(|v| v.as_str())
.filter(|s| !s.is_empty())?
.to_string();
let team_id = obj
.get("teamId")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let role = obj
.get("role")
.and_then(|v| v.as_str())
.unwrap_or(default_role);
let nick_name = obj
.get("nickName")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
Some(MemberRow {
user_id,
team_id,
role: normalize_role(role).to_string(),
nick_name,
})
}
fn normalize_role(role: &str) -> &'static str {
match role {
"OWNER" | "CREATOR" => "OWNER",
"ADMIN" | "MANAGER" => "ADMIN",
"BOSS" => "BOSS",
_ => "MEMBER",
}
}
fn dedupe_keep_last(members: Vec<MemberRow>) -> Vec<MemberRow> {
use std::collections::HashSet;
let mut seen: HashSet<String> = HashSet::with_capacity(members.len());
let mut result: Vec<MemberRow> = Vec::with_capacity(members.len());
for m in members.into_iter().rev() {
if seen.insert(m.user_id.clone()) {
result.push(m);
}
}
result.reverse();
result
}