Implement room_alias_name option for client room creation
This commit is contained in:
parent
b34fd58cf7
commit
064a398a37
6 changed files with 118 additions and 68 deletions
|
@ -50,7 +50,7 @@ Here, implemented and some unimplemented features are listed.
|
|||
- GET /_matrix/client/r0/login
|
||||
- POST /_matrix/client/r0/login: Only with password flow.
|
||||
- POST /_matrix/client/r0/register: Only with dummy flow.
|
||||
- POST /_matrix/client/r0/createRoom: Except with options invite_3pid, initial_state and room_alias_name.
|
||||
- POST /_matrix/client/r0/createRoom: Except with option invite_3pid.
|
||||
- GET /_matrix/client/r0/joined_rooms
|
||||
- POST /_matrix/client/r0/rooms/{roomId}/invite
|
||||
- POST /_matrix/client/r0/rooms/{roomId}/join: Except with third party invite.
|
||||
|
|
|
@ -21,7 +21,8 @@ defmodule Architex.RoomServer do
|
|||
Account,
|
||||
Device,
|
||||
DeviceTransaction,
|
||||
Membership
|
||||
Membership,
|
||||
Alias
|
||||
}
|
||||
|
||||
alias Architex.StateResolution.Authorization
|
||||
|
@ -203,19 +204,34 @@ defmodule Architex.RoomServer do
|
|||
|
||||
@impl true
|
||||
def handle_call(
|
||||
{:create_room, account, request},
|
||||
{:create_room, account, %CreateRoom{room_alias_name: room_alias_name} = request},
|
||||
_from,
|
||||
%{room: %Room{id: room_id} = room} = state
|
||||
) do
|
||||
case Repo.transaction(create_room_insert_events(room, account, request)) do
|
||||
{:ok, {state_set, room}} ->
|
||||
{:reply, {:ok, room_id}, %{state | state_set: state_set, room: room}}
|
||||
create_alias_result =
|
||||
if room_alias_name do
|
||||
Alias.create(room_alias_name, room_id)
|
||||
else
|
||||
{:ok, nil}
|
||||
end
|
||||
|
||||
{:error, reason} ->
|
||||
{:reply, {:error, reason}, state}
|
||||
case create_alias_result do
|
||||
{:ok, alias_} ->
|
||||
events = create_room_events(room, account, request, alias_)
|
||||
|
||||
_ ->
|
||||
{:reply, {:error, :unknown}, state}
|
||||
case Repo.transaction(process_events(room, %{}, events)) do
|
||||
{:ok, {state_set, room}} ->
|
||||
{:reply, {:ok, room_id}, %{state | state_set: state_set, room: room}}
|
||||
|
||||
{:error, reason} ->
|
||||
{:reply, {:error, reason}, state}
|
||||
|
||||
_ ->
|
||||
{:reply, {:error, :unknown}, state}
|
||||
end
|
||||
|
||||
{:error, _} ->
|
||||
{:reply, {:error, :alias}, state}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -275,7 +291,7 @@ defmodule Architex.RoomServer do
|
|||
def handle_call({:invite, account, user_id}, _from, %{room: room, state_set: state_set} = state) do
|
||||
invite_event = Event.Invite.new(room, account, user_id)
|
||||
|
||||
case Repo.transaction(insert_single_event(room, state_set, invite_event)) do
|
||||
case Repo.transaction(process_event(room, state_set, invite_event)) do
|
||||
{:ok, {state_set, room, _}} -> {:reply, :ok, %{state | state_set: state_set, room: room}}
|
||||
{:error, reason} -> {:reply, {:error, reason}, state}
|
||||
end
|
||||
|
@ -288,7 +304,7 @@ defmodule Architex.RoomServer do
|
|||
) do
|
||||
join_event = Event.Join.new(room, account)
|
||||
|
||||
case Repo.transaction(insert_single_event(room, state_set, join_event)) do
|
||||
case Repo.transaction(process_event(room, state_set, join_event)) do
|
||||
{:ok, {state_set, room, _}} ->
|
||||
{:reply, {:ok, room_id}, %{state | state_set: state_set, room: room}}
|
||||
|
||||
|
@ -300,7 +316,7 @@ defmodule Architex.RoomServer do
|
|||
def handle_call({:leave, account}, _from, %{room: room, state_set: state_set} = state) do
|
||||
leave_event = Event.Leave.new(room, account)
|
||||
|
||||
case Repo.transaction(insert_single_event(room, state_set, leave_event)) do
|
||||
case Repo.transaction(process_event(room, state_set, leave_event)) do
|
||||
{:ok, {state_set, room, _}} -> {:reply, :ok, %{state | state_set: state_set, room: room}}
|
||||
{:error, reason} -> {:reply, {:error, reason}, state}
|
||||
end
|
||||
|
@ -313,7 +329,7 @@ defmodule Architex.RoomServer do
|
|||
) do
|
||||
kick_event = Event.Kick.new(room, account, user_id, reason)
|
||||
|
||||
case Repo.transaction(insert_single_event(room, state_set, kick_event)) do
|
||||
case Repo.transaction(process_event(room, state_set, kick_event)) do
|
||||
{:ok, {state_set, room, _}} -> {:reply, :ok, %{state | state_set: state_set, room: room}}
|
||||
{:error, reason} -> {:reply, {:error, reason}, state}
|
||||
end
|
||||
|
@ -326,7 +342,7 @@ defmodule Architex.RoomServer do
|
|||
) do
|
||||
ban_event = Event.Ban.new(room, account, user_id, reason)
|
||||
|
||||
case Repo.transaction(insert_single_event(room, state_set, ban_event)) do
|
||||
case Repo.transaction(process_event(room, state_set, ban_event)) do
|
||||
{:ok, {state_set, room, _}} -> {:reply, :ok, %{state | state_set: state_set, room: room}}
|
||||
{:error, reason} -> {:reply, {:error, reason}, state}
|
||||
end
|
||||
|
@ -335,7 +351,7 @@ defmodule Architex.RoomServer do
|
|||
def handle_call({:unban, account, user_id}, _from, %{room: room, state_set: state_set} = state) do
|
||||
unban_event = Event.Unban.new(room, account, user_id)
|
||||
|
||||
case Repo.transaction(insert_single_event(room, state_set, unban_event)) do
|
||||
case Repo.transaction(process_event(room, state_set, unban_event)) do
|
||||
{:ok, {state_set, room, _}} -> {:reply, :ok, %{state | state_set: state_set, room: room}}
|
||||
{:error, reason} -> {:reply, {:error, reason}, state}
|
||||
end
|
||||
|
@ -368,7 +384,7 @@ defmodule Architex.RoomServer do
|
|||
) do
|
||||
message_event = Event.custom_event(room, account, event_type, content)
|
||||
|
||||
case Repo.transaction(insert_event_with_txn(state_set, room, device, message_event, txn_id)) do
|
||||
case Repo.transaction(process_event_with_txn(state_set, room, device, message_event, txn_id)) do
|
||||
{:ok, {state_set, room, event_id}} ->
|
||||
{:reply, {:ok, event_id}, %{state | state_set: state_set, room: room}}
|
||||
|
||||
|
@ -384,7 +400,7 @@ defmodule Architex.RoomServer do
|
|||
) do
|
||||
state_event = Event.custom_state_event(room, account, event_type, content, state_key)
|
||||
|
||||
case Repo.transaction(insert_single_event(room, state_set, state_event)) do
|
||||
case Repo.transaction(process_event(room, state_set, state_event)) do
|
||||
{:ok, {state_set, room, %Event{id: event_id}}} ->
|
||||
{:reply, {:ok, event_id}, %{state | state_set: state_set, room: room}}
|
||||
|
||||
|
@ -393,9 +409,9 @@ defmodule Architex.RoomServer do
|
|||
end
|
||||
end
|
||||
|
||||
@spec insert_event_with_txn(t(), Room.t(), Device.t(), %Event{}, String.t()) ::
|
||||
@spec process_event_with_txn(t(), Room.t(), Device.t(), %Event{}, String.t()) ::
|
||||
(() -> {t(), Room.t(), String.t()} | {:error, atom()})
|
||||
defp insert_event_with_txn(
|
||||
defp process_event_with_txn(
|
||||
state_set,
|
||||
room,
|
||||
%Device{nid: device_nid} = device,
|
||||
|
@ -413,7 +429,7 @@ defmodule Architex.RoomServer do
|
|||
|
||||
nil ->
|
||||
with {state_set, room, %Event{id: event_id}} <-
|
||||
insert_single_event(room, state_set, message_event).() do
|
||||
process_event(room, state_set, message_event).() do
|
||||
# Mark this transaction as done.
|
||||
Ecto.build_assoc(device, :device_transactions, txn_id: txn_id, event_id: event_id)
|
||||
|> Repo.insert!()
|
||||
|
@ -424,11 +440,11 @@ defmodule Architex.RoomServer do
|
|||
end
|
||||
end
|
||||
|
||||
@spec insert_single_event(Room.t(), t(), %Event{}) ::
|
||||
@spec process_event(Room.t(), t(), %Event{}) ::
|
||||
(() -> {t(), Room.t(), Event.t()} | {:error, atom()})
|
||||
defp insert_single_event(room, state_set, event) do
|
||||
defp process_event(room, state_set, event) do
|
||||
fn ->
|
||||
case finalize_and_insert_event(event, state_set, room) do
|
||||
case finalize_and_process_event(event, state_set, room) do
|
||||
{:ok, state_set, room, event} ->
|
||||
_ = update_room_state_set(room, state_set)
|
||||
{state_set, room, event}
|
||||
|
@ -439,27 +455,53 @@ defmodule Architex.RoomServer do
|
|||
end
|
||||
end
|
||||
|
||||
# Get a function that inserts all events for room creation.
|
||||
@spec create_room_insert_events(Room.t(), Account.t(), CreateRoom.t()) ::
|
||||
(() -> {:ok, t(), Room.t()} | {:error, atom()})
|
||||
defp create_room_insert_events(room, account, %CreateRoom{
|
||||
room_version: room_version,
|
||||
preset: preset,
|
||||
name: name,
|
||||
topic: topic,
|
||||
invite: invite,
|
||||
power_level_content_override: power_level_content_override,
|
||||
is_direct: is_direct,
|
||||
creation_content: creation_content,
|
||||
initial_state: initial_state
|
||||
}) do
|
||||
@spec process_events(Room.t(), t(), [%Event{}]) ::
|
||||
(() -> {t(), Room.t()} | {:error, atom()})
|
||||
defp process_events(room, state_set, events) do
|
||||
fn ->
|
||||
Enum.reduce_while(events, {state_set, room}, fn event, {state_set, room} ->
|
||||
case finalize_and_process_event(event, state_set, room) do
|
||||
{:ok, state_set, room, _} -> {:cont, {state_set, room}}
|
||||
{:error, reason} -> {:halt, {:error, reason}}
|
||||
end
|
||||
end)
|
||||
|> then(fn
|
||||
{:error, reason} ->
|
||||
Repo.rollback(reason)
|
||||
|
||||
{state_set, room} ->
|
||||
_ = update_room_state_set(room, state_set)
|
||||
{state_set, room}
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
@spec create_room_events(Room.t(), Account.t(), CreateRoom.t(), Alias.t() | nil) :: [%Event{}]
|
||||
defp create_room_events(
|
||||
room,
|
||||
account,
|
||||
%CreateRoom{
|
||||
room_version: room_version,
|
||||
preset: preset,
|
||||
name: name,
|
||||
topic: topic,
|
||||
invite: invite,
|
||||
power_level_content_override: power_level_content_override,
|
||||
is_direct: is_direct,
|
||||
creation_content: creation_content,
|
||||
initial_state: initial_state
|
||||
},
|
||||
alias_
|
||||
) do
|
||||
invite_events = room_creation_invite_events(account, invite, room, is_direct)
|
||||
|
||||
# Spec doesn't specify where to insert canonical_alias event, do it after topic event.
|
||||
name_and_topic_events =
|
||||
Enum.reject(
|
||||
[
|
||||
if(name, do: Event.Name.new(room, account, name)),
|
||||
if(topic, do: Event.Topic.new(room, account, topic))
|
||||
if(topic, do: Event.Topic.new(room, account, topic)),
|
||||
if(alias_, do: Event.CanonicalAlias.new(room, account, alias_.alias))
|
||||
],
|
||||
&Kernel.is_nil/1
|
||||
)
|
||||
|
@ -490,28 +532,8 @@ defmodule Architex.RoomServer do
|
|||
)
|
||||
]
|
||||
|
||||
events =
|
||||
basic_events ++
|
||||
preset_events ++ initial_state_events ++ name_and_topic_events ++ invite_events
|
||||
|
||||
fn ->
|
||||
result =
|
||||
Enum.reduce_while(events, {%{}, room}, fn event, {state_set, room} ->
|
||||
case finalize_and_insert_event(event, state_set, room) do
|
||||
{:ok, state_set, room, _} -> {:cont, {state_set, room}}
|
||||
{:error, reason} -> {:halt, {:error, reason}}
|
||||
end
|
||||
end)
|
||||
|
||||
case result do
|
||||
{:error, reason} ->
|
||||
Repo.rollback(reason)
|
||||
|
||||
{state_set, room} ->
|
||||
_ = update_room_state_set(room, state_set)
|
||||
{state_set, room}
|
||||
end
|
||||
end
|
||||
basic_events ++
|
||||
preset_events ++ initial_state_events ++ name_and_topic_events ++ invite_events
|
||||
end
|
||||
|
||||
# Update the given room in the database with the given state set.
|
||||
|
@ -579,9 +601,9 @@ defmodule Architex.RoomServer do
|
|||
# - Content hash
|
||||
# - Event ID
|
||||
# - Signature
|
||||
@spec finalize_and_insert_event(%Event{}, t(), Room.t()) ::
|
||||
@spec finalize_and_process_event(%Event{}, t(), Room.t()) ::
|
||||
{:ok, t(), Room.t(), Event.t()} | {:error, atom()}
|
||||
defp finalize_and_insert_event(
|
||||
defp finalize_and_process_event(
|
||||
event,
|
||||
state_set,
|
||||
%Room{forward_extremities: forward_extremities} = room
|
||||
|
@ -593,7 +615,7 @@ defmodule Architex.RoomServer do
|
|||
|> Map.put(:depth, get_depth(forward_extremities))
|
||||
|
||||
case Event.post_process(event) do
|
||||
{:ok, event} -> authenticate_and_insert_event(event, state_set, room)
|
||||
{:ok, event} -> authenticate_and_process_event(event, state_set, room)
|
||||
_ -> {:error, :event_creation}
|
||||
end
|
||||
end
|
||||
|
@ -660,9 +682,9 @@ defmodule Architex.RoomServer do
|
|||
# Authenticate and insert a new event using state resolution.
|
||||
# Implements the checks as described in the
|
||||
# [Matrix docs](https://matrix.org/docs/spec/server_server/latest#checks-performed-on-receipt-of-a-pdu).
|
||||
@spec authenticate_and_insert_event(Event.t(), t(), Room.t()) ::
|
||||
@spec authenticate_and_process_event(Event.t(), t(), Room.t()) ::
|
||||
{:ok, t(), Room.t(), Event.t()} | {:error, atom()}
|
||||
defp authenticate_and_insert_event(event, current_state_set, room) do
|
||||
defp authenticate_and_process_event(event, current_state_set, room) do
|
||||
# TODO: Correctly handle soft fails.
|
||||
# Check the following things:
|
||||
# 1. TODO: Is a valid event, otherwise it is dropped.
|
||||
|
|
|
@ -6,13 +6,18 @@ defmodule Architex.Alias do
|
|||
alias Architex.{Repo, Alias, Room}
|
||||
alias Ecto.Changeset
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
alias: String.t(),
|
||||
room_id: String.t()
|
||||
}
|
||||
|
||||
@primary_key {:alias, :string, []}
|
||||
schema "aliases" do
|
||||
belongs_to :room, Room, foreign_key: :room_id, references: :id, type: :string
|
||||
end
|
||||
|
||||
def create(alias, room_id) do
|
||||
change(%Alias{}, alias: alias, room_id: room_id)
|
||||
def create(alias_, room_id) do
|
||||
change(%Alias{}, alias: alias_, room_id: room_id)
|
||||
|> assoc_constraint(:room)
|
||||
|> unique_constraint(:alias, name: :aliases_pkey)
|
||||
|> Repo.insert()
|
||||
|
|
|
@ -282,6 +282,7 @@ end
|
|||
|
||||
defmodule Architex.Event.Unban do
|
||||
alias Architex.{Event, Account, Room}
|
||||
|
||||
@spec new(Room.t(), Account.t(), String.t()) :: %Event{}
|
||||
def new(room, sender, user_id) do
|
||||
%Event{
|
||||
|
@ -294,3 +295,21 @@ defmodule Architex.Event.Unban do
|
|||
}
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Architex.Event.CanonicalAlias do
|
||||
alias Architex.{Event, Account, Room}
|
||||
|
||||
@spec new(Room.t(), Account.t(), String.t() | nil, [String.t()] | nil) :: %Event{}
|
||||
def new(room, sender, alias_ \\ nil, alt_aliases \\ nil) do
|
||||
content = %{}
|
||||
content = if alias_, do: Map.put(content, "alias", alias_), else: content
|
||||
content = if alt_aliases, do: Map.put(content, "alt_aliases", alt_aliases), else: content
|
||||
|
||||
%Event{
|
||||
Event.new(room, sender)
|
||||
| type: "m.room.canonical_alias",
|
||||
state_key: "",
|
||||
content: content
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -25,6 +25,9 @@ defmodule ArchitexWeb.Client.RoomController do
|
|||
{:error, :authorization} ->
|
||||
put_error(conn, :invalid_room_state)
|
||||
|
||||
{:error, :alias} ->
|
||||
put_error(conn, :room_in_use, "The requested alias is already in use.")
|
||||
|
||||
{:error, :unknown} ->
|
||||
put_error(conn, :unknown)
|
||||
end
|
||||
|
|
|
@ -14,6 +14,7 @@ defmodule ArchitexWeb.Error do
|
|||
unauthorized: {400, "M_UNAUTHORIZED", "The request was unauthorized."},
|
||||
invalid_param: {400, "M_INVALID_PARAM", "A request parameter was invalid."},
|
||||
missing_param: {400, "M_MISSING_PARAM", "A request parameter is missing."},
|
||||
room_in_use: {400, "M_ROOM_IN_USE", "The room is already in use."},
|
||||
unknown_token: {401, "M_UNKNOWN_TOKEN", "Invalid access token."},
|
||||
missing_token: {401, "M_MISSING_TOKEN", "Access token required."},
|
||||
not_found: {404, "M_NOT_FOUND", "The requested resource was not found."},
|
||||
|
|
Loading…
Reference in a new issue