2024-11-06 18:27:40 +00:00
|
|
|
use std::cmp::max;
|
|
|
|
|
|
|
|
|
|
use axum::extract::State;
|
2025-04-04 03:30:13 +00:00
|
|
|
use conduwuit::{
|
2026-04-10 12:52:42 -04:00
|
|
|
Err, Error, Event, Result, debug, err, info,
|
2026-04-10 11:47:01 -04:00
|
|
|
matrix::{StateKey, pdu::PartialPdu},
|
2025-04-04 03:30:13 +00:00
|
|
|
};
|
2025-07-19 15:08:21 +01:00
|
|
|
use futures::{FutureExt, StreamExt};
|
2024-11-06 18:27:40 +00:00
|
|
|
use ruma::{
|
2025-02-23 01:17:45 -05:00
|
|
|
CanonicalJsonObject, RoomId, RoomVersionId,
|
2026-04-10 12:52:42 -04:00
|
|
|
api::{client::room::upgrade_room, error::ErrorKind},
|
|
|
|
|
assign,
|
2024-11-06 18:27:40 +00:00
|
|
|
events::{
|
2025-02-23 01:17:45 -05:00
|
|
|
StateEventType, TimelineEventType,
|
2024-11-06 18:27:40 +00:00
|
|
|
room::{
|
2026-04-10 12:52:42 -04:00
|
|
|
create::PreviousRoom,
|
2024-11-06 18:27:40 +00:00
|
|
|
member::{MembershipState, RoomMemberEventContent},
|
|
|
|
|
power_levels::RoomPowerLevelsEventContent,
|
|
|
|
|
tombstone::RoomTombstoneEventContent,
|
|
|
|
|
},
|
2025-07-19 15:08:21 +01:00
|
|
|
space::child::{RedactedSpaceChildEventContent, SpaceChildEventContent},
|
2024-11-06 18:27:40 +00:00
|
|
|
},
|
2025-02-23 01:17:45 -05:00
|
|
|
int,
|
2026-04-10 12:52:42 -04:00
|
|
|
room_version_rules::RoomIdFormatVersion,
|
2024-11-06 18:27:40 +00:00
|
|
|
};
|
|
|
|
|
use serde_json::{json, value::to_raw_value};
|
|
|
|
|
|
2025-07-19 15:08:21 +01:00
|
|
|
use crate::router::Ruma;
|
2024-11-06 18:27:40 +00:00
|
|
|
|
|
|
|
|
/// Recommended transferable state events list from the spec
|
2025-07-17 23:15:14 +01:00
|
|
|
const TRANSFERABLE_STATE_EVENTS: &[StateEventType; 11] = &[
|
2024-11-06 18:27:40 +00:00
|
|
|
StateEventType::RoomAvatar,
|
2024-12-15 01:41:50 -05:00
|
|
|
StateEventType::RoomEncryption,
|
2024-11-06 18:27:40 +00:00
|
|
|
StateEventType::RoomGuestAccess,
|
|
|
|
|
StateEventType::RoomHistoryVisibility,
|
|
|
|
|
StateEventType::RoomJoinRules,
|
2024-12-15 01:41:50 -05:00
|
|
|
StateEventType::RoomName,
|
2024-11-06 18:27:40 +00:00
|
|
|
StateEventType::RoomPowerLevels,
|
2024-12-15 01:41:50 -05:00
|
|
|
StateEventType::RoomServerAcl,
|
|
|
|
|
StateEventType::RoomTopic,
|
2025-07-17 23:15:14 +01:00
|
|
|
// Not explicitly recommended in spec, but very useful.
|
|
|
|
|
StateEventType::SpaceChild,
|
2025-07-19 15:08:21 +01:00
|
|
|
StateEventType::SpaceParent, // TODO: m.room.policy?
|
2024-11-06 18:27:40 +00:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
/// # `POST /_matrix/client/r0/rooms/{roomId}/upgrade`
|
|
|
|
|
///
|
|
|
|
|
/// Upgrades the room.
|
|
|
|
|
///
|
|
|
|
|
/// - Creates a replacement room
|
|
|
|
|
/// - Sends a tombstone event into the current room
|
|
|
|
|
/// - Sender user joins the room
|
|
|
|
|
/// - Transfers some state events
|
|
|
|
|
/// - Moves local aliases
|
|
|
|
|
/// - Modifies old room power levels to prevent users from speaking
|
|
|
|
|
pub(crate) async fn upgrade_room_route(
|
2024-12-15 00:05:47 -05:00
|
|
|
State(services): State<crate::State>,
|
|
|
|
|
body: Ruma<upgrade_room::v3::Request>,
|
2024-11-06 18:27:40 +00:00
|
|
|
) -> Result<upgrade_room::v3::Response> {
|
2025-07-17 23:15:14 +01:00
|
|
|
// TODO[v12]: Handle additional creators
|
2024-11-06 18:27:40 +00:00
|
|
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
|
|
|
|
|
2024-12-05 07:23:51 +00:00
|
|
|
if !services.server.supported_room_version(&body.new_version) {
|
2024-11-06 18:27:40 +00:00
|
|
|
return Err(Error::BadRequest(
|
|
|
|
|
ErrorKind::UnsupportedRoomVersion,
|
|
|
|
|
"This server does not support that room version.",
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-28 22:43:35 +01:00
|
|
|
if services.users.is_suspended(sender_user).await? {
|
|
|
|
|
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-27 04:05:26 +00:00
|
|
|
// Make sure this isn't the admin room
|
|
|
|
|
// Admin room upgrades are hacky and should be done manually instead.
|
|
|
|
|
if services.admin.is_admin_room(&body.room_id).await {
|
|
|
|
|
return Err!(Request(Forbidden("Upgrading the admin room this way is not allowed.")));
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-23 21:46:07 +01:00
|
|
|
// First, check if the user has permission to upgrade the room (send tombstone
|
|
|
|
|
// event)
|
2026-04-10 12:52:42 -04:00
|
|
|
let old_room_state_lock = services.rooms.state.mutex.lock(body.room_id.as_str()).await;
|
2025-09-23 21:46:07 +01:00
|
|
|
|
|
|
|
|
// Check tombstone permission by attempting to create (but not send) the event
|
|
|
|
|
// Note that this does internally call the policy server with a fake room ID,
|
|
|
|
|
// which may not be good?
|
|
|
|
|
let tombstone_test_result = services
|
|
|
|
|
.rooms
|
|
|
|
|
.timeline
|
|
|
|
|
.create_hash_and_sign_event(
|
2026-04-10 12:52:42 -04:00
|
|
|
PartialPdu::state(
|
|
|
|
|
StateKey::new(),
|
|
|
|
|
&RoomTombstoneEventContent::new(
|
|
|
|
|
String::new(),
|
|
|
|
|
RoomId::new_v1(services.globals.server_name()),
|
|
|
|
|
),
|
|
|
|
|
),
|
2025-09-23 21:46:07 +01:00
|
|
|
sender_user,
|
|
|
|
|
Some(&body.room_id),
|
|
|
|
|
&old_room_state_lock,
|
|
|
|
|
)
|
|
|
|
|
.await;
|
|
|
|
|
|
|
|
|
|
if let Err(_e) = tombstone_test_result {
|
|
|
|
|
return Err!(Request(Forbidden("User does not have permission to upgrade this room.")));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
drop(old_room_state_lock);
|
|
|
|
|
|
2024-11-06 18:27:40 +00:00
|
|
|
// Create a replacement room
|
2026-04-10 12:52:42 -04:00
|
|
|
let room_version_rules = body
|
|
|
|
|
.new_version
|
|
|
|
|
.rules()
|
|
|
|
|
.expect("new room version should have defined rules");
|
|
|
|
|
let replacement_room_owned = if room_version_rules.room_id_format == RoomIdFormatVersion::V2 {
|
|
|
|
|
Some(RoomId::new_v1(services.globals.server_name()))
|
2025-09-22 20:44:40 +01:00
|
|
|
} else {
|
2025-09-26 18:41:25 +01:00
|
|
|
None
|
2025-09-22 20:44:40 +01:00
|
|
|
};
|
2025-09-26 18:41:25 +01:00
|
|
|
let replacement_room: Option<&RoomId> = replacement_room_owned.as_ref().map(AsRef::as_ref);
|
2025-09-22 20:44:40 +01:00
|
|
|
let replacement_room_tmp = match replacement_room {
|
|
|
|
|
| Some(v) => v,
|
2026-04-10 12:52:42 -04:00
|
|
|
| None => &RoomId::new_v1(services.globals.server_name()),
|
2025-09-22 20:44:40 +01:00
|
|
|
};
|
2024-11-06 18:27:40 +00:00
|
|
|
|
|
|
|
|
let _short_id = services
|
|
|
|
|
.rooms
|
|
|
|
|
.short
|
2025-09-22 20:44:40 +01:00
|
|
|
.get_or_create_shortroomid(replacement_room_tmp)
|
2024-11-06 18:27:40 +00:00
|
|
|
.await;
|
|
|
|
|
|
2025-09-23 21:46:07 +01:00
|
|
|
// For pre-v12 rooms, send tombstone before creating replacement room
|
2026-04-10 12:52:42 -04:00
|
|
|
let tombstone_event_id = if room_version_rules.room_id_format != RoomIdFormatVersion::V2 {
|
|
|
|
|
let state_lock = services.rooms.state.mutex.lock(body.room_id.as_str()).await;
|
2025-09-22 20:44:40 +01:00
|
|
|
// Send a m.room.tombstone event to the old room to indicate that it is not
|
2025-09-23 21:46:07 +01:00
|
|
|
// intended to be used any further
|
2025-09-22 20:44:40 +01:00
|
|
|
let tombstone_event_id = services
|
|
|
|
|
.rooms
|
|
|
|
|
.timeline
|
|
|
|
|
.build_and_append_pdu(
|
2026-04-10 12:52:42 -04:00
|
|
|
PartialPdu::state(
|
|
|
|
|
StateKey::new(),
|
|
|
|
|
&RoomTombstoneEventContent::new(
|
|
|
|
|
"This room has been replaced".to_owned(),
|
|
|
|
|
replacement_room.unwrap().to_owned(),
|
|
|
|
|
),
|
|
|
|
|
),
|
2025-09-22 20:44:40 +01:00
|
|
|
sender_user,
|
|
|
|
|
Some(&body.room_id),
|
|
|
|
|
&state_lock,
|
|
|
|
|
)
|
2026-04-07 18:31:09 +01:00
|
|
|
.boxed()
|
2025-09-22 20:44:40 +01:00
|
|
|
.await?;
|
|
|
|
|
// Change lock to replacement room
|
|
|
|
|
drop(state_lock);
|
|
|
|
|
Some(tombstone_event_id)
|
2025-09-23 21:46:07 +01:00
|
|
|
} else {
|
|
|
|
|
None
|
2025-09-22 20:44:40 +01:00
|
|
|
};
|
2026-04-10 12:52:42 -04:00
|
|
|
let state_lock = services
|
|
|
|
|
.rooms
|
|
|
|
|
.state
|
|
|
|
|
.mutex
|
|
|
|
|
.lock(replacement_room_tmp.as_str())
|
|
|
|
|
.await;
|
2024-11-06 18:27:40 +00:00
|
|
|
|
|
|
|
|
// Get the old room creation event
|
|
|
|
|
let mut create_event_content: CanonicalJsonObject = services
|
|
|
|
|
.rooms
|
|
|
|
|
.state_accessor
|
|
|
|
|
.room_state_get_content(&body.room_id, &StateEventType::RoomCreate, "")
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|_| err!(Database("Found room without m.room.create event.")))?;
|
|
|
|
|
|
|
|
|
|
// Use the m.room.tombstone event as the predecessor
|
2026-04-10 12:52:42 -04:00
|
|
|
|
|
|
|
|
let predecessor = {
|
|
|
|
|
#[allow(deprecated, reason = "Clients still use event_id even though it's deprecated")]
|
|
|
|
|
Some(assign!(PreviousRoom::new(body.room_id.clone()), {
|
|
|
|
|
event_id: tombstone_event_id,
|
|
|
|
|
}))
|
|
|
|
|
};
|
2024-11-06 18:27:40 +00:00
|
|
|
|
|
|
|
|
// Send a m.room.create event containing a predecessor field and the applicable
|
|
|
|
|
// room_version
|
|
|
|
|
{
|
|
|
|
|
use RoomVersionId::*;
|
|
|
|
|
match body.new_version {
|
2024-12-15 00:05:47 -05:00
|
|
|
| V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 => {
|
2024-11-06 18:27:40 +00:00
|
|
|
create_event_content.insert(
|
|
|
|
|
"creator".into(),
|
|
|
|
|
json!(&sender_user).try_into().map_err(|e| {
|
|
|
|
|
info!("Error forming creation event: {e}");
|
|
|
|
|
Error::BadRequest(ErrorKind::BadJson, "Error forming creation event")
|
|
|
|
|
})?,
|
|
|
|
|
);
|
|
|
|
|
},
|
2024-12-15 00:05:47 -05:00
|
|
|
| _ => {
|
2025-07-19 15:08:21 +01:00
|
|
|
// "creator" key no longer exists in V11 rooms
|
2024-11-06 18:27:40 +00:00
|
|
|
create_event_content.remove("creator");
|
|
|
|
|
},
|
2025-09-22 20:44:40 +01:00
|
|
|
// TODO(hydra): additional_creators
|
2024-11-06 18:27:40 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
create_event_content.insert(
|
|
|
|
|
"room_version".into(),
|
|
|
|
|
json!(&body.new_version)
|
|
|
|
|
.try_into()
|
|
|
|
|
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Error forming creation event"))?,
|
|
|
|
|
);
|
|
|
|
|
create_event_content.insert(
|
|
|
|
|
"predecessor".into(),
|
|
|
|
|
json!(predecessor)
|
|
|
|
|
.try_into()
|
|
|
|
|
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Error forming creation event"))?,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Validate creation event content
|
|
|
|
|
if serde_json::from_str::<CanonicalJsonObject>(
|
|
|
|
|
to_raw_value(&create_event_content)
|
|
|
|
|
.expect("Error forming creation event")
|
|
|
|
|
.get(),
|
|
|
|
|
)
|
|
|
|
|
.is_err()
|
|
|
|
|
{
|
|
|
|
|
return Err(Error::BadRequest(ErrorKind::BadJson, "Error forming creation event"));
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-22 20:44:40 +01:00
|
|
|
let create_event_id = services
|
2024-11-06 18:27:40 +00:00
|
|
|
.rooms
|
|
|
|
|
.timeline
|
|
|
|
|
.build_and_append_pdu(
|
2026-04-10 12:52:42 -04:00
|
|
|
PartialPdu {
|
2024-11-06 18:27:40 +00:00
|
|
|
event_type: TimelineEventType::RoomCreate,
|
2024-12-15 00:05:47 -05:00
|
|
|
content: to_raw_value(&create_event_content)
|
|
|
|
|
.expect("event is valid, we just created it"),
|
2024-11-06 18:27:40 +00:00
|
|
|
unsigned: None,
|
2025-02-08 00:16:37 +00:00
|
|
|
state_key: Some(StateKey::new()),
|
2024-11-06 18:27:40 +00:00
|
|
|
redacts: None,
|
|
|
|
|
timestamp: None,
|
|
|
|
|
},
|
|
|
|
|
sender_user,
|
2025-09-22 20:44:40 +01:00
|
|
|
replacement_room,
|
2024-11-06 18:27:40 +00:00
|
|
|
&state_lock,
|
|
|
|
|
)
|
2025-07-19 15:08:21 +01:00
|
|
|
.boxed()
|
2024-11-06 18:27:40 +00:00
|
|
|
.await?;
|
2025-09-22 20:44:40 +01:00
|
|
|
let create_id = create_event_id.as_str().replace('$', "!");
|
2026-04-10 12:52:42 -04:00
|
|
|
let (replacement_room, state_lock) =
|
|
|
|
|
if room_version_rules.room_id_format == RoomIdFormatVersion::V2 {
|
|
|
|
|
let parsed_room_id = RoomId::parse(&create_id)?;
|
|
|
|
|
let lock = services
|
|
|
|
|
.rooms
|
|
|
|
|
.state
|
|
|
|
|
.mutex
|
|
|
|
|
.lock(parsed_room_id.as_str())
|
|
|
|
|
.await;
|
|
|
|
|
(Some(parsed_room_id), lock)
|
|
|
|
|
} else {
|
|
|
|
|
(replacement_room.map(ToOwned::to_owned), state_lock)
|
|
|
|
|
};
|
2024-11-06 18:27:40 +00:00
|
|
|
|
|
|
|
|
// Join the new room
|
|
|
|
|
services
|
|
|
|
|
.rooms
|
|
|
|
|
.timeline
|
|
|
|
|
.build_and_append_pdu(
|
2026-04-10 12:52:42 -04:00
|
|
|
PartialPdu::state(
|
|
|
|
|
sender_user.as_str(),
|
|
|
|
|
&assign!(RoomMemberEventContent::new(MembershipState::Join), {
|
2024-11-06 18:27:40 +00:00
|
|
|
displayname: services.users.displayname(sender_user).await.ok(),
|
|
|
|
|
avatar_url: services.users.avatar_url(sender_user).await.ok(),
|
2026-04-10 12:52:42 -04:00
|
|
|
}),
|
|
|
|
|
),
|
2024-11-06 18:27:40 +00:00
|
|
|
sender_user,
|
2026-04-10 12:52:42 -04:00
|
|
|
replacement_room.as_deref(),
|
2024-11-06 18:27:40 +00:00
|
|
|
&state_lock,
|
|
|
|
|
)
|
2025-07-19 15:08:21 +01:00
|
|
|
.boxed()
|
2024-11-06 18:27:40 +00:00
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
// Replicate transferable state events to the new room
|
|
|
|
|
for event_type in TRANSFERABLE_STATE_EVENTS {
|
2025-07-19 15:17:27 +01:00
|
|
|
let state_keys = services
|
2024-11-06 18:27:40 +00:00
|
|
|
.rooms
|
|
|
|
|
.state_accessor
|
2025-07-19 15:17:27 +01:00
|
|
|
.room_state_keys(&body.room_id, event_type)
|
2024-11-06 18:27:40 +00:00
|
|
|
.await?;
|
2025-07-19 15:17:27 +01:00
|
|
|
for state_key in state_keys {
|
2025-12-27 04:05:26 +00:00
|
|
|
let mut event_content = match services
|
2025-07-19 15:17:27 +01:00
|
|
|
.rooms
|
|
|
|
|
.state_accessor
|
|
|
|
|
.room_state_get(&body.room_id, event_type, &state_key)
|
|
|
|
|
.await
|
|
|
|
|
{
|
|
|
|
|
| Ok(v) => v.content().to_owned(),
|
|
|
|
|
| Err(_) => continue, // Skipping missing events.
|
|
|
|
|
};
|
2025-07-19 15:41:36 +01:00
|
|
|
if event_content.get() == "{}" {
|
|
|
|
|
// If the event content is empty, we skip it
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2025-12-27 04:05:26 +00:00
|
|
|
// If this is a power levels event, and the new room version has creators,
|
|
|
|
|
// we need to make sure they dont appear in the users block of power levels.
|
|
|
|
|
if *event_type == StateEventType::RoomPowerLevels {
|
|
|
|
|
// TODO(v12): additional creators
|
|
|
|
|
let creators = vec![sender_user];
|
|
|
|
|
let mut power_levels_event_content: RoomPowerLevelsEventContent =
|
|
|
|
|
serde_json::from_str(event_content.get()).map_err(|_| {
|
|
|
|
|
err!(Request(BadJson("Power levels event content is not valid")))
|
|
|
|
|
})?;
|
|
|
|
|
for creator in creators {
|
|
|
|
|
power_levels_event_content.users.remove(creator);
|
|
|
|
|
}
|
|
|
|
|
event_content = to_raw_value(&power_levels_event_content)
|
|
|
|
|
.expect("event is valid, we just deserialized and modified it");
|
|
|
|
|
}
|
2025-07-19 15:17:27 +01:00
|
|
|
|
|
|
|
|
services
|
|
|
|
|
.rooms
|
|
|
|
|
.timeline
|
|
|
|
|
.build_and_append_pdu(
|
2026-04-10 12:52:42 -04:00
|
|
|
PartialPdu {
|
2025-07-19 15:17:27 +01:00
|
|
|
event_type: event_type.to_string().into(),
|
|
|
|
|
content: event_content,
|
|
|
|
|
state_key: Some(StateKey::from(state_key)),
|
|
|
|
|
..Default::default()
|
|
|
|
|
},
|
|
|
|
|
sender_user,
|
2026-04-10 12:52:42 -04:00
|
|
|
replacement_room.as_deref(),
|
2025-07-19 15:17:27 +01:00
|
|
|
&state_lock,
|
|
|
|
|
)
|
|
|
|
|
.boxed()
|
|
|
|
|
.await?;
|
|
|
|
|
}
|
2024-11-06 18:27:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Moves any local aliases to the new room
|
|
|
|
|
let mut local_aliases = services
|
|
|
|
|
.rooms
|
|
|
|
|
.alias
|
|
|
|
|
.local_aliases_for_room(&body.room_id)
|
|
|
|
|
.boxed();
|
|
|
|
|
|
|
|
|
|
while let Some(alias) = local_aliases.next().await {
|
|
|
|
|
services
|
|
|
|
|
.rooms
|
|
|
|
|
.alias
|
2026-04-10 12:52:42 -04:00
|
|
|
.remove_alias(&alias, sender_user)
|
2024-11-06 18:27:40 +00:00
|
|
|
.await?;
|
|
|
|
|
|
2026-04-10 12:52:42 -04:00
|
|
|
services.rooms.alias.set_alias(
|
|
|
|
|
&alias,
|
|
|
|
|
replacement_room.as_ref().unwrap(),
|
|
|
|
|
sender_user,
|
|
|
|
|
)?;
|
2024-11-06 18:27:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get the old room power levels
|
2026-04-10 12:52:42 -04:00
|
|
|
let mut power_levels = services
|
2024-11-06 18:27:40 +00:00
|
|
|
.rooms
|
|
|
|
|
.state_accessor
|
2026-04-10 12:52:42 -04:00
|
|
|
.get_room_power_levels(&body.room_id)
|
|
|
|
|
.await;
|
2024-11-06 18:27:40 +00:00
|
|
|
|
|
|
|
|
// Setting events_default and invite to the greater of 50 and users_default + 1
|
|
|
|
|
let new_level = max(
|
|
|
|
|
int!(50),
|
2026-04-10 12:52:42 -04:00
|
|
|
power_levels
|
2024-11-06 18:27:40 +00:00
|
|
|
.users_default
|
|
|
|
|
.checked_add(int!(1))
|
2024-12-15 00:05:47 -05:00
|
|
|
.ok_or_else(|| {
|
|
|
|
|
err!(Request(BadJson("users_default power levels event content is not valid")))
|
|
|
|
|
})?,
|
2024-11-06 18:27:40 +00:00
|
|
|
);
|
|
|
|
|
|
2026-04-10 12:52:42 -04:00
|
|
|
power_levels.events_default = new_level;
|
|
|
|
|
power_levels.invite = new_level;
|
|
|
|
|
|
2024-11-06 18:27:40 +00:00
|
|
|
// Modify the power levels in the old room to prevent sending of events and
|
|
|
|
|
// inviting new users
|
|
|
|
|
services
|
|
|
|
|
.rooms
|
|
|
|
|
.timeline
|
|
|
|
|
.build_and_append_pdu(
|
2026-04-10 12:52:42 -04:00
|
|
|
PartialPdu::state(
|
|
|
|
|
StateKey::new(),
|
|
|
|
|
&RoomPowerLevelsEventContent::try_from(power_levels).unwrap(),
|
|
|
|
|
),
|
2024-11-06 18:27:40 +00:00
|
|
|
sender_user,
|
2025-09-17 20:46:03 +00:00
|
|
|
Some(&body.room_id),
|
2024-11-06 18:27:40 +00:00
|
|
|
&state_lock,
|
|
|
|
|
)
|
2025-07-19 15:08:21 +01:00
|
|
|
.boxed()
|
2024-11-06 18:27:40 +00:00
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
drop(state_lock);
|
|
|
|
|
|
2025-09-23 21:46:07 +01:00
|
|
|
// For v12 rooms, send tombstone AFTER creating replacement room
|
2026-04-10 12:52:42 -04:00
|
|
|
if room_version_rules.room_id_format == RoomIdFormatVersion::V2 {
|
|
|
|
|
let old_room_state_lock = services.rooms.state.mutex.lock(body.room_id.as_str()).await;
|
2025-09-23 21:46:07 +01:00
|
|
|
// For v12 rooms, no event reference in predecessor due to cyclic dependency -
|
|
|
|
|
// could best effort one maybe?
|
|
|
|
|
services
|
|
|
|
|
.rooms
|
|
|
|
|
.timeline
|
|
|
|
|
.build_and_append_pdu(
|
2026-04-10 12:52:42 -04:00
|
|
|
PartialPdu::state(
|
|
|
|
|
StateKey::new(),
|
|
|
|
|
&RoomTombstoneEventContent::new(
|
|
|
|
|
"This room has been replaced".to_owned(),
|
|
|
|
|
replacement_room.as_ref().unwrap().to_owned(),
|
|
|
|
|
),
|
|
|
|
|
),
|
2025-09-23 21:46:07 +01:00
|
|
|
sender_user,
|
|
|
|
|
Some(&body.room_id),
|
|
|
|
|
&old_room_state_lock,
|
|
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
drop(old_room_state_lock);
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-19 15:08:21 +01:00
|
|
|
// 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 Ok(child) = services
|
|
|
|
|
.rooms
|
|
|
|
|
.state_accessor
|
|
|
|
|
.room_state_get_content::<SpaceChildEventContent>(
|
2026-04-10 12:52:42 -04:00
|
|
|
&space_id,
|
2025-07-19 15:08:21 +01:00
|
|
|
&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;
|
|
|
|
|
};
|
2025-07-19 15:22:50 +01:00
|
|
|
debug!(
|
2025-09-22 20:44:40 +01:00
|
|
|
"Updating space {space_id} child event for room {} to {}",
|
|
|
|
|
&body.room_id,
|
2026-04-10 12:52:42 -04:00
|
|
|
replacement_room.as_ref().unwrap()
|
2025-07-19 15:22:50 +01:00
|
|
|
);
|
2025-07-19 15:08:21 +01:00
|
|
|
// First, drop the space's child event
|
2026-04-10 12:52:42 -04:00
|
|
|
let state_lock = services.rooms.state.mutex.lock(space_id.as_str()).await;
|
2025-07-19 15:22:50 +01:00
|
|
|
debug!("Removing space child event for room {} in space {space_id}", &body.room_id);
|
2025-07-19 15:08:21 +01:00
|
|
|
services
|
|
|
|
|
.rooms
|
|
|
|
|
.timeline
|
|
|
|
|
.build_and_append_pdu(
|
2026-04-10 12:52:42 -04:00
|
|
|
PartialPdu {
|
2025-07-19 15:08:21 +01:00
|
|
|
event_type: StateEventType::SpaceChild.into(),
|
2026-04-10 12:52:42 -04:00
|
|
|
content: to_raw_value(&RedactedSpaceChildEventContent::new())
|
2025-07-19 15:08:21 +01:00
|
|
|
.expect("event is valid, we just created it"),
|
2025-07-19 15:22:50 +01:00
|
|
|
state_key: Some(body.room_id.clone().as_str().into()),
|
2025-07-19 15:08:21 +01:00
|
|
|
..Default::default()
|
|
|
|
|
},
|
|
|
|
|
sender_user,
|
2026-04-10 12:52:42 -04:00
|
|
|
Some(&space_id),
|
2025-07-19 15:08:21 +01:00
|
|
|
&state_lock,
|
|
|
|
|
)
|
|
|
|
|
.boxed()
|
|
|
|
|
.await
|
|
|
|
|
.ok();
|
|
|
|
|
// Now, add a new child event for the replacement room
|
2025-09-22 20:44:40 +01:00
|
|
|
debug!(
|
|
|
|
|
"Adding space child event for room {} in space {space_id}",
|
2026-04-10 12:52:42 -04:00
|
|
|
replacement_room.as_ref().unwrap()
|
2025-09-22 20:44:40 +01:00
|
|
|
);
|
2025-07-19 15:08:21 +01:00
|
|
|
services
|
|
|
|
|
.rooms
|
|
|
|
|
.timeline
|
|
|
|
|
.build_and_append_pdu(
|
2026-04-10 12:52:42 -04:00
|
|
|
PartialPdu::state(
|
|
|
|
|
replacement_room.as_ref().unwrap().as_str(),
|
|
|
|
|
&assign!(SpaceChildEventContent::new(vec![sender_user.server_name().to_owned()]), {
|
2025-07-19 15:51:03 +01:00
|
|
|
order: child.order,
|
|
|
|
|
suggested: child.suggested,
|
2026-04-10 12:52:42 -04:00
|
|
|
}),
|
|
|
|
|
),
|
2025-07-19 15:08:21 +01:00
|
|
|
sender_user,
|
2026-04-10 12:52:42 -04:00
|
|
|
Some(&space_id),
|
2025-07-19 15:08:21 +01:00
|
|
|
&state_lock,
|
|
|
|
|
)
|
|
|
|
|
.boxed()
|
|
|
|
|
.await
|
|
|
|
|
.ok();
|
2025-07-19 15:22:50 +01:00
|
|
|
debug!(
|
2025-09-22 20:44:40 +01:00
|
|
|
"Finished updating space {space_id} child event for room {} to {}",
|
|
|
|
|
&body.room_id,
|
2026-04-10 12:52:42 -04:00
|
|
|
replacement_room.as_ref().unwrap()
|
2025-07-19 15:22:50 +01:00
|
|
|
);
|
2025-07-19 15:08:21 +01:00
|
|
|
drop(state_lock);
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-06 18:27:40 +00:00
|
|
|
// Return the replacement room id
|
2026-04-10 12:52:42 -04:00
|
|
|
Ok(upgrade_room::v3::Response::new(replacement_room.as_ref().unwrap().to_owned()))
|
2024-11-06 18:27:40 +00:00
|
|
|
}
|