fix: removed error from characters endpoint, and updated routes (#372)

This commit is contained in:
guarzo
2025-05-12 03:15:20 -04:00
committed by GitHub
parent 91d1ca201c
commit 4ba4119c2b
3 changed files with 213 additions and 88 deletions

View File

@@ -5,6 +5,16 @@ defmodule WandererApp.Api.MapCharacterSettings do
domain: WandererApp.Api,
data_layer: AshPostgres.DataLayer
@derive {Jason.Encoder, only: [
:id,
:map_id,
:character_id,
:tracked,
:followed,
:inserted_at,
:updated_at
]}
postgres do
repo(WandererApp.Repo)
table("map_character_settings_v1")

View File

@@ -185,9 +185,48 @@ defmodule WandererAppWeb.MapAPIController do
end
end
defp normalize_map_identifier(params) do
case Map.get(params, "map_identifier") do
nil -> params
id ->
if Ecto.UUID.cast(id) == :error,
do: Map.put(params, "slug", id),
else: Map.put(params, "map_id", id)
end
end
defp find_tracked_characters_by_map(map_id) do
case WandererApp.Api.MapCharacterSettings.tracked_by_map_all(%{map_id: map_id}) do
{:ok, settings} -> {:ok, settings}
# Create a query to select tracked characters for the map and preload the character relationship
query =
WandererApp.Api.MapCharacterSettings
|> Ash.Query.filter(map_id == ^map_id and tracked == true)
|> Ash.Query.load(:character)
case WandererApp.Api.read(query) do
{:ok, settings} ->
# Format the settings to include character data
formatted_settings = Enum.map(settings, fn setting ->
character_data =
if Ash.Resource.loaded?(setting, :character) do
WandererAppWeb.MapEventHandler.map_ui_character_stat(setting.character)
else
nil
end
# Extract only the fields we need for JSON serialization
%{
id: setting.id,
map_id: setting.map_id,
character_id: setting.character_id,
tracked: setting.tracked,
followed: setting.followed,
inserted_at: setting.inserted_at,
updated_at: setting.updated_at,
character: character_data
}
end)
{:ok, formatted_settings}
{:error, error} -> {:error, "Could not fetch tracked characters: #{inspect(error)}"}
end
end
@@ -208,22 +247,27 @@ defmodule WandererAppWeb.MapAPIController do
description: "Map slug",
type: :string,
example: "my-map",
required: true
required: false
],
map_id: [
in: :query,
description: "Map identifier (UUID)",
type: :string,
required: false
]
],
responses: [
ok: ResponseSchemas.ok(@tracked_characters_response_schema, "Tracked characters"),
bad_request: ResponseSchemas.bad_request(),
bad_request: ResponseSchemas.bad_request("Must provide either ?map_id=UUID or ?slug=SLUG as a query parameter"),
internal_server_error: ResponseSchemas.internal_server_error()
]
def list_tracked_characters(conn, params) do
with {:ok, slug} <- APIUtils.require_param(params, "slug"),
{:ok, map_id} <- get_map_id_by_slug(slug) do
with {:ok, map_id} <- APIUtils.fetch_map_id(params) do
# Find tracked characters for this map
case find_tracked_characters_by_map(map_id) do
{:ok, settings} ->
# Return the list of tracked characters
json(conn, %{data: settings})
{:ok, formatted_settings} ->
# Return the formatted tracked characters
json(conn, %{data: formatted_settings})
{:error, reason} ->
Logger.error("Error listing tracked characters: #{APIUtils.format_error(reason)}")
@@ -239,6 +283,41 @@ defmodule WandererAppWeb.MapAPIController do
end
end
@doc """
GET /api/maps/{map_identifier}/tracked-characters
"""
operation :show_tracked_characters,
summary: "Show Tracked Characters for a Map",
description: "Lists all characters that are tracked on a specified map.",
parameters: [
map_identifier: [
in: :path,
description: "Map identifier (UUID or slug). Provide either a UUID or a slug.",
type: :string,
required: true,
example: "map-slug or map UUID"
]
],
responses: [
ok: ResponseSchemas.ok(@tracked_characters_response_schema, "Tracked characters"),
bad_request: ResponseSchemas.bad_request("Map identifier is required"),
internal_server_error: ResponseSchemas.internal_server_error()
]
def show_tracked_characters(%{assigns: %{map_id: map_id}} = conn, _params) do
# Find tracked characters for this map
case find_tracked_characters_by_map(map_id) do
{:ok, formatted_settings} ->
# Return the formatted tracked characters
json(conn, %{data: formatted_settings})
{:error, reason} ->
Logger.error("Error listing tracked characters: #{APIUtils.format_error(reason)}")
conn
|> put_status(:internal_server_error)
|> json(%{error: APIUtils.format_error(reason)})
end
end
@doc """
GET /api/map/structure_timers
@@ -487,51 +566,7 @@ defmodule WandererAppWeb.MapAPIController do
]
def user_characters(conn, params) do
with {:ok, map_id} <- APIUtils.fetch_map_id(params) do
case MapCharacterSettingsRepo.get_all_by_map(map_id) do
{:ok, map_character_settings} when map_character_settings != [] ->
character_ids = Enum.map(map_character_settings, &(&1.character_id))
case WandererApp.Api.read(Character |> filter(id in ^character_ids)) do
{:ok, characters} when characters != [] ->
characters_by_user =
characters
|> Enum.filter(fn char -> not is_nil(char.user_id) end)
|> Enum.group_by(&(&1.user_id))
settings_query =
WandererApp.Api.MapUserSettings
|> Ash.Query.new()
|> Ash.Query.filter(map_id == ^map_id)
main_characters_by_user =
case WandererApp.Api.read(settings_query) do
{:ok, map_user_settings} ->
Map.new(map_user_settings, fn settings -> {settings.user_id, settings.main_character_eve_id} end)
_ -> %{}
end
character_groups =
Enum.map(characters_by_user, fn {user_id, user_characters} ->
%{
characters: Enum.map(user_characters, &character_to_json/1),
main_character_eve_id: Map.get(main_characters_by_user, user_id)
}
end)
json(conn, %{data: character_groups})
{:ok, []} -> json(conn, %{data: []})
{:error, reason} ->
conn
|> put_status(:internal_server_error)
|> json(%{error: "Failed to fetch characters: #{inspect(reason)}"})
end
{:ok, []} -> json(conn, %{data: []})
{:error, reason} ->
conn
|> put_status(:internal_server_error)
|> json(%{error: "Failed to fetch map character settings: #{inspect(reason)}"})
end
fetch_and_format_user_characters(conn, map_id)
else
{:error, msg} when is_binary(msg) ->
conn
@@ -546,50 +581,80 @@ defmodule WandererAppWeb.MapAPIController do
end
@doc """
GET /api/map/connections
Requires either `?map_id=<UUID>` **OR** `?slug=<map-slug>` in the query params.
GET /api/maps/{map_identifier}/user-characters
"""
@spec list_connections(Plug.Conn.t(), map()) :: Plug.Conn.t()
operation :list_connections,
summary: "List Map Connections",
description: "Lists all connections for a map. Requires either 'map_id' or 'slug' as a query parameter to identify the map.",
@spec show_user_characters(Plug.Conn.t(), map()) :: Plug.Conn.t()
operation :show_user_characters,
summary: "Show User Characters for a Map",
description: "Returns characters grouped by user for a specific map.",
parameters: [
map_id: [
in: :query,
description: "Map identifier (UUID) - Either map_id or slug must be provided",
map_identifier: [
in: :path,
description: "Map identifier (UUID or slug). Provide either a UUID or a slug.",
type: :string,
required: false,
example: ""
],
slug: [
in: :query,
description: "Map slug - Either map_id or slug must be provided",
type: :string,
required: false,
example: "map-name"
required: true,
example: "map-slug or map UUID"
]
],
responses: [
ok: ResponseSchemas.ok(@map_connections_response_schema, "List of map connections"),
bad_request: ResponseSchemas.bad_request("Must provide either ?map_id=UUID or ?slug=SLUG"),
not_found: ResponseSchemas.not_found("Could not fetch connections")
ok: ResponseSchemas.ok(@user_characters_response_schema, "User characters with main character indication"),
internal_server_error: ResponseSchemas.internal_server_error()
]
def list_connections(conn, params) do
with {:ok, map_id} <- APIUtils.fetch_map_id(params),
{:ok, connections} <- MapConnectionRepo.get_by_map(map_id) do
data = Enum.map(connections, &APIUtils.connection_to_json/1)
json(conn, %{data: data})
else
{:error, msg} when is_binary(msg) ->
conn
|> put_status(:bad_request)
|> json(%{error: msg})
def show_user_characters(%{assigns: %{map_id: map_id}} = conn, _params) do
fetch_and_format_user_characters(conn, map_id)
end
# Helper function to fetch and format user characters for a map
defp fetch_and_format_user_characters(conn, map_id) do
# Create a query to get all MapCharacterSettings for this map and preload characters
settings_query =
WandererApp.Api.MapCharacterSettings
|> Ash.Query.filter(map_id == ^map_id)
|> Ash.Query.load(:character)
case WandererApp.Api.read(settings_query) do
{:ok, map_character_settings} when map_character_settings != [] ->
# Extract characters and filter out those without a user_id
characters =
map_character_settings
|> Enum.map(& &1.character)
|> Enum.filter(fn char -> char != nil && not is_nil(char.user_id) end)
if characters != [] do
# Group characters by user_id
characters_by_user = Enum.group_by(characters, & &1.user_id)
# Get main character settings
user_settings_query =
WandererApp.Api.MapUserSettings
|> Ash.Query.new()
|> Ash.Query.filter(map_id == ^map_id)
main_characters_by_user =
case WandererApp.Api.read(user_settings_query) do
{:ok, map_user_settings} ->
Map.new(map_user_settings, fn settings -> {settings.user_id, settings.main_character_eve_id} end)
_ -> %{}
end
# Format the characters by user
character_groups =
Enum.map(characters_by_user, fn {user_id, user_characters} ->
%{
characters: Enum.map(user_characters, &character_to_json/1),
main_character_eve_id: Map.get(main_characters_by_user, user_id)
}
end)
json(conn, %{data: character_groups})
else
json(conn, %{data: []})
end
{:ok, []} -> json(conn, %{data: []})
{:error, reason} ->
conn
|> put_status(:not_found)
|> json(%{error: "Could not fetch connections: #{APIUtils.format_error(reason)}"})
|> put_status(:internal_server_error)
|> json(%{error: "Failed to fetch map character settings: #{inspect(reason)}"})
end
end
@@ -701,4 +766,52 @@ defmodule WandererAppWeb.MapAPIController do
defp character_to_json(ch) do
WandererAppWeb.MapEventHandler.map_ui_character_stat(ch)
end
@doc """
GET /api/map/connections
Requires either `?map_id=<UUID>` **OR** `?slug=<map-slug>` in the query params.
"""
@spec list_connections(Plug.Conn.t(), map()) :: Plug.Conn.t()
operation :list_connections,
summary: "List Map Connections",
description: "Lists all connections for a map. Requires either 'map_id' or 'slug' as a query parameter to identify the map.",
parameters: [
map_id: [
in: :query,
description: "Map identifier (UUID) - Either map_id or slug must be provided",
type: :string,
required: false,
example: ""
],
slug: [
in: :query,
description: "Map slug - Either map_id or slug must be provided",
type: :string,
required: false,
example: "map-name"
]
],
responses: [
ok: ResponseSchemas.ok(@map_connections_response_schema, "List of map connections"),
bad_request: ResponseSchemas.bad_request("Must provide either ?map_id=UUID or ?slug=SLUG"),
not_found: ResponseSchemas.not_found("Could not fetch connections")
]
def list_connections(conn, params) do
with {:ok, map_id} <- APIUtils.fetch_map_id(params),
{:ok, connections} <- MapConnectionRepo.get_by_map(map_id) do
data = Enum.map(connections, &APIUtils.connection_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 connections: #{APIUtils.format_error(reason)}"})
end
end
end

View File

@@ -234,6 +234,8 @@ defmodule WandererAppWeb.Router do
resources "/structures", MapSystemStructureAPIController, except: [:new, :edit]
get "/structure-timers", MapSystemStructureAPIController, :structure_timers
resources "/signatures", MapSystemSignatureAPIController, except: [:new, :edit]
get "/user-characters", MapAPIController, :show_user_characters
get "/tracked-characters", MapAPIController, :show_tracked_characters
end