Refactor room server
Serialize and save room state in database Get room state from database when creating a room server
This commit is contained in:
parent
65368dc2d4
commit
9e02d5b95c
10 changed files with 122 additions and 146 deletions
|
@ -6,6 +6,7 @@ Currently it is in a very early stage.
|
|||
Some noteworthy contributions:
|
||||
|
||||
* `lib/matrix_server/state_resolution.ex`: Implementation of version 2 of the Matrix state resolution algorithm.
|
||||
* `lib/matrix_server/state_resolution/authorization.ex`: Implementation of authorization rules for the state resolution algorithm.
|
||||
* `lib/matrix_server/room_server.ex`: A GenServer that holds and manages the state of a room.
|
||||
|
||||
To run the server in development mode, run:
|
||||
|
|
|
@ -28,7 +28,7 @@ defmodule MatrixServer.Event do
|
|||
}
|
||||
end
|
||||
|
||||
def create_room(room, %Account{localpart: localpart} = creator, room_version) do
|
||||
def create_room(room, %Account{localpart: localpart} = creator, room_version, auth_events \\ []) do
|
||||
mxid = MatrixServer.get_mxid(localpart)
|
||||
|
||||
%Event{
|
||||
|
@ -38,11 +38,12 @@ defmodule MatrixServer.Event do
|
|||
content: %{
|
||||
"creator" => mxid,
|
||||
"room_version" => room_version || MatrixServer.default_room_version()
|
||||
}
|
||||
},
|
||||
auth_events: auth_events
|
||||
}
|
||||
end
|
||||
|
||||
def join(room, %Account{localpart: localpart} = sender) do
|
||||
def join(room, %Account{localpart: localpart} = sender, auth_events \\ []) do
|
||||
mxid = MatrixServer.get_mxid(localpart)
|
||||
|
||||
%Event{
|
||||
|
@ -51,11 +52,12 @@ defmodule MatrixServer.Event do
|
|||
state_key: mxid,
|
||||
content: %{
|
||||
"membership" => "join"
|
||||
}
|
||||
},
|
||||
auth_events: auth_events
|
||||
}
|
||||
end
|
||||
|
||||
def power_levels(room, %Account{localpart: localpart} = sender) do
|
||||
def power_levels(room, %Account{localpart: localpart} = sender, auth_events \\ []) do
|
||||
mxid = MatrixServer.get_mxid(localpart)
|
||||
|
||||
%Event{
|
||||
|
@ -77,62 +79,68 @@ defmodule MatrixServer.Event do
|
|||
"notifications" => %{
|
||||
"room" => 50
|
||||
}
|
||||
}
|
||||
},
|
||||
auth_events: auth_events
|
||||
}
|
||||
end
|
||||
|
||||
def name(room, sender, name) do
|
||||
def name(room, sender, name, auth_events \\ []) do
|
||||
%Event{
|
||||
new(room, sender)
|
||||
| type: "m.room.name",
|
||||
state_key: "",
|
||||
content: %{
|
||||
"name" => name
|
||||
}
|
||||
},
|
||||
auth_events: auth_events
|
||||
}
|
||||
end
|
||||
|
||||
def topic(room, sender, topic) do
|
||||
def topic(room, sender, topic, auth_events \\ []) do
|
||||
%Event{
|
||||
new(room, sender)
|
||||
| type: "m.room.topic",
|
||||
state_key: "",
|
||||
content: %{
|
||||
"topic" => topic
|
||||
}
|
||||
},
|
||||
auth_events: auth_events
|
||||
}
|
||||
end
|
||||
|
||||
def join_rules(room, sender, join_rule) do
|
||||
def join_rules(room, sender, join_rule, auth_events \\ []) do
|
||||
%Event{
|
||||
new(room, sender)
|
||||
| type: "m.room.join_rules",
|
||||
state_key: "",
|
||||
content: %{
|
||||
"join_rule" => join_rule
|
||||
}
|
||||
},
|
||||
auth_events: auth_events
|
||||
}
|
||||
end
|
||||
|
||||
def history_visibility(room, sender, history_visibility) do
|
||||
def history_visibility(room, sender, history_visibility, auth_events \\ []) do
|
||||
%Event{
|
||||
new(room, sender)
|
||||
| type: "m.room.history_visibility",
|
||||
state_key: "",
|
||||
content: %{
|
||||
"history_visibility" => history_visibility
|
||||
}
|
||||
},
|
||||
auth_events: auth_events
|
||||
}
|
||||
end
|
||||
|
||||
def guest_access(room, sender, guest_access) do
|
||||
def guest_access(room, sender, guest_access, auth_events \\ []) do
|
||||
%Event{
|
||||
new(room, sender)
|
||||
| type: "m.room.guest_access",
|
||||
state_key: "",
|
||||
content: %{
|
||||
"guest_access" => guest_access
|
||||
}
|
||||
},
|
||||
auth_events: auth_events
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -4,9 +4,9 @@ defmodule MatrixServer.QuickCheck do
|
|||
alias MatrixServer.{Repo, Room, Account, RoomServer}
|
||||
alias MatrixServerWeb.API.CreateRoom
|
||||
|
||||
def create_room do
|
||||
def create_room(name \\ nil, topic \\ nil) do
|
||||
account = Repo.one!(from a in Account, limit: 1)
|
||||
input = %CreateRoom{}
|
||||
input = %CreateRoom{name: name, topic: topic}
|
||||
%Room{id: room_id} = Repo.insert!(Room.create_changeset(input))
|
||||
{:ok, pid} = RoomServer.get_room_server(room_id)
|
||||
RoomServer.create_room(pid, account, input)
|
||||
|
|
|
@ -10,7 +10,7 @@ defmodule MatrixServer.Room do
|
|||
@primary_key {:id, :string, []}
|
||||
schema "rooms" do
|
||||
field :visibility, Ecto.Enum, values: [:public, :private]
|
||||
field :state, :map
|
||||
field :state, {:array, {:array, :string}}
|
||||
field :forward_extremities, {:array, :string}
|
||||
has_many :events, Event, foreign_key: :event_id
|
||||
end
|
||||
|
@ -22,7 +22,7 @@ defmodule MatrixServer.Room do
|
|||
def create_changeset(%CreateRoom{} = input) do
|
||||
visibility = input.visibility || :public
|
||||
|
||||
%Room{id: generate_room_id(), forward_extremities: [], state: %{}}
|
||||
%Room{id: generate_room_id()}
|
||||
|> changeset(%{visibility: visibility})
|
||||
end
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ defmodule MatrixServer.RoomServer do
|
|||
use GenServer
|
||||
|
||||
import Ecto.Query
|
||||
import Ecto.Changeset
|
||||
|
||||
alias MatrixServer.{Repo, Room, Event, StateResolution}
|
||||
alias MatrixServerWeb.API.CreateRoom
|
||||
|
@ -24,7 +25,7 @@ defmodule MatrixServer.RoomServer do
|
|||
nil ->
|
||||
{:error, :not_found}
|
||||
|
||||
%Room{} ->
|
||||
%Room{state: serialized_state_set} ->
|
||||
case Registry.lookup(@registry, room_id) do
|
||||
[{pid, _}] ->
|
||||
{:ok, pid}
|
||||
|
@ -32,7 +33,8 @@ defmodule MatrixServer.RoomServer do
|
|||
[] ->
|
||||
opts = [
|
||||
name: {:via, Registry, {@registry, room_id}},
|
||||
room_id: room_id
|
||||
room_id: room_id,
|
||||
serialized_state_set: serialized_state_set
|
||||
]
|
||||
|
||||
DynamicSupervisor.start_child(@supervisor, {__MODULE__, opts})
|
||||
|
@ -49,49 +51,65 @@ defmodule MatrixServer.RoomServer do
|
|||
@impl true
|
||||
def init(opts) do
|
||||
room_id = Keyword.fetch!(opts, :room_id)
|
||||
serialized_state_set = Keyword.fetch!(opts, :serialized_state_set)
|
||||
state_event_ids = Enum.map(serialized_state_set, fn [_, _, event_id] -> event_id end)
|
||||
|
||||
{:ok, %{room_id: room_id, state_set: %{}}}
|
||||
state_set =
|
||||
Event
|
||||
|> where([e], e.event_id in ^state_event_ids)
|
||||
|> Repo.all()
|
||||
|> Enum.into(%{}, fn %Event{type: type, state_key: state_key} = event ->
|
||||
{{type, state_key}, event}
|
||||
end)
|
||||
|
||||
{:ok, %{room_id: room_id, state_set: state_set}}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:create_room, account, input}, _from, %{room_id: room_id} = state) do
|
||||
def handle_call(
|
||||
{:create_room, account,
|
||||
%CreateRoom{room_version: room_version, name: name, topic: topic, preset: preset}},
|
||||
_from,
|
||||
%{room_id: room_id} = state
|
||||
) do
|
||||
result =
|
||||
Repo.transaction(fn ->
|
||||
room = Repo.one!(from r in Room, where: r.id == ^room_id)
|
||||
create_room = Event.create_room(room, account, room_version)
|
||||
join_creator = Event.join(room, account, [create_room.event_id])
|
||||
pls = Event.power_levels(room, account, [create_room.event_id, join_creator.event_id])
|
||||
auth_events = [create_room.event_id, join_creator.event_id, pls.event_id]
|
||||
name_event = if name, do: Event.name(room, account, name, auth_events)
|
||||
topic_event = if topic, do: Event.topic(room, account, topic, auth_events)
|
||||
|
||||
# TODO: power_level_content_override, initial_state, invite, invite_3pid
|
||||
with room <- Repo.one!(from r in Room, where: r.id == ^room_id),
|
||||
{:ok, create_room_id, state_set, room} <-
|
||||
room_creation_create_room(account, input, room),
|
||||
{:ok, join_creator_id, state_set, room} <-
|
||||
room_creation_join_creator(account, room, state_set, [create_room_id]),
|
||||
{:ok, pl_id, state_set, room} <-
|
||||
room_creation_power_levels(
|
||||
account,
|
||||
room,
|
||||
state_set,
|
||||
[create_room_id, join_creator_id]
|
||||
),
|
||||
{:ok, _, state_set, room} <-
|
||||
room_creation_preset(account, input, room, state_set, [
|
||||
create_room_id,
|
||||
join_creator_id,
|
||||
pl_id
|
||||
]),
|
||||
{:ok, _, state_set, room} <-
|
||||
room_creation_name(account, input, room, state_set, [
|
||||
create_room_id,
|
||||
join_creator_id,
|
||||
pl_id
|
||||
]),
|
||||
{:ok, _, state_set, _} <-
|
||||
room_creation_topic(account, input, room, state_set, [
|
||||
create_room_id,
|
||||
join_creator_id,
|
||||
pl_id
|
||||
]) do
|
||||
state_set
|
||||
else
|
||||
reason ->
|
||||
events =
|
||||
[create_room, join_creator, pls] ++
|
||||
room_creation_preset(account, preset, room, auth_events) ++
|
||||
[name_event, topic_event]
|
||||
|
||||
result =
|
||||
events
|
||||
|> Enum.reject(&Kernel.is_nil/1)
|
||||
|> Enum.reduce_while({%{}, room}, fn event, {state_set, room} ->
|
||||
case verify_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} ->
|
||||
serialized_state_set =
|
||||
Enum.map(state_set, fn {{type, state_key}, event} ->
|
||||
[type, state_key, event.event_id]
|
||||
end)
|
||||
|
||||
Repo.update!(change(room, state: serialized_state_set))
|
||||
state_set
|
||||
end
|
||||
end)
|
||||
|
||||
|
@ -101,46 +119,19 @@ defmodule MatrixServer.RoomServer do
|
|||
end
|
||||
end
|
||||
|
||||
defp room_creation_create_room(account, %CreateRoom{room_version: room_version}, room) do
|
||||
Event.create_room(room, account, room_version)
|
||||
|> verify_and_insert_event(%{}, room)
|
||||
end
|
||||
|
||||
defp room_creation_join_creator(account, room, state_set, auth_events) do
|
||||
Event.join(room, account)
|
||||
|> Map.put(:auth_events, auth_events)
|
||||
|> verify_and_insert_event(state_set, room)
|
||||
end
|
||||
|
||||
defp room_creation_power_levels(account, room, state_set, auth_events) do
|
||||
Event.power_levels(room, account)
|
||||
|> Map.put(:auth_events, auth_events)
|
||||
|> verify_and_insert_event(state_set, room)
|
||||
end
|
||||
|
||||
# TODO: trusted_private_chat:
|
||||
# All invitees are given the same power level as the room creator.
|
||||
defp room_creation_preset(
|
||||
account,
|
||||
%CreateRoom{preset: nil},
|
||||
%Room{visibility: visibility} = room,
|
||||
state_set,
|
||||
auth_events
|
||||
) do
|
||||
defp room_creation_preset(account, nil, %Room{visibility: visibility} = room, auth_events) do
|
||||
preset =
|
||||
case visibility do
|
||||
:public -> "public_chat"
|
||||
:private -> "private_chat"
|
||||
end
|
||||
|
||||
room_creation_preset(account, preset, room, state_set, auth_events)
|
||||
room_creation_preset(account, preset, room, auth_events)
|
||||
end
|
||||
|
||||
defp room_creation_preset(account, %CreateRoom{preset: preset}, room, state_set, auth_events) do
|
||||
room_creation_preset(account, preset, room, state_set, auth_events)
|
||||
end
|
||||
|
||||
defp room_creation_preset(account, preset, room, state_set, auth_events) do
|
||||
defp room_creation_preset(account, preset, room, auth_events) do
|
||||
{join_rule, his_vis, guest_access} =
|
||||
case preset do
|
||||
"private_chat" -> {"invite", "shared", "can_join"}
|
||||
|
@ -148,53 +139,15 @@ defmodule MatrixServer.RoomServer do
|
|||
"public_chat" -> {"public", "shared", "forbidden"}
|
||||
end
|
||||
|
||||
with {:ok, _, _, _} <-
|
||||
room_creation_join_rules(account, join_rule, room, state_set, auth_events),
|
||||
{:ok, _, _, _} <- room_creation_his_vis(account, his_vis, room, state_set, auth_events) do
|
||||
room_creation_guest_access(account, guest_access, room, state_set, auth_events)
|
||||
end
|
||||
end
|
||||
|
||||
defp room_creation_join_rules(account, join_rule, room, state_set, auth_events) do
|
||||
Event.join_rules(room, account, join_rule)
|
||||
|> Map.put(:auth_events, auth_events)
|
||||
|> verify_and_insert_event(state_set, room)
|
||||
end
|
||||
|
||||
defp room_creation_his_vis(account, his_vis, room, state_set, auth_events) do
|
||||
Event.history_visibility(room, account, his_vis)
|
||||
|> Map.put(:auth_events, auth_events)
|
||||
|> verify_and_insert_event(state_set, room)
|
||||
end
|
||||
|
||||
defp room_creation_guest_access(account, guest_access, room, state_set, auth_events) do
|
||||
Event.guest_access(room, account, guest_access)
|
||||
|> Map.put(:auth_events, auth_events)
|
||||
|> verify_and_insert_event(state_set, room)
|
||||
end
|
||||
|
||||
defp room_creation_name(_, %CreateRoom{name: nil}, room, state_set, _) do
|
||||
{:ok, nil, state_set, room}
|
||||
end
|
||||
|
||||
defp room_creation_name(account, %CreateRoom{name: name}, room, state_set, auth_events) do
|
||||
Event.name(room, account, name)
|
||||
|> Map.put(:auth_events, auth_events)
|
||||
|> verify_and_insert_event(state_set, room)
|
||||
end
|
||||
|
||||
defp room_creation_topic(_, %CreateRoom{topic: nil}, room, state_set, _) do
|
||||
{:ok, nil, state_set, room}
|
||||
end
|
||||
|
||||
defp room_creation_topic(account, %CreateRoom{topic: topic}, room, state_set, auth_events) do
|
||||
Event.topic(room, account, topic)
|
||||
|> Map.put(:auth_events, auth_events)
|
||||
|> verify_and_insert_event(state_set, room)
|
||||
[
|
||||
Event.join_rules(room, account, join_rule, auth_events),
|
||||
Event.history_visibility(room, account, his_vis, auth_events),
|
||||
Event.guest_access(room, account, guest_access, auth_events)
|
||||
]
|
||||
end
|
||||
|
||||
defp verify_and_insert_event(
|
||||
%Event{event_id: event_id} = event,
|
||||
event,
|
||||
current_state_set,
|
||||
%Room{forward_extremities: forward_extremities} = room
|
||||
) do
|
||||
|
@ -215,9 +168,9 @@ defmodule MatrixServer.RoomServer do
|
|||
if Authorization.authorized?(event, current_state_set) do
|
||||
# We assume here that the event is always a forward extremity.
|
||||
room = Room.update_forward_extremities(event, room)
|
||||
{:ok, event} = Repo.insert(event)
|
||||
event = Repo.insert!(event)
|
||||
state_set = StateResolution.resolve_forward_extremities(event)
|
||||
{:ok, event_id, state_set, room}
|
||||
{:ok, state_set, room}
|
||||
else
|
||||
{:error, :soft_failed}
|
||||
end
|
||||
|
|
|
@ -264,9 +264,9 @@ defmodule MatrixServer.StateResolution do
|
|||
end
|
||||
|
||||
def update_state_set(
|
||||
%Event{type: event_type, state_key: state_key} = event,
|
||||
state_set
|
||||
) do
|
||||
%Event{type: event_type, state_key: state_key} = event,
|
||||
state_set
|
||||
) do
|
||||
Map.put(state_set, {event_type, state_key}, event)
|
||||
end
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ defmodule MatrixServerWeb.AuthController do
|
|||
|
||||
def register(conn, %{"auth" => _}) do
|
||||
# Other login types are unsupported for now.
|
||||
put_error(conn, :forbidden)
|
||||
put_error(conn, :unrecognized, "Only m.login.dummy is supported currently.")
|
||||
end
|
||||
|
||||
def register(conn, _params) do
|
||||
|
@ -87,8 +87,11 @@ defmodule MatrixServerWeb.AuthController do
|
|||
|> put_status(200)
|
||||
|> json(data)
|
||||
|
||||
{:error, error} ->
|
||||
{:error, error} when is_atom(error) ->
|
||||
put_error(conn, error)
|
||||
|
||||
{:error, _} ->
|
||||
put_error(conn, :unknown)
|
||||
end
|
||||
|
||||
_ ->
|
||||
|
@ -98,6 +101,6 @@ defmodule MatrixServerWeb.AuthController do
|
|||
|
||||
def login(conn, _params) do
|
||||
# Other login types and identifiers are unsupported for now.
|
||||
put_error(conn, :unknown)
|
||||
put_error(conn, :unrecognized, "Only m.login.password is supported currently.")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -15,9 +15,10 @@ defmodule MatrixServerWeb.RoomController do
|
|||
input = apply_changes(cs)
|
||||
|
||||
# TODO: refactor
|
||||
%Room{id: room_id} = Repo.insert!(Room.create_changeset(input))
|
||||
{:ok, pid} = RoomServer.get_room_server(room_id)
|
||||
RoomServer.create_room(pid, account, input)
|
||||
# Room.create(account, input)
|
||||
# %Room{id: room_id} = Repo.insert!(Room.create_changeset(input))
|
||||
# {:ok, pid} = RoomServer.get_room_server(room_id)
|
||||
# RoomServer.create_room(pid, account, input)
|
||||
|
||||
conn
|
||||
|> put_status(200)
|
||||
|
|
|
@ -2,7 +2,7 @@ defmodule MatrixServerWeb.Plug.Error do
|
|||
import Plug.Conn
|
||||
import Phoenix.Controller, only: [json: 2]
|
||||
|
||||
@error_code_and_message %{
|
||||
@error_map %{
|
||||
bad_json: {400, "M_BAD_JSON", "Bad request."},
|
||||
user_in_use: {400, "M_USER_IN_USE", "Username is already taken."},
|
||||
invalid_username: {400, "M_INVALID_USERNAME", "Invalid username."},
|
||||
|
@ -13,9 +13,9 @@ defmodule MatrixServerWeb.Plug.Error do
|
|||
missing_token: {401, "M_MISSING_TOKEN", "Access token required."}
|
||||
}
|
||||
|
||||
def put_error(conn, error) do
|
||||
{status, errcode, errmsg} = @error_code_and_message[error]
|
||||
data = %{errcode: errcode, error: errmsg}
|
||||
def put_error(conn, error, msg \\ nil) do
|
||||
{status, errcode, default_msg} = @error_map[error]
|
||||
data = %{errcode: errcode, error: msg or default_msg}
|
||||
|
||||
conn
|
||||
|> put_status(status)
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
defmodule MatrixServer.Repo.Migrations.ChangeRoomStateToArray do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
alter table(:rooms) do
|
||||
remove :state, :map, default: %{}, null: false
|
||||
add :state, {:array, {:array, :string}}, default: [], null: false
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue