From b34fd58cf748cf4de907dcb074f7bb18c6887968 Mon Sep 17 00:00:00 2001 From: Pim Kunis Date: Sun, 12 Sep 2021 11:54:05 +0200 Subject: [PATCH] Implement initial_state option for client room creation --- lib/architex/room_server.ex | 67 ++++++-- lib/architex/schema/event/generators.ex | 2 +- .../api_schemas/client/request/create_room.ex | 157 +++++++++++------- 3 files changed, 148 insertions(+), 78 deletions(-) diff --git a/lib/architex/room_server.ex b/lib/architex/room_server.ex index d20b1a4..45747f2 100644 --- a/lib/architex/room_server.ex +++ b/lib/architex/room_server.ex @@ -450,26 +450,49 @@ defmodule Architex.RoomServer do invite: invite, power_level_content_override: power_level_content_override, is_direct: is_direct, - creation_content: creation_content + creation_content: creation_content, + initial_state: initial_state }) do + invite_events = room_creation_invite_events(account, invite, room, is_direct) + + name_and_topic_events = + Enum.reject( + [ + if(name, do: Event.Name.new(room, account, name)), + if(topic, do: Event.Topic.new(room, account, topic)) + ], + &Kernel.is_nil/1 + ) + + initial_state_pairs = + if initial_state, do: Enum.map(initial_state, &{&1.type, &1.state_key}), else: [] + + initial_state_events = + room_creation_initial_state_events(account, initial_state, room) + |> Enum.reject(fn %Event{type: type, state_key: state_key} -> + ({type, state_key} == {"m.room.name", ""} and name) || + ({type, state_key} == {"m.room.topic", ""} and topic) + end) + + preset_events = + room_creation_preset(account, preset, room) + |> Enum.reject(&({&1.type, &1.state_key} in initial_state_pairs)) + + basic_events = [ + Event.CreateRoom.new(room, account, room_version, creation_content), + Event.Join.new(room, account), + Event.PowerLevels.create_room_new( + room, + account, + power_level_content_override, + invite, + preset + ) + ] + events = - ([ - Event.CreateRoom.new(room, account, room_version, creation_content), - Event.Join.new(room, account), - Event.PowerLevels.create_room_new( - room, - account, - power_level_content_override, - invite, - preset - ) - ] ++ - room_creation_preset(account, preset, room) ++ - [ - if(name, do: Event.Name.new(room, account, name)), - if(topic, do: Event.Topic.new(room, account, topic)) - ] ++ room_creation_invite_events(account, invite, room, is_direct)) - |> Enum.reject(&Kernel.is_nil/1) + basic_events ++ + preset_events ++ initial_state_events ++ name_and_topic_events ++ invite_events fn -> result = @@ -541,6 +564,14 @@ defmodule Architex.RoomServer do Enum.map(invite_user_ids, &Event.Invite.new(room, account, &1, is_direct)) end + defp room_creation_initial_state_events(_, nil, _), do: [] + + defp room_creation_initial_state_events(account, initial_state, room) do + Enum.map(initial_state, fn %{type: type, content: content, state_key: state_key} -> + Event.custom_state_event(room, account, type, content, state_key) + end) + end + # Finalize the event struct and insert it into the room's state using state resolution. # The values that are automatically added are: # - Auth events diff --git a/lib/architex/schema/event/generators.ex b/lib/architex/schema/event/generators.ex index 8c8a3c5..0a91216 100644 --- a/lib/architex/schema/event/generators.ex +++ b/lib/architex/schema/event/generators.ex @@ -66,7 +66,7 @@ defmodule Architex.Event.PowerLevels do @spec create_room_new( Room.t(), Account.t(), - CreateRoom.plco_t(), + CreateRoom.PowerLevelContentOverride.t(), [String.t()] | nil, String.t() | nil ) :: %Event{} diff --git a/lib/architex_web/api_schemas/client/request/create_room.ex b/lib/architex_web/api_schemas/client/request/create_room.ex index 95a4f85..046f84f 100644 --- a/lib/architex_web/api_schemas/client/request/create_room.ex +++ b/lib/architex_web/api_schemas/client/request/create_room.ex @@ -1,6 +1,97 @@ defmodule ArchitexWeb.Client.Request.CreateRoom do use ArchitexWeb.APIRequest + defmodule PowerLevelContentOverride do + use Ecto.Schema + + defmodule Notifications do + use Ecto.Schema + + @type t :: %__MODULE__{ + room: integer() | nil + } + + @primary_key false + embedded_schema do + field :room, :integer + end + + def changeset(data, params) do + cast(data, params, [:room]) + end + end + + @type t :: %__MODULE__{ + ban: integer() | nil, + events: %{optional(String.t()) => integer()} | nil, + events_default: integer() | nil, + invite: integer() | nil, + kick: integer() | nil, + redact: integer() | nil, + state_default: integer() | nil, + users: %{optional(String.t()) => integer()} | nil, + users_default: integer() | nil, + notifications: Notifications.t() | nil + } + + @primary_key false + embedded_schema do + field :ban, :integer + field :events, {:map, :integer} + field :events_default, :integer + field :invite, :integer + field :kick, :integer + field :redact, :integer + field :state_default, :integer + field :users, {:map, :integer} + field :users_default, :integer + + embeds_one :notifications, Notifications + end + + def changeset(data, params) do + data + |> cast(params, [ + :ban, + :events, + :events_default, + :invite, + :kick, + :redact, + :state_default, + :users, + :users_default + ]) + |> cast_embed(:notifications, + with: &Notifications.changeset/2, + required: false + ) + end + end + + defmodule StateEvent do + use Ecto.Schema + + @type t :: %__MODULE__{ + type: String.t(), + state_key: String.t(), + content: %{optional(String.t()) => any()} + } + + @primary_key false + embedded_schema do + field :type, :string + field :state_key, :string, default: "" + field :content, :map + end + + def changeset(data, params) do + data + |> cast(params, [:type, :state_key, :content]) + |> validate_required([:type, :content]) + end + end + @type t :: %__MODULE__{ visibility: String.t() | nil, room_alias_name: String.t() | nil, @@ -11,28 +102,13 @@ defmodule ArchitexWeb.Client.Request.CreateRoom do preset: String.t() | nil, is_direct: boolean() | nil, creation_content: %{optional(String.t()) => any()} | nil, - power_level_content_override: plco_t() | nil - } - - @type plco_t :: %__MODULE__.PowerLevelContentOverride{ - ban: integer() | nil, - events: %{optional(String.t()) => integer()} | nil, - events_default: integer() | nil, - invite: integer() | nil, - kick: integer() | nil, - redact: integer() | nil, - state_default: integer() | nil, - users: %{optional(String.t()) => integer()} | nil, - users_default: integer() | nil, - notifications: plco_n_t() | nil - } - - @type plco_n_t :: %__MODULE__.PowerLevelContentOverride.Notifications{ - room: integer() | nil + power_level_content_override: PowerLevelContentOverride.t() | nil, + initial_state: [StateEvent.t()] | nil } @primary_key false embedded_schema do + # TODO: unimplemented: invite_3pid and room_alias_name field :visibility, :string field :room_alias_name, :string field :name, :string @@ -43,23 +119,8 @@ defmodule ArchitexWeb.Client.Request.CreateRoom do field :is_direct, :boolean field :creation_content, :map - embeds_one :power_level_content_override, PowerLevelContentOverride, primary_key: false do - field :ban, :integer - field :events, {:map, :integer} - field :events_default, :integer - field :invite, :integer - field :kick, :integer - field :redact, :integer - field :state_default, :integer - field :users, {:map, :integer} - field :users_default, :integer - - embeds_one :notifications, Notifications, primary_key: false do - field :room, :integer - end - end - - # TODO: unimplemented: invite_3pid, initial_state, room_alias_name + embeds_many :initial_state, StateEvent + embeds_one :power_level_content_override, PowerLevelContentOverride end def changeset(data, params) do @@ -76,32 +137,10 @@ defmodule ArchitexWeb.Client.Request.CreateRoom do :creation_content ]) |> cast_embed(:power_level_content_override, - with: &power_level_content_override_changeset/2, + with: &PowerLevelContentOverride.changeset/2, required: false ) + |> cast_embed(:initial_state, with: &StateEvent.changeset/2, required: false) |> validate_inclusion(:preset, ["private_chat", "public_chat", "trusted_private_chat"]) end - - def power_level_content_override_changeset(data, params) do - data - |> cast(params, [ - :ban, - :events, - :events_default, - :invite, - :kick, - :redact, - :state_default, - :users, - :users_default - ]) - |> cast_embed(:notifications, - with: &power_level_content_override_notifications_changeset/2, - required: false - ) - end - - def power_level_content_override_notifications_changeset(data, params) do - cast(data, params, [:room]) - end end