diff --git a/lib/architex/schema/event/formatters.ex b/lib/architex/schema/event/formatters.ex index d195dc5..84b7ea1 100644 --- a/lib/architex/schema/event/formatters.ex +++ b/lib/architex/schema/event/formatters.ex @@ -4,8 +4,8 @@ defmodule Architex.Event.Formatters do """ alias Architex.Event - @spec for_client(Event.t()) :: map() - def for_client(%Event{ + @spec messages_response(Event.t()) :: map() + def messages_response(%Event{ content: content, type: type, id: event_id, @@ -30,6 +30,27 @@ defmodule Architex.Event.Formatters do data end + def sync_response(%Event{ + content: content, + type: type, + id: event_id, + sender: sender, + origin_server_ts: origin_server_ts, + unsigned: unsigned + }) do + data = %{ + content: content, + type: type, + event_id: event_id, + sender: to_string(sender), + origin_server_ts: origin_server_ts + } + + data = if unsigned, do: Map.put(data, :unsigned, unsigned), else: data + + data + end + @spec as_pdu(Event.t()) :: map() def as_pdu(%Event{ auth_events: auth_events, diff --git a/lib/architex/schema/room.ex b/lib/architex/schema/room.ex index c27a88f..d07413f 100644 --- a/lib/architex/schema/room.ex +++ b/lib/architex/schema/room.ex @@ -70,8 +70,6 @@ defmodule Architex.Room do @spec get_messages(Room.t(), Messages.t()) :: {[Event.t()], integer() | nil, integer() | nil} def get_messages(room, %Messages{from: from, to: to, dir: dir, limit: limit}) do - limit = limit || 10 - events = room |> Ecto.assoc(:events) @@ -90,6 +88,8 @@ defmodule Architex.Room do # When 'from' is empty, we return events from the start or end # of the room's history. + # TODO: Might actually not be needed, could be that Quaternion is passing + # an empty string because the /sync call fails. @spec events_from(Ecto.Query.t(), String.t(), String.t()) :: Ecto.Query.t() defp events_from(query, "", _), do: query diff --git a/lib/architex_web/client/controllers/room_controller.ex b/lib/architex_web/client/controllers/room_controller.ex index 3a9b244..207c531 100644 --- a/lib/architex_web/client/controllers/room_controller.ex +++ b/lib/architex_web/client/controllers/room_controller.ex @@ -249,7 +249,7 @@ defmodule ArchitexWeb.Client.RoomController do case Repo.one(room_query) do %Room{} = room -> {events, start, end_} = Room.get_messages(room, request) - events = Enum.map(events, &Event.Formatters.for_client/1) + events = Enum.map(events, &Event.Formatters.messages_response/1) data = %{chunk: events} data = if start, do: Map.put(data, :start, Integer.to_string(start)), else: data data = if end_, do: Map.put(data, :end, Integer.to_string(end_)), else: data diff --git a/lib/architex_web/client/controllers/sync_controller.ex b/lib/architex_web/client/controllers/sync_controller.ex new file mode 100644 index 0000000..ce6d2c9 --- /dev/null +++ b/lib/architex_web/client/controllers/sync_controller.ex @@ -0,0 +1,73 @@ +defmodule ArchitexWeb.Client.SyncController do + use ArchitexWeb, :controller + + import ArchitexWeb.Error + import Ecto.Query + + alias Architex.{Repo, Event, Account, Room, JoinedRoom} + alias Plug.Conn + + @doc """ + Synchronise the client's state with the latest state on the server. + + Parameters: %{"filter" => "{\"account_data\":{},\"presence\":{},\"room\":{\"account_data\":{},\"ephemeral\":{},\"state\":{\"lazy_load_members\":true},\"timeline\":{\"limit\":100}}}", "path" => ["_matrix", "client", "r0", "sync"], "timeout" => "30000"} + Action for GET /_matrix/client/r0/sync. + """ + # When no "since" is specified, return the most recent messages. + def sync(%Conn{assigns: %{account: %Account{id: account_id}}} = conn, params) + when not is_map_key(params, "since") do + # joined_rooms = + # account + # |> Ecto.assoc(:joined_rooms) + # |> Repo.all() + # |> Enum.into(%{}, fn %Room{id: room_id} = room -> + # {room_id, room} + # end) + + events_per_room = + Event + |> join(:inner, [e], jr in JoinedRoom, + on: jr.room_id == e.room_id and jr.account_id == ^account_id + ) + |> join(:inner, [e, jr], r in Room, on: r.id == jr.room_id) + |> order_by(asc: :origin_server_ts, asc: :nid) + |> Repo.all() + |> Enum.group_by(& &1.room_id) + + join = + Enum.into(events_per_room, %{}, fn {room_id, [%Event{nid: first_nid} | _] = events} -> + joined_room = %{ + timeline: %{ + events: Enum.map(events, &Event.Formatters.sync_response/1), + limited: false, + prev_batch: Integer.to_string(first_nid) + } + } + + {room_id, joined_room} + end) + + next_batch = Enum.map(events_per_room, fn {_, events} -> + %Event{nid: last_nid} = List.last(events) + last_nid + end) + |> Enum.max(fn -> 0 end) + + data = %{ + next_batch: Integer.to_string(next_batch), + rooms: %{ + join: join, + invite: %{}, + leave: %{} + } + } + + conn + |> put_status(200) + |> json(data) + end + + # TODO: Long-poll for new incoming events. + # Should think about how to implement this in a nice way. + def sync(conn, _params), do: put_error(conn, :unknown, "Not implemented yet.") +end diff --git a/lib/architex_web/client/request/messages.ex b/lib/architex_web/client/request/messages.ex index e6b80d9..346e14d 100644 --- a/lib/architex_web/client/request/messages.ex +++ b/lib/architex_web/client/request/messages.ex @@ -2,19 +2,19 @@ defmodule ArchitexWeb.Client.Request.Messages do use ArchitexWeb.Request @type t :: %__MODULE__{ - from: String.t(), - to: String.t() | nil, - dir: String.t(), - limit: integer() | nil, - filter: String.t() | nil - } + from: String.t(), + to: String.t() | nil, + dir: String.t(), + limit: integer() | nil, + filter: String.t() | nil + } @primary_key false embedded_schema do field :from, :string field :to, :string field :dir, :string - field :limit, :integer + field :limit, :integer, default: 10 field :filter, :string end diff --git a/lib/architex_web/client/request/sync.ex b/lib/architex_web/client/request/sync.ex new file mode 100644 index 0000000..f73bd4f --- /dev/null +++ b/lib/architex_web/client/request/sync.ex @@ -0,0 +1,14 @@ +defmodule ArchitexWeb.Client.Request.Sync do + use ArchitexWeb.Request + + @type t :: %__MODULE__{} + + @primary_key false + embedded_schema do + field :filter, :string + field :since, :string + field :full_state, :boolean, default: false + field :set_presence, :string, default: "online" + field :timeout, :integer, default: 0 + end +end diff --git a/lib/architex_web/router.ex b/lib/architex_web/router.ex index 015d0f2..eb99026 100644 --- a/lib/architex_web/router.ex +++ b/lib/architex_web/router.ex @@ -54,6 +54,7 @@ defmodule ArchitexWeb.Router do post "/createRoom", RoomController, :create get "/joined_rooms", RoomController, :joined_rooms get "/capabilities", InfoController, :capabilities + get "/sync", SyncController, :sync scope "/directory" do put "/room/:alias", AliasesController, :create