From d4b058e7390ce1dfc301d574a2ad02022bc34dc3 Mon Sep 17 00:00:00 2001 From: Pim Kunis Date: Fri, 10 Sep 2021 21:51:45 +0200 Subject: [PATCH] Implement client send state event endpoint --- README.md | 5 ++- lib/architex/room_server.ex | 43 +++++++++++++++---- lib/architex/schema/event.ex | 9 +++- .../client/controllers/room_controller.ex | 43 ++++++++++++++++++- lib/architex_web/router.ex | 3 +- 5 files changed, 88 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 9936946..6212a9f 100644 --- a/README.md +++ b/README.md @@ -58,15 +58,16 @@ Here, implemented and some unimplemented features are listed. - POST /_matrix/client/r0/rooms/{roomId}/kick - POST /_matrix/client/r0/rooms/{roomId}/ban - POST /_matrix/client/r0/rooms/{roomId}/unban +- PUT /_matrix/client/r0/rooms/{roomId}/state/{eventType}/{stateKey} - PUT /_matrix/client/r0/rooms/{roomId}/send/{eventType}/{txnId} - GET /_matrix/client/r0/rooms/{roomId}/messages: Except filtering. - GET /_matrix/client/r0/directory/list/room/{roomId} - PUT /_matrix/client/r0/directory/list/room/{roomId} - GET /_matrix/client/r0/capabilities - GET /_matrix/client/r0/profile/{userId} -- GET /_matrix/client/r0/profile/{userId}/avatar_url: Except federation. +- GET /_matrix/client/r0/profile/{userId}/avatar_url - PUT /_matrix/client/r0/profile/{userId}/avatar_url -- GET /_matrix/client/r0/profile/{userId}/displayname: Except federation. +- GET /_matrix/client/r0/profile/{userId}/displayname - PUT /_matrix/client/r0/profile/{userId}/displayname #### Federation API diff --git a/lib/architex/room_server.ex b/lib/architex/room_server.ex index 8e712c2..1d79648 100644 --- a/lib/architex/room_server.ex +++ b/lib/architex/room_server.ex @@ -165,12 +165,21 @@ defmodule Architex.RoomServer do end @doc """ - Send a message to this room. + Send a message event to this room. """ - @spec send_message(pid(), Account.t(), Device.t(), String.t(), map(), String.t()) :: + @spec send_message_event(pid(), Account.t(), Device.t(), String.t(), map(), String.t()) :: {:ok, String.t()} | {:error, atom()} - def send_message(pid, account, device, event_type, content, txn_id) do - GenServer.call(pid, {:send_message, account, device, event_type, content, txn_id}) + def send_message_event(pid, account, device, event_type, content, txn_id) do + GenServer.call(pid, {:send_message_event, account, device, event_type, content, txn_id}) + end + + @doc """ + Send a state event to this room. + """ + @spec send_state_event(pid(), Account.t(), String.t(), map(), String.t()) :: + {:ok, String.t()} | {:error, atom()} + def send_state_event(pid, account, event_type, content, state_key) do + GenServer.call(pid, {:send_state_event, account, event_type, content, state_key}) end ### Implementation @@ -354,13 +363,13 @@ defmodule Architex.RoomServer do end def handle_call( - {:send_message, account, device, event_type, content, txn_id}, + {:send_message_event, account, device, event_type, content, txn_id}, _from, %{room: room, state_set: state_set} = state ) do - message_event = Event.custom_message(room, account, event_type, content) + message_event = Event.custom_event(room, account, event_type, content) - case Repo.transaction(insert_custom_message(state_set, room, device, message_event, txn_id)) do + case Repo.transaction(insert_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}} @@ -369,7 +378,25 @@ defmodule Architex.RoomServer do end end - defp insert_custom_message( + def handle_call( + {:send_state_event, account, event_type, content, state_key}, + _from, + %{room: room, state_set: state_set} = state + ) 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 + {:ok, {state_set, room, %Event{id: event_id}}} -> + {:reply, {:ok, event_id}, %{state | state_set: state_set, room: room}} + + {:error, reason} -> + {:reply, {:error, reason}, state} + end + end + + @spec insert_event_with_txn(t(), Room.t(), Device.t(), %Event{}, String.t()) :: + (() -> {t(), Room.t(), String.t()} | {:error, atom()}) + defp insert_event_with_txn( state_set, room, %Device{nid: device_nid} = device, diff --git a/lib/architex/schema/event.ex b/lib/architex/schema/event.ex index 5825e77..174e97c 100644 --- a/lib/architex/schema/event.ex +++ b/lib/architex/schema/event.ex @@ -56,8 +56,8 @@ defmodule Architex.Event do } end - @spec custom_message(Room.t(), Account.t(), String.t(), map()) :: %Event{} - def custom_message(room, sender, type, content) do + @spec custom_event(Room.t(), Account.t(), String.t(), map()) :: %Event{} + def custom_event(room, sender, type, content) do %Event{ Event.new(room, sender) | type: type, @@ -65,6 +65,11 @@ defmodule Architex.Event do } end + @spec custom_state_event(Room.t(), Account.t(), String.t(), map(), String.t()) :: %Event{} + def custom_state_event(room, sender, type, content, state_key) do + %Event{custom_event(room, sender, type, content) | state_key: state_key} + end + @spec is_control_event(t()) :: boolean() def is_control_event(%Event{type: "m.room.power_levels", state_key: ""}), do: true def is_control_event(%Event{type: "m.room.join_rules", state_key: ""}), do: true diff --git a/lib/architex_web/client/controllers/room_controller.ex b/lib/architex_web/client/controllers/room_controller.ex index 4869561..c694482 100644 --- a/lib/architex_web/client/controllers/room_controller.ex +++ b/lib/architex_web/client/controllers/room_controller.ex @@ -205,7 +205,7 @@ defmodule ArchitexWeb.Client.RoomController do Action for PUT /_matrix/client/r0/rooms/{roomId}/send/{eventType}/{txnId}. """ - def send_message( + def send_message_event( %Conn{assigns: %{account: account, device: device}, body_params: body_params} = conn, %{ "room_id" => room_id, @@ -215,7 +215,46 @@ defmodule ArchitexWeb.Client.RoomController do ) do case RoomServer.get_room_server(room_id) do {:ok, pid} -> - case RoomServer.send_message(pid, account, device, event_type, body_params, txn_id) do + case RoomServer.send_message_event(pid, account, device, event_type, body_params, txn_id) do + {:ok, event_id} -> + conn + |> put_status(200) + |> json(%{event_id: event_id}) + + {:error, _} -> + put_error(conn, :unknown) + end + + {:error, :not_found} -> + put_error(conn, :not_found, "The given room was not found.") + end + end + + @doc """ + State events can be sent using this endpoint. + + I don't know why, but the spec is very scared of trailing slashes and accidentally + using a transaction ID as the state key. + I take no precaution against these things, it's the responsibility of the client. + Action for PUT /_matrix/client/r0/rooms/{roomId}/state/{eventType}/{stateKey}. + """ + def send_state_event(conn, %{"state_key" => [state_key | _]} = params) do + do_send_state_event(conn, params, state_key) + end + + def send_state_event(conn, params) do + do_send_state_event(conn, params, "") + end + + defp do_send_state_event( + %Conn{assigns: %{account: account}, body_params: body_params} = conn, + %{"room_id" => room_id, "event_type" => event_type}, + state_key + ) do + # TODO: Check aliases according to spec. + case RoomServer.get_room_server(room_id) do + {:ok, pid} -> + case RoomServer.send_state_event(pid, account, event_type, body_params, state_key) do {:ok, event_id} -> conn |> put_status(200) diff --git a/lib/architex_web/router.ex b/lib/architex_web/router.ex index a78247d..5827b21 100644 --- a/lib/architex_web/router.ex +++ b/lib/architex_web/router.ex @@ -85,8 +85,9 @@ defmodule ArchitexWeb.Router do post "/kick", RoomController, :kick post "/ban", RoomController, :ban post "/unban", RoomController, :unban - put "/send/:event_type/:txn_id", RoomController, :send_message + put "/send/:event_type/:txn_id", RoomController, :send_message_event get "/messages", RoomController, :messages + put "/state/:event_type/*state_key", RoomController, :send_state_event end end end