diff --git a/CHANGELOG.md b/CHANGELOG.md index 525eeb75..bd78f737 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,87 @@ +## [v1.65.6](https://github.com/wanderer-industries/wanderer/compare/v1.65.5...v1.65.6) (2025-05-26) + + + + +### Bug Fixes: + +* Core: Fixed map character tracking issues + +## [v1.65.5](https://github.com/wanderer-industries/wanderer/compare/v1.65.4...v1.65.5) (2025-05-26) + + + + +### Bug Fixes: + +* Core: Fixed map character tracking issues + +* Signature: Update restored signature character + +## [v1.65.4](https://github.com/wanderer-industries/wanderer/compare/v1.65.3...v1.65.4) (2025-05-24) + + + + +### Bug Fixes: + +* Signature: Force signature update even if there are no any changes + +## [v1.65.3](https://github.com/wanderer-industries/wanderer/compare/v1.65.2...v1.65.3) (2025-05-23) + + + + +### Bug Fixes: + +* Signature: Fixed signature clenup + +## [v1.65.2](https://github.com/wanderer-industries/wanderer/compare/v1.65.1...v1.65.2) (2025-05-23) + + + + +### Bug Fixes: + +* Signature: Fixed signature updates + +## [v1.65.1](https://github.com/wanderer-industries/wanderer/compare/v1.65.0...v1.65.1) (2025-05-22) + + + + +### Bug Fixes: + +* Core: Added unsync map events timeout handling (force page refresh if outdated map events found) + +## [v1.65.0](https://github.com/wanderer-industries/wanderer/compare/v1.64.8...v1.65.0) (2025-05-22) + + + + +### Features: + +* default connections from c1 holes to medium size + +* support german and french signatures + +* improve signature undo process + +### Bug Fixes: + +* remove required id field from character schema + +* update openapi spec response types + +* fix issue with connection generation between k-space + +* Signature: Fixed signatures updates + +* update openapi spec for other apis + ## [v1.64.8](https://github.com/wanderer-industries/wanderer/compare/v1.64.7...v1.64.8) (2025-05-20) diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/helpers/getState.ts b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/helpers/getState.ts index 189780a4..b4ea3466 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/helpers/getState.ts +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/helpers/getState.ts @@ -1,14 +1,15 @@ import { UNKNOWN_SIGNATURE_NAME } from '@/hooks/Mapper/helpers'; -import { SystemSignature } from '@/hooks/Mapper/types'; +import { SignatureGroup, SystemSignature } from '@/hooks/Mapper/types'; export const getState = (_: string[], newSig: SystemSignature) => { let state = -1; - if (!newSig.group) { + if (!newSig.group || newSig.group === SignatureGroup.CosmicSignature) { state = 0; } else if (!newSig.name || newSig.name === '' || newSig.name === UNKNOWN_SIGNATURE_NAME) { state = 1; } else if (newSig.name !== '') { state = 2; } + return state; }; diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/hooks/useSystemSignaturesData.ts b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/hooks/useSystemSignaturesData.ts index 5f05a3fc..3aeddcb1 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/hooks/useSystemSignaturesData.ts +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/hooks/useSystemSignaturesData.ts @@ -70,7 +70,7 @@ export const useSystemSignaturesData = ({ ? signaturesRef.current.filter(sig => !sig.pendingDeletion) : signaturesRef.current.filter(sig => !sig.pendingDeletion || !sig.pendingAddition); - const { added, updated, removed } = getActualSigs(currentNonPending, incomingSignatures, !lazyDeleteValue, true); + const { added, updated, removed } = getActualSigs(currentNonPending, incomingSignatures, !lazyDeleteValue, false); if (removed.length > 0) { await processRemovedSignatures(removed, added, updated); diff --git a/lib/wanderer_app/api/map_character_settings.ex b/lib/wanderer_app/api/map_character_settings.ex index b84e0268..2bd0a82d 100644 --- a/lib/wanderer_app/api/map_character_settings.ex +++ b/lib/wanderer_app/api/map_character_settings.ex @@ -3,17 +3,19 @@ defmodule WandererApp.Api.MapCharacterSettings do use Ash.Resource, domain: WandererApp.Api, - data_layer: AshPostgres.DataLayer + data_layer: AshPostgres.DataLayer, + extensions: [AshCloak] - @derive {Jason.Encoder, only: [ - :id, - :map_id, - :character_id, - :tracked, - :followed, - :inserted_at, - :updated_at - ]} + @derive {Jason.Encoder, + only: [ + :id, + :map_id, + :character_id, + :tracked, + :followed, + :inserted_at, + :updated_at + ]} postgres do repo(WandererApp.Repo) @@ -23,8 +25,10 @@ defmodule WandererApp.Api.MapCharacterSettings do code_interface do define(:create, action: :create) define(:destroy, action: :destroy) + define(:update, action: :update) define(:read_by_map, action: :read_by_map) + define(:read_by_map_and_character, action: :read_by_map_and_character) define(:by_map_filtered, action: :by_map_filtered) define(:tracked_by_map_filtered, action: :tracked_by_map_filtered) define(:tracked_by_character, action: :tracked_by_character) @@ -44,7 +48,31 @@ defmodule WandererApp.Api.MapCharacterSettings do :tracked ] - defaults [:create, :read, :update, :destroy] + defaults [:read, :destroy] + + create :create do + primary? true + upsert? true + upsert_identity :uniq_map_character + + upsert_fields [ + :map_id, + :character_id + ] + + accept [ + :map_id, + :character_id, + :tracked, + :followed + ] + + argument :map_id, :uuid, allow_nil?: false + argument :character_id, :uuid, allow_nil?: false + + change manage_relationship(:map_id, :map, on_lookup: :relate, on_no_match: nil) + change manage_relationship(:character_id, :character, on_lookup: :relate, on_no_match: nil) + end read :by_map_filtered do argument(:map_id, :string, allow_nil?: false) @@ -67,6 +95,15 @@ defmodule WandererApp.Api.MapCharacterSettings do filter(expr(map_id == ^arg(:map_id))) end + read :read_by_map_and_character do + get? true + + argument(:map_id, :string, allow_nil?: false) + argument(:character_id, :uuid, allow_nil?: false) + + filter(expr(map_id == ^arg(:map_id) and character_id == ^arg(:character_id))) + end + read :tracked_by_map_all do argument(:map_id, :string, allow_nil?: false) filter(expr(map_id == ^arg(:map_id) and tracked == true)) @@ -77,6 +114,20 @@ defmodule WandererApp.Api.MapCharacterSettings do filter(expr(character_id == ^arg(:character_id) and tracked == true)) end + update :update do + primary? true + require_atomic? false + + accept([ + :ship, + :ship_name, + :ship_item_id, + :solar_system_id, + :structure_id, + :station_id + ]) + end + update :track do accept [:map_id, :character_id] argument :map_id, :string, allow_nil?: false @@ -134,6 +185,28 @@ defmodule WandererApp.Api.MapCharacterSettings do end end + cloak do + vault(WandererApp.Vault) + + attributes([ + :ship, + :ship_name, + :ship_item_id, + :solar_system_id, + :structure_id, + :station_id + ]) + + decrypt_by_default([ + :ship, + :ship_name, + :ship_item_id, + :solar_system_id, + :structure_id, + :station_id + ]) + end + attributes do uuid_primary_key :id @@ -147,6 +220,13 @@ defmodule WandererApp.Api.MapCharacterSettings do allow_nil? true end + attribute :solar_system_id, :integer + attribute :structure_id, :integer + attribute :station_id, :integer + attribute :ship, :integer + attribute :ship_name, :string + attribute :ship_item_id, :integer + create_timestamp(:inserted_at) update_timestamp(:updated_at) end diff --git a/lib/wanderer_app/api/map_system_signature.ex b/lib/wanderer_app/api/map_system_signature.ex index 1925307a..2c1e642e 100644 --- a/lib/wanderer_app/api/map_system_signature.ex +++ b/lib/wanderer_app/api/map_system_signature.ex @@ -25,9 +25,18 @@ defmodule WandererApp.Api.MapSystemSignature do define(:by_system_id, action: :by_system_id, args: [:system_id]) define(:by_system_id_all, action: :by_system_id_all, args: [:system_id]) - define(:by_system_id_and_eve_ids, action: :by_system_id_and_eve_ids, args: [:system_id, :eve_ids]) + + define(:by_system_id_and_eve_ids, + action: :by_system_id_and_eve_ids, + args: [:system_id, :eve_ids] + ) + define(:by_linked_system_id, action: :by_linked_system_id, args: [:linked_system_id]) - define(:by_deleted_and_updated_before!, action: :by_deleted_and_updated_before, args: [:deleted, :updated_before]) + + define(:by_deleted_and_updated_before!, + action: :by_deleted_and_updated_before, + args: [:deleted, :updated_before] + ) end actions do @@ -88,7 +97,8 @@ defmodule WandererApp.Api.MapSystemSignature do :group, :type, :custom_info, - :deleted + :deleted, + :update_forced_at ] primary? true @@ -177,6 +187,10 @@ defmodule WandererApp.Api.MapSystemSignature do default false end + attribute :update_forced_at, :utc_datetime do + allow_nil? true + end + create_timestamp(:inserted_at) update_timestamp(:updated_at) end @@ -192,21 +206,20 @@ defmodule WandererApp.Api.MapSystemSignature do end @derive {Jason.Encoder, - only: [ - :id, - :system_id, - :eve_id, - :character_eve_id, - :name, - :description, - :type, - :linked_system_id, - :kind, - :group, - :custom_info, - :deleted, - :inserted_at, - :updated_at - ] - } + only: [ + :id, + :system_id, + :eve_id, + :character_eve_id, + :name, + :description, + :type, + :linked_system_id, + :kind, + :group, + :custom_info, + :deleted, + :inserted_at, + :updated_at + ]} end diff --git a/lib/wanderer_app/character.ex b/lib/wanderer_app/character.ex index c36ada5b..8cb1868f 100644 --- a/lib/wanderer_app/character.ex +++ b/lib/wanderer_app/character.ex @@ -7,6 +7,15 @@ defmodule WandererApp.Character do @read_character_wallet_scope "esi-wallet.read_character_wallet.v1" @read_corp_wallet_scope "esi-wallet.read_corporation_wallets.v1" + @default_character_tracking_data %{ + solar_system_id: nil, + structure_id: nil, + station_id: nil, + ship: nil, + ship_name: nil, + ship_item_id: nil + } + @decorate cacheable( cache: WandererApp.Cache, key: "characters-#{character_eve_id}" @@ -45,6 +54,32 @@ defmodule WandererApp.Character do end end + def get_map_character(map_id, character_id) do + case get_character(character_id) do + {:ok, character} -> + {:ok, + character + |> maybe_merge_map_character_settings( + map_id, + WandererApp.Character.TrackerManager.Impl.character_is_present(map_id, character_id) + )} + + _ -> + {:ok, nil} + end + end + + def get_map_character!(map_id, character_id) do + case get_map_character(map_id, character_id) do + {:ok, character} -> + character + + _ -> + Logger.error("Failed to get map character #{map_id} #{character_id}") + nil + end + end + def get_character_eve_ids!(character_ids), do: character_ids @@ -146,7 +181,7 @@ defmodule WandererApp.Character do params: opts[:params] ) do {:ok, result} -> - {:ok, result |> _prepare_search_results()} + {:ok, result |> prepare_search_results()} {:error, error} -> Logger.warning("#{__MODULE__} failed search: #{inspect(error)}") @@ -208,7 +243,28 @@ defmodule WandererApp.Character do end end - defp _prepare_search_results(result) do + defp maybe_merge_map_character_settings(character, map_id, true), do: character + + defp maybe_merge_map_character_settings( + %{id: character_id} = character, + map_id, + _character_is_present + ) do + WandererApp.MapCharacterSettingsRepo.get(map_id, character_id) + |> case do + {:ok, settings} when not is_nil(settings) -> + character + |> Map.put(:online, false) + |> Map.merge(settings) + + _ -> + character + |> Map.put(:online, false) + |> Map.merge(@default_character_tracking_data) + end + end + + defp prepare_search_results(result) do {:ok, characters} = _load_eve_info(Map.get(result, "character"), :get_character_info, &_map_character_info/1) diff --git a/lib/wanderer_app/character/tracker_manager_impl.ex b/lib/wanderer_app/character/tracker_manager_impl.ex index 355a10ff..2bdb8165 100644 --- a/lib/wanderer_app/character/tracker_manager_impl.ex +++ b/lib/wanderer_app/character/tracker_manager_impl.ex @@ -13,7 +13,7 @@ defmodule WandererApp.Character.TrackerManager.Impl do } @garbage_collection_interval :timer.minutes(15) - @untrack_characters_interval :timer.minutes(5) + @untrack_characters_interval :timer.minutes(1) @inactive_character_timeout :timer.minutes(5) @logger Application.compile_env(:wanderer_app, :logger) @@ -220,6 +220,18 @@ defmodule WandererApp.Character.TrackerManager.Impl do track: false }) + {:ok, character} = WandererApp.Character.get_character(character_id) + + {:ok, _updated} = + WandererApp.MapCharacterSettingsRepo.update(map_id, character_id, %{ + ship: character.ship, + ship_name: character.ship_name, + ship_item_id: character.ship_item_id, + solar_system_id: character.solar_system_id, + structure_id: character.structure_id, + station_id: character.station_id + }) + WandererApp.Character.update_character_state(character_id, character_state) end end, @@ -246,7 +258,7 @@ defmodule WandererApp.Character.TrackerManager.Impl do def handle_info(_event, state), do: state - defp character_is_present(map_id, character_id) do + def character_is_present(map_id, character_id) do {:ok, presence_character_ids} = WandererApp.Cache.lookup("map_#{map_id}:presence_character_ids", []) diff --git a/lib/wanderer_app/character/tracking_utils.ex b/lib/wanderer_app/character/tracking_utils.ex index 6cc4f8db..7d27180f 100644 --- a/lib/wanderer_app/character/tracking_utils.ex +++ b/lib/wanderer_app/character/tracking_utils.ex @@ -121,7 +121,6 @@ defmodule WandererApp.Character.TrackingUtils do WandererApp.MapCharacterSettingsRepo.untrack(existing_settings) :ok = untrack([character], map_id, caller_pid) - :ok = remove_characters([character], map_id) {:ok, updated_settings} else {:ok, existing_settings} @@ -132,7 +131,6 @@ defmodule WandererApp.Character.TrackingUtils do if track do {:ok, updated_settings} = WandererApp.MapCharacterSettingsRepo.track(existing_settings) :ok = track([character], map_id, true, caller_pid) - :ok = add_characters([character], map_id, true) {:ok, updated_settings} else {:ok, existing_settings} @@ -149,7 +147,6 @@ defmodule WandererApp.Character.TrackingUtils do }) :ok = track([character], map_id, true, caller_pid) - :ok = add_characters([character], map_id, true) {:ok, settings} else {:error, "Character settings not found"} @@ -231,15 +228,15 @@ defmodule WandererApp.Character.TrackingUtils do with false <- is_nil(caller_pid) do character_ids = characters |> Enum.map(& &1.id) - characters - |> Enum.each(fn character -> - WandererAppWeb.Presence.update(caller_pid, map_id, character.id, %{ + character_ids + |> Enum.each(fn character_id -> + WandererAppWeb.Presence.update(caller_pid, map_id, character_id, %{ tracked: false, from: DateTime.utc_now() }) end) - WandererApp.Map.Server.untrack_characters(map_id, character_ids) + # WandererApp.Map.Server.untrack_characters(map_id, character_ids) :ok else @@ -249,19 +246,19 @@ defmodule WandererApp.Character.TrackingUtils do end end - def add_characters([], _map_id, _track_character), do: :ok + # def add_characters([], _map_id, _track_character), do: :ok - def add_characters([character | characters], map_id, track_character) do - :ok = WandererApp.Map.Server.add_character(map_id, character, track_character) - add_characters(characters, map_id, track_character) - end + # def add_characters([character | characters], map_id, track_character) do + # :ok = WandererApp.Map.Server.add_character(map_id, character, track_character) + # add_characters(characters, map_id, track_character) + # end - def remove_characters([], _map_id), do: :ok + # def remove_characters([], _map_id), do: :ok - def remove_characters([character | characters], map_id) do - :ok = WandererApp.Map.Server.remove_character(map_id, character.id) - remove_characters(characters, map_id) - end + # def remove_characters([character | characters], map_id) do + # :ok = WandererApp.Map.Server.remove_character(map_id, character.id) + # remove_characters(characters, map_id) + # end def get_main_character( nil, diff --git a/lib/wanderer_app/map.ex b/lib/wanderer_app/map.ex index aca75a48..d4c0fb73 100644 --- a/lib/wanderer_app/map.ex +++ b/lib/wanderer_app/map.ex @@ -96,7 +96,7 @@ defmodule WandererApp.Map do map_id |> get_map!() |> Map.get(:characters, []) - |> Enum.map(&WandererApp.Character.get_character!(&1)) + |> 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()} diff --git a/lib/wanderer_app/map/server/map_server_characters_impl.ex b/lib/wanderer_app/map/server/map_server_characters_impl.ex index 0820fde6..f5fee5c1 100644 --- a/lib/wanderer_app/map/server/map_server_characters_impl.ex +++ b/lib/wanderer_app/map/server/map_server_characters_impl.ex @@ -11,19 +11,19 @@ defmodule WandererApp.Map.Server.CharactersImpl do def add_character(%{map_id: map_id} = state, %{id: character_id} = character, track_character) do Task.start_link(fn -> with :ok <- map_id |> WandererApp.Map.add_character(character), - {:ok, _} <- + {:ok, _settings} <- WandererApp.MapCharacterSettingsRepo.create(%{ character_id: character_id, map_id: map_id, tracked: track_character }), - {:ok, character} <- WandererApp.Character.get_character(character_id) do + {:ok, character} <- WandererApp.Character.get_map_character(map_id, character_id) do Impl.broadcast!(map_id, :character_added, character) :telemetry.execute([:wanderer_app, :map, :character, :added], %{count: 1}) :ok else _error -> - {:ok, character} = WandererApp.Character.get_character(character_id) + {:ok, character} = WandererApp.Character.get_map_character(map_id, character_id) Impl.broadcast!(map_id, :character_added, character) :ok end @@ -35,7 +35,7 @@ defmodule WandererApp.Map.Server.CharactersImpl do def remove_character(map_id, character_id) do Task.start_link(fn -> with :ok <- WandererApp.Map.remove_character(map_id, character_id), - {:ok, character} <- WandererApp.Character.get_character(character_id) do + {:ok, character} <- WandererApp.Character.get_map_character(map_id, character_id) do Impl.broadcast!(map_id, :character_removed, character) :telemetry.execute([:wanderer_app, :map, :character, :removed], %{count: 1}) @@ -64,7 +64,9 @@ defmodule WandererApp.Map.Server.CharactersImpl do map_tracked_character_ids |> Enum.filter(fn character -> character in tracked_characters end) - {:ok, old_map_tracked_characters} = WandererApp.Cache.lookup("maps:#{map_id}:tracked_characters", []) + {:ok, old_map_tracked_characters} = + WandererApp.Cache.lookup("maps:#{map_id}:tracked_characters", []) + characters_to_remove = old_map_tracked_characters -- map_active_tracked_characters {:ok, invalidate_character_ids} = @@ -73,7 +75,11 @@ defmodule WandererApp.Map.Server.CharactersImpl do [] ) - WandererApp.Cache.insert("map_#{map_id}:invalidate_character_ids", (invalidate_character_ids ++ characters_to_remove) |> Enum.uniq()) + WandererApp.Cache.insert( + "map_#{map_id}:invalidate_character_ids", + (invalidate_character_ids ++ characters_to_remove) |> Enum.uniq() + ) + WandererApp.Cache.insert("maps:#{map_id}:tracked_characters", map_active_tracked_characters) :ok @@ -84,12 +90,26 @@ defmodule WandererApp.Map.Server.CharactersImpl do do: character_ids |> Enum.each(fn character_id -> - WandererApp.Character.TrackerManager.update_track_settings(character_id, %{ - map_id: map_id, - track: false - }) + if is_character_map_active?(map_id, character_id) do + WandererApp.Character.TrackerManager.update_track_settings(character_id, %{ + map_id: map_id, + track: false + }) + + Impl.broadcast!(map_id, :untrack_character, character_id) + end end) + def is_character_map_active?(map_id, character_id) do + case WandererApp.Character.get_character_state(character_id) do + {:ok, %{active_maps: active_maps}} -> + map_id in active_maps + + _ -> + false + end + end + def cleanup_characters(map_id, owner_id) do {:ok, invalidate_character_ids} = WandererApp.Cache.lookup( @@ -265,7 +285,7 @@ defmodule WandererApp.Map.Server.CharactersImpl do end defp update_character(map_id, character_id) do - {:ok, character} = WandererApp.Character.get_character(character_id) + {:ok, character} = WandererApp.Character.get_map_character(map_id, character_id) Impl.broadcast!(map_id, :character_updated, character) end @@ -315,15 +335,18 @@ defmodule WandererApp.Map.Server.CharactersImpl do is_nil(structure_id) and is_nil(station_id) end - defp track_character(map_id, character_id), - do: - WandererApp.Character.TrackerManager.update_track_settings(character_id, %{ - map_id: map_id, - track: true, - track_online: true, - track_location: true, - track_ship: true - }) + defp track_character(map_id, character_id) do + {:ok, character} = WandererApp.Character.get_character(character_id) + add_character(%{map_id: map_id}, character, true) + + WandererApp.Character.TrackerManager.update_track_settings(character_id, %{ + map_id: map_id, + track: true, + track_online: true, + track_location: true, + track_ship: true + }) + end defp maybe_update_online(map_id, character_id) do with {:ok, old_online} <- @@ -394,8 +417,7 @@ defmodule WandererApp.Map.Server.CharactersImpl do {:ok, old_structure_id} = WandererApp.Cache.lookup("map:#{map_id}:character:#{character_id}:structure_id") - {:ok, - %{solar_system_id: solar_system_id, structure_id: structure_id, station_id: station_id}} = + {:ok, %{solar_system_id: solar_system_id, structure_id: structure_id, station_id: station_id}} = WandererApp.Character.get_character(character_id) WandererApp.Cache.insert( @@ -413,14 +435,15 @@ defmodule WandererApp.Map.Server.CharactersImpl do structure_id ) - if solar_system_id != old_solar_system_id || structure_id != old_structure_id || station_id != old_station_id do + if solar_system_id != old_solar_system_id || structure_id != old_structure_id || + station_id != old_station_id do [ {:character_location, - %{ - solar_system_id: solar_system_id, - structure_id: structure_id, - station_id: station_id - }, %{solar_system_id: old_solar_system_id}} + %{ + solar_system_id: solar_system_id, + structure_id: structure_id, + station_id: station_id + }, %{solar_system_id: old_solar_system_id}} ] else [:skip] diff --git a/lib/wanderer_app/map/server/map_server_impl.ex b/lib/wanderer_app/map/server/map_server_impl.ex index c75cd561..265d1957 100644 --- a/lib/wanderer_app/map/server/map_server_impl.ex +++ b/lib/wanderer_app/map/server/map_server_impl.ex @@ -94,7 +94,6 @@ defmodule WandererApp.Map.Server.Impl do Process.send_after(self(), :cleanup_connections, 5_000) Process.send_after(self(), :cleanup_systems, 10_000) Process.send_after(self(), :cleanup_characters, :timer.minutes(5)) - Process.send_after(self(), :cleanup_signatures, :timer.minutes(30)) Process.send_after(self(), :backup_state, @backup_state_timeout) WandererApp.Cache.insert("map_#{map_id}:started", true) @@ -317,11 +316,6 @@ defmodule WandererApp.Map.Server.Impl do state end - def handle_event(:cleanup_signatures, state) do - Process.send_after(self(), :cleanup_signatures, :timer.minutes(30)) - state |> SignaturesImpl.cleanup_signatures() - end - def handle_event(msg, state) do Logger.warning("Unhandled event: #{inspect(msg)}") diff --git a/lib/wanderer_app/map/server/map_server_signatures_impl.ex b/lib/wanderer_app/map/server/map_server_signatures_impl.ex index 7792e5e1..6a0b2b76 100644 --- a/lib/wanderer_app/map/server/map_server_signatures_impl.ex +++ b/lib/wanderer_app/map/server/map_server_signatures_impl.ex @@ -25,7 +25,10 @@ defmodule WandererApp.Map.Server.SignaturesImpl do ) when not is_nil(char_id) do with {:ok, system} <- - MapSystem.read_by_map_and_solar_system(%{map_id: map_id, solar_system_id: system_solar_id}), + MapSystem.read_by_map_and_solar_system(%{ + map_id: map_id, + solar_system_id: system_solar_id + }), {:ok, %{eve_id: char_eve_id}} <- Character.get_character(char_id) do do_update_signatures( state, @@ -84,9 +87,11 @@ defmodule WandererApp.Map.Server.SignaturesImpl do # 3. Additions & restorations added_eve_ids = Enum.map(added_sigs, & &1.eve_id) - existing_index = MapSystemSignature.by_system_id_all!(system.id) - |> Enum.filter(&(&1.eve_id in added_eve_ids)) - |> Map.new(&{&1.eve_id, &1}) + + existing_index = + MapSystemSignature.by_system_id_all!(system.id) + |> Enum.filter(&(&1.eve_id in added_eve_ids)) + |> Map.new(&{&1.eve_id, &1}) added_sigs |> Enum.each(fn sig -> @@ -97,7 +102,17 @@ defmodule WandererApp.Map.Server.SignaturesImpl do %MapSystemSignature{deleted: true} = deleted_sig -> MapSystemSignature.update!( deleted_sig, - Map.take(sig, [:name, :description, :kind, :group, :type, :custom_info, :deleted]) + Map.take(sig, [ + :name, + :description, + :kind, + :group, + :type, + :character_eve_id, + :custom_info, + :deleted, + :update_forced_at + ]) ) _ -> @@ -107,7 +122,12 @@ defmodule WandererApp.Map.Server.SignaturesImpl do # 4. Activity tracking if added_ids != [] do - track_activity(:signatures_added, state.map_id, system.solar_system_id, user_id, character_eve_id, + track_activity( + :signatures_added, + state.map_id, + system.solar_system_id, + user_id, + character_eve_id, added_ids ) end @@ -152,8 +172,13 @@ defmodule WandererApp.Map.Server.SignaturesImpl do defp apply_update_signature(%MapSystemSignature{} = existing, update_params) when not is_nil(update_params) do - case MapSystemSignature.update(existing, update_params) do - {:ok, _} -> :ok + case MapSystemSignature.update( + existing, + update_params |> Map.put(:update_forced_at, DateTime.utc_now()) + ) do + {:ok, _updated} -> + :ok + {:error, reason} -> Logger.error("Failed to update signature #{existing.id}: #{inspect(reason)}") end diff --git a/lib/wanderer_app/repositories/map_character_settings_repo.ex b/lib/wanderer_app/repositories/map_character_settings_repo.ex index 26c1f598..d26688ce 100644 --- a/lib/wanderer_app/repositories/map_character_settings_repo.ex +++ b/lib/wanderer_app/repositories/map_character_settings_repo.ex @@ -1,8 +1,33 @@ defmodule WandererApp.MapCharacterSettingsRepo do use WandererApp, :repository - def create(settings), - do: WandererApp.Api.MapCharacterSettings.create(settings) + def get(map_id, character_id) do + case WandererApp.Api.MapCharacterSettings.read_by_map_and_character(%{ + map_id: map_id, + character_id: character_id + }) do + {:ok, settings} -> + {:ok, settings} + + {:error, reason} -> + {:ok, nil} + end + end + + def create(settings) do + WandererApp.Api.MapCharacterSettings.create(settings) + end + + def update(map_id, character_id, updated_settings) do + case get(map_id, character_id) do + {:ok, settings} when not is_nil(settings) -> + settings + |> WandererApp.Api.MapCharacterSettings.update(updated_settings) + + {:error, reason} -> + {:ok, nil} + end + end def get_tracked_by_map_filtered(map_id, character_ids), do: diff --git a/lib/wanderer_app_web/live/characters/characters_tracking_live.ex b/lib/wanderer_app_web/live/characters/characters_tracking_live.ex index ecf484dc..bb7022a7 100755 --- a/lib/wanderer_app_web/live/characters/characters_tracking_live.ex +++ b/lib/wanderer_app_web/live/characters/characters_tracking_live.ex @@ -91,6 +91,8 @@ defmodule WandererAppWeb.CharactersTrackingLive do character_setting |> WandererApp.MapCharacterSettingsRepo.untrack!() + WandererApp.Map.Server.untrack_characters(selected_map.id, [character_setting.character_id]) + _ -> character_setting |> WandererApp.MapCharacterSettingsRepo.track!() diff --git a/lib/wanderer_app_web/live/map/event_handlers/map_characters_event_handler.ex b/lib/wanderer_app_web/live/map/event_handlers/map_characters_event_handler.ex index 5f192394..69b64df4 100644 --- a/lib/wanderer_app_web/live/map/event_handlers/map_characters_event_handler.ex +++ b/lib/wanderer_app_web/live/map/event_handlers/map_characters_event_handler.ex @@ -32,6 +32,16 @@ defmodule WandererAppWeb.MapCharactersEventHandler do ) end + def handle_server_event(%{event: :untrack_character, payload: character_id}, %{ + assigns: %{ + map_id: map_id + } + } = socket) do + :ok = WandererApp.Character.TrackingUtils.untrack([%{id: character_id}], map_id, self()) + socket + end + + def handle_server_event( %{event: :characters_updated}, %{ @@ -342,9 +352,6 @@ defmodule WandererAppWeb.MapCharactersEventHandler do self() ) - :ok = - WandererApp.Character.TrackingUtils.add_characters(map_characters, map_id, track_character) - socket end diff --git a/lib/wanderer_app_web/live/map/event_handlers/map_core_event_handler.ex b/lib/wanderer_app_web/live/map/event_handlers/map_core_event_handler.ex index 851f01e5..f1f2226c 100644 --- a/lib/wanderer_app_web/live/map/event_handlers/map_core_event_handler.ex +++ b/lib/wanderer_app_web/live/map/event_handlers/map_core_event_handler.ex @@ -57,7 +57,6 @@ defmodule WandererAppWeb.MapCoreEventHandler do case track_character do false -> :ok = WandererApp.Character.TrackingUtils.untrack(map_characters, map_id, self()) - :ok = WandererApp.Character.TrackingUtils.remove_characters(map_characters, map_id) _ -> :ok = @@ -67,13 +66,6 @@ defmodule WandererAppWeb.MapCoreEventHandler do true, self() ) - - :ok = - WandererApp.Character.TrackingUtils.add_characters( - map_characters, - map_id, - track_character - ) end socket diff --git a/lib/wanderer_app_web/live/map/map_characters_live.ex b/lib/wanderer_app_web/live/map/map_characters_live.ex index 69b8acc8..4ed38963 100755 --- a/lib/wanderer_app_web/live/map/map_characters_live.ex +++ b/lib/wanderer_app_web/live/map/map_characters_live.ex @@ -84,11 +84,7 @@ defmodule WandererAppWeb.MapCharactersLive do character_setting -> case character_setting.tracked do true -> - {:ok, map_character_settings} = - character_setting - |> WandererApp.MapCharacterSettingsRepo.untrack() - - WandererApp.Map.Server.remove_character(map_id, map_character_settings.character_id) + WandererApp.Map.Server.untrack_characters(map_id, [character_setting.character_id]) socket |> put_flash(:info, "Character untracked!") |> load_characters() diff --git a/lib/wanderer_app_web/live/map/map_event_handler.ex b/lib/wanderer_app_web/live/map/map_event_handler.ex index 4420ac67..59a9ff90 100644 --- a/lib/wanderer_app_web/live/map/map_event_handler.ex +++ b/lib/wanderer_app_web/live/map/map_event_handler.ex @@ -24,7 +24,8 @@ defmodule WandererAppWeb.MapEventHandler do :characters_updated, :present_characters_updated, :refresh_user_characters, - :show_tracking + :show_tracking, + :untrack_character ] @map_characters_ui_events [ diff --git a/lib/wanderer_app_web/live/map/map_live.ex b/lib/wanderer_app_web/live/map/map_live.ex index 3d429283..7e4627ae 100644 --- a/lib/wanderer_app_web/live/map/map_live.ex +++ b/lib/wanderer_app_web/live/map/map_live.ex @@ -4,7 +4,7 @@ defmodule WandererAppWeb.MapLive do require Logger - @server_event_unsync_timeout :timer.minutes(5) + @server_event_unsync_timeout :timer.minutes(2) @impl true def mount(%{"slug" => map_slug} = _params, _session, socket) when is_connected?(socket) do diff --git a/mix.exs b/mix.exs index e5303a9a..6ab84f7c 100644 --- a/mix.exs +++ b/mix.exs @@ -3,7 +3,7 @@ defmodule WandererApp.MixProject do @source_url "https://github.com/wanderer-industries/wanderer" - @version "1.64.8" + @version "1.65.6" def project do [ diff --git a/priv/repo/migrations/20250524170825_add_sig_update_forced_at.exs b/priv/repo/migrations/20250524170825_add_sig_update_forced_at.exs new file mode 100644 index 00000000..772d7dee --- /dev/null +++ b/priv/repo/migrations/20250524170825_add_sig_update_forced_at.exs @@ -0,0 +1,21 @@ +defmodule WandererApp.Repo.Migrations.AddSigUpdateForcedAt 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(:map_system_signatures_v1) do + add :update_forced_at, :utc_datetime + end + end + + def down do + alter table(:map_system_signatures_v1) do + remove :update_forced_at + end + end +end diff --git a/priv/repo/migrations/20250526074425_add_map_character_tracking_info.exs b/priv/repo/migrations/20250526074425_add_map_character_tracking_info.exs new file mode 100644 index 00000000..f7922990 --- /dev/null +++ b/priv/repo/migrations/20250526074425_add_map_character_tracking_info.exs @@ -0,0 +1,31 @@ +defmodule WandererApp.Repo.Migrations.AddMapCharacterTrackingInfo 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(:map_character_settings_v1) do + add :encrypted_ship, :binary + add :encrypted_ship_name, :binary + add :encrypted_ship_item_id, :binary + add :encrypted_solar_system_id, :binary + add :encrypted_structure_id, :binary + add :encrypted_station_id, :binary + end + end + + def down do + alter table(:map_character_settings_v1) do + remove :encrypted_station_id + remove :encrypted_structure_id + remove :encrypted_solar_system_id + remove :encrypted_ship_item_id + remove :encrypted_ship_name + remove :encrypted_ship + end + end +end diff --git a/priv/resource_snapshots/repo/map_character_settings_v1/20250526074425.json b/priv/resource_snapshots/repo/map_character_settings_v1/20250526074425.json new file mode 100644 index 00000000..b49345c1 --- /dev/null +++ b/priv/resource_snapshots/repo/map_character_settings_v1/20250526074425.json @@ -0,0 +1,206 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "primary_key?": true, + "references": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "false", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "tracked", + "type": "boolean" + }, + { + "allow_nil?": true, + "default": "false", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "followed", + "type": "boolean" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "inserted_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "updated_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "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_character_settings_v1_map_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "maps_v1" + }, + "size": null, + "source": "map_id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "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_character_settings_v1_character_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "character_v1" + }, + "size": null, + "source": "character_id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "encrypted_ship", + "type": "binary" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "encrypted_ship_name", + "type": "binary" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "encrypted_ship_item_id", + "type": "binary" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "encrypted_solar_system_id", + "type": "binary" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "encrypted_structure_id", + "type": "binary" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "encrypted_station_id", + "type": "binary" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "3F585D1C545A5264AEFA05502C0F625F9B27B15CA36699DCF37E4F834E6339AE", + "identities": [ + { + "all_tenants?": false, + "base_filter": null, + "index_name": "map_character_settings_v1_uniq_map_character_index", + "keys": [ + { + "type": "atom", + "value": "map_id" + }, + { + "type": "atom", + "value": "character_id" + } + ], + "name": "uniq_map_character", + "nils_distinct?": true, + "where": null + } + ], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.WandererApp.Repo", + "schema": null, + "table": "map_character_settings_v1" +} \ No newline at end of file diff --git a/priv/resource_snapshots/repo/map_system_signatures_v1/20250524170825.json b/priv/resource_snapshots/repo/map_system_signatures_v1/20250524170825.json new file mode 100644 index 00000000..634c58a4 --- /dev/null +++ b/priv/resource_snapshots/repo/map_system_signatures_v1/20250524170825.json @@ -0,0 +1,207 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "primary_key?": true, + "references": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "eve_id", + "type": "text" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "character_eve_id", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "name", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "description", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "type", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "linked_system_id", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "kind", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "group", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "custom_info", + "type": "text" + }, + { + "allow_nil?": false, + "default": "false", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "deleted", + "type": "boolean" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "update_forced_at", + "type": "utc_datetime" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "inserted_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "updated_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "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": "map_system_signatures_v1_system_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "map_system_v1" + }, + "size": null, + "source": "system_id", + "type": "uuid" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "915C0896211ECCB6C38871664117E7D470C794825536E7F0887DC5B92681F17B", + "identities": [ + { + "all_tenants?": false, + "base_filter": null, + "index_name": "map_system_signatures_v1_uniq_system_eve_id_index", + "keys": [ + { + "type": "atom", + "value": "system_id" + }, + { + "type": "atom", + "value": "eve_id" + } + ], + "name": "uniq_system_eve_id", + "nils_distinct?": true, + "where": null + } + ], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.WandererApp.Repo", + "schema": null, + "table": "map_system_signatures_v1" +} \ No newline at end of file