mirror of
https://github.com/wanderer-industries/wanderer
synced 2025-12-11 18:26:04 +00:00
Add api for visible system kill information (#133)
* feat: api for zkill information
This commit is contained in:
406
lib/wanderer_app_web/controllers/map_api_controller.ex
Normal file
406
lib/wanderer_app_web/controllers/map_api_controller.ex
Normal file
@@ -0,0 +1,406 @@
|
||||
defmodule WandererAppWeb.MapAPIController do
|
||||
use WandererAppWeb, :controller
|
||||
|
||||
import Ash.Query, only: [filter: 2]
|
||||
require Logger
|
||||
|
||||
alias WandererApp.Api
|
||||
alias WandererApp.Api.Character
|
||||
alias WandererApp.MapSystemRepo
|
||||
alias WandererApp.MapCharacterSettingsRepo
|
||||
|
||||
alias WandererApp.Zkb.KillsProvider.KillsCache
|
||||
|
||||
alias WandererAppWeb.UtilAPIController, as: Util
|
||||
|
||||
# -----------------------------------------------------------------
|
||||
# MAP endpoints
|
||||
# -----------------------------------------------------------------
|
||||
|
||||
@doc """
|
||||
GET /api/map/systems
|
||||
|
||||
Requires either `?map_id=<UUID>` **OR** `?slug=<map-slug>` in the query params.
|
||||
|
||||
Only "visible" systems are returned.
|
||||
|
||||
Examples:
|
||||
GET /api/map/systems?map_id=466e922b-e758-485e-9b86-afae06b88363
|
||||
GET /api/map/systems?slug=my-unique-wormhole-map
|
||||
"""
|
||||
def list_systems(conn, params) do
|
||||
with {:ok, map_id} <- Util.fetch_map_id(params),
|
||||
{:ok, systems} <- MapSystemRepo.get_visible_by_map(map_id) do
|
||||
data = Enum.map(systems, &map_system_to_json/1)
|
||||
json(conn, %{data: data})
|
||||
else
|
||||
{:error, msg} when is_binary(msg) ->
|
||||
conn
|
||||
|> put_status(:bad_request)
|
||||
|> json(%{error: msg})
|
||||
|
||||
{:error, reason} ->
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|> json(%{error: "Could not fetch systems: #{inspect(reason)}"})
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
GET /api/map/system
|
||||
|
||||
Requires 'id' (the solar_system_id)
|
||||
plus either ?map_id=<UUID> or ?slug=<map-slug>.
|
||||
|
||||
Example:
|
||||
GET /api/map/system?id=31002229&map_id=466e922b-e758-485e-9b86-afae06b88363
|
||||
GET /api/map/system?id=31002229&slug=my-unique-wormhole-map
|
||||
"""
|
||||
def show_system(conn, params) do
|
||||
with {:ok, solar_system_str} <- Util.require_param(params, "id"),
|
||||
{:ok, solar_system_id} <- Util.parse_int(solar_system_str),
|
||||
{:ok, map_id} <- Util.fetch_map_id(params),
|
||||
{:ok, system} <- MapSystemRepo.get_by_map_and_solar_system_id(map_id, solar_system_id) do
|
||||
data = map_system_to_json(system)
|
||||
json(conn, %{data: data})
|
||||
else
|
||||
{:error, msg} when is_binary(msg) ->
|
||||
conn
|
||||
|> put_status(:bad_request)
|
||||
|> json(%{error: msg})
|
||||
|
||||
{:error, :not_found} ->
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|> json(%{error: "System not found"})
|
||||
|
||||
{:error, reason} ->
|
||||
conn
|
||||
|> put_status(:internal_server_error)
|
||||
|> json(%{error: "Could not load system: #{inspect(reason)}"})
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
GET /api/map/tracked_characters_with_info
|
||||
|
||||
Example usage:
|
||||
GET /api/map/tracked_characters_with_info?map_id=<uuid>
|
||||
GET /api/map/tracked_characters_with_info?slug=<map-slug>
|
||||
|
||||
Returns a list of tracked records, plus their fully-loaded `character` data.
|
||||
"""
|
||||
def tracked_characters_with_info(conn, params) do
|
||||
with {:ok, map_id} <- Util.fetch_map_id(params),
|
||||
{:ok, settings_list} <- get_tracked_by_map_ids(map_id),
|
||||
{:ok, char_list} <- read_characters_by_ids_wrapper(Enum.map(settings_list, & &1.character_id)) do
|
||||
chars_by_id = Map.new(char_list, &{&1.id, &1})
|
||||
|
||||
data =
|
||||
Enum.map(settings_list, fn setting ->
|
||||
found_char = Map.get(chars_by_id, setting.character_id)
|
||||
|
||||
%{
|
||||
id: setting.id,
|
||||
map_id: setting.map_id,
|
||||
character_id: setting.character_id,
|
||||
tracked: setting.tracked,
|
||||
inserted_at: setting.inserted_at,
|
||||
updated_at: setting.updated_at,
|
||||
character:
|
||||
if found_char do
|
||||
character_to_json(found_char)
|
||||
else
|
||||
%{}
|
||||
end
|
||||
}
|
||||
end)
|
||||
|
||||
json(conn, %{data: data})
|
||||
else
|
||||
{:error, :get_tracked_error, reason} ->
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|> json(%{error: "No tracked records found for map_id: #{inspect(reason)}"})
|
||||
|
||||
{:error, :read_characters_by_ids_error, reason} ->
|
||||
conn
|
||||
|> put_status(:internal_server_error)
|
||||
|> json(%{error: "Could not load Character records: #{inspect(reason)}"})
|
||||
|
||||
{:error, message} ->
|
||||
conn
|
||||
|> put_status(:bad_request)
|
||||
|> json(%{error: message})
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
GET /api/map/structure_timers
|
||||
|
||||
Returns structure timers for visible systems on the map
|
||||
or for a specific system if `system_id` is specified.
|
||||
|
||||
**Example usage**:
|
||||
- All visible systems:
|
||||
```
|
||||
GET /api/map/structure_timers?map_id=<uuid>
|
||||
```
|
||||
- For a single system:
|
||||
```
|
||||
GET /api/map/structure_timers?map_id=<uuid>&system_id=31002229
|
||||
```
|
||||
"""
|
||||
def show_structure_timers(conn, params) do
|
||||
with {:ok, map_id} <- Util.fetch_map_id(params) do
|
||||
system_id_str = params["system_id"]
|
||||
|
||||
case system_id_str do
|
||||
nil ->
|
||||
handle_all_structure_timers(conn, map_id)
|
||||
|
||||
_ ->
|
||||
case Util.parse_int(system_id_str) do
|
||||
{:ok, system_id} ->
|
||||
handle_single_structure_timers(conn, map_id, system_id)
|
||||
|
||||
{:error, reason} ->
|
||||
conn
|
||||
|> put_status(:bad_request)
|
||||
|> json(%{error: "system_id must be int: #{reason}"})
|
||||
end
|
||||
end
|
||||
else
|
||||
{:error, msg} ->
|
||||
conn
|
||||
|> put_status(:bad_request)
|
||||
|> json(%{error: msg})
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
GET /api/map/systems_kills
|
||||
|
||||
Returns kills data for all *visible* systems on the map.
|
||||
|
||||
Requires either `?map_id=<UUID>` or `?slug=<map-slug>`.
|
||||
Optional hours_ago
|
||||
|
||||
Example:
|
||||
GET /api/map/systems_kills?map_id=<uuid>
|
||||
GET /api/map/systems_kills?slug=<map-slug>
|
||||
GET /api/map/systems_kills?map_id=<uuid>&hour_ago=<somehours>
|
||||
|
||||
"""
|
||||
def list_systems_kills(conn, params) do
|
||||
with {:ok, map_id} <- Util.fetch_map_id(params),
|
||||
# fetch visible systems from the repo
|
||||
{:ok, systems} <- MapSystemRepo.get_visible_by_map(map_id) do
|
||||
|
||||
Logger.debug(fn -> "[list_systems_kills] Found #{length(systems)} visible systems for map_id=#{map_id}" end)
|
||||
|
||||
# Parse the hours_ago param
|
||||
hours_ago = parse_hours_ago(params["hours_ago"])
|
||||
|
||||
# Gather system IDs
|
||||
solar_ids = Enum.map(systems, & &1.solar_system_id)
|
||||
|
||||
# Fetch kills for each system from the cache
|
||||
kills_map = KillsCache.fetch_cached_kills_for_systems(solar_ids)
|
||||
|
||||
# Build final JSON data
|
||||
data =
|
||||
Enum.map(systems, fn sys ->
|
||||
kills = Map.get(kills_map, sys.solar_system_id, [])
|
||||
|
||||
# Filter out kills older than hours_ago
|
||||
filtered_kills = maybe_filter_kills_by_time(kills, hours_ago)
|
||||
|
||||
Logger.debug(fn -> "
|
||||
[list_systems_kills] For system_id=#{sys.solar_system_id},
|
||||
found #{length(kills)} kills total,
|
||||
returning #{length(filtered_kills)} kills after hours_ago filter
|
||||
" end)
|
||||
|
||||
%{
|
||||
solar_system_id: sys.solar_system_id,
|
||||
kills: filtered_kills
|
||||
}
|
||||
end)
|
||||
|
||||
json(conn, %{data: data})
|
||||
else
|
||||
{:error, msg} when is_binary(msg) ->
|
||||
Logger.warn("[list_systems_kills] Bad request: #{msg}")
|
||||
conn
|
||||
|> put_status(:bad_request)
|
||||
|> json(%{error: msg})
|
||||
|
||||
{:error, reason} ->
|
||||
Logger.error("[list_systems_kills] Could not fetch systems: #{inspect(reason)}")
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|> json(%{error: "Could not fetch systems: #{inspect(reason)}"})
|
||||
end
|
||||
end
|
||||
|
||||
# If hours_str is present and valid, parse it. Otherwise return nil (no filter).
|
||||
defp parse_hours_ago(nil), do: nil
|
||||
defp parse_hours_ago(hours_str) do
|
||||
case Integer.parse(hours_str) do
|
||||
{num, ""} when num > 0 -> num
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_filter_kills_by_time(kills, hours_ago) when is_integer(hours_ago) do
|
||||
cutoff = DateTime.utc_now() |> DateTime.add(-hours_ago * 3600, :second)
|
||||
|
||||
Enum.filter(kills, fn kill ->
|
||||
kill_time = kill["kill_time"]
|
||||
|
||||
case kill_time do
|
||||
%DateTime{} = dt ->
|
||||
# Keep kills that occurred after the cutoff
|
||||
DateTime.compare(dt, cutoff) != :lt
|
||||
|
||||
# If it's something else (nil, or a weird format), skip
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
# If hours_ago is nil, maybe no time filtering:
|
||||
defp maybe_filter_kills_by_time(kills, nil), do: kills
|
||||
|
||||
defp handle_all_structure_timers(conn, map_id) do
|
||||
case MapSystemRepo.get_visible_by_map(map_id) do
|
||||
{:ok, systems} ->
|
||||
all_timers =
|
||||
systems
|
||||
|> Enum.flat_map(&get_timers_for_system/1)
|
||||
|
||||
json(conn, %{data: all_timers})
|
||||
|
||||
{:error, reason} ->
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|> json(%{error: "Could not fetch visible systems for map_id=#{map_id}: #{inspect(reason)}"})
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_single_structure_timers(conn, map_id, system_id) do
|
||||
case MapSystemRepo.get_by_map_and_solar_system_id(map_id, system_id) do
|
||||
{:ok, map_system} ->
|
||||
timers = get_timers_for_system(map_system)
|
||||
json(conn, %{data: timers})
|
||||
|
||||
{:error, :not_found} ->
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|> json(%{error: "No system with solar_system_id=#{system_id} in map=#{map_id}"})
|
||||
|
||||
{:error, reason} ->
|
||||
conn
|
||||
|> put_status(:internal_server_error)
|
||||
|> json(%{error: "Failed to retrieve system: #{inspect(reason)}"})
|
||||
end
|
||||
end
|
||||
|
||||
defp get_timers_for_system(map_system) do
|
||||
structures = WandererApp.Api.MapSystemStructure.by_system_id!(map_system.id)
|
||||
|
||||
structures
|
||||
|> Enum.filter(&timer_needed?/1)
|
||||
|> Enum.map(&structure_to_timer_json/1)
|
||||
end
|
||||
|
||||
defp timer_needed?(structure) do
|
||||
structure.status in ["Anchoring", "Reinforced"] and not is_nil(structure.end_time)
|
||||
end
|
||||
|
||||
defp structure_to_timer_json(s) do
|
||||
Map.take(s, [
|
||||
:system_id,
|
||||
:solar_system_name,
|
||||
:solar_system_id,
|
||||
:structure_type_id,
|
||||
:structure_type,
|
||||
:character_eve_id,
|
||||
:name,
|
||||
:notes,
|
||||
:owner_name,
|
||||
:owner_ticker,
|
||||
:owner_id,
|
||||
:status,
|
||||
:end_time
|
||||
])
|
||||
end
|
||||
|
||||
defp get_tracked_by_map_ids(map_id) do
|
||||
case MapCharacterSettingsRepo.get_tracked_by_map_all(map_id) do
|
||||
{:ok, settings_list} -> {:ok, settings_list}
|
||||
{:error, reason} -> {:error, :get_tracked_error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
defp read_characters_by_ids_wrapper(ids) do
|
||||
case read_characters_by_ids(ids) do
|
||||
{:ok, char_list} ->
|
||||
{:ok, char_list}
|
||||
|
||||
{:error, reason} ->
|
||||
{:error, :read_characters_by_ids_error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
defp read_characters_by_ids(ids) when is_list(ids) do
|
||||
if ids == [] do
|
||||
{:ok, []}
|
||||
else
|
||||
query =
|
||||
Character
|
||||
|> filter(id in ^ids)
|
||||
|
||||
Api.read(query)
|
||||
end
|
||||
end
|
||||
|
||||
defp map_system_to_json(system) do
|
||||
Map.take(system, [
|
||||
:id,
|
||||
:map_id,
|
||||
:solar_system_id,
|
||||
:name,
|
||||
:custom_name,
|
||||
:temporary_name,
|
||||
:description,
|
||||
:tag,
|
||||
:labels,
|
||||
:locked,
|
||||
:visible,
|
||||
:status,
|
||||
:position_x,
|
||||
:position_y,
|
||||
:inserted_at,
|
||||
:updated_at
|
||||
])
|
||||
end
|
||||
|
||||
defp character_to_json(ch) do
|
||||
Map.take(ch, [
|
||||
:id,
|
||||
:eve_id,
|
||||
:name,
|
||||
:corporation_id,
|
||||
:corporation_name,
|
||||
:corporation_ticker,
|
||||
:alliance_id,
|
||||
:alliance_name,
|
||||
:alliance_ticker,
|
||||
:inserted_at,
|
||||
:updated_at
|
||||
])
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user