mirror of
https://github.com/wanderer-industries/wanderer
synced 2026-05-01 06:50:41 +00:00
843 lines
22 KiB
Elixir
843 lines
22 KiB
Elixir
defmodule WandererApp.Map do
|
|
@moduledoc """
|
|
Represents the map structure and exposes actions that can be taken to update
|
|
it
|
|
"""
|
|
import Ecto.Query
|
|
|
|
require Logger
|
|
|
|
@map_state_cache :map_state_cache
|
|
# Default plan indicates no active subscription (free tier)
|
|
@default_subscription_plan :alpha
|
|
|
|
defstruct map_id: nil,
|
|
name: nil,
|
|
scope: :none,
|
|
scopes: nil,
|
|
owner_id: nil,
|
|
characters: [],
|
|
systems: Map.new(),
|
|
hubs: [],
|
|
connections: Map.new(),
|
|
acls: [],
|
|
options: Map.new(),
|
|
characters_limit: nil,
|
|
hubs_limit: nil,
|
|
sse_enabled: false,
|
|
webhooks_enabled: false,
|
|
subscription_plan: @default_subscription_plan
|
|
|
|
def new(
|
|
%{id: map_id, name: name, scope: scope, owner_id: owner_id, acls: acls, hubs: hubs} =
|
|
input
|
|
) do
|
|
# Extract the new scopes array field if present (nil if not set)
|
|
scopes = Map.get(input, :scopes)
|
|
# Extract SSE/webhooks settings (default to false if not present)
|
|
sse_enabled = Map.get(input, :sse_enabled, false)
|
|
webhooks_enabled = Map.get(input, :webhooks_enabled, false)
|
|
|
|
map =
|
|
struct!(__MODULE__,
|
|
map_id: map_id,
|
|
scope: scope,
|
|
scopes: scopes,
|
|
owner_id: owner_id,
|
|
name: name,
|
|
acls: acls,
|
|
hubs: hubs,
|
|
sse_enabled: sse_enabled,
|
|
webhooks_enabled: webhooks_enabled
|
|
)
|
|
|
|
update_map(map_id, map)
|
|
|
|
map
|
|
end
|
|
|
|
def get_map(map_id) do
|
|
case Cachex.get(:map_cache, map_id) do
|
|
{:ok, nil} ->
|
|
{:error, :not_found}
|
|
|
|
{:ok, map} ->
|
|
{:ok, map}
|
|
end
|
|
end
|
|
|
|
def get_map!(map_id) do
|
|
case get_map(map_id) do
|
|
{:ok, map} ->
|
|
map
|
|
|
|
error ->
|
|
Logger.error("Failed to get map #{map_id}: #{inspect(error)}")
|
|
%{}
|
|
end
|
|
end
|
|
|
|
def update_map(map_id, map_update) do
|
|
Cachex.get_and_update(:map_cache, map_id, fn map ->
|
|
case map do
|
|
nil ->
|
|
{:commit, map_update}
|
|
|
|
_ ->
|
|
{:commit, Map.merge(map, map_update)}
|
|
end
|
|
end)
|
|
end
|
|
|
|
def get_map_state(map_id, init_if_empty? \\ true) do
|
|
case Cachex.get(@map_state_cache, map_id) do
|
|
{:ok, nil} ->
|
|
case init_if_empty? do
|
|
true ->
|
|
map_state = WandererApp.Map.Server.Impl.do_init_state(map_id: map_id)
|
|
Cachex.put(@map_state_cache, map_id, map_state)
|
|
{:ok, map_state}
|
|
|
|
_ ->
|
|
{:ok, nil}
|
|
end
|
|
|
|
{:ok, map_state} ->
|
|
{:ok, map_state}
|
|
end
|
|
end
|
|
|
|
def get_map_state!(map_id) do
|
|
case get_map_state(map_id) do
|
|
{:ok, map_state} ->
|
|
map_state
|
|
|
|
_ ->
|
|
Logger.error("Failed to get map_state #{map_id}")
|
|
throw("Failed to get map_state #{map_id}")
|
|
end
|
|
end
|
|
|
|
def update_map_state(map_id, state_update),
|
|
do:
|
|
Cachex.get_and_update(@map_state_cache, map_id, fn map_state ->
|
|
case map_state do
|
|
nil ->
|
|
new_state = WandererApp.Map.Server.Impl.do_init_state(map_id: map_id)
|
|
{:commit, Map.merge(new_state, state_update)}
|
|
|
|
_ ->
|
|
{:commit, Map.merge(map_state, state_update)}
|
|
end
|
|
end)
|
|
|
|
def delete_map_state(map_id), do: Cachex.del(@map_state_cache, map_id)
|
|
|
|
def get_characters_limit(map_id),
|
|
do: {:ok, map_id |> get_map!() |> Map.get(:characters_limit, 50)}
|
|
|
|
def get_hubs_limit(map_id),
|
|
do: {:ok, map_id |> get_map!() |> Map.get(:hubs_limit, 20)}
|
|
|
|
def is_subscription_active?(map_id),
|
|
do: is_subscription_active?(map_id, WandererApp.Env.map_subscriptions_enabled?())
|
|
|
|
def is_subscription_active?(_map_id, false), do: {:ok, true}
|
|
|
|
def is_subscription_active?(map_id, _map_subscriptions_enabled) do
|
|
{:ok, %{plan: plan}} = WandererApp.Map.SubscriptionManager.get_active_map_subscription(map_id)
|
|
{:ok, plan != @default_subscription_plan}
|
|
end
|
|
|
|
def get_options(map_id),
|
|
do: {:ok, map_id |> get_map!() |> Map.get(:options, Map.new())}
|
|
|
|
def get_tracked_character_ids(map_id) do
|
|
{:ok,
|
|
map_id
|
|
|> get_map!()
|
|
|> Map.get(:characters, [])
|
|
|> Enum.filter(fn character_id ->
|
|
{:ok, tracking_start_time} =
|
|
WandererApp.Cache.lookup(
|
|
"character:#{character_id}:map:#{map_id}:tracking_start_time",
|
|
nil
|
|
)
|
|
|
|
not is_nil(tracking_start_time)
|
|
end)}
|
|
end
|
|
|
|
@doc """
|
|
Returns a full list of characters in the map
|
|
"""
|
|
def list_characters(map_id),
|
|
do:
|
|
map_id
|
|
|> get_map!()
|
|
|> Map.get(:characters, [])
|
|
|> Enum.map(fn character_id ->
|
|
WandererApp.Character.get_map_character!(map_id, character_id)
|
|
end)
|
|
|
|
def list_systems(map_id),
|
|
do: {:ok, map_id |> get_map!() |> Map.get(:systems, Map.new()) |> Map.values()}
|
|
|
|
def list_systems!(map_id) do
|
|
{:ok, systems} = list_systems(map_id)
|
|
systems
|
|
end
|
|
|
|
def list_hubs(map_id) do
|
|
{:ok, map} = map_id |> get_map()
|
|
|
|
{:ok, map |> Map.get(:hubs, [])}
|
|
end
|
|
|
|
def list_hubs(map_id, hubs) do
|
|
{:ok, _map} = map_id |> get_map()
|
|
|
|
{:ok, hubs}
|
|
end
|
|
|
|
def list_connections(map_id),
|
|
do: {:ok, map_id |> get_map!() |> Map.get(:connections, Map.new()) |> Map.values()}
|
|
|
|
def list_connections!(map_id) do
|
|
{:ok, connections} = list_connections(map_id)
|
|
connections
|
|
end
|
|
|
|
def get_connection(map_id, solar_system_source, solar_system_target),
|
|
do:
|
|
map_id
|
|
|> get_map!()
|
|
|> Map.get(:connections, Map.new())
|
|
|> Map.get("#{solar_system_source}_#{solar_system_target}")
|
|
|
|
def add_characters!(map, []), do: map
|
|
|
|
def add_characters!(%{map_id: map_id} = map, characters) when is_list(characters) do
|
|
# Get current characters list once
|
|
current_characters = Map.get(map, :characters, [])
|
|
|
|
characters_ids =
|
|
characters
|
|
|> Enum.map(fn %{character_id: char_id} -> char_id end)
|
|
|
|
# Filter out characters that already exist
|
|
new_character_ids =
|
|
characters_ids
|
|
|> Enum.reject(fn char_id -> char_id in current_characters end)
|
|
|
|
# If all characters already exist, return early
|
|
if new_character_ids == [] do
|
|
map
|
|
else
|
|
case update_map(map_id, %{characters: new_character_ids ++ current_characters}) do
|
|
{:commit, map} ->
|
|
map
|
|
|
|
_ ->
|
|
map
|
|
end
|
|
end
|
|
end
|
|
|
|
def add_character(
|
|
map_id,
|
|
%{
|
|
id: character_id
|
|
} = _character
|
|
) do
|
|
characters = map_id |> get_map!() |> Map.get(:characters, [])
|
|
|
|
case not (characters |> Enum.member?(character_id)) do
|
|
true ->
|
|
map_id
|
|
|> update_map(%{characters: [character_id | characters]})
|
|
|
|
:ok
|
|
|
|
_ ->
|
|
:ok
|
|
end
|
|
end
|
|
|
|
def get_system_characters(map_id, solar_system_id),
|
|
do:
|
|
map_id
|
|
|> list_characters()
|
|
|> filter(%{solar_system_id: solar_system_id}, match: :any)
|
|
|
|
@doc """
|
|
Removes a character with a given id
|
|
"""
|
|
def remove_character(map_id, character_id) do
|
|
characters = map_id |> get_map!() |> Map.get(:characters, [])
|
|
|
|
case characters |> Enum.member?(character_id) do
|
|
true ->
|
|
map_id
|
|
|> update_map(%{characters: characters |> Enum.reject(fn id -> id == character_id end)})
|
|
|
|
:ok
|
|
|
|
_ ->
|
|
:ok
|
|
end
|
|
end
|
|
|
|
def check_location(map_id, location) do
|
|
case find_system_by_location(map_id, location) do
|
|
nil ->
|
|
{:ok, location}
|
|
|
|
%{} ->
|
|
{:error, :already_exists}
|
|
end
|
|
end
|
|
|
|
def find_system_by_location(_map_id, nil), do: nil
|
|
|
|
def find_system_by_location(map_id, %{solar_system_id: solar_system_id} = _location) do
|
|
case map_id |> get_map!() |> Map.get(:systems, Map.new()) |> Map.get(solar_system_id) do
|
|
nil ->
|
|
nil
|
|
|
|
%{visible: true} = system ->
|
|
system
|
|
|
|
_system ->
|
|
nil
|
|
end
|
|
end
|
|
|
|
def check_connection(
|
|
map_id,
|
|
%{solar_system_id: solar_system_id} = _location,
|
|
%{solar_system_id: old_solar_system_id} = _old_location
|
|
) do
|
|
case map_id
|
|
|> get_map!()
|
|
|> Map.get(:connections, Map.new())
|
|
|> is_connection_exist?(%{
|
|
solar_system_source: solar_system_id,
|
|
solar_system_target: old_solar_system_id
|
|
}) do
|
|
true ->
|
|
{:error, :already_exists}
|
|
|
|
_ ->
|
|
:ok
|
|
end
|
|
end
|
|
|
|
def update_subscription_settings!(%{map_id: map_id} = _map, subscription_settings) do
|
|
characters_limit = Map.get(subscription_settings, :characters_limit)
|
|
hubs_limit = Map.get(subscription_settings, :hubs_limit)
|
|
plan = Map.get(subscription_settings, :plan, @default_subscription_plan)
|
|
|
|
map_id
|
|
|> update_map(%{
|
|
characters_limit: characters_limit,
|
|
hubs_limit: hubs_limit,
|
|
subscription_plan: plan
|
|
})
|
|
|
|
map_id
|
|
|> get_map!()
|
|
end
|
|
|
|
def update_options!(%{map_id: map_id} = _map, options) do
|
|
map_id
|
|
|> update_map(%{options: options})
|
|
|
|
map_id
|
|
|> get_map!()
|
|
end
|
|
|
|
@doc """
|
|
Updates SSE enabled setting in the map cache.
|
|
Called when the map's sse_enabled setting changes.
|
|
"""
|
|
def update_sse_enabled(map_id, sse_enabled)
|
|
when is_binary(map_id) and is_boolean(sse_enabled) do
|
|
update_map(map_id, %{sse_enabled: sse_enabled})
|
|
:ok
|
|
end
|
|
|
|
@doc """
|
|
Updates webhooks enabled setting in the map cache.
|
|
Called when the map's webhooks_enabled setting changes.
|
|
"""
|
|
def update_webhooks_enabled(map_id, webhooks_enabled)
|
|
when is_binary(map_id) and is_boolean(webhooks_enabled) do
|
|
update_map(map_id, %{webhooks_enabled: webhooks_enabled})
|
|
:ok
|
|
end
|
|
|
|
@doc """
|
|
Checks if SSE is enabled for a map using the cache.
|
|
Falls back to DB query if map is not in cache.
|
|
Returns a boolean (defaults to false if map not found).
|
|
"""
|
|
def sse_enabled?(map_id) do
|
|
case get_map(map_id) do
|
|
{:ok, map} ->
|
|
Map.get(map, :sse_enabled, false)
|
|
|
|
{:error, :not_found} ->
|
|
# Cache miss - fall back to DB
|
|
case WandererApp.Api.Map.by_id(map_id) do
|
|
{:ok, db_map} -> db_map.sse_enabled
|
|
_ -> false
|
|
end
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Checks if SSE is enabled for a map with explicit not_found handling.
|
|
Returns {:ok, boolean} or {:error, :not_found}.
|
|
"""
|
|
def sse_enabled_with_status(map_id) do
|
|
case get_map(map_id) do
|
|
{:ok, map} ->
|
|
{:ok, Map.get(map, :sse_enabled, false)}
|
|
|
|
{:error, :not_found} ->
|
|
# Cache miss - fall back to DB
|
|
case WandererApp.Api.Map.by_id(map_id) do
|
|
{:ok, db_map} -> {:ok, db_map.sse_enabled}
|
|
_ -> {:error, :not_found}
|
|
end
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Checks if webhooks are enabled for a map using the cache.
|
|
Falls back to DB query if map is not in cache.
|
|
"""
|
|
def webhooks_enabled?(map_id) do
|
|
case get_map(map_id) do
|
|
{:ok, map} ->
|
|
Map.get(map, :webhooks_enabled, false)
|
|
|
|
{:error, :not_found} ->
|
|
# Cache miss - fall back to DB
|
|
case WandererApp.Api.Map.by_id(map_id) do
|
|
{:ok, db_map} -> db_map.webhooks_enabled
|
|
_ -> false
|
|
end
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Checks if subscription is active for a map using the cache.
|
|
Returns {:ok, true} if active, {:ok, false} if not, or {:error, :not_cached} if not in cache.
|
|
|
|
Note: In CE mode (subscriptions disabled), use is_subscription_active?/1 which
|
|
handles this case without cache lookup.
|
|
"""
|
|
def subscription_active_cached?(map_id) do
|
|
case get_map(map_id) do
|
|
{:ok, map} ->
|
|
plan = Map.get(map, :subscription_plan, @default_subscription_plan)
|
|
{:ok, plan != @default_subscription_plan}
|
|
|
|
_ ->
|
|
{:error, :not_cached}
|
|
end
|
|
end
|
|
|
|
def add_systems!(map, []), do: map
|
|
|
|
def add_systems!(%{map_id: map_id} = map, [system | rest]) do
|
|
:ok = add_system(map_id, system)
|
|
add_systems!(map, rest)
|
|
end
|
|
|
|
def add_system(map_id, %{solar_system_id: solar_system_id} = system) do
|
|
systems = map_id |> get_map!() |> Map.get(:systems, Map.new())
|
|
|
|
case not Map.has_key?(systems, solar_system_id) do
|
|
true ->
|
|
map_id
|
|
|> update_map(%{systems: Map.put(systems, solar_system_id, system)})
|
|
|
|
:ok
|
|
|
|
_ ->
|
|
:ok
|
|
end
|
|
end
|
|
|
|
def update_system_by_solar_system_id(
|
|
map_id,
|
|
update
|
|
) do
|
|
updated_systems =
|
|
map_id |> get_map!() |> Map.get(:systems, Map.new()) |> update_by_solar_system_id(update)
|
|
|
|
map_id
|
|
|> update_map(%{systems: updated_systems})
|
|
|
|
:ok
|
|
end
|
|
|
|
def remove_system(map_id, solar_system_id) do
|
|
systems = map_id |> get_map!() |> Map.get(:systems, Map.new())
|
|
|
|
case systems |> Map.get(solar_system_id) do
|
|
nil ->
|
|
:ok
|
|
|
|
_system ->
|
|
map_id
|
|
|> update_map(%{systems: Map.drop(systems, [solar_system_id])})
|
|
|
|
:ok
|
|
end
|
|
end
|
|
|
|
def remove_systems(_map_id, []), do: :ok
|
|
|
|
def remove_systems(map_id, [solar_system_id | rest]) do
|
|
:ok = remove_system(map_id, solar_system_id)
|
|
remove_systems(map_id, rest)
|
|
end
|
|
|
|
def add_hub(map_id, %{solar_system_id: solar_system_id} = _hub_info) do
|
|
hubs = map_id |> get_map!() |> Map.get(:hubs, [])
|
|
|
|
case hubs |> Enum.member?("#{solar_system_id}") do
|
|
false ->
|
|
map_id
|
|
|> update_map(%{hubs: ["#{solar_system_id}" | hubs]})
|
|
|
|
:ok
|
|
|
|
_ ->
|
|
:ok
|
|
end
|
|
end
|
|
|
|
def remove_hub(map_id, %{solar_system_id: solar_system_id} = _hub_info) do
|
|
hubs = map_id |> get_map!() |> Map.get(:hubs, [])
|
|
|
|
case hubs |> Enum.member?("#{solar_system_id}") do
|
|
true ->
|
|
map_id
|
|
|> update_map(%{hubs: Enum.reject(hubs, fn hub -> hub == "#{solar_system_id}" end)})
|
|
|
|
:ok
|
|
|
|
_ ->
|
|
:ok
|
|
end
|
|
end
|
|
|
|
def add_connections!(map, []), do: map
|
|
|
|
def add_connections!(%{map_id: map_id} = map, [connection | rest]) do
|
|
case add_connection(map_id, connection) do
|
|
:ok ->
|
|
add_connections!(map, rest)
|
|
|
|
{:error, :already_exists} ->
|
|
connection
|
|
|> WandererApp.MapConnectionRepo.destroy!()
|
|
|
|
add_connections!(map, rest)
|
|
end
|
|
end
|
|
|
|
def add_connection(
|
|
map_id,
|
|
%{solar_system_source: solar_system_source, solar_system_target: solar_system_target} =
|
|
connection
|
|
) do
|
|
connections = map_id |> get_map!() |> Map.get(:connections, Map.new())
|
|
|
|
case not (connections |> is_connection_exist?(connection)) do
|
|
true ->
|
|
map_id
|
|
|> update_map(%{
|
|
connections:
|
|
Map.put_new(connections, "#{solar_system_source}_#{solar_system_target}", connection)
|
|
})
|
|
|
|
:ok
|
|
|
|
_ ->
|
|
:ok
|
|
end
|
|
end
|
|
|
|
def is_connection_exist?(
|
|
connections,
|
|
%{solar_system_source: solar_system_source, solar_system_target: solar_system_target} =
|
|
_connection
|
|
) do
|
|
connections |> Map.has_key?("#{solar_system_source}_#{solar_system_target}") or
|
|
connections |> Map.has_key?("#{solar_system_target}_#{solar_system_source}")
|
|
end
|
|
|
|
def update_connection(
|
|
map_id,
|
|
%{solar_system_source: solar_system_source, solar_system_target: solar_system_target} =
|
|
connection
|
|
) do
|
|
connections = map_id |> get_map!() |> Map.get(:connections, Map.new())
|
|
|
|
map_id
|
|
|> update_map(%{
|
|
connections:
|
|
Map.put(connections, "#{solar_system_source}_#{solar_system_target}", connection)
|
|
})
|
|
|
|
:ok
|
|
end
|
|
|
|
def remove_connection(
|
|
map_id,
|
|
%{solar_system_source: solar_system_source, solar_system_target: solar_system_target} =
|
|
_connection
|
|
) do
|
|
connections = map_id |> get_map!() |> Map.get(:connections, Map.new())
|
|
|
|
map_id
|
|
|> update_map(%{
|
|
connections: Map.drop(connections, ["#{solar_system_source}_#{solar_system_target}"])
|
|
})
|
|
|
|
:ok
|
|
end
|
|
|
|
def remove_connections(_map_id, []), do: :ok
|
|
|
|
def remove_connections(map_id, [connection | rest]) do
|
|
:ok = remove_connection(map_id, connection)
|
|
remove_connections(map_id, rest)
|
|
end
|
|
|
|
def find_connections(map_id, solar_system_id),
|
|
do:
|
|
map_id
|
|
|> list_connections!()
|
|
|> filter(
|
|
%{solar_system_source: solar_system_id, solar_system_target: solar_system_id},
|
|
match: :any
|
|
)
|
|
|
|
def find_connection(
|
|
map_id,
|
|
solar_system_source,
|
|
solar_system_target
|
|
) do
|
|
connections =
|
|
map_id
|
|
|> get_map!()
|
|
|> Map.get(:connections, Map.new())
|
|
|
|
case connections
|
|
|> Map.get("#{solar_system_source}_#{solar_system_target}") do
|
|
nil ->
|
|
{:ok,
|
|
connections
|
|
|> Map.get("#{solar_system_target}_#{solar_system_source}")}
|
|
|
|
connection ->
|
|
{:ok, connection}
|
|
end
|
|
end
|
|
|
|
def get_by_id(list, id) do
|
|
case find(list, %{id: id}, match: :any) do
|
|
%{} = item -> {:ok, item}
|
|
nil -> {:error, :item_not_found}
|
|
end
|
|
end
|
|
|
|
def find(list, %{} = attrs, match: :any) do
|
|
list
|
|
|> Enum.find(fn item ->
|
|
Enum.any?(attrs, &has_equal_attribute?(item, &1))
|
|
end)
|
|
end
|
|
|
|
def find(list, %{} = attrs, match: :all) do
|
|
list
|
|
|> Enum.find(fn item ->
|
|
Enum.all?(attrs, &has_equal_attribute?(item, &1))
|
|
end)
|
|
end
|
|
|
|
def filter(list, %{} = attrs, match: :any) do
|
|
list
|
|
|> Enum.filter(fn item ->
|
|
Enum.any?(attrs, &has_equal_attribute?(item, &1))
|
|
end)
|
|
end
|
|
|
|
defp has_equal_attribute?(%{} = map, {key, {:case_insensitive, value}}) when is_binary(value) do
|
|
String.downcase(Map.get(map, key, "")) == String.downcase(value)
|
|
end
|
|
|
|
defp has_equal_attribute?(%{} = map, {key, value}) do
|
|
Map.get(map, key) == value
|
|
end
|
|
|
|
defp update_by_solar_system_id(systems, %{solar_system_id: solar_system_id} = item) do
|
|
case systems |> Map.get(solar_system_id) do
|
|
nil ->
|
|
systems
|
|
|
|
system ->
|
|
systems |> Map.put(solar_system_id, system |> Map.merge(item))
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Returns the raw activity data that can be processed by WandererApp.Character.Activity.
|
|
Only includes characters that are on the map's ACL.
|
|
If days parameter is provided, filters activity to that time period.
|
|
"""
|
|
def get_character_activity(map_id, days \\ nil) do
|
|
with {:ok, map} <- WandererApp.Api.Map.by_id(map_id) do
|
|
_map_with_acls = Ash.load!(map, :acls)
|
|
|
|
# Calculate cutoff date if days is provided
|
|
cutoff_date =
|
|
if days, do: DateTime.utc_now() |> DateTime.add(-days * 24 * 3600, :second), else: nil
|
|
|
|
# Get activity data
|
|
passages_activity = get_passages_activity(map_id, cutoff_date)
|
|
connections_activity = get_connections_activity(map_id, cutoff_date)
|
|
signatures_activity = get_signatures_activity(map_id, cutoff_date)
|
|
|
|
# Return activity data
|
|
result =
|
|
passages_activity
|
|
|> Enum.map(fn passage ->
|
|
%{
|
|
character: passage.character,
|
|
passages: passage.count,
|
|
connections: Map.get(connections_activity, passage.character.id, 0),
|
|
signatures: Map.get(signatures_activity, passage.character.id, 0),
|
|
timestamp: DateTime.utc_now(),
|
|
character_id: passage.character.id,
|
|
user_id: passage.character.user_id
|
|
}
|
|
end)
|
|
|
|
{:ok, result}
|
|
end
|
|
end
|
|
|
|
defp get_passages_activity(map_id, nil) do
|
|
# Query all map chain passages without time filter
|
|
from(p in WandererApp.Api.MapChainPassages,
|
|
join: c in assoc(p, :character),
|
|
where: p.map_id == ^map_id,
|
|
group_by: [c.id],
|
|
select: {c, count(p.id)}
|
|
)
|
|
|> WandererApp.Repo.all()
|
|
|> Enum.map(fn {character, count} -> %{character: character, count: count} end)
|
|
end
|
|
|
|
defp get_passages_activity(map_id, cutoff_date) do
|
|
# Query map chain passages with time filter
|
|
from(p in WandererApp.Api.MapChainPassages,
|
|
join: c in assoc(p, :character),
|
|
where:
|
|
p.map_id == ^map_id and
|
|
p.inserted_at > ^cutoff_date,
|
|
group_by: [c.id],
|
|
select: {c, count(p.id)}
|
|
)
|
|
|> WandererApp.Repo.all()
|
|
|> Enum.map(fn {character, count} -> %{character: character, count: count} end)
|
|
end
|
|
|
|
defp get_connections_activity(map_id, nil) do
|
|
# Query all connection activity without time filter
|
|
from(ua in WandererApp.Api.UserActivity,
|
|
join: c in assoc(ua, :character),
|
|
where:
|
|
ua.entity_id == ^map_id and
|
|
ua.entity_type == :map and
|
|
ua.event_type == :map_connection_added,
|
|
group_by: [c.id],
|
|
select: {c.id, count(ua.id)}
|
|
)
|
|
|> WandererApp.Repo.all()
|
|
|> Map.new()
|
|
end
|
|
|
|
defp get_connections_activity(map_id, cutoff_date) do
|
|
from(ua in WandererApp.Api.UserActivity,
|
|
join: c in assoc(ua, :character),
|
|
where:
|
|
ua.entity_id == ^map_id and
|
|
ua.entity_type == :map and
|
|
ua.event_type == :map_connection_added and
|
|
ua.inserted_at > ^cutoff_date,
|
|
group_by: [c.id],
|
|
select: {c.id, count(ua.id)}
|
|
)
|
|
|> WandererApp.Repo.all()
|
|
|> Map.new()
|
|
end
|
|
|
|
defp get_signatures_activity(map_id, nil) do
|
|
# Query all signature activity without time filter
|
|
from(ua in WandererApp.Api.UserActivity,
|
|
join: c in assoc(ua, :character),
|
|
where:
|
|
ua.entity_id == ^map_id and
|
|
ua.entity_type == :map and
|
|
ua.event_type == :signatures_added,
|
|
select: {ua.character_id, ua.event_data}
|
|
)
|
|
|> WandererApp.Repo.all()
|
|
|> process_signatures_data()
|
|
end
|
|
|
|
defp get_signatures_activity(map_id, cutoff_date) do
|
|
from(ua in WandererApp.Api.UserActivity,
|
|
join: c in assoc(ua, :character),
|
|
where:
|
|
ua.entity_id == ^map_id and
|
|
ua.entity_type == :map and
|
|
ua.event_type == :signatures_added and
|
|
ua.inserted_at > ^cutoff_date,
|
|
select: {ua.character_id, ua.event_data}
|
|
)
|
|
|> WandererApp.Repo.all()
|
|
|> process_signatures_data()
|
|
end
|
|
|
|
defp process_signatures_data(signatures_data) do
|
|
signatures_data
|
|
|> Enum.group_by(fn {character_id, _} -> character_id end)
|
|
|> Enum.map(&process_character_signatures/1)
|
|
|> Map.new()
|
|
end
|
|
|
|
defp process_character_signatures({character_id, activities}) do
|
|
signature_count =
|
|
activities
|
|
|> Enum.map(fn {_, event_data} ->
|
|
case Jason.decode(event_data) do
|
|
{:ok, data} -> length(Map.get(data, "signatures", []))
|
|
_ -> 0
|
|
end
|
|
end)
|
|
|> Enum.sum()
|
|
|
|
{character_id, signature_count}
|
|
end
|
|
end
|