refactor(spaces): consolidate duplicated code, delete redundant tests
Some checks failed
Documentation / Build and Deploy Documentation (pull_request) Has been skipped
Checks / Prek / Pre-commit & Formatting (pull_request) Failing after 5s
Checks / Prek / Clippy and Cargo Tests (pull_request) Failing after 5s
Update flake hashes / update-flake-hashes (pull_request) Failing after 5s

- Delete cache_tests.rs and integration_tests.rs (only tested same two
  free functions already covered in tests.rs via MockCache indirection)
- Extract invite_and_join_user helper, eliminating duplicate invite+join
  PDU pattern in auto_join and handle_space_child_change
- Extract send_space_state! macro, eliminating 8 repeated lock+send
  blocks in admin commands
- Extract resolve_room_as_space! macro for enable/disable/status
- Remove redundant user_qualifies check in SPACE_ROLE_ROOM handler
  (kick_unqualified_from_rooms already checks)
- Add parent_spaces.is_empty() short-circuit in build.rs PL enforcement
- Hoist Arc::clone in append.rs to single binding
- Rename space_roles_cache_capacity -> space_roles_cache_flush_threshold
- Remove remaining trivial serde tests
This commit is contained in:
ember33 2026-03-19 18:11:08 +01:00
parent f5ab4da12d
commit 482f9145e6
9 changed files with 113 additions and 505 deletions

View file

@ -480,7 +480,7 @@
# Maximum number of spaces to cache role data for. When exceeded the
# cache is cleared and repopulated on demand.
#
#space_roles_cache_capacity = 1000
#space_roles_cache_flush_threshold = 1000
# Enabling this setting opens registration to anyone without restrictions.
# This makes your server vulnerable to abuse

View file

