diff --git a/README.md b/README.md index a85079d..d0553d9 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,7 @@ Here, implemented and some unimplemented features are listed. - 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/rooms/{roomId}/state - GET /_matrix/client/r0/directory/list/room/{roomId} - PUT /_matrix/client/r0/directory/list/room/{roomId} - GET /_matrix/client/r0/capabilities diff --git a/lib/architex/room_server.ex b/lib/architex/room_server.ex index f97ee37..b4a029e 100644 --- a/lib/architex/room_server.ex +++ b/lib/architex/room_server.ex @@ -189,6 +189,17 @@ defmodule Architex.RoomServer do GenServer.call(pid, {:send_state_event, account, event_type, content, state_key}) end + @doc """ + Get the current state of a room. + If the requesting user is not a member of the room, + get the state when the user left the room. + If the user has never been in the room, return an error. + """ + @spec get_current_state(pid(), Account.t()) :: {:ok, [Event.t()]} | :error + def get_current_state(pid, account) do + GenServer.call(pid, {:get_current_state, account}) + end + ### Implementation @impl true @@ -424,6 +435,24 @@ defmodule Architex.RoomServer do end end + def handle_call({:get_current_state, account}, _from, %{state_set: state_set} = state) do + mxid = Account.get_mxid(account) + + case state_set[{"m.room.member", mxid}] do + %Event{content: %{"membership" => "join"}} -> + # Get the current state of the room. + {:reply, {:ok, Map.values(state_set)}, state} + + %Event{content: %{"membership" => "leave"}} = event -> + # Get the state of the room, after leaving. + state_set = StateResolution.resolve(event) + {:reply, {:ok, Map.values(state_set)}, state} + + _ -> + {:reply, :error, state} + end + end + @spec process_event_with_txn(t(), Room.t(), Device.t(), %Event{}, String.t()) :: (() -> {t(), Room.t(), String.t()} | {:error, atom()}) defp process_event_with_txn( diff --git a/lib/architex/schema/event/formatters.ex b/lib/architex/schema/event/formatters.ex index 84b7ea1..6d96bcc 100644 --- a/lib/architex/schema/event/formatters.ex +++ b/lib/architex/schema/event/formatters.ex @@ -4,33 +4,10 @@ defmodule Architex.Event.Formatters do """ alias Architex.Event - @spec messages_response(Event.t()) :: map() - def messages_response(%Event{ - content: content, - type: type, - id: event_id, - sender: sender, - origin_server_ts: origin_server_ts, - unsigned: unsigned, - room_id: room_id, - state_key: state_key - }) do - data = %{ - content: content, - type: type, - event_id: event_id, - sender: to_string(sender), - origin_server_ts: origin_server_ts, - room_id: room_id - } - - data = if unsigned, do: Map.put(data, :unsigned, unsigned), else: data - data = if state_key, do: Map.put(data, :state_key, state_key), else: data - - data - end - - def sync_response(%Event{ + @doc """ + Event format with keys that all formats have in common. + """ + def base_client_response(%Event{ content: content, type: type, id: event_id, @@ -46,11 +23,37 @@ defmodule Architex.Event.Formatters do origin_server_ts: origin_server_ts } - data = if unsigned, do: Map.put(data, :unsigned, unsigned), else: data - - data + if unsigned, do: Map.put(data, :unsigned, unsigned), else: data end + @doc """ + The base event format, with room_id and state_key added. + Used in the client /messages response. + """ + @spec messages_response(Event.t()) :: map() + def messages_response(%Event{room_id: room_id, state_key: state_key} = event) do + data = + base_client_response(event) + |> Map.put(:room_id, room_id) + + if state_key, do: Map.put(data, :state_key, state_key), else: data + end + + @doc """ + The event format used in the client /state response. + TODO: prev_content + """ + def state_response(event), do: messages_response(event) + + @doc """ + The base event format, used in the client /sync response. + """ + @spec sync_response(Event.t()) :: map() + def sync_response(event), do: base_client_response(event) + + @doc """ + The PDU format used for federation. + """ @spec as_pdu(Event.t()) :: map() def as_pdu(%Event{ auth_events: auth_events, diff --git a/lib/architex_web/client/controllers/room_controller.ex b/lib/architex_web/client/controllers/room_controller.ex index 70beeb5..f713010 100644 --- a/lib/architex_web/client/controllers/room_controller.ex +++ b/lib/architex_web/client/controllers/room_controller.ex @@ -311,4 +311,33 @@ defmodule ArchitexWeb.Client.RoomController do {:error, _} -> put_error(conn, :bad_json) end end + + @doc """ + Get the state events for the current state of a room. + + Action for GET /_matrix/client/r0/rooms/{roomId}/state. + """ + def state(%Conn{assigns: %{account: account}} = conn, %{"room_id" => room_id}) do + case RoomServer.get_room_server(room_id) do + {:ok, pid} -> + case RoomServer.get_current_state(pid, account) do + {:ok, events} -> + events = Enum.map(events, &Event.Formatters.state_response/1) + + conn + |> put_status(200) + |> json(events) + + :error -> + put_error( + conn, + :forbidden, + "You aren't a member of the room and weren't previously a member of the room." + ) + end + + {:error, :not_found} -> + put_error(conn, :not_found, "The given room was not found.") + end + end end diff --git a/lib/architex_web/router.ex b/lib/architex_web/router.ex index 5827b21..757d557 100644 --- a/lib/architex_web/router.ex +++ b/lib/architex_web/router.ex @@ -87,7 +87,11 @@ defmodule ArchitexWeb.Router do post "/unban", RoomController, :unban put "/send/:event_type/:txn_id", RoomController, :send_message_event get "/messages", RoomController, :messages - put "/state/:event_type/*state_key", RoomController, :send_state_event + + scope "/state" do + get "/", RoomController, :state + put "/:event_type/*state_key", RoomController, :send_state_event + end end end end