1use super::roles::{CommunityRoles, Permissions};
8use super::{CommunityId, ConcordProtocol};
9
10pub fn owner_hex(community_id: &str) -> Option<String> {
17 let id = CommunityId(crate::simd::hex::hex_to_bytes_32(community_id));
18 match crate::db::community::community_protocol(&id).ok().flatten() {
19 Some(ConcordProtocol::V2) => crate::db::community::load_community_v2(&id)
20 .ok()
21 .flatten()
22 .and_then(|c| c.owner().ok())
23 .map(|pk| pk.to_hex()),
24 _ => crate::db::community::load_community(&id)
25 .ok()
26 .flatten()
27 .and_then(|c| super::service::proven_owner_hex(&c)),
28 }
29}
30
31pub fn can_hide(
40 owner_hex: Option<&str>,
41 roster: &CommunityRoles,
42 actor_hex: &str,
43 author_hex: &str,
44) -> bool {
45 let to_hex = |s: &str| {
46 nostr_sdk::prelude::PublicKey::parse(s)
47 .map(|pk| pk.to_hex())
48 .unwrap_or_else(|_| s.to_string())
49 };
50 roster.can_act_on_member(
51 &to_hex(actor_hex),
52 owner_hex,
53 &to_hex(author_hex),
54 Permissions::MANAGE_MESSAGES,
55 )
56}
57
58pub fn can_hide_in(community_id: &str, actor_hex: &str, author_hex: &str) -> bool {
61 let roster = crate::db::community::get_community_roles(community_id).unwrap_or_default();
62 can_hide(owner_hex(community_id).as_deref(), &roster, actor_hex, author_hex)
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68 use crate::community::roles::{MemberGrant, Role};
69
70 fn roster(owner: &str, admin_a: &str, admin_b: &str, mod_hex: &str) -> CommunityRoles {
71 let admin = Role::admin("a".repeat(64));
72 let mut moderator = Role::admin("b".repeat(64));
73 moderator.position = admin.position + 1;
74 let grants = vec![
75 MemberGrant { member: owner.to_string(), role_ids: vec![admin.role_id.clone()] },
76 MemberGrant { member: admin_a.to_string(), role_ids: vec![admin.role_id.clone()] },
77 MemberGrant { member: admin_b.to_string(), role_ids: vec![admin.role_id.clone()] },
78 MemberGrant { member: mod_hex.to_string(), role_ids: vec![moderator.role_id.clone()] },
79 ];
80 CommunityRoles { grants, roles: vec![admin, moderator] }
81 }
82
83 #[test]
84 fn the_owner_outranks_an_admin_even_holding_the_same_role() {
85 let (owner, admin_a, admin_b, moderator) =
86 ("11".repeat(32), "22".repeat(32), "33".repeat(32), "44".repeat(32));
87 let r = roster(&owner, &admin_a, &admin_b, &moderator);
88
89 assert!(can_hide(Some(&owner), &r, &owner, &admin_a));
92 assert!(can_hide(Some(&owner), &r, &owner, &moderator));
93 assert!(!can_hide(Some(&owner), &r, &admin_a, &admin_b));
95 assert!(!can_hide(Some(&owner), &r, &admin_a, &owner));
96 assert!(can_hide(Some(&owner), &r, &admin_a, &moderator));
97 }
98
99 #[test]
100 fn an_unresolved_owner_costs_supremacy_and_protection_both_ways() {
101 let (owner, admin, moderator) = ("11".repeat(32), "22".repeat(32), "44".repeat(32));
105 let admin_role = Role::admin("a".repeat(64));
106 let r = CommunityRoles {
107 grants: vec![MemberGrant { member: admin.clone(), role_ids: vec![admin_role.role_id.clone()] }],
108 roles: vec![admin_role],
109 };
110
111 assert!(!can_hide(None, &r, &owner, &admin), "the owner can't moderate anyone");
112 assert!(!can_hide(None, &r, &owner, &moderator));
113 assert!(can_hide(None, &r, &admin, &owner), "and is exposed to their own admins");
114
115 assert!(can_hide(Some(&owner), &r, &owner, &admin));
117 assert!(!can_hide(Some(&owner), &r, &admin, &owner));
118 }
119
120 #[test]
121 fn a_bech32_author_resolves_to_the_same_verdict_as_hex() {
122 use nostr_sdk::prelude::*;
123 let owner_keys = Keys::generate();
124 let admin_keys = Keys::generate();
125 let owner = owner_keys.public_key().to_hex();
126 let admin = admin_keys.public_key().to_hex();
127 let r = roster(&owner, &admin, &"33".repeat(32), &"44".repeat(32));
128 let admin_bech32 = admin_keys.public_key().to_bech32().unwrap();
129 let owner_bech32 = owner_keys.public_key().to_bech32().unwrap();
130
131 assert!(can_hide(Some(&owner), &r, &owner_bech32, &admin_bech32));
132 assert!(!can_hide(Some(&owner), &r, &admin_bech32, &owner_bech32));
134 }
135}