Skip to content
Snippets Groups Projects
room.rs 28 KiB
Newer Older
  • Learn to ignore specific revisions
  • Jonathan de Jong's avatar
    Jonathan de Jong committed
    use crate::{
    
    Timo Kösters's avatar
    Timo Kösters committed
        api::client_server::invite_helper, service::pdu::PduBuilder, services, Error, Result, Ruma,
    
    Jonathan de Jong's avatar
    Jonathan de Jong committed
    };
    
    use ruma::{
        api::client::{
            error::ErrorKind,
    
    Jonathan de Jong's avatar
    Jonathan de Jong committed
            room::{self, aliases, create_room, get_room_event, upgrade_room},
    
        },
        events::{
    
    Jonas Platte's avatar
    Jonas Platte committed
            room::{
                canonical_alias::RoomCanonicalAliasEventContent,
                create::RoomCreateEventContent,
                guest_access::{GuestAccess, RoomGuestAccessEventContent},
                history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
                join_rules::{JoinRule, RoomJoinRulesEventContent},
                member::{MembershipState, RoomMemberEventContent},
                name::RoomNameEventContent,
                power_levels::RoomPowerLevelsEventContent,
                tombstone::RoomTombstoneEventContent,
                topic::RoomTopicEventContent,
            },
    
    Kévin Commaille's avatar
    Kévin Commaille committed
            StateEventType, TimelineEventType,
    
    Nyaaori's avatar
    Nyaaori committed
        int,
        serde::JsonObject,
    
        CanonicalJsonObject, OwnedRoomAliasId, RoomAliasId, RoomId, RoomVersionId,
    
    use serde_json::{json, value::to_raw_value};
    
    use std::{cmp::max, collections::BTreeMap, sync::Arc};
    
    use tracing::{info, warn};
    
    /// # `POST /_matrix/client/r0/createRoom`
    ///
    /// Creates a new room.
    ///
    /// - Room ID is randomly generated
    /// - Create alias if room_alias_name is set
    /// - Send create event
    /// - Join sender user
    /// - Send power levels event
    /// - Send canonical room alias
    /// - Send join rules
    /// - Send history visibility
    /// - Send guest access
    /// - Send events listed in initial state
    /// - Send events implied by `name` and `topic`
    /// - Send invite events
    
    pub async fn create_room_route(
    
    Jonas Platte's avatar
    Jonas Platte committed
        body: Ruma<create_room::v3::Request>,
    
    Jonathan de Jong's avatar
    Jonathan de Jong committed
    ) -> Result<create_room::v3::Response> {
        use create_room::v3::RoomPreset;
    
    
        let sender_user = body.sender_user.as_ref().expect("user is authenticated");
    
        let room_id = RoomId::new(services().globals.server_name());
    
    Timo Kösters's avatar
    Timo Kösters committed
        services().rooms.short.get_or_create_shortroomid(&room_id)?;
    
    Timo Kösters's avatar
    Timo Kösters committed
        let mutex_state = Arc::clone(
    
    Timo Kösters's avatar
    Timo Kösters committed
            services()
                .globals
    
    Timo Kösters's avatar
    Timo Kösters committed
                .roomid_mutex_state
    
    Timo Kösters's avatar
    Timo Kösters committed
                .write()
                .unwrap()
                .entry(room_id.clone())
                .or_default(),
        );
    
    Timo Kösters's avatar
    Timo Kösters committed
        let state_lock = mutex_state.lock().await;
    
    Timo Kösters's avatar
    Timo Kösters committed
    
    
        if !services().globals.allow_room_creation()
    
            && !services().users.is_admin(sender_user)?
    
        {
            return Err(Error::BadRequest(
                ErrorKind::Forbidden,
                "Room creation has been disabled.",
            ));
        }
    
    
    Timo Kösters's avatar
    Timo Kösters committed
        let alias: Option<OwnedRoomAliasId> =
    
            body.room_alias_name
                .as_ref()
                .map_or(Ok(None), |localpart| {
                    // TODO: Check for invalid characters and maximum length
    
    Timo Kösters's avatar
    Timo Kösters committed
                    let alias = RoomAliasId::parse(format!(
                        "#{}:{}",
                        localpart,
                        services().globals.server_name()
                    ))
                    .map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid alias."))?;
    
                    if services()
                        .rooms
                        .alias
                        .resolve_local_alias(&alias)?
                        .is_some()
                    {
    
                        Err(Error::BadRequest(
                            ErrorKind::RoomInUse,
                            "Room alias already exists.",
                        ))
                    } else {
                        Ok(Some(alias))
                    }
                })?;
    
        let room_version = match body.room_version.clone() {
    
            Some(room_version) => {
    
    Timo Kösters's avatar
    Timo Kösters committed
                if services()
                    .globals
                    .supported_room_versions()
                    .contains(&room_version)
                {
    
                    room_version
                } else {
                    return Err(Error::BadRequest(
                        ErrorKind::UnsupportedRoomVersion,
                        "This server does not support that room version.",
                    ));
                }
            }
    
            None => services().globals.default_room_version(),
    
        let content = match &body.creation_content {
            Some(content) => {
                let mut content = content
                    .deserialize_as::<CanonicalJsonObject>()
                    .expect("Invalid creation content");
    
                match room_version {
                    RoomVersionId::V1
                    | RoomVersionId::V2
                    | RoomVersionId::V3
                    | RoomVersionId::V4
                    | RoomVersionId::V5
                    | RoomVersionId::V6
                    | RoomVersionId::V7
                    | RoomVersionId::V8
                    | RoomVersionId::V9
                    | RoomVersionId::V10 => {
                        content.insert(
                            "creator".into(),
                            json!(&sender_user).try_into().map_err(|e| {
                                info!("Invalid creation content: {e}");
                                Error::BadRequest(ErrorKind::BadJson, "Invalid creation content")
                            })?,
                        );
                    }
    
                    RoomVersionId::V11 => {} // V11 removed the "creator" key
    
                    _ => {
                        warn!("Unexpected or unsupported room version {}", room_version);
                        return Err(Error::BadRequest(
                            ErrorKind::BadJson,
                            "Unexpected or unsupported room version found",
                        ));
                    }
    
                content.insert(
                    "room_version".into(),
    
                    json!(room_version.as_str()).try_into().map_err(|_| {
                        Error::BadRequest(ErrorKind::BadJson, "Invalid creation content")
                    })?,
    
                // TODO: Add correct value for v11
    
                let content = match room_version {
                    RoomVersionId::V1
                    | RoomVersionId::V2
                    | RoomVersionId::V3
                    | RoomVersionId::V4
                    | RoomVersionId::V5
                    | RoomVersionId::V6
                    | RoomVersionId::V7
                    | RoomVersionId::V8
                    | RoomVersionId::V9
                    | RoomVersionId::V10 => RoomCreateEventContent::new_v1(sender_user.clone()),
    
                    RoomVersionId::V11 => RoomCreateEventContent::new_v11(),
    
                    _ => {
                        warn!("Unexpected or unsupported room version {}", room_version);
                        return Err(Error::BadRequest(
                            ErrorKind::BadJson,
                            "Unexpected or unsupported room version found",
                        ));
                    }
    
                let mut content = serde_json::from_str::<CanonicalJsonObject>(
    
                        .map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Invalid creation content"))?
                        .get(),
    
                )
                .unwrap();
                content.insert(
                    "room_version".into(),
    
                    json!(room_version.as_str()).try_into().map_err(|_| {
                        Error::BadRequest(ErrorKind::BadJson, "Invalid creation content")
                    })?,
    
        let de_result = serde_json::from_str::<CanonicalJsonObject>(
            to_raw_value(&content)
                .expect("Invalid creation content")
                .get(),
        );
    
    
    Jonas Platte's avatar
    Jonas Platte committed
        if de_result.is_err() {
    
            return Err(Error::BadRequest(
                ErrorKind::BadJson,
                "Invalid creation content",
            ));
        }
    
        // 1. The room create event
    
        services()
            .rooms
            .timeline
            .build_and_append_pdu(
                PduBuilder {
                    event_type: TimelineEventType::RoomCreate,
                    content: to_raw_value(&content).expect("event is valid, we just created it"),
                    unsigned: None,
                    state_key: Some("".to_owned()),
                    redacts: None,
                },
                sender_user,
                &room_id,
                &state_lock,
            )
            .await?;
    
    
        // 2. Let the room creator join
    
        services()
            .rooms
            .timeline
            .build_and_append_pdu(
                PduBuilder {
                    event_type: TimelineEventType::RoomMember,
                    content: to_raw_value(&RoomMemberEventContent {
                        membership: MembershipState::Join,
                        displayname: services().users.displayname(sender_user)?,
                        avatar_url: services().users.avatar_url(sender_user)?,
                        is_direct: Some(body.is_direct),
                        third_party_invite: None,
                        blurhash: services().users.blurhash(sender_user)?,
                        reason: None,
                        join_authorized_via_users_server: None,
                    })
                    .expect("event is valid, we just created it"),
                    unsigned: None,
                    state_key: Some(sender_user.to_string()),
                    redacts: None,
                },
                sender_user,
                &room_id,
                &state_lock,
            )
            .await?;
    
    
        // 3. Power levels
    
    
        // Figure out preset. We need it for preset specific events
    
    Nyaaori's avatar
    Nyaaori committed
        let preset = body.preset.clone().unwrap_or(match &body.visibility {
            room::Visibility::Private => RoomPreset::PrivateChat,
            room::Visibility::Public => RoomPreset::PublicChat,
            _ => RoomPreset::PrivateChat, // Room visibility should not be custom
        });
    
        let mut users = BTreeMap::new();
    
        users.insert(sender_user.clone(), int!(100));
    
    Jonathan de Jong's avatar
    Jonathan de Jong committed
        if preset == RoomPreset::TrustedPrivateChat {
    
            for invite_ in &body.invite {
    
                users.insert(invite_.clone(), int!(100));
    
    Jonas Platte's avatar
    Jonas Platte committed
        let mut power_levels_content = serde_json::to_value(RoomPowerLevelsEventContent {
            users,
            ..Default::default()
        })
        .expect("event is valid, we just created it");
    
    
        if let Some(power_level_content_override) = &body.power_level_content_override {
    
    Jonas Platte's avatar
    Jonas Platte committed
            let json: JsonObject = serde_json::from_str(power_level_content_override.json().get())
                .map_err(|_| {
                    Error::BadRequest(ErrorKind::BadJson, "Invalid power_level_content_override.")
                })?;
    
    
            for (key, value) in json {
                power_levels_content[key] = value;
            }
        }
    
    
        services()
            .rooms
            .timeline
            .build_and_append_pdu(
    
                    event_type: TimelineEventType::RoomPowerLevels,
                    content: to_raw_value(&power_levels_content)
                        .expect("to_raw_value always works on serde_json::Value"),
    
                    unsigned: None,
                    state_key: Some("".to_owned()),
                    redacts: None,
                },
    
            )
            .await?;
    
        // 4. Canonical room alias
        if let Some(room_alias_id) = &alias {
            services()
                .rooms
                .timeline
                .build_and_append_pdu(
                    PduBuilder {
                        event_type: TimelineEventType::RoomCanonicalAlias,
                        content: to_raw_value(&RoomCanonicalAliasEventContent {
                            alias: Some(room_alias_id.to_owned()),
                            alt_aliases: vec![],
                        })
                        .expect("We checked that alias earlier, it must be fine"),
                        unsigned: None,
                        state_key: Some("".to_owned()),
                        redacts: None,
                    },
                    sender_user,
                    &room_id,
                    &state_lock,
                )
                .await?;
    
        }
    
        // 5. Events set by preset
    
        services()
            .rooms
            .timeline
            .build_and_append_pdu(
                PduBuilder {
                    event_type: TimelineEventType::RoomJoinRules,
                    content: to_raw_value(&RoomJoinRulesEventContent::new(match preset {
                        RoomPreset::PublicChat => JoinRule::Public,
                        // according to spec "invite" is the default
                        _ => JoinRule::Invite,
                    }))
                    .expect("event is valid, we just created it"),
                    unsigned: None,
                    state_key: Some("".to_owned()),
                    redacts: None,
                },
                sender_user,
                &room_id,
                &state_lock,
            )
            .await?;
    
    Timo Kösters's avatar
    Timo Kösters committed
    
    
        // 5.2 History Visibility
    
        services()
            .rooms
            .timeline
            .build_and_append_pdu(
                PduBuilder {
                    event_type: TimelineEventType::RoomHistoryVisibility,
                    content: to_raw_value(&RoomHistoryVisibilityEventContent::new(
                        HistoryVisibility::Shared,
                    ))
                    .expect("event is valid, we just created it"),
                    unsigned: None,
                    state_key: Some("".to_owned()),
                    redacts: None,
                },
                sender_user,
                &room_id,
                &state_lock,
            )
            .await?;
    
    Timo Kösters's avatar
    Timo Kösters committed
    
    
        services()
            .rooms
            .timeline
            .build_and_append_pdu(
                PduBuilder {
                    event_type: TimelineEventType::RoomGuestAccess,
                    content: to_raw_value(&RoomGuestAccessEventContent::new(match preset {
                        RoomPreset::PublicChat => GuestAccess::Forbidden,
                        _ => GuestAccess::CanJoin,
                    }))
                    .expect("event is valid, we just created it"),
                    unsigned: None,
                    state_key: Some("".to_owned()),
                    redacts: None,
                },
                sender_user,
                &room_id,
                &state_lock,
            )
            .await?;
    
    Timo Kösters's avatar
    Timo Kösters committed
    
    
        // 6. Events listed in initial_state
    
    Timo Kösters's avatar
    Timo Kösters committed
        for event in &body.initial_state {
    
            let mut pdu_builder = event.deserialize_as::<PduBuilder>().map_err(|e| {
    
                warn!("Invalid initial state event: {:?}", e);
    
                Error::BadRequest(ErrorKind::InvalidParam, "Invalid initial state event.")
    
            })?;
    
            // Implicit state key defaults to ""
            pdu_builder.state_key.get_or_insert_with(|| "".to_owned());
    
    Timo Kösters's avatar
    Timo Kösters committed
    
            // Silently skip encryption events if they are not allowed
    
    Kévin Commaille's avatar
    Kévin Commaille committed
            if pdu_builder.event_type == TimelineEventType::RoomEncryption
    
    Timo Kösters's avatar
    Timo Kösters committed
                && !services().globals.allow_encryption()
    
    Timo Kösters's avatar
    Timo Kösters committed
            {
    
    Timo Kösters's avatar
    Timo Kösters committed
                continue;
            }
    
    
            services()
                .rooms
                .timeline
                .build_and_append_pdu(pdu_builder, sender_user, &room_id, &state_lock)
                .await?;
    
    Timo Kösters's avatar
    Timo Kösters committed
        }
    
        // 7. Events implied by name and topic
    
    Timo Kösters's avatar
    Timo Kösters committed
        if let Some(name) = &body.name {
    
            services()
                .rooms
                .timeline
                .build_and_append_pdu(
                    PduBuilder {
                        event_type: TimelineEventType::RoomName,
                        content: to_raw_value(&RoomNameEventContent::new(name.clone()))
                            .expect("event is valid, we just created it"),
                        unsigned: None,
                        state_key: Some("".to_owned()),
                        redacts: None,
                    },
                    sender_user,
                    &room_id,
                    &state_lock,
                )
                .await?;
    
    Timo Kösters's avatar
    Timo Kösters committed
        }
    
    Timo Kösters's avatar
    Timo Kösters committed
        if let Some(topic) = &body.topic {
    
            services()
                .rooms
                .timeline
                .build_and_append_pdu(
                    PduBuilder {
                        event_type: TimelineEventType::RoomTopic,
                        content: to_raw_value(&RoomTopicEventContent {
                            topic: topic.clone(),
                        })
                        .expect("event is valid, we just created it"),
                        unsigned: None,
                        state_key: Some("".to_owned()),
                        redacts: None,
                    },
                    sender_user,
                    &room_id,
                    &state_lock,
                )
                .await?;
    
        // 8. Events implied by invite (and TODO: invite_3pid)
    
    Timo Kösters's avatar
    Timo Kösters committed
        drop(state_lock);
    
        for user_id in &body.invite {
    
            let _ = invite_helper(sender_user, user_id, &room_id, None, body.is_direct).await;
    
        }
    
        // Homeserver specific stuff
        if let Some(alias) = alias {
    
    Timo Kösters's avatar
    Timo Kösters committed
            services().rooms.alias.set_alias(&alias, &room_id)?;
    
    Timo Kösters's avatar
    Timo Kösters committed
        if body.visibility == room::Visibility::Public {
    
    Timo Kösters's avatar
    Timo Kösters committed
            services().rooms.directory.set_public(&room_id)?;
    
        info!("{} created a room", sender_user);
    
    
    Jonathan de Jong's avatar
    Jonathan de Jong committed
        Ok(create_room::v3::Response::new(room_id))
    
    /// # `GET /_matrix/client/r0/rooms/{roomId}/event/{eventId}`
    ///
    /// Gets a single event.
    ///
    /// - You have to currently be joined to the room (TODO: Respect history visibility)
    
    pub async fn get_room_event_route(
    
    Jonas Platte's avatar
    Jonas Platte committed
        body: Ruma<get_room_event::v3::Request>,
    
    Jonathan de Jong's avatar
    Jonathan de Jong committed
    ) -> Result<get_room_event::v3::Response> {
    
        let sender_user = body.sender_user.as_ref().expect("user is authenticated");
    
        let event = services()
            .rooms
            .timeline
            .get_pdu(&body.event_id)?
            .ok_or_else(|| {
                warn!("Event not found, event ID: {:?}", &body.event_id);
                Error::BadRequest(ErrorKind::NotFound, "Event not found.")
            })?;
    
    
        if !services().rooms.state_accessor.user_can_see_event(
            sender_user,
            &event.room_id,
            &body.event_id,
        )? {
    
            return Err(Error::BadRequest(
                ErrorKind::Forbidden,
    
                "You don't have permission to view this event.",
    
        let mut event = (*event).clone();
        event.add_age()?;
    
    
    Jonathan de Jong's avatar
    Jonathan de Jong committed
        Ok(get_room_event::v3::Response {
    
            event: event.to_room_event(),
    
    Faelar's avatar
    Faelar committed
    
    
    /// # `GET /_matrix/client/r0/rooms/{roomId}/aliases`
    ///
    /// Lists all aliases of the room.
    ///
    /// - Only users joined to the room are allowed to call this TODO: Allow any user to call it if history_visibility is world readable
    
    pub async fn get_room_aliases_route(
    
    Jonas Platte's avatar
    Jonas Platte committed
        body: Ruma<aliases::v3::Request>,
    
    Jonathan de Jong's avatar
    Jonathan de Jong committed
    ) -> Result<aliases::v3::Response> {
    
        let sender_user = body.sender_user.as_ref().expect("user is authenticated");
    
    
    Timo Kösters's avatar
    Timo Kösters committed
        if !services()
            .rooms
            .state_cache
            .is_joined(sender_user, &body.room_id)?
        {
    
            return Err(Error::BadRequest(
                ErrorKind::Forbidden,
                "You don't have permission to view this room.",
            ));
        }
    
    
    Jonathan de Jong's avatar
    Jonathan de Jong committed
        Ok(aliases::v3::Response {
    
            aliases: services()
    
                .rooms
    
    Timo Kösters's avatar
    Timo Kösters committed
                .alias
                .local_aliases_for_room(&body.room_id)
    
                .filter_map(|a| a.ok())
                .collect(),
    
    /// # `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 async fn upgrade_room_route(
    
    Jonas Platte's avatar
    Jonas Platte committed
        body: Ruma<upgrade_room::v3::Request>,
    
    Jonathan de Jong's avatar
    Jonathan de Jong committed
    ) -> Result<upgrade_room::v3::Response> {
    
        let sender_user = body.sender_user.as_ref().expect("user is authenticated");
    
    Faelar's avatar
    Faelar committed
    
    
    Timo Kösters's avatar
    Timo Kösters committed
        if !services()
            .globals
            .supported_room_versions()
            .contains(&body.new_version)
        {
    
    Faelar's avatar
    Faelar committed
            return Err(Error::BadRequest(
                ErrorKind::UnsupportedRoomVersion,
                "This server does not support that room version.",
            ));
        }
    
        // Create a replacement room
    
        let replacement_room = RoomId::new(services().globals.server_name());
    
    Timo Kösters's avatar
    Timo Kösters committed
        services()
            .rooms
            .short
            .get_or_create_shortroomid(&replacement_room)?;
    
    Faelar's avatar
    Faelar committed
    
    
    Timo Kösters's avatar
    Timo Kösters committed
        let mutex_state = Arc::clone(
    
    Timo Kösters's avatar
    Timo Kösters committed
            services()
                .globals
    
    Timo Kösters's avatar
    Timo Kösters committed
                .roomid_mutex_state
    
    Timo Kösters's avatar
    Timo Kösters committed
                .write()
                .unwrap()
                .entry(body.room_id.clone())
                .or_default(),
        );
    
    Timo Kösters's avatar
    Timo Kösters committed
        let state_lock = mutex_state.lock().await;
    
    Faelar's avatar
    Faelar committed
        // Send a m.room.tombstone event to the old room to indicate that it is not intended to be used any further
        // Fail if the sender does not have the required permissions
    
        let tombstone_event_id = services()
            .rooms
            .timeline
            .build_and_append_pdu(
                PduBuilder {
                    event_type: TimelineEventType::RoomTombstone,
                    content: to_raw_value(&RoomTombstoneEventContent {
                        body: "This room has been replaced".to_owned(),
                        replacement_room: replacement_room.clone(),
                    })
                    .expect("event is valid, we just created it"),
                    unsigned: None,
                    state_key: Some("".to_owned()),
                    redacts: None,
                },
                sender_user,
                &body.room_id,
                &state_lock,
            )
            .await?;
    
    Faelar's avatar
    Faelar committed
    
    
    Timo Kösters's avatar
    Timo Kösters committed
        // Change lock to replacement room
        drop(state_lock);
        let mutex_state = Arc::clone(
    
    Timo Kösters's avatar
    Timo Kösters committed
            services()
                .globals
    
    Timo Kösters's avatar
    Timo Kösters committed
                .roomid_mutex_state
                .write()
                .unwrap()
                .entry(replacement_room.clone())
                .or_default(),
        );
        let state_lock = mutex_state.lock().await;
    
    
        // Get the old room creation event
        let mut create_event_content = serde_json::from_str::<CanonicalJsonObject>(
    
    Timo Kösters's avatar
    Timo Kösters committed
            services()
                .rooms
    
    Timo Kösters's avatar
    Timo Kösters committed
                .state_accessor
    
    Timo Kösters's avatar
    Timo Kösters committed
                .room_state_get(&body.room_id, &StateEventType::RoomCreate, "")?
    
    Faelar's avatar
    Faelar committed
                .ok_or_else(|| Error::bad_database("Found room without m.room.create event."))?
    
    Jonas Platte's avatar
    Jonas Platte committed
                .get(),
    
    Faelar's avatar
    Faelar committed
        )
    
        .map_err(|_| Error::bad_database("Invalid room event in database."))?;
    
    Faelar's avatar
    Faelar committed
    
        // Use the m.room.tombstone event as the predecessor
        let predecessor = Some(ruma::events::room::create::PreviousRoom::new(
            body.room_id.clone(),
    
            (*tombstone_event_id).to_owned(),
    
    Faelar's avatar
    Faelar committed
        ));
    
        // Send a m.room.create event containing a predecessor field and the applicable room_version
    
        match body.new_version {
            RoomVersionId::V1
            | RoomVersionId::V2
            | RoomVersionId::V3
            | RoomVersionId::V4
            | RoomVersionId::V5
            | RoomVersionId::V6
            | RoomVersionId::V7
            | RoomVersionId::V8
            | RoomVersionId::V9
            | RoomVersionId::V10 => {
                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")
                    })?,
                );
            }
    
            RoomVersionId::V11 => {
    
                // "creator" key no longer exists in V11 rooms
                create_event_content.remove("creator");
            }
    
            _ => {
                warn!(
                    "Unexpected or unsupported room version {}",
                    body.new_version
                );
                return Err(Error::BadRequest(
                    ErrorKind::BadJson,
                    "Unexpected or unsupported room version found",
                ));
            }
    
        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
    
        let de_result = serde_json::from_str::<CanonicalJsonObject>(
            to_raw_value(&create_event_content)
                .expect("Error forming creation event")
                .get(),
        );
    
    
    Jonas Platte's avatar
    Jonas Platte committed
        if de_result.is_err() {
    
            return Err(Error::BadRequest(
                ErrorKind::BadJson,
                "Error forming creation event",
            ));
        }
    
    Faelar's avatar
    Faelar committed
    
    
        services()
            .rooms
            .timeline
            .build_and_append_pdu(
                PduBuilder {
                    event_type: TimelineEventType::RoomCreate,
                    content: to_raw_value(&create_event_content)
                        .expect("event is valid, we just created it"),
                    unsigned: None,
                    state_key: Some("".to_owned()),
                    redacts: None,
                },
                sender_user,
                &replacement_room,
                &state_lock,
            )
            .await?;
    
    Faelar's avatar
    Faelar committed
    
        // Join the new room
    
        services()
            .rooms
            .timeline
            .build_and_append_pdu(
                PduBuilder {
                    event_type: TimelineEventType::RoomMember,
                    content: to_raw_value(&RoomMemberEventContent {
                        membership: MembershipState::Join,
                        displayname: services().users.displayname(sender_user)?,
                        avatar_url: services().users.avatar_url(sender_user)?,
                        is_direct: None,
                        third_party_invite: None,
                        blurhash: services().users.blurhash(sender_user)?,
                        reason: None,
                        join_authorized_via_users_server: None,
                    })
                    .expect("event is valid, we just created it"),
                    unsigned: None,
                    state_key: Some(sender_user.to_string()),
                    redacts: None,
                },
                sender_user,
                &replacement_room,
                &state_lock,
            )
            .await?;
    
    Faelar's avatar
    Faelar committed
    
        // Recommended transferable state events list from the specs
        let transferable_state_events = vec![
    
    Timo Kösters's avatar
    Timo Kösters committed
            StateEventType::RoomServerAcl,
            StateEventType::RoomEncryption,
            StateEventType::RoomName,
            StateEventType::RoomAvatar,
            StateEventType::RoomTopic,
            StateEventType::RoomGuestAccess,
            StateEventType::RoomHistoryVisibility,
            StateEventType::RoomJoinRules,
            StateEventType::RoomPowerLevels,
    
    Faelar's avatar
    Faelar committed
        ];
    
        // Replicate transferable state events to the new room
        for event_type in transferable_state_events {
    
    Timo Kösters's avatar
    Timo Kösters committed
            let event_content =
                match services()
                    .rooms
                    .state_accessor
                    .room_state_get(&body.room_id, &event_type, "")?
                {
                    Some(v) => v.content.clone(),
                    None => continue, // Skipping missing events.
                };
    
    Faelar's avatar
    Faelar committed
    
    
            services()
                .rooms
                .timeline
                .build_and_append_pdu(
                    PduBuilder {
                        event_type: event_type.to_string().into(),
                        content: event_content,
                        unsigned: None,
                        state_key: Some("".to_owned()),
                        redacts: None,
                    },
                    sender_user,
                    &replacement_room,
                    &state_lock,
                )
                .await?;
    
    Faelar's avatar
    Faelar committed
        }
    
        // Moves any local aliases to the new room
    
    Timo Kösters's avatar
    Timo Kösters committed
        for alias in services()
            .rooms
            .alias
            .local_aliases_for_room(&body.room_id)
            .filter_map(|r| r.ok())
        {
            services()
                .rooms
                .alias
                .set_alias(&alias, &replacement_room)?;
    
    Faelar's avatar
    Faelar committed
        }
    
        // Get the old room power levels
    
    Jonas Platte's avatar
    Jonas Platte committed
        let mut power_levels_event_content: RoomPowerLevelsEventContent = serde_json::from_str(
    
    Timo Kösters's avatar
    Timo Kösters committed
            services()
                .rooms
    
    Timo Kösters's avatar
    Timo Kösters committed
                .state_accessor
    
    Timo Kösters's avatar
    Timo Kösters committed
                .room_state_get(&body.room_id, &StateEventType::RoomPowerLevels, "")?
    
    Jonas Platte's avatar
    Jonas Platte committed
                .ok_or_else(|| Error::bad_database("Found room without m.room.create event."))?
                .content
                .get(),
        )
        .map_err(|_| Error::bad_database("Invalid room event in database."))?;
    
    Faelar's avatar
    Faelar committed
    
        // Setting events_default and invite to the greater of 50 and users_default + 1
    
        let new_level = max(int!(50), power_levels_event_content.users_default + int!(1));
    
    Faelar's avatar
    Faelar committed
        power_levels_event_content.events_default = new_level;
        power_levels_event_content.invite = new_level;
    
        // Modify the power levels in the old room to prevent sending of events and inviting new users
    
        let _ = services()
            .rooms
            .timeline
            .build_and_append_pdu(
                PduBuilder {
                    event_type: TimelineEventType::RoomPowerLevels,
                    content: to_raw_value(&power_levels_event_content)
                        .expect("event is valid, we just created it"),
                    unsigned: None,
                    state_key: Some("".to_owned()),
                    redacts: None,
                },
                sender_user,
                &body.room_id,
                &state_lock,
            )
            .await?;
    
    Faelar's avatar
    Faelar committed
    
    
    Timo Kösters's avatar
    Timo Kösters committed
        drop(state_lock);
    
    Faelar's avatar
    Faelar committed
        // Return the replacement room id
    
    Jonathan de Jong's avatar
    Jonathan de Jong committed
        Ok(upgrade_room::v3::Response { replacement_room })
    
    Faelar's avatar
    Faelar committed
    }