1
0
Fork 0
mirror of https://forgejo.ellis.link/continuwuation/continuwuity.git synced 2025-07-27 10:18:30 +00:00

feat(space-upgrades): Update parent spaces in upgrade

This relies on the room being upgraded referencing
the space itself, but there isn't an easy way to
do it otherwise.
This commit is contained in:
nexy7574 2025-07-19 15:08:21 +01:00 committed by Jade Ellis
parent 62bdfe1ce8
commit b2883c3d6e
No known key found for this signature in database
GPG key ID: 8705A2A3EBF77BD2
3 changed files with 124 additions and 5 deletions

View file

@ -5,7 +5,7 @@ use conduwuit::{
Err, Error, Event, Result, err, info, Err, Error, Event, Result, err, info,
matrix::{StateKey, pdu::PduBuilder}, matrix::{StateKey, pdu::PduBuilder},
}; };
use futures::StreamExt; use futures::{FutureExt, StreamExt};
use ruma::{ use ruma::{
CanonicalJsonObject, RoomId, RoomVersionId, CanonicalJsonObject, RoomId, RoomVersionId,
api::client::{error::ErrorKind, room::upgrade_room}, api::client::{error::ErrorKind, room::upgrade_room},
@ -16,12 +16,13 @@ use ruma::{
power_levels::RoomPowerLevelsEventContent, power_levels::RoomPowerLevelsEventContent,
tombstone::RoomTombstoneEventContent, tombstone::RoomTombstoneEventContent,
}, },
space::child::{RedactedSpaceChildEventContent, SpaceChildEventContent},
}, },
int, int,
}; };
use serde_json::{json, value::to_raw_value}; use serde_json::{json, value::to_raw_value};
use crate::Ruma; use crate::router::Ruma;
/// Recommended transferable state events list from the spec /// Recommended transferable state events list from the spec
const TRANSFERABLE_STATE_EVENTS: &[StateEventType; 11] = &[ const TRANSFERABLE_STATE_EVENTS: &[StateEventType; 11] = &[
@ -36,7 +37,7 @@ const TRANSFERABLE_STATE_EVENTS: &[StateEventType; 11] = &[
StateEventType::RoomTopic, StateEventType::RoomTopic,
// Not explicitly recommended in spec, but very useful. // Not explicitly recommended in spec, but very useful.
StateEventType::SpaceChild, StateEventType::SpaceChild,
StateEventType::SpaceParent, // TODO: m.room.policy StateEventType::SpaceParent, // TODO: m.room.policy?
]; ];
/// # `POST /_matrix/client/r0/rooms/{roomId}/upgrade` /// # `POST /_matrix/client/r0/rooms/{roomId}/upgrade`
@ -128,7 +129,7 @@ pub(crate) async fn upgrade_room_route(
); );
}, },
| _ => { | _ => {
// "creator" key no longer exists in V11+ rooms // "creator" key no longer exists in V11 rooms
create_event_content.remove("creator"); create_event_content.remove("creator");
}, },
} }
@ -175,6 +176,7 @@ pub(crate) async fn upgrade_room_route(
&replacement_room, &replacement_room,
&state_lock, &state_lock,
) )
.boxed()
.await?; .await?;
// Join the new room // Join the new room
@ -205,6 +207,7 @@ pub(crate) async fn upgrade_room_route(
&replacement_room, &replacement_room,
&state_lock, &state_lock,
) )
.boxed()
.await?; .await?;
// Replicate transferable state events to the new room // Replicate transferable state events to the new room
@ -233,6 +236,7 @@ pub(crate) async fn upgrade_room_route(
&replacement_room, &replacement_room,
&state_lock, &state_lock,
) )
.boxed()
.await?; .await?;
} }
@ -290,10 +294,76 @@ pub(crate) async fn upgrade_room_route(
&body.room_id, &body.room_id,
&state_lock, &state_lock,
) )
.boxed()
.await?; .await?;
drop(state_lock); drop(state_lock);
// Check if the old room has a space parent, and if so, whether we should update
// it (m.space.parent, room_id)
let parents = services
.rooms
.state_accessor
.room_state_keys(&body.room_id, &StateEventType::SpaceParent)
.await?;
for raw_space_id in parents {
let space_id = RoomId::parse(&raw_space_id)?;
let state_key = StateKey::from(raw_space_id.clone());
let Ok(child) = services
.rooms
.state_accessor
.room_state_get_content::<SpaceChildEventContent>(
space_id,
&StateEventType::SpaceChild,
body.room_id.as_str(),
)
.await
else {
// If the space does not have a child event for this room, we can skip it
continue;
};
// First, drop the space's child event
let state_lock = services.rooms.state.mutex.lock(space_id).await;
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder {
event_type: StateEventType::SpaceChild.into(),
content: to_raw_value(&RedactedSpaceChildEventContent {})
.expect("event is valid, we just created it"),
state_key: Some(state_key),
..Default::default()
},
sender_user,
space_id,
&state_lock,
)
.boxed()
.await
.ok();
// Now, add a new child event for the replacement room
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder {
event_type: StateEventType::SpaceChild.into(),
content: to_raw_value(&child).expect("event is valid, we just created it"),
state_key: Some(StateKey::new()),
..Default::default()
},
sender_user,
space_id,
&state_lock,
)
.boxed()
.await
.ok();
drop(state_lock);
}
// Return the replacement room id // Return the replacement room id
Ok(upgrade_room::v3::Response { replacement_room }) Ok(upgrade_room::v3::Response { replacement_room })
} }

