mirror of
https://github.com/wanderer-industries/wanderer
synced 2026-08-25 07:16:31 +00:00
fmt
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
defmodule WandererApp.Api.MapWebhookSubscription do
|
||||
@moduledoc """
|
||||
Ash resource for managing webhook subscriptions for map events.
|
||||
|
||||
|
||||
Stores webhook endpoint configurations that receive HTTP POST notifications
|
||||
when events occur on a specific map.
|
||||
"""
|
||||
@@ -18,7 +18,7 @@ defmodule WandererApp.Api.MapWebhookSubscription do
|
||||
|
||||
cloak do
|
||||
vault(WandererApp.Vault)
|
||||
|
||||
|
||||
attributes([:secret])
|
||||
end
|
||||
|
||||
@@ -66,44 +66,44 @@ defmodule WandererApp.Api.MapWebhookSubscription do
|
||||
:events,
|
||||
:active?
|
||||
]
|
||||
|
||||
|
||||
# Validate webhook URL format
|
||||
change fn changeset, _context ->
|
||||
case Ash.Changeset.get_attribute(changeset, :url) do
|
||||
nil ->
|
||||
nil ->
|
||||
changeset
|
||||
|
||||
|
||||
url ->
|
||||
case validate_webhook_url_format(url) do
|
||||
:ok ->
|
||||
:ok ->
|
||||
changeset
|
||||
|
||||
|
||||
{:error, message} ->
|
||||
Ash.Changeset.add_error(changeset, field: :url, message: message)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Validate events list
|
||||
change fn changeset, _context ->
|
||||
case Ash.Changeset.get_attribute(changeset, :events) do
|
||||
nil ->
|
||||
nil ->
|
||||
changeset
|
||||
|
||||
|
||||
events when is_list(events) ->
|
||||
case validate_events_list(events) do
|
||||
:ok ->
|
||||
:ok ->
|
||||
changeset
|
||||
|
||||
|
||||
{:error, message} ->
|
||||
Ash.Changeset.add_error(changeset, field: :events, message: message)
|
||||
end
|
||||
|
||||
|
||||
_ ->
|
||||
changeset
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Generate secret on creation
|
||||
change fn changeset, _context ->
|
||||
secret = generate_webhook_secret()
|
||||
@@ -114,7 +114,7 @@ defmodule WandererApp.Api.MapWebhookSubscription do
|
||||
update :rotate_secret do
|
||||
accept []
|
||||
require_atomic? false
|
||||
|
||||
|
||||
change fn changeset, _context ->
|
||||
new_secret = generate_webhook_secret()
|
||||
Ash.Changeset.change_attribute(changeset, :secret, new_secret)
|
||||
@@ -122,7 +122,6 @@ defmodule WandererApp.Api.MapWebhookSubscription do
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
validations do
|
||||
validate present(:url), message: "URL is required"
|
||||
validate present(:events), message: "Events array is required"
|
||||
@@ -138,22 +137,25 @@ defmodule WandererApp.Api.MapWebhookSubscription do
|
||||
|
||||
attribute :url, :string do
|
||||
allow_nil? false
|
||||
constraints max_length: 2000 # 2KB limit as per security requirements
|
||||
# 2KB limit as per security requirements
|
||||
constraints max_length: 2000
|
||||
end
|
||||
|
||||
attribute :events, {:array, :string} do
|
||||
allow_nil? false
|
||||
default []
|
||||
constraints [
|
||||
min_length: 1,
|
||||
max_length: 50, # Reasonable limit on number of event types
|
||||
items: [max_length: 100] # Max length per event type
|
||||
]
|
||||
|
||||
constraints min_length: 1,
|
||||
# Reasonable limit on number of event types
|
||||
max_length: 50,
|
||||
# Max length per event type
|
||||
items: [max_length: 100]
|
||||
end
|
||||
|
||||
attribute :secret, :string do
|
||||
allow_nil? false
|
||||
sensitive? true # Hide in logs and API responses
|
||||
# Hide in logs and API responses
|
||||
sensitive? true
|
||||
end
|
||||
|
||||
attribute :active?, :boolean do
|
||||
@@ -205,23 +207,24 @@ defmodule WandererApp.Api.MapWebhookSubscription do
|
||||
|
||||
defp validate_webhook_url_format(url) do
|
||||
uri = URI.parse(url)
|
||||
|
||||
|
||||
cond do
|
||||
uri.scheme != "https" ->
|
||||
{:error, "Webhook URL must use HTTPS"}
|
||||
|
||||
|
||||
uri.host == nil ->
|
||||
{:error, "Webhook URL must have a valid host"}
|
||||
|
||||
|
||||
uri.host in ["localhost", "127.0.0.1", "0.0.0.0"] ->
|
||||
{:error, "Webhook URL cannot use localhost or loopback addresses"}
|
||||
|
||||
String.starts_with?(uri.host, "192.168.") or String.starts_with?(uri.host, "10.") or is_private_ip_172_range?(uri.host) ->
|
||||
|
||||
String.starts_with?(uri.host, "192.168.") or String.starts_with?(uri.host, "10.") or
|
||||
is_private_ip_172_range?(uri.host) ->
|
||||
{:error, "Webhook URL cannot use private network addresses"}
|
||||
|
||||
|
||||
byte_size(url) > 2000 ->
|
||||
{:error, "Webhook URL cannot exceed 2000 characters"}
|
||||
|
||||
|
||||
true ->
|
||||
:ok
|
||||
end
|
||||
@@ -229,30 +232,32 @@ defmodule WandererApp.Api.MapWebhookSubscription do
|
||||
|
||||
defp validate_events_list(events) do
|
||||
alias WandererApp.ExternalEvents.Event
|
||||
|
||||
|
||||
# Get valid event types as strings
|
||||
valid_event_strings = Event.supported_event_types()
|
||||
valid_event_strings =
|
||||
Event.supported_event_types()
|
||||
|> Enum.map(&Atom.to_string/1)
|
||||
|
||||
|
||||
# Add wildcard as valid option
|
||||
valid_events = ["*" | valid_event_strings]
|
||||
|
||||
invalid_events = Enum.reject(events, fn event -> event in valid_events end)
|
||||
|
||||
|
||||
if Enum.empty?(invalid_events) do
|
||||
:ok
|
||||
else
|
||||
{:error, "Invalid event types: #{Enum.join(invalid_events, ", ")}"}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Check if IP is in the 172.16.0.0/12 range (172.16.0.0 to 172.31.255.255)
|
||||
defp is_private_ip_172_range?(host) do
|
||||
case :inet.parse_address(String.to_charlist(host)) do
|
||||
{:ok, {172, b, _, _}} when b >= 16 and b <= 31 ->
|
||||
true
|
||||
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -228,8 +228,12 @@ defmodule WandererApp.Kills.Client do
|
||||
end
|
||||
|
||||
:needs_reconnect_with_timestamp ->
|
||||
Logger.warning("[Client] Health check triggering reconnect (retry count: #{state.retry_count})")
|
||||
Logger.warning(
|
||||
"[Client] Health check triggering reconnect (retry count: #{state.retry_count})"
|
||||
)
|
||||
|
||||
new_state = %{state | last_health_reconnect_attempt: System.system_time(:millisecond)}
|
||||
|
||||
if state.connected or state.connecting do
|
||||
send(self(), {:disconnected, :health_check_failed})
|
||||
%{new_state | connected: false, connecting: false, socket_pid: nil}
|
||||
@@ -242,6 +246,7 @@ defmodule WandererApp.Kills.Client do
|
||||
:needs_reconnect_reset_retries ->
|
||||
Logger.warning("[Client] Health check resetting retry count and triggering reconnect")
|
||||
new_state = %{state | retry_count: 0, last_retry_cycle_end: nil}
|
||||
|
||||
if state.connected or state.connecting do
|
||||
send(self(), {:disconnected, :health_check_failed})
|
||||
%{new_state | connected: false, connecting: false, socket_pid: nil}
|
||||
@@ -446,6 +451,7 @@ defmodule WandererApp.Kills.Client do
|
||||
defp should_retry?(_), do: true
|
||||
|
||||
defp should_start_new_retry_cycle?(%{last_retry_cycle_end: nil}), do: true
|
||||
|
||||
defp should_start_new_retry_cycle?(%{last_retry_cycle_end: end_time}) do
|
||||
System.system_time(:millisecond) - end_time >= @message_timeout
|
||||
end
|
||||
@@ -453,8 +459,9 @@ defmodule WandererApp.Kills.Client do
|
||||
# Prevent health check from triggering reconnects too frequently
|
||||
# Allow health check reconnects only every 2 minutes to avoid spam
|
||||
@health_check_reconnect_cooldown :timer.minutes(2)
|
||||
|
||||
|
||||
defp should_health_check_reconnect?(%{last_health_reconnect_attempt: nil}), do: true
|
||||
|
||||
defp should_health_check_reconnect?(%{last_health_reconnect_attempt: last_attempt}) do
|
||||
System.system_time(:millisecond) - last_attempt >= @health_check_reconnect_cooldown
|
||||
end
|
||||
@@ -465,14 +472,15 @@ defmodule WandererApp.Kills.Client do
|
||||
|
||||
# Increment retry count first
|
||||
new_retry_count = state.retry_count + 1
|
||||
|
||||
|
||||
# If we've hit max retries, mark the end of this retry cycle
|
||||
state = if new_retry_count >= @max_retries do
|
||||
%{state | last_retry_cycle_end: System.system_time(:millisecond)}
|
||||
else
|
||||
state
|
||||
end
|
||||
|
||||
state =
|
||||
if new_retry_count >= @max_retries do
|
||||
%{state | last_retry_cycle_end: System.system_time(:millisecond)}
|
||||
else
|
||||
state
|
||||
end
|
||||
|
||||
delay = Enum.at(@retry_delays, min(state.retry_count, length(@retry_delays) - 1))
|
||||
|
||||
timer_ref = Process.send_after(self(), :retry_connection, delay)
|
||||
@@ -509,7 +517,8 @@ defmodule WandererApp.Kills.Client do
|
||||
if should_health_check_reconnect?(state) do
|
||||
:needs_reconnect_with_timestamp
|
||||
else
|
||||
:healthy # Recent health check reconnect attempt
|
||||
# Recent health check reconnect attempt
|
||||
:healthy
|
||||
end
|
||||
else
|
||||
# Max retries reached, check if 15 minutes have passed since last retry cycle
|
||||
@@ -517,7 +526,8 @@ defmodule WandererApp.Kills.Client do
|
||||
Logger.info("[Client] 15 minutes elapsed since max retries, starting new retry cycle")
|
||||
:needs_reconnect_reset_retries
|
||||
else
|
||||
:healthy # Still within 15-minute cooldown period
|
||||
# Still within 15-minute cooldown period
|
||||
:healthy
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -533,17 +543,21 @@ defmodule WandererApp.Kills.Client do
|
||||
end
|
||||
end
|
||||
|
||||
defp check_health(%{socket_pid: pid, last_message_time: last_msg_time} = state) when not is_nil(pid) and not is_nil(last_msg_time) do
|
||||
defp check_health(%{socket_pid: pid, last_message_time: last_msg_time} = state)
|
||||
when not is_nil(pid) and not is_nil(last_msg_time) do
|
||||
cond do
|
||||
not socket_alive?(pid) ->
|
||||
Logger.warning("[Client] Health check: Socket process #{inspect(pid)} is dead")
|
||||
:needs_reconnect
|
||||
|
||||
|
||||
# Check if we haven't received a message in the configured timeout
|
||||
System.system_time(:millisecond) - last_msg_time > @message_timeout ->
|
||||
Logger.warning("[Client] Health check: No messages received for 15+ minutes, reconnecting")
|
||||
Logger.warning(
|
||||
"[Client] Health check: No messages received for 15+ minutes, reconnecting"
|
||||
)
|
||||
|
||||
:needs_reconnect
|
||||
|
||||
|
||||
true ->
|
||||
:healthy
|
||||
end
|
||||
@@ -663,7 +677,7 @@ defmodule WandererApp.Kills.Client do
|
||||
{"killmails:lobby", "killmail_update"} ->
|
||||
# Notify parent that we received a message
|
||||
send(state.parent, {:message_received, :killmail_update})
|
||||
|
||||
|
||||
# Use supervised task to handle failures gracefully
|
||||
Task.Supervisor.start_child(
|
||||
WandererApp.Kills.TaskSupervisor,
|
||||
@@ -673,7 +687,7 @@ defmodule WandererApp.Kills.Client do
|
||||
{"killmails:lobby", "kill_count_update"} ->
|
||||
# Notify parent that we received a message
|
||||
send(state.parent, {:message_received, :kill_count_update})
|
||||
|
||||
|
||||
# Use supervised task to handle failures gracefully
|
||||
Task.Supervisor.start_child(
|
||||
WandererApp.Kills.TaskSupervisor,
|
||||
|
||||
@@ -179,7 +179,10 @@ defmodule WandererApp.Kills.Subscription.MapIntegration do
|
||||
WandererApp.ExternalEvents.broadcast(map_id, :map_kill, kill_data)
|
||||
rescue
|
||||
error ->
|
||||
Logger.error("Failed to broadcast external event for map #{map_id}: #{inspect(error)}")
|
||||
Logger.error(
|
||||
"Failed to broadcast external event for map #{map_id}: #{inspect(error)}"
|
||||
)
|
||||
|
||||
# Continue processing other maps even if one fails
|
||||
end
|
||||
end)
|
||||
|
||||
@@ -140,7 +140,8 @@ defmodule WandererApp.Map.ZkbDataFetcher do
|
||||
:ok
|
||||
else
|
||||
# Build new details for each changed system
|
||||
updated_details_map = build_updated_details_map(changed_systems, old_details_map, new_ids_map)
|
||||
updated_details_map =
|
||||
build_updated_details_map(changed_systems, old_details_map, new_ids_map)
|
||||
|
||||
# Update the ID map cache
|
||||
updated_ids_map = build_updated_ids_map(changed_systems, old_ids_map, new_ids_map)
|
||||
@@ -206,7 +207,10 @@ defmodule WandererApp.Map.ZkbDataFetcher do
|
||||
defp maybe_initialize_empty_details_map(%{}, systems, cache_key_details) do
|
||||
# First time initialization - create empty structure
|
||||
initial_map = Enum.into(systems, %{}, fn {system_id, _} -> {system_id, []} end)
|
||||
WandererApp.Cache.insert(cache_key_details, initial_map, ttl: :timer.hours(@killmail_ttl_hours))
|
||||
|
||||
WandererApp.Cache.insert(cache_key_details, initial_map,
|
||||
ttl: :timer.hours(@killmail_ttl_hours)
|
||||
)
|
||||
end
|
||||
|
||||
defp maybe_initialize_empty_details_map(_old_details_map, _systems, _cache_key_details), do: :ok
|
||||
|
||||
@@ -16,7 +16,7 @@ defmodule WandererApp.Map.Server.CharactersImpl do
|
||||
}),
|
||||
{:ok, character} <- WandererApp.Character.get_character(character_id) do
|
||||
Impl.broadcast!(map_id, :character_added, character)
|
||||
|
||||
|
||||
# ADDITIVE: Also broadcast to external event system (webhooks/WebSocket)
|
||||
WandererApp.ExternalEvents.broadcast(map_id, :character_added, character)
|
||||
:telemetry.execute([:wanderer_app, :map, :character, :added], %{count: 1})
|
||||
@@ -28,7 +28,7 @@ defmodule WandererApp.Map.Server.CharactersImpl do
|
||||
_error ->
|
||||
{:ok, character} = WandererApp.Character.get_character(character_id)
|
||||
Impl.broadcast!(map_id, :character_added, character)
|
||||
|
||||
|
||||
# ADDITIVE: Also broadcast to external event system (webhooks/WebSocket)
|
||||
WandererApp.ExternalEvents.broadcast(map_id, :character_added, character)
|
||||
:ok
|
||||
@@ -43,7 +43,7 @@ defmodule WandererApp.Map.Server.CharactersImpl do
|
||||
with :ok <- WandererApp.Map.remove_character(map_id, character_id),
|
||||
{:ok, character} <- WandererApp.Character.get_map_character(map_id, character_id) do
|
||||
Impl.broadcast!(map_id, :character_removed, character)
|
||||
|
||||
|
||||
# ADDITIVE: Also broadcast to external event system (webhooks/WebSocket)
|
||||
WandererApp.ExternalEvents.broadcast(map_id, :character_removed, character)
|
||||
|
||||
@@ -309,7 +309,7 @@ defmodule WandererApp.Map.Server.CharactersImpl do
|
||||
defp update_character(map_id, character_id) do
|
||||
{:ok, character} = WandererApp.Character.get_map_character(map_id, character_id)
|
||||
Impl.broadcast!(map_id, :character_updated, character)
|
||||
|
||||
|
||||
# ADDITIVE: Also broadcast to external event system (webhooks/WebSocket)
|
||||
WandererApp.ExternalEvents.broadcast(map_id, :character_updated, character)
|
||||
end
|
||||
|
||||
@@ -389,7 +389,7 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
|
||||
})
|
||||
|
||||
Impl.broadcast!(map_id, :add_connection, connection)
|
||||
|
||||
|
||||
# ADDITIVE: Also broadcast to external event system (webhooks/WebSocket)
|
||||
WandererApp.ExternalEvents.broadcast(map_id, :connection_added, %{
|
||||
connection_id: connection.id,
|
||||
@@ -572,7 +572,7 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
|
||||
|
||||
Impl.broadcast!(map_id, :remove_connections, [connection])
|
||||
map_id |> WandererApp.Map.remove_connection(connection)
|
||||
|
||||
|
||||
# ADDITIVE: Also broadcast to external event system (webhooks/WebSocket)
|
||||
WandererApp.ExternalEvents.broadcast(map_id, :connection_removed, %{
|
||||
connection_id: connection.id,
|
||||
@@ -621,7 +621,7 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
|
||||
end
|
||||
|
||||
Impl.broadcast!(map_id, :update_connection, updated_connection)
|
||||
|
||||
|
||||
# ADDITIVE: Also broadcast to external event system (webhooks/WebSocket)
|
||||
WandererApp.ExternalEvents.broadcast(map_id, :connection_updated, %{
|
||||
connection_id: updated_connection.id,
|
||||
|
||||
@@ -155,7 +155,7 @@ defmodule WandererApp.Map.Server.SignaturesImpl do
|
||||
|
||||
# 5. Broadcast to any live subscribers
|
||||
Impl.broadcast!(state.map_id, :signatures_updated, system.solar_system_id)
|
||||
|
||||
|
||||
# ADDITIVE: Also broadcast to external event system (webhooks/WebSocket)
|
||||
# Send individual signature events
|
||||
Enum.each(added_sigs, fn sig ->
|
||||
@@ -168,14 +168,14 @@ defmodule WandererApp.Map.Server.SignaturesImpl do
|
||||
type: sig.type
|
||||
})
|
||||
end)
|
||||
|
||||
|
||||
Enum.each(removed_ids, fn sig_eve_id ->
|
||||
WandererApp.ExternalEvents.broadcast(state.map_id, :signature_removed, %{
|
||||
solar_system_id: system.solar_system_id,
|
||||
signature_id: sig_eve_id
|
||||
})
|
||||
end)
|
||||
|
||||
|
||||
# Also send the summary event for backwards compatibility
|
||||
WandererApp.ExternalEvents.broadcast(state.map_id, :signatures_updated, %{
|
||||
solar_system_id: system.solar_system_id,
|
||||
|
||||
@@ -278,16 +278,21 @@ defmodule WandererApp.Map.Server.SystemsImpl do
|
||||
:ok = WandererApp.Map.remove_system(map_id, solar_system_id)
|
||||
@ddrt.delete([solar_system_id], rtree_name)
|
||||
Impl.broadcast!(map_id, :systems_removed, [solar_system_id])
|
||||
|
||||
|
||||
# ADDITIVE: Also broadcast to external event system (webhooks/WebSocket)
|
||||
Logger.debug(fn -> "SystemsImpl.delete_systems calling ExternalEvents.broadcast for map #{map_id}, system: #{solar_system_id}" end)
|
||||
Logger.debug(fn ->
|
||||
"SystemsImpl.delete_systems calling ExternalEvents.broadcast for map #{map_id}, system: #{solar_system_id}"
|
||||
end)
|
||||
|
||||
# For consistency, include basic fields even for deleted systems
|
||||
WandererApp.ExternalEvents.broadcast(map_id, :deleted_system, %{
|
||||
solar_system_id: solar_system_id,
|
||||
name: nil, # System is deleted, name not available
|
||||
# System is deleted, name not available
|
||||
name: nil,
|
||||
position_x: nil,
|
||||
position_y: nil
|
||||
})
|
||||
|
||||
track_systems_removed(map_id, user_id, character_id, [solar_system_id])
|
||||
remove_system_connections(map_id, [solar_system_id])
|
||||
|
||||
@@ -435,7 +440,7 @@ defmodule WandererApp.Map.Server.SystemsImpl do
|
||||
WandererApp.Map.add_system(map_id, updated_system)
|
||||
|
||||
Impl.broadcast!(map_id, :add_system, updated_system)
|
||||
|
||||
|
||||
# ADDITIVE: Also broadcast to external event system (webhooks/WebSocket)
|
||||
WandererApp.ExternalEvents.broadcast(map_id, :add_system, %{
|
||||
solar_system_id: updated_system.solar_system_id,
|
||||
@@ -443,6 +448,7 @@ defmodule WandererApp.Map.Server.SystemsImpl do
|
||||
position_x: updated_system.position_x,
|
||||
position_y: updated_system.position_y
|
||||
})
|
||||
|
||||
:ok
|
||||
|
||||
_ ->
|
||||
@@ -472,7 +478,7 @@ defmodule WandererApp.Map.Server.SystemsImpl do
|
||||
|
||||
WandererApp.Map.add_system(map_id, new_system)
|
||||
Impl.broadcast!(map_id, :add_system, new_system)
|
||||
|
||||
|
||||
# ADDITIVE: Also broadcast to external event system (webhooks/WebSocket)
|
||||
WandererApp.ExternalEvents.broadcast(map_id, :add_system, %{
|
||||
solar_system_id: new_system.solar_system_id,
|
||||
@@ -586,9 +592,12 @@ defmodule WandererApp.Map.Server.SystemsImpl do
|
||||
)
|
||||
|
||||
Impl.broadcast!(map_id, :add_system, system)
|
||||
|
||||
|
||||
# ADDITIVE: Also broadcast to external event system (webhooks/WebSocket)
|
||||
Logger.debug(fn -> "SystemsImpl._add_system calling ExternalEvents.broadcast for map #{map_id}, system: #{solar_system_id}" end)
|
||||
Logger.debug(fn ->
|
||||
"SystemsImpl._add_system calling ExternalEvents.broadcast for map #{map_id}, system: #{solar_system_id}"
|
||||
end)
|
||||
|
||||
WandererApp.ExternalEvents.broadcast(map_id, :add_system, %{
|
||||
solar_system_id: system.solar_system_id,
|
||||
name: system.name,
|
||||
@@ -659,7 +668,7 @@ defmodule WandererApp.Map.Server.SystemsImpl do
|
||||
)
|
||||
|
||||
Impl.broadcast!(map_id, :update_system, updated_system)
|
||||
|
||||
|
||||
# ADDITIVE: Also broadcast to external event system (webhooks/WebSocket)
|
||||
WandererApp.ExternalEvents.broadcast(map_id, :system_metadata_changed, %{
|
||||
solar_system_id: updated_system.solar_system_id,
|
||||
|
||||
@@ -53,19 +53,17 @@ defmodule WandererAppWeb.MapEventsAPIController do
|
||||
map_id: "550e8400-e29b-41d4-a716-446655440000",
|
||||
type: "add_system",
|
||||
payload: %{
|
||||
solar_system_id: 30000142,
|
||||
solar_system_id: 30_000_142,
|
||||
solar_system_name: "Jita"
|
||||
},
|
||||
ts: "2025-01-20T12:34:56Z"
|
||||
}
|
||||
}
|
||||
|
||||
@events_response_schema ApiSchemas.data_wrapper(
|
||||
%OpenApiSpex.Schema{
|
||||
type: :array,
|
||||
items: @event_schema
|
||||
}
|
||||
)
|
||||
@events_response_schema ApiSchemas.data_wrapper(%OpenApiSpex.Schema{
|
||||
type: :array,
|
||||
items: @event_schema
|
||||
})
|
||||
|
||||
@events_list_params %OpenApiSpex.Schema{
|
||||
type: :object,
|
||||
@@ -89,7 +87,7 @@ defmodule WandererAppWeb.MapEventsAPIController do
|
||||
# OpenApiSpex Operations
|
||||
# -----------------------------------------------------------------
|
||||
|
||||
operation :list_events,
|
||||
operation(:list_events,
|
||||
summary: "List recent events for a map",
|
||||
description: """
|
||||
Retrieves recent events for the specified map. This endpoint provides a way to catch up on missed events
|
||||
@@ -124,6 +122,7 @@ defmodule WandererAppWeb.MapEventsAPIController do
|
||||
404 => ResponseSchemas.not_found("Map not found"),
|
||||
500 => ResponseSchemas.internal_server_error("Internal server error")
|
||||
}
|
||||
)
|
||||
|
||||
# -----------------------------------------------------------------
|
||||
# Controller Actions
|
||||
@@ -133,47 +132,47 @@ defmodule WandererAppWeb.MapEventsAPIController do
|
||||
with {:ok, map} <- get_map(conn, map_identifier),
|
||||
{:ok, since} <- parse_since_param(params),
|
||||
{:ok, limit} <- parse_limit_param(params) do
|
||||
|
||||
# If no 'since' parameter provided, default to 10 minutes ago
|
||||
since_datetime = since || DateTime.add(DateTime.utc_now(), -10, :minute)
|
||||
|
||||
|
||||
# Check if MapEventRelay is running before calling
|
||||
events = if Process.whereis(MapEventRelay) do
|
||||
try do
|
||||
MapEventRelay.get_events_since(map.id, since_datetime, limit)
|
||||
catch
|
||||
:exit, {:noproc, _} ->
|
||||
Logger.error("MapEventRelay process not available")
|
||||
[]
|
||||
|
||||
:exit, reason ->
|
||||
Logger.error("Failed to get events from MapEventRelay: #{inspect(reason)}")
|
||||
[]
|
||||
events =
|
||||
if Process.whereis(MapEventRelay) do
|
||||
try do
|
||||
MapEventRelay.get_events_since(map.id, since_datetime, limit)
|
||||
catch
|
||||
:exit, {:noproc, _} ->
|
||||
Logger.error("MapEventRelay process not available")
|
||||
[]
|
||||
|
||||
:exit, reason ->
|
||||
Logger.error("Failed to get events from MapEventRelay: #{inspect(reason)}")
|
||||
[]
|
||||
end
|
||||
else
|
||||
Logger.error("MapEventRelay is not running")
|
||||
[]
|
||||
end
|
||||
else
|
||||
Logger.error("MapEventRelay is not running")
|
||||
[]
|
||||
end
|
||||
|
||||
|
||||
# Events are already in JSON format from ETS
|
||||
|
||||
|
||||
json(conn, %{data: events})
|
||||
else
|
||||
{:error, :map_not_found} ->
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|> json(%{error: "Map not found"})
|
||||
|
||||
|
||||
{:error, :invalid_since} ->
|
||||
conn
|
||||
|> put_status(:bad_request)
|
||||
|> json(%{error: "Invalid 'since' parameter. Must be ISO8601 datetime."})
|
||||
|
||||
|
||||
{:error, :invalid_limit} ->
|
||||
conn
|
||||
|> put_status(:bad_request)
|
||||
|> json(%{error: "Invalid 'limit' parameter. Must be between 1 and 100."})
|
||||
|
||||
|
||||
{:error, reason} ->
|
||||
conn
|
||||
|> put_status(:internal_server_error)
|
||||
@@ -199,6 +198,7 @@ defmodule WandererAppWeb.MapEventsAPIController do
|
||||
{:error, _} -> {:error, :invalid_since}
|
||||
end
|
||||
end
|
||||
|
||||
defp parse_since_param(_), do: {:ok, nil}
|
||||
|
||||
defp parse_limit_param(%{"limit" => limit_str}) when is_binary(limit_str) do
|
||||
@@ -207,6 +207,7 @@ defmodule WandererAppWeb.MapEventsAPIController do
|
||||
_ -> {:error, :invalid_limit}
|
||||
end
|
||||
end
|
||||
|
||||
defp parse_limit_param(%{"limit" => limit}) when is_integer(limit) do
|
||||
if limit >= 1 and limit <= 100 do
|
||||
{:ok, limit}
|
||||
@@ -214,5 +215,6 @@ defmodule WandererAppWeb.MapEventsAPIController do
|
||||
{:error, :invalid_limit}
|
||||
end
|
||||
end
|
||||
|
||||
defp parse_limit_param(_), do: {:ok, 100}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -17,7 +17,7 @@ defmodule WandererAppWeb.MapWebhooksAPIController do
|
||||
id: %OpenApiSpex.Schema{type: :string, description: "Webhook subscription UUID"},
|
||||
map_id: %OpenApiSpex.Schema{type: :string, description: "Map UUID"},
|
||||
url: %OpenApiSpex.Schema{
|
||||
type: :string,
|
||||
type: :string,
|
||||
description: "HTTPS webhook endpoint URL",
|
||||
example: "https://example.com/webhook"
|
||||
},
|
||||
@@ -29,14 +29,14 @@ defmodule WandererAppWeb.MapWebhooksAPIController do
|
||||
},
|
||||
active: %OpenApiSpex.Schema{type: :boolean, description: "Whether webhook is active"},
|
||||
last_delivery_at: %OpenApiSpex.Schema{
|
||||
type: :string,
|
||||
type: :string,
|
||||
format: :date_time,
|
||||
description: "Last successful delivery timestamp",
|
||||
nullable: true
|
||||
},
|
||||
last_error: %OpenApiSpex.Schema{
|
||||
type: :string,
|
||||
description: "Last error message if delivery failed",
|
||||
description: "Last error message if delivery failed",
|
||||
nullable: true
|
||||
},
|
||||
consecutive_failures: %OpenApiSpex.Schema{
|
||||
@@ -49,7 +49,7 @@ defmodule WandererAppWeb.MapWebhooksAPIController do
|
||||
required: [:id, :map_id, :url, :events, :active, :consecutive_failures],
|
||||
example: %{
|
||||
id: "550e8400-e29b-41d4-a716-446655440000",
|
||||
map_id: "550e8400-e29b-41d4-a716-446655440001",
|
||||
map_id: "550e8400-e29b-41d4-a716-446655440001",
|
||||
url: "https://example.com/wanderer-webhook",
|
||||
events: ["add_system", "map_kill"],
|
||||
active: true,
|
||||
@@ -126,12 +126,10 @@ defmodule WandererAppWeb.MapWebhooksAPIController do
|
||||
}
|
||||
}
|
||||
|
||||
@webhooks_response_schema ApiSchemas.data_wrapper(
|
||||
%OpenApiSpex.Schema{
|
||||
type: :array,
|
||||
items: @webhook_subscription_schema
|
||||
}
|
||||
)
|
||||
@webhooks_response_schema ApiSchemas.data_wrapper(%OpenApiSpex.Schema{
|
||||
type: :array,
|
||||
items: @webhook_subscription_schema
|
||||
})
|
||||
|
||||
@webhook_response_schema ApiSchemas.data_wrapper(@webhook_subscription_schema)
|
||||
@secret_response_schema ApiSchemas.data_wrapper(@webhook_secret_response_schema)
|
||||
@@ -140,7 +138,7 @@ defmodule WandererAppWeb.MapWebhooksAPIController do
|
||||
# OpenApiSpex Operations
|
||||
# -----------------------------------------------------------------
|
||||
|
||||
operation :index,
|
||||
operation(:index,
|
||||
summary: "List webhook subscriptions for a map",
|
||||
description: "Retrieves all webhook subscriptions configured for the specified map.",
|
||||
tags: ["Webhook Management"],
|
||||
@@ -158,8 +156,9 @@ defmodule WandererAppWeb.MapWebhooksAPIController do
|
||||
404 => ResponseSchemas.not_found("Map not found"),
|
||||
500 => ResponseSchemas.internal_server_error("Internal server error")
|
||||
}
|
||||
)
|
||||
|
||||
operation :show,
|
||||
operation(:show,
|
||||
summary: "Get a specific webhook subscription",
|
||||
description: "Retrieves details of a specific webhook subscription.",
|
||||
tags: ["Webhook Management"],
|
||||
@@ -183,8 +182,9 @@ defmodule WandererAppWeb.MapWebhooksAPIController do
|
||||
404 => ResponseSchemas.not_found("Webhook not found"),
|
||||
500 => ResponseSchemas.internal_server_error("Internal server error")
|
||||
}
|
||||
)
|
||||
|
||||
operation :create,
|
||||
operation(:create,
|
||||
summary: "Create a new webhook subscription",
|
||||
description: """
|
||||
Creates a new webhook subscription for the map. The webhook will receive HTTP POST
|
||||
@@ -204,12 +204,13 @@ defmodule WandererAppWeb.MapWebhooksAPIController do
|
||||
responses: %{
|
||||
201 => {"Created", "application/json", @webhook_response_schema},
|
||||
400 => ResponseSchemas.bad_request("Invalid webhook data"),
|
||||
401 => ResponseSchemas.bad_request("Unauthorized"),
|
||||
401 => ResponseSchemas.bad_request("Unauthorized"),
|
||||
409 => ResponseSchemas.bad_request("Webhook URL already exists for this map"),
|
||||
500 => ResponseSchemas.internal_server_error("Internal server error")
|
||||
}
|
||||
)
|
||||
|
||||
operation :update,
|
||||
operation(:update,
|
||||
summary: "Update a webhook subscription",
|
||||
description: "Updates an existing webhook subscription. Partial updates are supported.",
|
||||
tags: ["Webhook Management"],
|
||||
@@ -236,8 +237,9 @@ defmodule WandererAppWeb.MapWebhooksAPIController do
|
||||
409 => ResponseSchemas.bad_request("Webhook URL already exists for this map"),
|
||||
500 => ResponseSchemas.internal_server_error("Internal server error")
|
||||
}
|
||||
)
|
||||
|
||||
operation :delete,
|
||||
operation(:delete,
|
||||
summary: "Delete a webhook subscription",
|
||||
description: "Permanently deletes a webhook subscription.",
|
||||
tags: ["Webhook Management"],
|
||||
@@ -261,8 +263,9 @@ defmodule WandererAppWeb.MapWebhooksAPIController do
|
||||
404 => ResponseSchemas.not_found("Webhook not found"),
|
||||
500 => ResponseSchemas.internal_server_error("Internal server error")
|
||||
}
|
||||
)
|
||||
|
||||
operation :rotate_secret,
|
||||
operation(:rotate_secret,
|
||||
summary: "Rotate webhook secret",
|
||||
description: """
|
||||
Generates a new secret for the webhook subscription. The old secret will be
|
||||
@@ -290,6 +293,7 @@ defmodule WandererAppWeb.MapWebhooksAPIController do
|
||||
404 => ResponseSchemas.not_found("Webhook not found"),
|
||||
500 => ResponseSchemas.internal_server_error("Internal server error")
|
||||
}
|
||||
)
|
||||
|
||||
# -----------------------------------------------------------------
|
||||
# Controller Actions
|
||||
@@ -298,7 +302,7 @@ defmodule WandererAppWeb.MapWebhooksAPIController do
|
||||
def index(conn, %{"map_identifier" => map_identifier}) do
|
||||
with {:ok, map} <- get_map(conn, map_identifier) do
|
||||
webhooks = MapWebhookSubscription.by_map!(map.id)
|
||||
|
||||
|
||||
json_webhooks = Enum.map(webhooks, &webhook_to_json/1)
|
||||
json(conn, %{data: json_webhooks})
|
||||
else
|
||||
@@ -306,9 +310,10 @@ defmodule WandererAppWeb.MapWebhooksAPIController do
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|> json(%{error: "Map not found"})
|
||||
|
||||
|
||||
{:error, reason} ->
|
||||
Logger.error("Failed to list webhooks: #{inspect(reason)}")
|
||||
|
||||
conn
|
||||
|> put_status(:internal_server_error)
|
||||
|> json(%{error: "Internal server error"})
|
||||
@@ -318,21 +323,21 @@ defmodule WandererAppWeb.MapWebhooksAPIController do
|
||||
def show(conn, %{"map_identifier" => map_identifier, "id" => webhook_id}) do
|
||||
with {:ok, map} <- get_map(conn, map_identifier),
|
||||
{:ok, webhook} <- get_webhook(webhook_id, map.id) do
|
||||
|
||||
json(conn, %{data: webhook_to_json(webhook)})
|
||||
else
|
||||
{:error, :map_not_found} ->
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|> json(%{error: "Map not found"})
|
||||
|
||||
|
||||
{:error, :webhook_not_found} ->
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|> json(%{error: "Webhook not found"})
|
||||
|
||||
|
||||
{:error, reason} ->
|
||||
Logger.error("Failed to get webhook: #{inspect(reason)}")
|
||||
|
||||
conn
|
||||
|> put_status(:internal_server_error)
|
||||
|> json(%{error: "Internal server error"})
|
||||
@@ -353,21 +358,22 @@ defmodule WandererAppWeb.MapWebhooksAPIController do
|
||||
defp do_create_webhook(conn, map_identifier, params) do
|
||||
with {:ok, map} <- get_map(conn, map_identifier),
|
||||
{:ok, webhook_params} <- validate_create_params(params, map.id) do
|
||||
|
||||
case MapWebhookSubscription.create(webhook_params) do
|
||||
{:ok, webhook} ->
|
||||
conn
|
||||
|> put_status(:created)
|
||||
|> json(%{data: webhook_to_json(webhook)})
|
||||
|
||||
|
||||
{:error, %Ash.Error.Invalid{errors: errors}} ->
|
||||
error_messages = Enum.map(errors, & &1.message)
|
||||
|
||||
conn
|
||||
|> put_status(:bad_request)
|
||||
|> json(%{error: "Validation failed", details: error_messages})
|
||||
|
||||
|
||||
{:error, reason} ->
|
||||
Logger.error("Failed to create webhook: #{inspect(reason)}")
|
||||
|
||||
conn
|
||||
|> put_status(:internal_server_error)
|
||||
|> json(%{error: "Internal server error"})
|
||||
@@ -377,14 +383,15 @@ defmodule WandererAppWeb.MapWebhooksAPIController do
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|> json(%{error: "Map not found"})
|
||||
|
||||
|
||||
{:error, :invalid_params} ->
|
||||
conn
|
||||
|> put_status(:bad_request)
|
||||
|> json(%{error: "Invalid webhook parameters"})
|
||||
|
||||
|
||||
{:error, reason} ->
|
||||
Logger.error("Failed to create webhook: #{inspect(reason)}")
|
||||
|
||||
conn
|
||||
|> put_status(:internal_server_error)
|
||||
|> json(%{error: "Internal server error"})
|
||||
@@ -395,19 +402,20 @@ defmodule WandererAppWeb.MapWebhooksAPIController do
|
||||
with {:ok, map} <- get_map(conn, map_identifier),
|
||||
{:ok, webhook} <- get_webhook(webhook_id, map.id),
|
||||
{:ok, update_params} <- validate_update_params(params) do
|
||||
|
||||
case MapWebhookSubscription.update(webhook, update_params) do
|
||||
{:ok, updated_webhook} ->
|
||||
json(conn, %{data: webhook_to_json(updated_webhook)})
|
||||
|
||||
|
||||
{:error, %Ash.Error.Invalid{errors: errors}} ->
|
||||
error_messages = Enum.map(errors, & &1.message)
|
||||
|
||||
conn
|
||||
|> put_status(:bad_request)
|
||||
|> json(%{error: "Validation failed", details: error_messages})
|
||||
|
||||
|
||||
{:error, reason} ->
|
||||
Logger.error("Failed to update webhook: #{inspect(reason)}")
|
||||
|
||||
conn
|
||||
|> put_status(:internal_server_error)
|
||||
|> json(%{error: "Internal server error"})
|
||||
@@ -417,19 +425,20 @@ defmodule WandererAppWeb.MapWebhooksAPIController do
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|> json(%{error: "Map not found"})
|
||||
|
||||
|
||||
{:error, :webhook_not_found} ->
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|> json(%{error: "Webhook not found"})
|
||||
|
||||
|
||||
{:error, :invalid_params} ->
|
||||
conn
|
||||
|> put_status(:bad_request)
|
||||
|> json(%{error: "Invalid webhook parameters"})
|
||||
|
||||
|
||||
{:error, reason} ->
|
||||
Logger.error("Failed to update webhook: #{inspect(reason)}")
|
||||
|
||||
conn
|
||||
|> put_status(:internal_server_error)
|
||||
|> json(%{error: "Internal server error"})
|
||||
@@ -439,13 +448,13 @@ defmodule WandererAppWeb.MapWebhooksAPIController do
|
||||
def delete(conn, %{"map_identifier" => map_identifier, "id" => webhook_id}) do
|
||||
with {:ok, map} <- get_map(conn, map_identifier),
|
||||
{:ok, webhook} <- get_webhook(webhook_id, map.id) do
|
||||
|
||||
case MapWebhookSubscription.destroy(webhook) do
|
||||
:ok ->
|
||||
conn |> put_status(:no_content)
|
||||
|
||||
|
||||
{:error, reason} ->
|
||||
Logger.error("Failed to delete webhook: #{inspect(reason)}")
|
||||
|
||||
conn
|
||||
|> put_status(:internal_server_error)
|
||||
|> json(%{error: "Internal server error"})
|
||||
@@ -455,14 +464,15 @@ defmodule WandererAppWeb.MapWebhooksAPIController do
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|> json(%{error: "Map not found"})
|
||||
|
||||
|
||||
{:error, :webhook_not_found} ->
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|> json(%{error: "Webhook not found"})
|
||||
|
||||
|
||||
{:error, reason} ->
|
||||
Logger.error("Failed to delete webhook: #{inspect(reason)}")
|
||||
|
||||
conn
|
||||
|> put_status(:internal_server_error)
|
||||
|> json(%{error: "Internal server error"})
|
||||
@@ -472,14 +482,14 @@ defmodule WandererAppWeb.MapWebhooksAPIController do
|
||||
def rotate_secret(conn, %{"map_identifier" => map_identifier, "id" => webhook_id}) do
|
||||
with {:ok, map} <- get_map(conn, map_identifier),
|
||||
{:ok, webhook} <- get_webhook(webhook_id, map.id) do
|
||||
|
||||
case MapWebhookSubscription.rotate_secret(webhook) do
|
||||
{:ok, updated_webhook} ->
|
||||
# Return the new secret (this is the only time it's exposed)
|
||||
json(conn, %{data: %{secret: updated_webhook.secret}})
|
||||
|
||||
|
||||
{:error, reason} ->
|
||||
Logger.error("Failed to rotate webhook secret: #{inspect(reason)}")
|
||||
|
||||
conn
|
||||
|> put_status(:internal_server_error)
|
||||
|> json(%{error: "Internal server error"})
|
||||
@@ -489,14 +499,15 @@ defmodule WandererAppWeb.MapWebhooksAPIController do
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|> json(%{error: "Map not found"})
|
||||
|
||||
|
||||
{:error, :webhook_not_found} ->
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|> json(%{error: "Webhook not found"})
|
||||
|
||||
|
||||
{:error, reason} ->
|
||||
Logger.error("Failed to rotate webhook secret: #{inspect(reason)}")
|
||||
|
||||
conn
|
||||
|> put_status(:internal_server_error)
|
||||
|> json(%{error: "Internal server error"})
|
||||
@@ -518,7 +529,9 @@ defmodule WandererAppWeb.MapWebhooksAPIController do
|
||||
defp get_webhook(webhook_id, map_id) do
|
||||
try do
|
||||
case MapWebhookSubscription.by_id(webhook_id) do
|
||||
nil -> {:error, :webhook_not_found}
|
||||
nil ->
|
||||
{:error, :webhook_not_found}
|
||||
|
||||
webhook ->
|
||||
if webhook.map_id == map_id do
|
||||
{:ok, webhook}
|
||||
@@ -528,7 +541,7 @@ defmodule WandererAppWeb.MapWebhooksAPIController do
|
||||
end
|
||||
rescue
|
||||
# Only catch specific Ash-related exceptions
|
||||
error in [Ash.Error.Query.NotFound, Ash.Error.Invalid] ->
|
||||
error in [Ash.Error.Query.NotFound, Ash.Error.Invalid] ->
|
||||
Logger.debug("Webhook lookup error: #{inspect(error)}")
|
||||
{:error, :webhook_not_found}
|
||||
end
|
||||
@@ -536,7 +549,7 @@ defmodule WandererAppWeb.MapWebhooksAPIController do
|
||||
|
||||
defp validate_create_params(params, map_id) do
|
||||
required_fields = ["url", "events"]
|
||||
|
||||
|
||||
if Enum.all?(required_fields, &Map.has_key?(params, &1)) do
|
||||
webhook_params = %{
|
||||
map_id: map_id,
|
||||
@@ -544,6 +557,7 @@ defmodule WandererAppWeb.MapWebhooksAPIController do
|
||||
events: params["events"],
|
||||
active?: Map.get(params, "active", true)
|
||||
}
|
||||
|
||||
{:ok, webhook_params}
|
||||
else
|
||||
{:error, :invalid_params}
|
||||
@@ -553,18 +567,19 @@ defmodule WandererAppWeb.MapWebhooksAPIController do
|
||||
defp validate_update_params(params) do
|
||||
# Filter out non-updatable fields and map identifier
|
||||
allowed_fields = ["url", "events", "active"]
|
||||
|
||||
update_params = params
|
||||
|> Map.take(allowed_fields)
|
||||
|> Enum.reduce(%{}, fn {k, v}, acc ->
|
||||
case k do
|
||||
"active" -> Map.put(acc, :active?, v)
|
||||
"url" -> Map.put(acc, :url, v)
|
||||
"events" -> Map.put(acc, :events, v)
|
||||
_ -> acc
|
||||
end
|
||||
end)
|
||||
|
||||
|
||||
update_params =
|
||||
params
|
||||
|> Map.take(allowed_fields)
|
||||
|> Enum.reduce(%{}, fn {k, v}, acc ->
|
||||
case k do
|
||||
"active" -> Map.put(acc, :active?, v)
|
||||
"url" -> Map.put(acc, :url, v)
|
||||
"events" -> Map.put(acc, :events, v)
|
||||
_ -> acc
|
||||
end
|
||||
end)
|
||||
|
||||
{:ok, update_params}
|
||||
end
|
||||
|
||||
@@ -582,4 +597,4 @@ defmodule WandererAppWeb.MapWebhooksAPIController do
|
||||
updated_at: webhook.updated_at
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -12,4 +12,4 @@ defmodule WandererAppWeb.Plugs.CheckWebsocketDisabled do
|
||||
conn
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -16,7 +16,6 @@ defmodule WandererAppWeb.MapKillsEventHandler do
|
||||
# Get kill counts from cache
|
||||
case WandererApp.Map.get_map(map_id) do
|
||||
{:ok, %{systems: systems}} ->
|
||||
|
||||
kill_counts = build_kill_counts(systems)
|
||||
|
||||
kills_payload =
|
||||
@@ -266,6 +265,7 @@ defmodule WandererAppWeb.MapKillsEventHandler do
|
||||
Logger.warning(
|
||||
"[#{__MODULE__}] Invalid kill count data for system #{solar_system_id}: #{inspect(invalid_data)}"
|
||||
)
|
||||
|
||||
0
|
||||
end
|
||||
end
|
||||
@@ -282,6 +282,7 @@ defmodule WandererAppWeb.MapKillsEventHandler do
|
||||
Logger.warning(
|
||||
"[#{__MODULE__}] Invalid cache data structure for key: #{cache_key}, got: #{inspect(invalid_data)}"
|
||||
)
|
||||
|
||||
# Clear invalid cache entry
|
||||
WandererApp.Cache.delete(cache_key)
|
||||
%{}
|
||||
@@ -293,10 +294,10 @@ defmodule WandererAppWeb.MapKillsEventHandler do
|
||||
case Map.get(cached_map, system_id) do
|
||||
kills when is_list(kills) ->
|
||||
Map.put(acc, system_id, kills)
|
||||
|
||||
_ ->
|
||||
acc
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -37,9 +37,7 @@
|
||||
id={"map-audit-#{@map_slug}"}
|
||||
navigate={~p"/#{@map_slug}/audit?period=1H&activity=all"}
|
||||
>
|
||||
<button
|
||||
class="h-8 w-8 hover:text-white"
|
||||
>
|
||||
<button class="h-8 w-8 hover:text-white">
|
||||
<.icon name="hero-key-solid" class="w-6 h-6" />
|
||||
</button>
|
||||
</.link>
|
||||
@@ -49,9 +47,7 @@
|
||||
id={"map-characters-#{@map_slug}"}
|
||||
navigate={~p"/#{@map_slug}/characters"}
|
||||
>
|
||||
<button
|
||||
class="h-8 w-8 hover:text-white"
|
||||
>
|
||||
<button class="h-8 w-8 hover:text-white">
|
||||
<.icon name="hero-user-group-solid" class="w-6 h-6" />
|
||||
</button>
|
||||
</.link>
|
||||
@@ -75,12 +71,13 @@
|
||||
<li
|
||||
class={[
|
||||
"p-unselectable-text",
|
||||
classes("p-tabview-selected p-highlight": @active_subscription_tab == "balance")
|
||||
classes(
|
||||
"p-tabview-selected p-highlight": @active_subscription_tab == "balance"
|
||||
)
|
||||
]}
|
||||
role="presentation"
|
||||
data-pc-name=""
|
||||
data-pc-section="header"
|
||||
|
||||
>
|
||||
<a
|
||||
role="tab"
|
||||
@@ -147,7 +144,8 @@
|
||||
phx-value-tab="balance"
|
||||
>
|
||||
<span class="p-tabview-title" data-pc-section="headertitle">
|
||||
<.icon name="hero-arrow-up-solid" class="w-4 h-4" /> Top Donators <span class="badge">coming soon</span>
|
||||
<.icon name="hero-arrow-up-solid" class="w-4 h-4" /> Top Donators
|
||||
<span class="badge">coming soon</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
@@ -164,7 +162,9 @@
|
||||
data-pc-section="content"
|
||||
>
|
||||
<.live_component
|
||||
:if={@active_subscription_tab == "balance" && not is_nil(assigns |> Map.get(:map_id))}
|
||||
:if={
|
||||
@active_subscription_tab == "balance" && not is_nil(assigns |> Map.get(:map_id))
|
||||
}
|
||||
module={WandererAppWeb.Maps.MapBalanceComponent}
|
||||
id="map-balance-component"
|
||||
map_id={@map_id}
|
||||
@@ -181,7 +181,9 @@
|
||||
notify_to={self()}
|
||||
event_name="subscriptions_event"
|
||||
current_user={@current_user}
|
||||
readonly={(@user_permissions || %{}) |> Map.get(:delete_map, false) |> Kernel.not()}
|
||||
readonly={
|
||||
(@user_permissions || %{}) |> Map.get(:delete_map, false) |> Kernel.not()
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -190,7 +192,5 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-action">
|
||||
|
||||
</div>
|
||||
<div class="modal-action"></div>
|
||||
</.modal>
|
||||
|
||||
@@ -252,9 +252,9 @@ defmodule WandererAppWeb.Router do
|
||||
# WebSocket events and webhook management endpoints (disabled by default)
|
||||
scope "/api/maps/:map_identifier", WandererAppWeb do
|
||||
pipe_through [:api, :api_map, :api_websocket_events]
|
||||
|
||||
|
||||
get "/events", MapEventsAPIController, :list_events
|
||||
|
||||
|
||||
# Webhook management endpoints
|
||||
resources "/webhooks", MapWebhooksAPIController, except: [:new, :edit] do
|
||||
post "/rotate-secret", MapWebhooksAPIController, :rotate_secret
|
||||
|
||||
Reference in New Issue
Block a user