@ -13,9 +13,27 @@ use serde_json::value::to_raw_value;
use crate::{admin_command, admin_command_dispatch};
macro_rules! resolve_space {
macro_rules! resolve_room_as_space {
($self:expr, $space:expr) => {{
let space_id = $self.services.rooms.alias.resolve(&$space).await?;
if !matches!(
$self
.services
.rooms
.state_accessor
.get_room_type(&space_id)
.await,
Ok(ruma::room::RoomType::Space)
) {
return Err!("The specified room is not a Space.");
}
space_id
}};
}
macro_rules! resolve_space {
($self:expr, $space:expr) => {{
let space_id = resolve_room_as_space!($self, $space);
if !$self
.services
.rooms
@ -31,17 +49,6 @@ macro_rules! resolve_space {
)
.await;
}
if !matches!(
$self
.services
.rooms
.state_accessor
.get_room_type(&space_id)
.await,
Ok(ruma::room::RoomType::Space)
) {
return Err!("The specified room is not a Space.");
}
space_id
}};
}
@ -59,6 +66,24 @@ macro_rules! custom_state_pdu {
};
}
macro_rules! send_space_state {
($self:expr, $space_id:expr, $event_type:expr, $state_key:expr, $content:expr) => {{
let state_lock = $self.services.rooms.state.mutex.lock(&$space_id).await;
let server_user = &$self.services.globals.server_user;
$self
.services
.rooms
.timeline
.build_and_append_pdu(
custom_state_pdu!($event_type, $state_key, $content),
server_user,
Some(&$space_id),
&state_lock,
)
.await?
}};
}
#[admin_command_dispatch]
#[derive(Debug, Subcommand)]
pub enum SpaceRolesCommand {
@ -189,19 +214,7 @@ async fn add(
power_level,
});
let state_lock = self.services.rooms.state.mutex.lock(&space_id).await;
let server_user = &self.services.globals.server_user;
self.services
.rooms
.timeline
.build_and_append_pdu(
custom_state_pdu!(SPACE_ROLES_EVENT_TYPE, "", &content),
server_user,
Some(&space_id),
&state_lock,
)
.await?;
send_space_state!(self, space_id, SPACE_ROLES_EVENT_TYPE, "", &content);
self.write_str(&format!("Added role '{role_name}' to space {space_id}."))
.await
@ -224,22 +237,10 @@ async fn remove(&self, space: OwnedRoomOrAliasId, role_name: String) -> Result {
return Err!("Role '{role_name}' does not exist in this space.");
}
let state_lock = self.services.rooms.state.mutex.lock(&space_id).await;
let server_user = &self.services.globals.server_user;
send_space_state!(self, space_id, SPACE_ROLES_EVENT_TYPE, "", &content);
self.services
.rooms
.timeline
.build_and_append_pdu(
custom_state_pdu!(SPACE_ROLES_EVENT_TYPE, "", &content),
server_user,
Some(&space_id),
&state_lock,
)
.await?;
// Cascade: remove the role from all users' member events
let member_event_type = StateEventType::from(SPACE_ROLE_MEMBER_EVENT_TYPE.to_owned());
let server_user = &self.services.globals.server_user;
if let Ok(shortstatehash) = self
.services
.rooms
@ -247,6 +248,7 @@ async fn remove(&self, space: OwnedRoomOrAliasId, role_name: String) -> Result {
.get_room_shortstatehash(&space_id)
.await
{
let state_lock = self.services.rooms.state.mutex.lock(&space_id).await;
let user_entries: Vec<(_, ruma::OwnedEventId)> = self
.services
.rooms
@ -357,19 +359,7 @@ async fn assign(
content.roles.push(role_name.clone());
let state_lock = self.services.rooms.state.mutex.lock(&space_id).await;
let server_user = &self.services.globals.server_user;
self.services
.rooms
.timeline
.build_and_append_pdu(
custom_state_pdu!(SPACE_ROLE_MEMBER_EVENT_TYPE, user_id.as_str(), &content),
server_user,
Some(&space_id),
&state_lock,
)
.await?;
send_space_state!(self, space_id, SPACE_ROLE_MEMBER_EVENT_TYPE, user_id.as_str(), &content);
self.write_str(&format!("Assigned role '{role_name}' to {user_id} in space {space_id}."))
.await
@ -400,19 +390,7 @@ async fn revoke(
return Err!("User {user_id} does not have role '{role_name}' in this space.");
}
let state_lock = self.services.rooms.state.mutex.lock(&space_id).await;
let server_user = &self.services.globals.server_user;
self.services
.rooms
.timeline
.build_and_append_pdu(
custom_state_pdu!(SPACE_ROLE_MEMBER_EVENT_TYPE, user_id.as_str(), &content),
server_user,
Some(&space_id),
&state_lock,
)
.await?;
send_space_state!(self, space_id, SPACE_ROLE_MEMBER_EVENT_TYPE, user_id.as_str(), &content);
self.write_str(&format!("Revoked role '{role_name}' from {user_id} in space {space_id}."))
.await
@ -456,19 +434,7 @@ async fn require(
content.required_roles.push(role_name.clone());
let state_lock = self.services.rooms.state.mutex.lock(&space_id).await;
let server_user = &self.services.globals.server_user;
self.services
.rooms
.timeline
.build_and_append_pdu(
custom_state_pdu!(SPACE_ROLE_ROOM_EVENT_TYPE, room_id.as_str(), &content),
server_user,
Some(&space_id),
&state_lock,
)
.await?;
send_space_state!(self, space_id, SPACE_ROLE_ROOM_EVENT_TYPE, room_id.as_str(), &content);
self.write_str(&format!(
"Room {room_id} now requires role '{role_name}' in space {space_id}."
@ -501,19 +467,7 @@ async fn unrequire(
return Err!("Room {room_id} does not require role '{role_name}' in this space.");
}
let state_lock = self.services.rooms.state.mutex.lock(&space_id).await;
let server_user = &self.services.globals.server_user;
self.services
.rooms
.timeline
.build_and_append_pdu(
custom_state_pdu!(SPACE_ROLE_ROOM_EVENT_TYPE, room_id.as_str(), &content),
server_user,
Some(&space_id),
&state_lock,
)
.await?;
send_space_state!(self, space_id, SPACE_ROLE_ROOM_EVENT_TYPE, room_id.as_str(), &content);
self.write_str(&format!(
"Removed role requirement '{role_name}' from room {room_id} in space {space_id}."
@ -581,32 +535,10 @@ async fn room(&self, space: OwnedRoomOrAliasId, room_id: OwnedRoomId) -> Result
#[admin_command]
async fn enable(&self, space: OwnedRoomOrAliasId) -> Result {
let space_id = self.services.rooms.alias.resolve(&space).await?;
if !matches!(
self.services
.rooms
.state_accessor
.get_room_type(&space_id)
.await,
Ok(ruma::room::RoomType::Space)
) {
return Err!("The specified room is not a Space.");
}
let space_id = resolve_room_as_space!(self, space);
let content = SpaceCascadingEventContent { enabled: true };
let state_lock = self.services.rooms.state.mutex.lock(&space_id).await;
let server_user = &self.services.globals.server_user;
self.services
.rooms
.timeline
.build_and_append_pdu(
custom_state_pdu!(SPACE_CASCADING_EVENT_TYPE, "", &content),
server_user,
Some(&space_id),
&state_lock,
)
.await?;
send_space_state!(self, space_id, SPACE_CASCADING_EVENT_TYPE, "", &content);
self.services
.rooms
@ -620,32 +552,10 @@ async fn enable(&self, space: OwnedRoomOrAliasId) -> Result {
#[admin_command]
async fn disable(&self, space: OwnedRoomOrAliasId) -> Result {
let space_id = self.services.rooms.alias.resolve(&space).await?;
if !matches!(
self.services
.rooms
.state_accessor
.get_room_type(&space_id)
.await,
Ok(ruma::room::RoomType::Space)
) {
return Err!("The specified room is not a Space.");
}
let space_id = resolve_room_as_space!(self, space);
let content = SpaceCascadingEventContent { enabled: false };
let state_lock = self.services.rooms.state.mutex.lock(&space_id).await;
let server_user = &self.services.globals.server_user;
self.services
.rooms
.timeline
.build_and_append_pdu(
custom_state_pdu!(SPACE_CASCADING_EVENT_TYPE, "", &content),
server_user,
Some(&space_id),
&state_lock,
)
.await?;
send_space_state!(self, space_id, SPACE_CASCADING_EVENT_TYPE, "", &content);
self.write_str(&format!("Space permission cascading disabled for {space_id}."))
.await
@ -653,17 +563,7 @@ async fn disable(&self, space: OwnedRoomOrAliasId) -> Result {
#[admin_command]
async fn status(&self, space: OwnedRoomOrAliasId) -> Result {
let space_id = self.services.rooms.alias.resolve(&space).await?;
if !matches!(
self.services
.rooms
.state_accessor
.get_room_type(&space_id)
.await,
Ok(ruma::room::RoomType::Space)
) {
return Err!("The specified room is not a Space.");
}
let space_id = resolve_room_as_space!(self, space);
let global_default = self.services.rooms.roles.is_enabled();
let cascading_event_type = StateEventType::from(SPACE_CASCADING_EVENT_TYPE.to_owned());

View file

@ -616,8 +616,8 @@ pub struct Config {
/// cache is cleared and repopulated on demand.
///
/// default: 1000
#[serde(default = "default_space_roles_cache_capacity")]
pub space_roles_cache_capacity: u32,
#[serde(default = "default_space_roles_cache_flush_threshold")]
pub space_roles_cache_flush_threshold: u32,
/// Enabling this setting opens registration to anyone without restrictions.
/// This makes your server vulnerable to abuse
@ -2843,4 +2843,4 @@ fn default_ldap_uid_attribute() -> String { String::from("uid") }
fn default_ldap_name_attribute() -> String { String::from("givenName") }
fn default_space_roles_cache_capacity() -> u32 { 1000 }
fn default_space_roles_cache_flush_threshold() -> u32 { 1000 }

View file

@ -52,7 +52,6 @@ mod tests {
let content = SpaceRolesEventContent { roles };
let json = serde_json::to_string(&content).unwrap();
let deserialized: SpaceRolesEventContent = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.roles.len(), 2);
assert_eq!(deserialized.roles["admin"].power_level, Some(100));
assert!(deserialized.roles["nsfw"].power_level.is_none());
}
@ -79,16 +78,4 @@ mod tests {
let json = r#"{"power_level":100}"#;
serde_json::from_str::<RoleDefinition>(json).unwrap_err();
}
#[test]
fn wrong_type_for_roles_fails() {
let json = r#"{"roles":"not_an_array"}"#;
serde_json::from_str::<SpaceRoleMemberEventContent>(json).unwrap_err();
}
#[test]
fn wrong_type_for_required_roles_fails() {
let json = r#"{"required_roles":42}"#;
serde_json::from_str::<SpaceRoleRoomEventContent>(json).unwrap_err();
}
}

View file

@ -1,161 +0,0 @@
use std::collections::{BTreeMap, HashMap, HashSet};
use conduwuit_core::matrix::space_roles::RoleDefinition;
use ruma::{OwnedRoomId, OwnedUserId, room_id, user_id};
use super::{
compute_user_power_level, roles_satisfy_requirements,
tests::{make_roles, make_set},
};
struct MockCache {
roles: HashMap<OwnedRoomId, BTreeMap<String, RoleDefinition>>,
user_roles: HashMap<OwnedRoomId, HashMap<OwnedUserId, HashSet<String>>>,
room_requirements: HashMap<OwnedRoomId, HashMap<OwnedRoomId, HashSet<String>>>,
room_to_space: HashMap<OwnedRoomId, HashSet<OwnedRoomId>>,
}
impl MockCache {
fn new() -> Self {
Self {
roles: HashMap::new(),
user_roles: HashMap::new(),
room_requirements: HashMap::new(),
room_to_space: HashMap::new(),
}
}
fn add_space(&mut self, space: OwnedRoomId, roles: BTreeMap<String, RoleDefinition>) {
self.roles.insert(space, roles);
}
fn add_child(&mut self, space: &OwnedRoomId, child: OwnedRoomId) {
self.room_to_space
.entry(child)
.or_default()
.insert(space.clone());
}
fn assign_role(&mut self, space: &OwnedRoomId, user: OwnedUserId, role: String) {
self.user_roles
.entry(space.clone())
.or_default()
.entry(user)
.or_default()
.insert(role);
}
fn revoke_role(&mut self, space: &OwnedRoomId, user: &OwnedUserId, role: &str) {
if let Some(space_users) = self.user_roles.get_mut(space) {
if let Some(user_roles) = space_users.get_mut(user) {
user_roles.remove(role);
}
}
}
fn set_room_requirements(
&mut self,
space: &OwnedRoomId,
room: OwnedRoomId,
reqs: HashSet<String>,
) {
self.room_requirements
.entry(space.clone())
.or_default()
.insert(room, reqs);
}
fn user_qualifies(
&self,
space: &OwnedRoomId,
room: &OwnedRoomId,
user: &OwnedUserId,
) -> bool {
let Some(required) = self.room_requirements.get(space).and_then(|r| r.get(room)) else {
return true;
};
if required.is_empty() {
return true;
}
let Some(assigned) = self.user_roles.get(space).and_then(|u| u.get(user)) else {
return false;
};
roles_satisfy_requirements(required, assigned)
}
fn get_power_level(&self, space: &OwnedRoomId, user: &OwnedUserId) -> Option<i64> {
let role_defs = self.roles.get(space)?;
let assigned = self.user_roles.get(space)?.get(user)?;
compute_user_power_level(role_defs, assigned)
}
}
#[test]
fn cache_populate_and_lookup() {
let mut cache = MockCache::new();
let space = room_id!("!space:example.com").to_owned();
let child = room_id!("!child:example.com").to_owned();
let alice = user_id!("@alice:example.com").to_owned();
cache.add_space(space.clone(), make_roles(&[("admin", Some(100)), ("nsfw", None)]));
cache.add_child(&space, child.clone());
cache.assign_role(&space, alice.clone(), "nsfw".to_owned());
cache.set_room_requirements(&space, child.clone(), make_set(&["nsfw"]));
assert!(cache.user_qualifies(&space, &child, &alice));
assert_eq!(cache.get_power_level(&space, &alice), None);
}
#[test]
fn cache_invalidation_on_role_revoke() {
let mut cache = MockCache::new();
let space = room_id!("!space:example.com").to_owned();
let child = room_id!("!nsfw:example.com").to_owned();
let alice = user_id!("@alice:example.com").to_owned();
cache.add_space(space.clone(), make_roles(&[("nsfw", None)]));
cache.assign_role(&space, alice.clone(), "nsfw".to_owned());
cache.set_room_requirements(&space, child.clone(), make_set(&["nsfw"]));
assert!(cache.user_qualifies(&space, &child, &alice));
cache.revoke_role(&space, &alice, "nsfw");
assert!(!cache.user_qualifies(&space, &child, &alice));
}
#[test]
fn cache_invalidation_on_requirement_change() {
let mut cache = MockCache::new();
let space = room_id!("!space:example.com").to_owned();
let child = room_id!("!room:example.com").to_owned();
let alice = user_id!("@alice:example.com").to_owned();
cache.add_space(space.clone(), make_roles(&[("nsfw", None), ("vip", None)]));
cache.assign_role(&space, alice.clone(), "vip".to_owned());
cache.set_room_requirements(&space, child.clone(), make_set(&["vip"]));
assert!(cache.user_qualifies(&space, &child, &alice));
cache.set_room_requirements(&space, child.clone(), make_set(&["vip", "nsfw"]));
assert!(!cache.user_qualifies(&space, &child, &alice));
}
#[test]
fn cache_power_level_updates_on_role_change() {
let mut cache = MockCache::new();
let space = room_id!("!space:example.com").to_owned();
let alice = user_id!("@alice:example.com").to_owned();
cache.add_space(space.clone(), make_roles(&[("admin", Some(100)), ("mod", Some(50))]));
assert_eq!(cache.get_power_level(&space, &alice), None);
cache.assign_role(&space, alice.clone(), "mod".to_owned());
assert_eq!(cache.get_power_level(&space, &alice), Some(50));
cache.assign_role(&space, alice.clone(), "admin".to_owned());
assert_eq!(cache.get_power_level(&space, &alice), Some(100));
cache.revoke_role(&space, &alice, "admin");
assert_eq!(cache.get_power_level(&space, &alice), Some(50));
}

View file

@ -1,79 +0,0 @@
use std::collections::{HashMap, HashSet};
use super::{roles_satisfy_requirements, tests::make_set};
#[test]
fn scenario_room_adds_requirement_existing_members_checked() {
let alice_roles = make_set(&["vip"]);
let bob_roles = make_set(&["vip", "nsfw"]);
let empty_reqs: HashSet<String> = HashSet::new();
assert!(roles_satisfy_requirements(&empty_reqs, &alice_roles));
assert!(roles_satisfy_requirements(&empty_reqs, &bob_roles));
let new_reqs = make_set(&["nsfw"]);
assert!(!roles_satisfy_requirements(&new_reqs, &alice_roles));
assert!(roles_satisfy_requirements(&new_reqs, &bob_roles));
}
#[test]
fn scenario_multiple_rooms_different_requirements() {
let alice_roles = make_set(&["nsfw", "vip"]);
let bob_roles = make_set(&["nsfw"]);
let nsfw_reqs = make_set(&["nsfw"]);
let vip_reqs = make_set(&["vip"]);
let both_reqs = make_set(&["nsfw", "vip"]);
assert!(roles_satisfy_requirements(&nsfw_reqs, &alice_roles));
assert!(roles_satisfy_requirements(&vip_reqs, &alice_roles));
assert!(roles_satisfy_requirements(&both_reqs, &alice_roles));
assert!(roles_satisfy_requirements(&nsfw_reqs, &bob_roles));
assert!(!roles_satisfy_requirements(&vip_reqs, &bob_roles));
assert!(!roles_satisfy_requirements(&both_reqs, &bob_roles));
}
#[test]
fn scenario_identify_auto_join_candidates() {
let alice_roles = make_set(&["nsfw", "vip"]);
let mut room_reqs: HashMap<String, HashSet<String>> = HashMap::new();
room_reqs.insert("general".to_owned(), HashSet::new());
room_reqs.insert("nsfw-chat".to_owned(), make_set(&["nsfw"]));
room_reqs.insert("vip-lounge".to_owned(), make_set(&["vip"]));
room_reqs.insert("staff-only".to_owned(), make_set(&["staff"]));
let qualifying: Vec<_> = room_reqs
.iter()
.filter(|(_, reqs)| roles_satisfy_requirements(reqs, &alice_roles))
.map(|(name, _)| name.clone())
.collect();
assert!(qualifying.contains(&"general".to_owned()));
assert!(qualifying.contains(&"nsfw-chat".to_owned()));
assert!(qualifying.contains(&"vip-lounge".to_owned()));
assert!(!qualifying.contains(&"staff-only".to_owned()));
}
#[test]
fn scenario_identify_kick_candidates_after_role_revocation() {
let alice_roles_after = make_set(&["vip"]);
let mut rooms: HashMap<String, HashSet<String>> = HashMap::new();
rooms.insert("general".to_owned(), HashSet::new());
rooms.insert("nsfw-chat".to_owned(), make_set(&["nsfw"]));
rooms.insert("vip-lounge".to_owned(), make_set(&["vip"]));
rooms.insert("nsfw-vip".to_owned(), make_set(&["nsfw", "vip"]));
let kick_from: Vec<_> = rooms
.iter()
.filter(|(_, reqs)| !roles_satisfy_requirements(reqs, &alice_roles_after))
.map(|(name, _)| name.clone())
.collect();
assert!(kick_from.contains(&"nsfw-chat".to_owned()));
assert!(kick_from.contains(&"nsfw-vip".to_owned()));
assert!(!kick_from.contains(&"general".to_owned()));
assert!(!kick_from.contains(&"vip-lounge".to_owned()));
}

View file

@ -1,8 +1,4 @@
#[cfg(test)]
mod cache_tests;
#[cfg(test)]
mod integration_tests;
#[cfg(test)]
mod tests;
use std::{
@ -239,7 +235,8 @@ pub async fn populate_space(&self, space_id: &RoomId) {
}
if self.roles.read().await.len()
>= usize::try_from(self.server.config.space_roles_cache_capacity).unwrap_or(usize::MAX)
>= usize::try_from(self.server.config.space_roles_cache_flush_threshold)
.unwrap_or(usize::MAX)
{
self.roles.write().await.clear();
self.user_roles.write().await.clear();
@ -607,47 +604,55 @@ pub async fn auto_join_qualifying_rooms(&self, space_id: &RoomId, user_id: &User
continue;
}
let state_lock = self.services.state.mutex.lock(child_room_id).await;
if let Err(e) = self
.services
.timeline
.build_and_append_pdu(
PduBuilder::state(
user_id.to_string(),
&RoomMemberEventContent::new(MembershipState::Invite),
),
server_user,
Some(child_room_id),
&state_lock,
)
.invite_and_join_user(child_room_id, user_id, server_user)
.await
{
debug_warn!(user_id = %user_id, room_id = %child_room_id, error = ?e, "Failed to invite user during auto-join");
continue;
}
if let Err(e) = self
.services
.timeline
.build_and_append_pdu(
PduBuilder::state(
user_id.to_string(),
&RoomMemberEventContent::new(MembershipState::Join),
),
user_id,
Some(child_room_id),
&state_lock,
)
.await
{
warn!(user_id = %user_id, room_id = %child_room_id, error = ?e, "Failed to auto-join user");
debug_warn!(user_id = %user_id, room_id = %child_room_id, error = ?e, "Failed to auto-join user");
}
}
Ok(())
}
#[implement(Service)]
async fn invite_and_join_user(
&self,
room_id: &RoomId,
user_id: &UserId,
server_user: &UserId,
) -> Result {
let state_lock = self.services.state.mutex.lock(room_id).await;
self.services
.timeline
.build_and_append_pdu(
PduBuilder::state(
user_id.to_string(),
&RoomMemberEventContent::new(MembershipState::Invite),
),
server_user,
Some(room_id),
&state_lock,
)
.await?;
self.services
.timeline
.build_and_append_pdu(
PduBuilder::state(
user_id.to_string(),
&RoomMemberEventContent::new(MembershipState::Join),
),
user_id,
Some(room_id),
&state_lock,
)
.await?;
Ok(())
}
impl Service {
pub fn handle_state_event_change(
self: &Arc<Self>,
@ -729,16 +734,11 @@ impl Service {
.collect()
.await;
for member in &members {
if !this
.user_qualifies_for_room(&space_id, target_room, member)
.await
if let Err(e) =
Box::pin(this.kick_unqualified_from_rooms(&space_id, member))
.await
{
if let Err(e) =
Box::pin(this.kick_unqualified_from_rooms(&space_id, member))
.await
{
debug_warn!(user_id = %member, error = ?e, "Space role requirement kick failed");
}
debug_warn!(user_id = %member, error = ?e, "Space role requirement kick failed");
}
}
}
@ -830,55 +830,17 @@ impl Service {
for member in &space_members {
if this
.user_qualifies_for_room(&space_id, &child_room_id, member)
.await && !this
.services
.state_cache
.is_joined(member, &child_room_id)
.await
{
if !this
.services
.state_cache
.is_joined(member, &child_room_id)
if let Err(e) = this
.invite_and_join_user(&child_room_id, member, server_user)
.await
{
let state_lock =
this.services.state.mutex.lock(&child_room_id).await;
if let Err(e) = this
.services
.timeline
.build_and_append_pdu(
PduBuilder::state(
member.to_string(),
&RoomMemberEventContent::new(
MembershipState::Invite,
),
),
server_user,
Some(&child_room_id),
&state_lock,
)
.await
{
debug_warn!(user_id = %member, room_id = %child_room_id, error = ?e, "Failed to invite user");
continue;
}
if let Err(e) = this
.services
.timeline
.build_and_append_pdu(
PduBuilder::state(
member.to_string(),
&RoomMemberEventContent::new(
MembershipState::Join,
),
),
member,
Some(&child_room_id),
&state_lock,
)
.await
{
warn!(user_id = %member, room_id = %child_room_id, error = ?e, "Failed to auto-join user");
}
debug_warn!(user_id = %member, room_id = %child_room_id, error = ?e, "Failed to auto-join user");
}
}
}

View file

@ -363,6 +363,7 @@ where
| _ => {},
}
let roles: Arc<crate::rooms::roles::Service> = Arc::clone(&*self.services.roles);
if let Some(state_key) = pdu.state_key() {
let event_type_str = pdu.event_type().to_string();
match event_type_str.as_str() {
@ -374,8 +375,6 @@ where
self.services.state_accessor.get_room_type(room_id).await,
Ok(ruma::room::RoomType::Space)
) {
let roles: Arc<crate::rooms::roles::Service> =
Arc::clone(&*self.services.roles);
roles.handle_state_event_change(
room_id.to_owned(),
event_type_str,
@ -386,10 +385,9 @@ where
| _ => {},
}
}
if *pdu.kind() == TimelineEventType::SpaceChild {
if let Some(state_key) = pdu.state_key() {
if let Some(state_key) = pdu.state_key() {
if *pdu.kind() == TimelineEventType::SpaceChild {
if let Ok(child_room_id) = ruma::RoomId::parse(state_key) {
let roles: Arc<crate::rooms::roles::Service> = Arc::clone(&*self.services.roles);
roles.handle_space_child_change(room_id.to_owned(), child_room_id.to_owned());
}
}
@ -404,7 +402,6 @@ where
self.services.state_accessor.get_room_type(room_id).await,
Ok(ruma::room::RoomType::Space)
) {
let roles: Arc<crate::rooms::roles::Service> = Arc::clone(&*self.services.roles);
roles.handle_space_member_join(room_id.to_owned(), user_id.to_owned());
}

View file

@ -110,7 +110,9 @@ pub async fn build_and_append_pdu(
use ruma::events::room::power_levels::RoomPowerLevelsEventContent;
let parent_spaces = self.services.roles.get_parent_spaces(&room_id).await;
if let Ok(proposed) = pdu.get_content::<RoomPowerLevelsEventContent>() {
if !parent_spaces.is_empty()
&& let Ok(proposed) = pdu.get_content::<RoomPowerLevelsEventContent>()
{
let space_data: Vec<SpaceEnforcementData> = {
let user_roles_guard = self.services.roles.user_roles.read().await;
let roles_guard = self.services.roles.roles.read().await;