View file

@ -91,3 +91,22 @@ pub async fn room_state_get(
.and_then(|shortstatehash| self.state_get(shortstatehash, event_type, state_key)) .and_then(|shortstatehash| self.state_get(shortstatehash, event_type, state_key))
.await .await
} }
/// Returns all state keys for the given `room_id` and `event_type`.
#[implement(super::Service)]
#[tracing::instrument(skip(self), level = "debug")]
pub async fn room_state_keys(
&self,
room_id: &RoomId,
event_type: &StateEventType,
) -> Result<Vec<String>> {
let shortstatehash = self.services.state.get_room_shortstatehash(room_id).await?;
let state_keys: Vec<String> = self
.state_keys(shortstatehash, event_type)
.map(|state_key| state_key.to_string())
.collect()
.await;
Ok(state_keys)
}

View file

@ -1,6 +1,6 @@
use conduwuit::{Err, Result, implement, matrix::Event, pdu::PduBuilder}; use conduwuit::{Err, Result, implement, matrix::Event, pdu::PduBuilder};
use ruma::{ use ruma::{
EventId, RoomId, UserId, EventId, Int, RoomId, UserId,
events::{ events::{
StateEventType, TimelineEventType, StateEventType, TimelineEventType,
room::{ room::{
@ -167,3 +167,33 @@ pub async fn user_can_invite(
.await .await
.is_ok() .is_ok()
} }
#[implement(super::Service)]
pub async fn current_power_levels(
&self,
room_id: &RoomId,
) -> Result<RoomPowerLevelsEventContent> {
// fetches the current power levels event content for a room, returning the
// default power levels if no power levels event is found
let pl_event_content = self
.room_state_get_content::<RoomPowerLevelsEventContent>(
room_id,
&StateEventType::RoomPowerLevels,
"",
)
.await;
if let Ok(pl_event_content) = pl_event_content {
Ok(pl_event_content)
} else {
let mut default_power_levels = RoomPowerLevelsEventContent::default();
// set the creator as PL100
let create_event = self
.room_state_get(room_id, &StateEventType::RoomCreate, "")
.await?;
default_power_levels
.users
.insert(create_event.sender().to_owned(), Int::from(100));
Ok(default_power_levels)
}
}