diff --git a/lib/matrix_server/state_resolution.ex b/lib/matrix_server/state_resolution.ex index d4c7186..ecaa923 100644 --- a/lib/matrix_server/state_resolution.ex +++ b/lib/matrix_server/state_resolution.ex @@ -12,58 +12,73 @@ defmodule MatrixServer.StateResolution do |> Map.put(:auth_events, ["create", "join_charlie", "b"]) end - def resolve(%Event{type: type, state_key: state_key, event_id: event_id, prev_events: prev_event_ids}) do - state_sets = Event - |> where([e], e.event_id in ^prev_event_ids) + def resolve(%Event{room_id: room_id} = event) do + room_events = + Event + |> where([e], e.room_id == ^room_id) + |> select([e], {e.event_id, e}) |> Repo.all() - |> Enum.map(&resolve/1) - - resolved_state = resolve(state_sets) + |> Enum.into(%{}) + + resolve(event, room_events) + end + + def resolve( + %Event{type: type, state_key: state_key, event_id: event_id, prev_events: prev_event_ids}, + room_events + ) do + state_sets = + prev_event_ids + |> Enum.map(&room_events[&1]) + |> Enum.map(&resolve(&1, room_events)) + + resolved_state = resolve(state_sets, room_events) # TODO: check if state event Map.put(resolved_state, {type, state_key}, event_id) end - def resolve([]), do: %{} + def resolve([], _), do: %{} - def resolve(state_sets) do + def resolve(state_sets, room_events) do {unconflicted_state_map, conflicted_state_set} = calculate_conflict(state_sets) + if MapSet.size(conflicted_state_set) == 0 do unconflicted_state_map else - full_conflicted_set = MapSet.union(conflicted_state_set, auth_difference(state_sets)) + full_conflicted_set = + MapSet.union(conflicted_state_set, auth_difference(state_sets, room_events)) conflicted_control_event_ids = - Enum.filter(full_conflicted_set, &is_control_event/1) |> MapSet.new() + Enum.filter(full_conflicted_set, &is_control_event(&1, room_events)) |> MapSet.new() conflicted_control_event_ids_with_auth = conflicted_control_event_ids |> MapSet.to_list() - |> full_auth_chain() + |> full_auth_chain(room_events) |> MapSet.intersection(full_conflicted_set) |> MapSet.union(conflicted_control_event_ids) conflicted_control_events_with_auth = - Repo.all( - from e in Event, - where: e.event_id in ^MapSet.to_list(conflicted_control_event_ids_with_auth) - ) + Enum.map(conflicted_control_event_ids_with_auth, &room_events[&1]) - sorted_control_events = Enum.sort(conflicted_control_events_with_auth, &rev_top_pow_order/2) + sorted_control_events = + Enum.sort(conflicted_control_events_with_auth, rev_top_pow_order(room_events)) - partial_resolved_state = iterative_auth_checks(sorted_control_events, unconflicted_state_map) + partial_resolved_state = + iterative_auth_checks(sorted_control_events, unconflicted_state_map, room_events) other_conflicted_event_ids = MapSet.difference(full_conflicted_set, conflicted_control_event_ids_with_auth) - other_conflicted_events = - Repo.all(from e in Event, where: e.event_id in ^MapSet.to_list(other_conflicted_event_ids)) + other_conflicted_events = Enum.map(other_conflicted_event_ids, &room_events[&1]) resolved_power_levels = partial_resolved_state[{"m.room.power_levels", ""}] sorted_other_events = - Enum.sort(other_conflicted_events, mainline_order(resolved_power_levels)) + Enum.sort(other_conflicted_events, mainline_order(resolved_power_levels, room_events)) - nearly_final_state = iterative_auth_checks(sorted_other_events, partial_resolved_state) + nearly_final_state = + iterative_auth_checks(sorted_other_events, partial_resolved_state, room_events) Map.merge(nearly_final_state, unconflicted_state_map) end @@ -101,13 +116,13 @@ defmodule MatrixServer.StateResolution do {unconflicted_state_map, conflicted_state_set} end - def auth_difference(state_sets) do + def auth_difference(state_sets, room_events) do # TODO: memoization possible full_auth_chains = Enum.map(state_sets, fn state_set -> state_set |> Map.values() - |> full_auth_chain() + |> full_auth_chain(room_events) end) auth_chain_union = Enum.reduce(full_auth_chains, MapSet.new(), &MapSet.union/2) @@ -116,32 +131,24 @@ defmodule MatrixServer.StateResolution do MapSet.difference(auth_chain_union, auth_chain_intersection) end - def full_auth_chain(event_ids) do + def full_auth_chain(event_ids, room_events) do event_ids - |> Enum.map(&auth_chain/1) + |> Enum.map(&auth_chain(&1, room_events)) |> Enum.reduce(MapSet.new(), &MapSet.union/2) end - def auth_chain(event_id) do + def auth_chain(event_id, room_events) do # TODO: handle when auth event is not found. - Event - |> where([e], e.event_id == ^event_id) - |> select([e], e.auth_events) - |> Repo.one!() + room_events[event_id].auth_events |> Enum.reduce(MapSet.new(), fn auth_event_id, acc -> auth_event_id - |> auth_chain() + |> auth_chain(room_events) |> MapSet.union(acc) |> MapSet.put(auth_event_id) end) end - def is_control_event(event_id) when is_binary(event_id) do - Event - |> where([e], e.event_id == ^event_id) - |> Repo.one!() - |> is_control_event() - end + def is_control_event(event_id, room_events), do: is_control_event(room_events[event_id]) 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 @@ -157,50 +164,47 @@ defmodule MatrixServer.StateResolution do def is_control_event(_), do: false - def rev_top_pow_order( - %Event{origin_server_ts: timestamp1, event_id: event_id1} = event1, - %Event{origin_server_ts: timestamp2, event_id: event_id2} = event2 - ) do - {power1, power2} = {get_power_level(event1), get_power_level(event2)} + def rev_top_pow_order(room_events) do + fn %Event{origin_server_ts: timestamp1, event_id: event_id1} = event1, + %Event{origin_server_ts: timestamp2, event_id: event_id2} = event2 -> + power1 = get_power_level(event1, room_events) + power2 = get_power_level(event2, room_events) - if power1 == power2 do - if timestamp1 == timestamp2 do - event_id1 <= event_id2 + if power1 == power2 do + if timestamp1 == timestamp2 do + event_id1 <= event_id2 + else + timestamp1 < timestamp2 + end else - timestamp1 < timestamp2 + power1 < power2 end - else - power1 < power2 end end - def get_power_level(%Event{sender: sender, auth_events: auth_event_ids}) do - pl_content = - Event - |> where([e], e.event_id in ^auth_event_ids) - |> where([e], e.type == "m.room.power_levels") - |> select([e], e.content) - |> Repo.one() + def get_power_level(%Event{sender: sender, auth_events: auth_event_ids}, room_events) do + pl_event_id = + Enum.find(auth_event_ids, fn id -> + room_events[id].type == "m.room.power_levels" + end) - case pl_content do - %{"users" => pl_users} -> Map.get(pl_users, sender, 0) + case room_events[pl_event_id] do + %Event{content: %{"users" => pl_users}} -> Map.get(pl_users, sender, 0) nil -> 0 end end - def mainline_order(event_id) do + def mainline_order(event_id, room_events) do mainline_map = - Event - |> where([e], e.event_id == ^event_id) - |> Repo.one!() - |> mainline() + room_events[event_id] + |> mainline(room_events) |> Enum.with_index() |> Enum.into(%{}) fn %Event{origin_server_ts: timestamp1, event_id: event_id1} = event1, %Event{origin_server_ts: timestamp2, event_id: event_id2} = event2 -> - mainline_depth1 = get_mainline_depth(mainline_map, event1) - mainline_depth2 = get_mainline_depth(mainline_map, event2) + mainline_depth1 = get_mainline_depth(mainline_map, event1, room_events) + mainline_depth2 = get_mainline_depth(mainline_map, event2, room_events) if mainline_depth1 == mainline_depth2 do if timestamp1 == timestamp2 do @@ -214,8 +218,8 @@ defmodule MatrixServer.StateResolution do end end - defp get_mainline_depth(mainline_map, event) do - mainline = mainline(event) + defp get_mainline_depth(mainline_map, event, room_events) do + mainline = mainline(event, room_events) result = Enum.find_value(mainline, fn mainline_event -> @@ -232,28 +236,27 @@ defmodule MatrixServer.StateResolution do end end - def mainline(event) do + def mainline(event, room_events) do event - |> mainline([]) + |> mainline([], room_events) |> Enum.reverse() end - def mainline(%Event{auth_events: auth_event_ids} = event, acc) do - pl_event = - Event - |> where([e], e.event_id in ^auth_event_ids) - |> where([e], e.type == "m.room.power_levels") - |> Repo.one() + def mainline(%Event{auth_events: auth_event_ids} = event, acc, room_events) do + pl_event_id = + Enum.find(auth_event_ids, fn id -> + room_events[id].type == "m.room.power_levels" + end) - case pl_event do - %Event{} -> mainline(pl_event, [event | acc]) + case room_events[pl_event_id] do + %Event{} = pl_event -> mainline(pl_event, [event | acc], room_events) nil -> [event | acc] end end - def iterative_auth_checks(events, state_set) do + def iterative_auth_checks(events, state_set, room_events) do Enum.reduce(events, state_set, fn event, acc -> - if is_authorized2(event, acc), do: insert_event(event, acc), else: acc + if is_authorized2(event, acc, room_events), do: insert_event(event, acc), else: acc end) end @@ -261,11 +264,10 @@ defmodule MatrixServer.StateResolution do Map.put(state_set, {event_type, state_key}, event_id) end - def is_authorized2(%Event{auth_events: auth_event_ids} = event, state_set) do + def is_authorized2(%Event{auth_events: auth_event_ids} = event, state_set, room_events) do state_set = - Event - |> where([e], e.event_id in ^auth_event_ids) - |> Repo.all() + auth_event_ids + |> Enum.map(&room_events[&1]) |> Enum.reduce(state_set, fn %Event{ type: event_type, state_key: state_key, @@ -275,50 +277,48 @@ defmodule MatrixServer.StateResolution do Map.put_new(acc, {event_type, state_key}, event_id) end) - is_authorized(event, state_set) + is_authorized(event, state_set, room_events) end # TODO: join and power levels events - def is_authorized(%Event{type: "m.room.create", prev_events: prev_events}, _), + def is_authorized(%Event{type: "m.room.create", prev_events: prev_events}, _, _), do: prev_events == [] def is_authorized( %Event{type: "m.room.member", content: %{"membership" => "join"}, state_key: user}, - state_set + state_set, + room_events ) do - allowed_to_join(user, state_set) + allowed_to_join(user, state_set, room_events) end - def is_authorized(%Event{sender: sender} = event, state_set) do - in_room(sender, state_set) and - has_power_level(sender, get_power_levels(state_set), get_event_power_level(event)) - end - - def in_room(user, state_set) when is_map_key(state_set, {"m.room.member", user}) do - content = - Repo.one!( - from e in Event, - where: e.event_id == ^state_set[{"m.room.member", user}], - select: e.content + def is_authorized(%Event{sender: sender} = event, state_set, room_events) do + in_room(sender, state_set, room_events) and + has_power_level( + sender, + get_power_levels(state_set, room_events), + get_event_power_level(event) ) + end - case content["membership"] do + def in_room(user, state_set, room_events) when is_map_key(state_set, {"m.room.member", user}) do + event_id = state_set[{"m.room.member", user}] + + case room_events[event_id].content["membership"] do "join" -> true _ -> false end end - def in_room(_, _), do: false + def in_room(_, _, _), do: false - def get_power_levels(state_set) when is_map_key(state_set, {"m.room.power_levels", ""}) do - Repo.one!( - from e in Event, - where: e.event_id == ^state_set[{"m.room.power_levels", ""}], - select: e.content - ) + def get_power_levels(state_set, room_events) + when is_map_key(state_set, {"m.room.power_levels", ""}) do + event_id = state_set[{"m.room.power_levels", ""}] + room_events[event_id].content end - def get_power_levels(_), do: nil + def get_power_levels(_, _), do: nil def has_power_level(user, %{"users" => users}, level) do Map.get(users, user, 0) >= level @@ -332,10 +332,9 @@ defmodule MatrixServer.StateResolution do defp get_event_power_level(_), do: 50 # No join rules specified, allow joining for room creator only. - def allowed_to_join(user, state_set) + def allowed_to_join(user, state_set, room_events) when not is_map_key(state_set, {"m.room.join_rules", ""}) do - Repo.one!( - from e in Event, where: e.event_id == ^state_set[{"m.room.create", ""}], select: e.sender - ) == user + event_id = state_set[{"m.room.create", ""}] + room_events[event_id].sender == user end end diff --git a/lib/matrix_server/state_resolution_example.ex b/lib/matrix_server/state_resolution_example.ex deleted file mode 100644 index bef12c3..0000000 --- a/lib/matrix_server/state_resolution_example.ex +++ /dev/null @@ -1,469 +0,0 @@ -# https://matrix.uhoreg.ca/stateres/reloaded.html -defmodule MatrixServer.StateResolutionExample do - @derive {Inspect, except: [:prev_events, :auth_events]} - defstruct [ - :event_id, - :event_type, - :timestamp, - :state_key, - :sender, - :content, - :prev_events, - :auth_events, - :power_levels - ] - - alias __MODULE__, as: Event - - @type t :: %Event{event_id: String.t(), event_type: Atom.t(), timestamp: Integer.t()} - - def new_state_event, do: %Event{new() | event_type: :state} - def new_message_event, do: %Event{new() | event_type: :message} - - # TODO: remove state_key default here - def new do - %Event{ - event_id: "", - timestamp: 0, - state_key: "", - sender: "", - content: "", - prev_events: [], - auth_events: [], - power_levels: %{} - } - end - - def join(user), do: %Event{membership(user) | content: "join"} - def leave(user), do: %Event{membership(user) | content: "leave"} - def invite(actor, subject), do: %Event{membership(actor, subject) | content: "invite"} - def kick(actor, subject), do: %Event{membership(actor, subject) | content: "leave"} - def ban(actor, subject), do: %Event{membership(actor, subject) | content: "ban"} - - def set_power_levels(user, power_levels) do - %Event{new() | event_type: :power_levels, sender: user, power_levels: power_levels} - end - - def set_topic(user, topic) do - %Event{new() | event_type: :topic, sender: user, content: topic} - end - - def get_state_set_from_event_list(events) do - Enum.reduce(events, %{}, fn - %Event{event_type: event_type, state_key: state_key} = event, acc -> - Map.put(acc, {event_type, state_key}, event) - end) - end - - def auth_chain(event), do: auth_chain(event, MapSet.new()) - - def auth_chain(%Event{auth_events: auth_events}, set) do - Enum.reduce(auth_events, set, fn event, acc -> - event - |> auth_chain() - |> MapSet.union(acc) - |> MapSet.put(event) - end) - end - - def in_room(user, state_set) when is_map_key(state_set, {:membership, user}) do - state_set[{:membership, user}].content == "join" - end - - def in_room(_, _), do: false - - def get_power_levels(state_set) when is_map_key(state_set, {:power_levels, ""}) do - state_set[{:power_levels, ""}].power_levels - end - - def get_power_levels(_), do: nil - - def has_power_level(_, nil, _), do: true - - def has_power_level(user, power_levels, level) do - Map.get(power_levels, user, 0) >= level - end - - # No join rules specified, allow joining for room creator only. - def allowed_to_join(user, state_set) when not is_map_key(state_set, {:join_rules, ""}) do - state_set[{:create, ""}].sender == user - end - - # TODO: join and power levels events - def is_authorized(%Event{event_type: :create, prev_events: prev_events}, _), - do: prev_events == [] - - def is_authorized(%Event{event_type: :membership, content: "join", state_key: user}, state_set) do - allowed_to_join(user, state_set) - end - - def is_authorized(%Event{sender: sender} = event, state_set) do - in_room(sender, state_set) and - has_power_level(sender, get_power_levels(state_set), get_event_power_level(event)) - end - - def is_authorized2(%Event{auth_events: auth_events} = event, state_set) do - state_set = - Enum.reduce(auth_events, state_set, fn %Event{event_type: event_type, state_key: state_key} = - event, - acc -> - Map.put_new(acc, {event_type, state_key}, event) - end) - - is_authorized(event, state_set) - end - - def iterative_auth_checks(events, state_set) do - Enum.reduce(events, state_set, fn event, acc -> - if is_authorized2(event, acc), do: insert_event(event, acc), else: acc - end) - end - - def insert_event(%Event{event_type: event_type, state_key: state_key} = event, state_set) do - Map.put(state_set, {event_type, state_key}, event) - end - - def is_control_event(%Event{event_type: :power_levels, state_key: ""}), do: true - - def is_control_event(%Event{event_type: :join_rules, state_key: ""}), do: true - - def is_control_event(%Event{ - event_type: :membership, - state_key: state_key, - sender: sender, - content: "ban" - }), - do: sender != state_key - - def is_control_event(%Event{ - event_type: :membership, - state_key: state_key, - sender: sender, - content: "leave" - }), - do: sender != state_key - - def is_control_event(_), do: false - - def calculate_conflict(state_sets) do - domain = - state_sets - |> Enum.map(&Map.keys/1) - |> List.flatten() - |> MapSet.new() - - full_state_map_list = - Enum.map(domain, fn k -> - events = - Enum.map(state_sets, &Map.get(&1, k)) - |> MapSet.new() - - {k, events} - end) - - {unconflicted, conflicted} = - Enum.split_with(full_state_map_list, fn {_k, events} -> - MapSet.size(events) == 1 - end) - - unconflicted_state_map = - Enum.map(unconflicted, fn {k, events} -> - event = - events - |> MapSet.to_list() - |> hd() - - {k, event} - end) - |> Enum.into(%{}) - - conflicted_state_map = - Enum.flat_map(conflicted, fn {_, events} -> - events - |> MapSet.delete(nil) - |> MapSet.to_list() - end) - |> MapSet.new() - - {unconflicted_state_map, conflicted_state_map} - end - - def full_auth_chain(events) do - events - |> Enum.map(&auth_chain/1) - |> Enum.reduce(MapSet.new(), &MapSet.union/2) - end - - def auth_difference(state_sets) do - full_auth_chains = - Enum.map(state_sets, fn state_set -> - state_set - |> Map.values() - |> full_auth_chain() - end) - - auth_chain_union = Enum.reduce(full_auth_chains, MapSet.new(), &MapSet.union/2) - auth_chain_intersection = Enum.reduce(full_auth_chains, MapSet.new(), &MapSet.intersection/2) - - MapSet.difference(auth_chain_union, auth_chain_intersection) - end - - def rev_top_pow_order( - %Event{timestamp: timestamp1, event_id: event_id1} = event1, - %Event{timestamp: timestamp2, event_id: event_id2} = event2 - ) do - {power1, power2} = {get_power_level(event1), get_power_level(event2)} - - if power1 == power2 do - if timestamp1 == timestamp2 do - event_id1 <= event_id2 - else - timestamp1 < timestamp2 - end - else - power1 < power2 - end - end - - def get_power_level(%Event{sender: sender, auth_events: auth_events}) do - pl_event = Enum.find(auth_events, &(&1.event_type == :power_levels)) - - case pl_event do - %Event{power_levels: power_levels} -> Map.get(power_levels, sender, 0) - _ -> 0 - end - end - - def mainline(event) do - event - |> mainline([]) - |> Enum.reverse() - end - - def mainline(%Event{auth_events: auth_events} = event, acc) do - case Enum.find(auth_events, &(&1.event_type == :power_levels)) do - nil -> [event | acc] - pl_event -> mainline(pl_event, [event | acc]) - end - end - - def mainline_order(p) do - mainline_map = - p - |> mainline() - |> Enum.with_index() - |> Enum.into(%{}) - - fn %Event{timestamp: timestamp1, event_id: event_id1} = event1, - %Event{timestamp: timestamp2, event_id: event_id2} = event2 -> - mainline_depth1 = get_mainline_depth(mainline_map, event1) - mainline_depth2 = get_mainline_depth(mainline_map, event2) - - if mainline_depth1 == mainline_depth2 do - if timestamp1 == timestamp2 do - event_id1 <= event_id2 - else - timestamp1 < timestamp2 - end - else - mainline_depth1 < mainline_depth2 - end - end - end - - defp get_mainline_depth(mainline_map, event) do - mainline = mainline(event) - - result = - Enum.find_value(mainline, fn mainline_event -> - if Map.has_key?(mainline_map, mainline_event) do - {:ok, mainline_map[mainline_event]} - else - nil - end - end) - - case result do - {:ok, index} -> -index - nil -> nil - end - end - - def resolve(state_sets) do - {unconflicted_state_map, conflicted_set} = calculate_conflict(state_sets) - full_conflicted_set = MapSet.union(conflicted_set, auth_difference(state_sets)) - - conflicted_control_events = - Enum.filter(full_conflicted_set, &is_control_event/1) |> MapSet.new() - - conflicted_control_events_with_auth = - MapSet.union( - conflicted_control_events, - MapSet.intersection( - full_conflicted_set, - full_auth_chain(MapSet.to_list(conflicted_control_events)) - ) - ) - - sorted_control_events = Enum.sort(conflicted_control_events_with_auth, &rev_top_pow_order/2) - partial_resolved_state = iterative_auth_checks(sorted_control_events, unconflicted_state_map) - - other_conflicted_events = - MapSet.difference(full_conflicted_set, conflicted_control_events_with_auth) - - resolved_power_levels = partial_resolved_state[{:power_levels, ""}] - - sorted_other_events = - Enum.sort(other_conflicted_events, mainline_order(resolved_power_levels)) - - nearly_final_state = iterative_auth_checks(sorted_other_events, partial_resolved_state) - - Map.merge(nearly_final_state, unconflicted_state_map) - end - - def example1 do - create = %Event{new() | event_id: "create", event_type: :create, sender: "@alice:example.com"} - - alice_joins = %Event{ - join("@alice:example.com") - | event_id: "alice joins", - prev_events: [create], - auth_events: [create] - } - - pl = %Event{ - set_power_levels("@alice:example.com", %{"@alice:example.com" => 100}) - | event_id: "power level", - prev_events: [alice_joins], - auth_events: [alice_joins, create] - } - - join_rules = %Event{ - new() - | event_id: "join rules", - event_type: :join_rules, - sender: "@alice:example.com", - content: "private", - prev_events: [pl], - auth_events: [pl, alice_joins, create] - } - - invite_bob = %Event{ - invite("@alice:example.com", "@bob:example.com") - | event_id: "invite bob", - prev_events: [join_rules], - auth_events: [pl, alice_joins, create] - } - - invite_carol = %Event{ - invite("@alice:example.com", "@carol:example.com") - | event_id: "invite carol", - prev_events: [invite_bob], - auth_events: [pl, alice_joins, create] - } - - bob_join = %Event{ - join("@bob:example.com") - | event_id: "bob joins", - prev_events: [invite_carol], - auth_events: [invite_bob, join_rules, create] - } - - [create, alice_joins, pl, join_rules, invite_bob, invite_carol, bob_join] - end - - def example2 do - create = %Event{ - new_state_event() - | event_id: "create", - event_type: :create, - sender: "@alice:example.com" - } - - alice_joins = join("@alice:example.com") - - pl1 = %Event{ - set_power_levels("@alice:example.com", %{"@alice:example.com" => 100}) - | event_id: "power levels 1" - } - - pl2 = %Event{ - set_power_levels("@alice:example.com", %{ - "@alice:example.com" => 100, - "@bob:example.com" => 50 - }) - | event_id: "power levels 2" - } - - topic = %Event{set_topic("@alice:example.com", "This is a topic") | event_id: "topic"} - - state_set1 = get_state_set_from_event_list([create, alice_joins, pl1]) - state_set2 = get_state_set_from_event_list([create, alice_joins, pl2, topic]) - state_set3 = get_state_set_from_event_list([create, alice_joins, pl2]) - [state_set1, state_set2, state_set3] - end - - def example3 do - pl1 = %Event{set_power_levels("alice", %{}) | event_id: "pl1", timestamp: 1} - - pl2 = %Event{ - set_power_levels("alice", %{}) - | event_id: "pl2", - auth_events: [pl1], - timestamp: 2 - } - - pl3 = %Event{ - set_power_levels("alice", %{}) - | event_id: "pl3", - auth_events: [pl1], - timestamp: 4 - } - - pl4 = %Event{ - set_power_levels("alice", %{}) - | event_id: "pl4", - auth_events: [pl2], - timestamp: 6 - } - - pl5 = %Event{ - set_power_levels("alice", %{}) - | event_id: "pl5", - auth_events: [pl4], - timestamp: 6 - } - - pl6 = %Event{ - set_power_levels("alice", %{}) - | event_id: "pl6", - auth_events: [pl4], - timestamp: 5 - } - - pl7 = %Event{ - set_power_levels("alice", %{}) - | event_id: "pl7", - auth_events: [pl2], - timestamp: 5 - } - - pl8 = %Event{ - set_power_levels("alice", %{}) - | event_id: "pl8", - auth_events: [pl7], - timestamp: 6 - } - - [pl1, pl2, pl3, pl4, pl5, pl6, pl7, pl8] - end - - defp membership(user), do: membership(user, user) - - defp membership(actor, subject) do - %Event{new() | event_type: :membership, sender: actor, state_key: subject} - end - - defp get_event_power_level(%Event{state_key: ""}), do: 0 - defp get_event_power_level(_), do: 50 -end