Compare commits

...

1 Commits

Author SHA1 Message Date
Dmitry Popov
8e5ed22bc0 feat(core): Add support for editing passages mass 2026-04-01 11:55:25 +02:00
13 changed files with 558 additions and 10 deletions

View File

@@ -17,12 +17,15 @@ defmodule WandererApp.Api.MapChainPassages do
define(:read, action: :read)
define(:by_map_id, action: :by_map_id)
define(:by_connection, action: :by_connection)
define(:update_mass, action: :update_mass)
define(:by_id, get_by: [:id], action: :read)
end
actions do
default_accept [
:ship_type_id,
:ship_name,
:mass,
:solar_system_source_id,
:solar_system_target_id
]
@@ -30,6 +33,12 @@ defmodule WandererApp.Api.MapChainPassages do
defaults [:create, :read, :destroy]
update :update do
accept [:mass]
require_atomic? false
end
update :update_mass do
accept [:mass]
require_atomic? false
end
@@ -37,6 +46,7 @@ defmodule WandererApp.Api.MapChainPassages do
accept [
:ship_type_id,
:ship_name,
:mass,
:solar_system_source_id,
:solar_system_target_id,
:map_id,
@@ -85,8 +95,10 @@ defmodule WandererApp.Api.MapChainPassages do
|> WandererApp.Repo.all()
|> Enum.map(fn [passage, character] ->
%{
id: passage.id,
ship_type_id: passage.ship_type_id,
ship_name: passage.ship_name,
mass: passage.mass,
inserted_at: passage.inserted_at,
character: character
}
@@ -108,6 +120,7 @@ defmodule WandererApp.Api.MapChainPassages do
attribute :ship_type_id, :integer
attribute :ship_name, :string
attribute :mass, :integer
attribute :solar_system_source_id, :integer
attribute :solar_system_target_id, :integer

View File

@@ -804,7 +804,8 @@ defmodule WandererApp.Esi.ApiClient do
})
if count >= 3 do
Logger.warning("TOKEN_REFRESH_FAILED: Invalid grant error (#{count}/3, invalidating tokens)",
Logger.warning(
"TOKEN_REFRESH_FAILED: Invalid grant error (#{count}/3, invalidating tokens)",
character_id: character_id,
error_message: error_message,
time_since_expiry_seconds: time_since_expiry,
@@ -861,7 +862,8 @@ defmodule WandererApp.Esi.ApiClient do
expires_at,
_scopes
) do
time_since_expiry = DateTime.diff(DateTime.utc_now(), DateTime.from_unix!(expires_at), :second)
time_since_expiry =
DateTime.diff(DateTime.utc_now(), DateTime.from_unix!(expires_at), :second)
Logger.warning("TOKEN_REFRESH_FAILED: Transient OAuth2 error during token refresh",
character_id: character_id,
@@ -879,7 +881,8 @@ defmodule WandererApp.Esi.ApiClient do
end
defp handle_refresh_token_result(error, _character, character_id, expires_at, _scopes) do
time_since_expiry = DateTime.diff(DateTime.utc_now(), DateTime.from_unix!(expires_at), :second)
time_since_expiry =
DateTime.diff(DateTime.utc_now(), DateTime.from_unix!(expires_at), :second)
Logger.warning("TOKEN_REFRESH_FAILED: Unexpected error during token refresh",
character_id: character_id,
@@ -910,7 +913,12 @@ defmodule WandererApp.Esi.ApiClient do
{:ok, current_character} ->
# Only invalidate if tokens haven't been refreshed since we started
if current_character.access_token == character.access_token do
attrs = %{access_token: nil, refresh_token: nil, expires_at: expires_at, scopes: scopes}
attrs = %{
access_token: nil,
refresh_token: nil,
expires_at: expires_at,
scopes: scopes
}
with {:ok, _} <- WandererApp.Api.Character.update(current_character, attrs) do
WandererApp.Character.update_character(character_id, attrs)

View File

@@ -336,7 +336,11 @@ defmodule WandererApp.Map.Server.CharactersImpl do
"[CharacterCleanup] Map #{map_id} - untracking settings and removing character #{s.character_id}"
end)
WandererApp.MapCharacterSettingsRepo.untrack!(%{map_id: s.map_id, character_id: s.character_id})
WandererApp.MapCharacterSettingsRepo.untrack!(%{
map_id: s.map_id,
character_id: s.character_id
})
remove_character(map_id, s.character_id)
end)

View File

@@ -278,8 +278,7 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
),
do:
update_connection(map_id, :update_mass_status, [:mass_status], connection_update, fn
%{mass_status: old_mass_status},
%{mass_status: mass_status} = updated_connection ->
%{mass_status: old_mass_status}, %{mass_status: mass_status} = updated_connection ->
if mass_status != old_mass_status do
maybe_update_linked_signature_mass_status(map_id, updated_connection)
end

View File

@@ -19,6 +19,7 @@ defmodule WandererAppWeb.RouteBuilderController do
{:error, reason} ->
Logger.warning("[RouteBuilderController] find_closest failed: #{inspect(reason)}")
conn
|> put_status(:bad_gateway)
|> json(%{error: "route_builder_failed"})

View File

@@ -265,6 +265,42 @@ defmodule WandererAppWeb.MapConnectionsEventHandler do
{:reply, passages, socket}
end
def handle_ui_event(
"update_passage_mass",
%{"id" => passage_id, "mass" => mass} = _event,
%{
assigns: %{
has_tracked_characters?: true,
user_permissions: %{update_system: true}
}
} = socket
) do
mass_value =
cond do
is_integer(mass) ->
mass
is_binary(mass) ->
case Integer.parse(mass) do
{int_val, _} -> int_val
:error -> nil
end
true ->
nil
end
case WandererApp.Api.MapChainPassages.by_id(passage_id) do
{:ok, passage} when not is_nil(passage) ->
WandererApp.Api.MapChainPassages.update_mass(passage, %{mass: mass_value})
_ ->
Logger.warning("update_passage_mass: passage not found id=#{passage_id}")
end
{:noreply, socket}
end
def handle_ui_event(event, body, socket),
do: MapCoreEventHandler.handle_ui_event(event, body, socket)

View File

@@ -614,7 +614,8 @@ defmodule WandererAppWeb.MapCoreEventHandler do
nil
end
expired_characters = tracked_characters |> Enum.filter(&(&1.access_token == nil)) |> Enum.map(& &1.eve_id)
expired_characters =
tracked_characters |> Enum.filter(&(&1.access_token == nil)) |> Enum.map(& &1.eve_id)
initial_data =
%{

View File

@@ -176,12 +176,14 @@ defmodule WandererAppWeb.MapRoutesEventHandler do
routes_type = Map.get(event, "type", "blueLoot")
security_type = Map.get(event, "securityType", "both")
is_subscription_active? = Map.get(socket.assigns, :is_subscription_active?, false)
routes_limit =
if is_subscription_active? == true do
@paid_routes_limit
else
Map.get(@alpha_routes_limit_by_type, routes_type, @default_alpha_routes_limit)
end
routes_settings =
routes_settings
|> get_routes_settings()

View File

@@ -88,7 +88,8 @@ defmodule WandererAppWeb.MapEventHandler do
"update_connection_mass_status",
"update_connection_ship_size_type",
"update_connection_locked",
"update_connection_custom_info"
"update_connection_custom_info",
"update_passage_mass"
]
@map_activity_events [

View File

@@ -0,0 +1,29 @@
defmodule WandererApp.Repo.Migrations.AddMassToMapChainPassages do
@moduledoc """
Updates resources based on their most recent snapshots.
This file was autogenerated with `mix ash_postgres.generate_migrations`
"""
use Ecto.Migration
def up do
alter table(:maps_v1) do
modify :scopes, {:array, :text}, default: '{wormholes}'
end
alter table(:map_chain_passages_v1) do
add :mass, :bigint
end
end
def down do
alter table(:map_chain_passages_v1) do
remove :mass
end
alter table(:maps_v1) do
modify :scopes, {:array, :text}, default: nil
end
end
end

View File

@@ -0,0 +1,177 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"gen_random_uuid()\")",
"generated?": false,
"precision": null,
"primary_key?": true,
"references": null,
"scale": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "ship_type_id",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "ship_name",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "mass",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "solar_system_source_id",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "solar_system_target_id",
"type": "bigint"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "inserted_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "updated_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": true,
"references": {
"deferrable": false,
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"index?": false,
"match_type": null,
"match_with": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "map_chain_passages_v1_map_id_fkey",
"on_delete": null,
"on_update": null,
"primary_key?": true,
"schema": null,
"table": "maps_v1"
},
"scale": null,
"size": null,
"source": "map_id",
"type": "uuid"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": true,
"references": {
"deferrable": false,
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"index?": false,
"match_type": null,
"match_with": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "map_chain_passages_v1_character_id_fkey",
"on_delete": null,
"on_update": null,
"primary_key?": true,
"schema": null,
"table": "character_v1"
},
"scale": null,
"size": null,
"source": "character_id",
"type": "uuid"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "00AA9FB7759FCDF16C5C627E6735E0B568E517A360F2002AFE00018BD6CD8F2A",
"identities": [],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.WandererApp.Repo",
"schema": null,
"table": "map_chain_passages_v1"
}

View File

@@ -0,0 +1,277 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"gen_random_uuid()\")",
"generated?": false,
"precision": null,
"primary_key?": true,
"references": null,
"scale": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "name",
"type": "text"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "slug",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "description",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "personal_note",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "public_api_key",
"type": "text"
},
{
"allow_nil?": true,
"default": "[]",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "hubs",
"type": [
"array",
"text"
]
},
{
"allow_nil?": false,
"default": "\"wormholes\"",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "scope",
"type": "text"
},
{
"allow_nil?": true,
"default": "false",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "deleted",
"type": "boolean"
},
{
"allow_nil?": true,
"default": "false",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "only_tracked_characters",
"type": "boolean"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "options",
"type": "text"
},
{
"allow_nil?": false,
"default": "false",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "webhooks_enabled",
"type": "boolean"
},
{
"allow_nil?": false,
"default": "false",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "sse_enabled",
"type": "boolean"
},
{
"allow_nil?": true,
"default": "'{wormholes}'",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "scopes",
"type": [
"array",
"text"
]
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "inserted_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "updated_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": {
"deferrable": false,
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"index?": false,
"match_type": null,
"match_with": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "maps_v1_owner_id_fkey",
"on_delete": null,
"on_update": null,
"primary_key?": true,
"schema": null,
"table": "character_v1"
},
"scale": null,
"size": null,
"source": "owner_id",
"type": "uuid"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "21B2A84E49086754B40476C11B4EA5F576E8537449FB776941098773C5CD705F",
"identities": [
{
"all_tenants?": false,
"base_filter": null,
"index_name": "maps_v1_unique_public_api_key_index",
"keys": [
{
"type": "atom",
"value": "public_api_key"
}
],
"name": "unique_public_api_key",
"nils_distinct?": true,
"where": null
},
{
"all_tenants?": false,
"base_filter": null,
"index_name": "maps_v1_unique_slug_index",
"keys": [
{
"type": "atom",
"value": "slug"
}
],
"name": "unique_slug",
"nils_distinct?": true,
"where": null
}
],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.WandererApp.Repo",
"schema": null,
"table": "maps_v1"
}

View File

@@ -155,7 +155,7 @@ defmodule WandererAppWeb.OpenAPISpecAnalyzer do
# Categorize schemas based on naming patterns
request_schemas = Enum.filter(schema_names, &String.contains?(&1, "Request"))
response_schemas = Enum.filter(schema_names, &String.contains?(&1, "Response"))
shared_schemas = schema_names -- request_schemas -- response_schemas
shared_schemas = schema_names -- (request_schemas -- response_schemas)
%{
total: length(schema_names),