Compare commits

...

7 Commits

Author SHA1 Message Date
CI
1625f16c8f chore: release version v1.91.2 2025-12-27 22:11:03 +00:00
Dmitry Popov
b4ef9ae983 fix(core): fixed map scopes updates & logic 2025-12-27 23:10:26 +01:00
CI
3b9c2dd996 chore: [skip ci] 2025-12-25 18:20:20 +00:00
CI
8a0f9a58d0 chore: release version v1.91.1 2025-12-25 18:20:20 +00:00
Dmitry Popov
5fe8caac0d Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-12-25 19:19:47 +01:00
Dmitry Popov
f18f567727 chore: fix blog link styles 2025-12-25 19:19:44 +01:00
CI
91acc49980 chore: [skip ci] 2025-12-24 15:09:40 +00:00
9 changed files with 358 additions and 48 deletions

View File

@@ -2,6 +2,20 @@
<!-- changelog -->
## [v1.91.2](https://github.com/wanderer-industries/wanderer/compare/v1.91.1...v1.91.2) (2025-12-27)
### Bug Fixes:
* core: fixed map scopes updates & logic
## [v1.91.1](https://github.com/wanderer-industries/wanderer/compare/v1.91.0...v1.91.1) (2025-12-25)
## [v1.91.0](https://github.com/wanderer-industries/wanderer/compare/v1.90.13...v1.91.0) (2025-12-24)

View File

@@ -1001,3 +1001,27 @@ body > div:first-of-type {
.verticalTabsContainer .p-tabview-panel {
flex-grow: 1;
}
/* Blog post CTA links - only in main post content */
.post-content a {
display: inline-block;
background: linear-gradient(135deg, #ec4899 0%, #8b5cf6 100%);
color: white !important;
padding: 0.5rem 1.25rem;
border-radius: 0.5rem;
text-decoration: none !important;
font-weight: 600;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(236, 72, 153, 0.3);
}
.post-content a:hover {
background: linear-gradient(135deg, #db2777 0%, #7c3aed 100%);
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(236, 72, 153, 0.4);
}
.post-content a:active {
transform: translateY(0);
box-shadow: 0 2px 10px rgba(236, 72, 153, 0.3);
}

View File

@@ -56,7 +56,7 @@ defmodule WandererApp.Map.Server.AclsImpl do
end
)
map_update = %{acls: map.acls, scope: map.scope}
map_update = %{acls: map.acls, scope: map.scope, scopes: map.scopes}
WandererApp.Map.update_map(map_id, map_update)
WandererApp.Cache.delete("map_characters-#{map_id}")

View File

@@ -780,17 +780,39 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
to_is_wormhole = to_system_static_info.system_class in @wh_space
wormholes_enabled = :wormholes in scopes
# Wormhole border behavior: if wormholes scope is enabled AND at least one
# system is a wormhole, allow the connection (adds border k-space systems)
# Otherwise: BOTH systems must match the configured scopes
if wormholes_enabled and (from_is_wormhole or to_is_wormhole) do
# At least one system matches (wormhole matches :wormholes, or other matches its scope)
system_matches_any_scope?(from_system_static_info.system_class, scopes) or
system_matches_any_scope?(to_system_static_info.system_class, scopes)
else
# Non-wormhole movement: both systems must match scopes
system_matches_any_scope?(from_system_static_info.system_class, scopes) and
system_matches_any_scope?(to_system_static_info.system_class, scopes)
cond do
# Case 1: Wormhole border behavior - at least one system is a wormhole
# and :wormholes is enabled, allow the connection (adds border k-space systems)
wormholes_enabled and (from_is_wormhole or to_is_wormhole) ->
# At least one system matches (wormhole matches :wormholes, or other matches its scope)
system_matches_any_scope?(from_system_static_info.system_class, scopes) or
system_matches_any_scope?(to_system_static_info.system_class, scopes)
# Case 2: K-space to K-space with :wormholes enabled - check if it's a wormhole connection
# If neither system is a wormhole AND there's no stargate between them, it's a wormhole connection
wormholes_enabled and not from_is_wormhole and not to_is_wormhole ->
# Check if there's a known stargate connection
case find_solar_system_jump(from_solar_system_id, to_solar_system_id) do
{:ok, known_jumps} when known_jumps == [] ->
# No stargate exists - this is a wormhole connection through k-space
true
{:ok, _known_jumps} ->
# Stargate exists - this is NOT a wormhole, check normal scope matching
system_matches_any_scope?(from_system_static_info.system_class, scopes) and
system_matches_any_scope?(to_system_static_info.system_class, scopes)
_ ->
# Error fetching jumps - fall back to scope matching
system_matches_any_scope?(from_system_static_info.system_class, scopes) and
system_matches_any_scope?(to_system_static_info.system_class, scopes)
end
# Case 3: Non-wormhole movement without :wormholes scope
# Both systems must match the configured scopes
true ->
system_matches_any_scope?(from_system_static_info.system_class, scopes) and
system_matches_any_scope?(to_system_static_info.system_class, scopes)
end
else
false

View File

@@ -115,7 +115,9 @@
{@post.description}
</h4>
<!--Post Content-->
{raw(@post.body)}
<div class="post-content">
{raw(@post.body)}
</div>
</div>
</div>
<!--/container-->

View File

@@ -3,7 +3,7 @@ defmodule WandererApp.MixProject do
@source_url "https://github.com/wanderer-industries/wanderer"
@version "1.91.0"
@version "1.91.2"
def project do
[

View File

@@ -11,12 +11,16 @@ description: "Join our Advent Christmas Giveaway Challenge! Win exclusive partne
![Christmas Giveaway Challenge](/images/news/2025/12-18-advent-giveaway/cover.jpg "Christmas Giveaway Challenge")
### Event Details
- **Event Name:** Advent Christmas Giveaway
- **Duration:** 1 week (7 days, 7 codes)
- **Event Link:** [Advent Christmas Giveaway](https://eventcortex.com/events/invite/cYdBywu1ygfVS3UN6ZZcmDzL1q85aDmH)
### The Season of Giving
This holiday season, we're spreading some festive cheer with a special event for our community: the **Advent Christmas Giveaway Challenge**!
Starting next week, we'll be giving away **1 exclusive partnership code every day for 7 days**. But here's the twist — it's a challenge!
---
### How It Works
@@ -34,14 +38,7 @@ Starting next week, we'll be giving away **1 exclusive partnership code every da
- Each day features a single partnership code.
- Miss today? Come back tomorrow for another chance!
---
### Event Details
- **Event Name:** Advent Christmas Giveaway
- **Duration:** 1 week (7 days, 7 codes)
- **Organizer:** @Demiro (Wanderer core developer, EventCortex CTO)
- **Event Link:** [Advent Christmas Giveaway - EventCortex](https://eventcortex.com/events/invite/cYdBywu1ygfVS3UN6ZZcmDzL1q85aDmH)
---

View File

@@ -0,0 +1,187 @@
defmodule WandererApp.Map.Server.AclScopesPropagationTest do
@moduledoc """
Unit tests for verifying that map scopes are properly propagated
when ACL updates occur.
This test verifies the fix in lib/wanderer_app/map/server/map_server_acls_impl.ex:59
where `scopes` was added to the map_update struct.
Bug: When users update map scope settings (Wormholes, High-Sec, Low-Sec, Null-Sec,
Pochven checkboxes), the map server's cached state wasn't being updated with the
new scopes array. This caused connection tracking to use stale scope settings
until the server was restarted.
Fix: Changed `map_update = %{acls: map.acls, scope: map.scope}`
To: `map_update = %{acls: map.acls, scope: map.scope, scopes: map.scopes}`
"""
use WandererApp.DataCase, async: false
import WandererAppWeb.Factory
describe "MapRepo.get returns scopes field" do
test "map scopes are loaded when fetching map data" do
# Create a user and character for map ownership
user = create_user()
character = create_character(%{user_id: user.id})
# Create a map with specific scopes
map =
create_map(%{
owner_id: character.id,
name: "Scopes Test",
slug: "scopes-prop-test-#{:rand.uniform(1_000_000)}",
scope: :wormholes,
scopes: [:wormholes, :hi, :low]
})
# Verify the map was created with the expected scopes
assert map.scopes == [:wormholes, :hi, :low]
# Fetch the map the same way AclsImpl.handle_map_acl_updated does
{:ok, fetched_map} =
WandererApp.MapRepo.get(map.id,
acls: [
:owner_id,
members: [:role, :eve_character_id, :eve_corporation_id, :eve_alliance_id]
]
)
# Verify scopes are returned - this is what the fix relies on
assert fetched_map.scopes == [:wormholes, :hi, :low],
"MapRepo.get should return the scopes field. Got: #{inspect(fetched_map.scopes)}"
# Verify the scope (legacy) field is also present
assert fetched_map.scope == :wormholes
end
test "map scopes field is available for map_update construction" do
# Create test data
user = create_user()
character = create_character(%{user_id: user.id})
map =
create_map(%{
owner_id: character.id,
name: "Update Test",
slug: "scopes-update-test-#{:rand.uniform(1_000_000)}",
scope: :all,
scopes: [:wormholes, :hi, :low, :null, :pochven]
})
# Fetch map as AclsImpl does
{:ok, fetched_map} = WandererApp.MapRepo.get(map.id, acls: [:owner_id])
# Build map_update the same way the fixed code does
# This is the exact line that was fixed in map_server_acls_impl.ex:59
map_update = %{acls: fetched_map.acls, scope: fetched_map.scope, scopes: fetched_map.scopes}
# Verify all fields are present in the update struct
assert Map.has_key?(map_update, :acls), "map_update should include :acls"
assert Map.has_key?(map_update, :scope), "map_update should include :scope"
assert Map.has_key?(map_update, :scopes), "map_update should include :scopes"
# Verify the scopes value is correct
assert map_update.scopes == [:wormholes, :hi, :low, :null, :pochven],
"map_update.scopes should have the complete scopes array"
end
end
describe "scopes update in database" do
test "updating map scopes persists correctly" do
# Create test data
user = create_user()
character = create_character(%{user_id: user.id})
map =
create_map(%{
owner_id: character.id,
name: "DB Update Test",
slug: "scopes-db-test-#{:rand.uniform(1_000_000)}",
scope: :wormholes,
scopes: [:wormholes]
})
# Initial state
assert map.scopes == [:wormholes]
# Update scopes (simulating what the LiveView does)
{:ok, updated_map} =
WandererApp.Api.Map.update(map, %{
scopes: [:wormholes, :hi, :low, :null]
})
assert updated_map.scopes == [:wormholes, :hi, :low, :null],
"Database update should persist new scopes"
# Fetch again to confirm persistence
{:ok, refetched_map} = WandererApp.MapRepo.get(map.id, [])
assert refetched_map.scopes == [:wormholes, :hi, :low, :null],
"Refetched map should have updated scopes"
end
test "partial scopes update works correctly" do
# Create test data
user = create_user()
character = create_character(%{user_id: user.id})
map =
create_map(%{
owner_id: character.id,
name: "Partial Update",
slug: "partial-scopes-#{:rand.uniform(1_000_000)}",
scope: :wormholes,
scopes: [:wormholes, :hi, :low, :null, :pochven]
})
# Update to a subset of scopes
{:ok, updated_map} =
WandererApp.Api.Map.update(map, %{
scopes: [:wormholes, :null]
})
assert updated_map.scopes == [:wormholes, :null],
"Should be able to update to partial scopes"
end
end
describe "get_effective_scopes uses scopes array" do
alias WandererApp.Map.Server.CharactersImpl
test "get_effective_scopes returns scopes array when present" do
map_struct = %{scopes: [:wormholes, :hi, :low], scope: :all}
effective_scopes = CharactersImpl.get_effective_scopes(map_struct)
assert effective_scopes == [:wormholes, :hi, :low],
"get_effective_scopes should return scopes array when present"
end
test "get_effective_scopes falls back to legacy scope when scopes is empty" do
map_struct = %{scopes: [], scope: :wormholes}
effective_scopes = CharactersImpl.get_effective_scopes(map_struct)
assert effective_scopes == [:wormholes],
"get_effective_scopes should fall back to legacy scope conversion"
end
test "get_effective_scopes falls back to legacy scope when scopes is nil" do
map_struct = %{scopes: nil, scope: :all}
effective_scopes = CharactersImpl.get_effective_scopes(map_struct)
assert effective_scopes == [:wormholes, :hi, :low, :null, :pochven],
"get_effective_scopes should convert :all to full scope list"
end
test "get_effective_scopes defaults to [:wormholes] when no scope info" do
map_struct = %{}
effective_scopes = CharactersImpl.get_effective_scopes(map_struct)
assert effective_scopes == [:wormholes],
"get_effective_scopes should default to [:wormholes]"
end
end
end

View File

@@ -244,18 +244,19 @@ defmodule WandererApp.Map.Server.MapScopesTest do
test "connection with multiple scopes" do
# With [:wormholes, :hi]:
# - WH to WH: valid (both match :wormholes)
# - HS to HS: valid (both match :hi)
# - HS to HS: valid (both match :hi, or wormhole if no stargate)
# - WH to HS: valid (wormhole border behavior - WH is wormhole, :wormholes enabled)
scopes = [:wormholes, :hi]
assert ConnectionsImpl.is_connection_valid(scopes, @wh_system_id, @c2_system_id) == true
assert ConnectionsImpl.is_connection_valid(scopes, @hs_system_id, 30_000_002) == true
assert ConnectionsImpl.is_connection_valid(scopes, @wh_system_id, @hs_system_id) == true
# LS to NS should not be valid with [:wormholes, :hi] (neither is WH, neither matches)
assert ConnectionsImpl.is_connection_valid(scopes, @ls_system_id, @ns_system_id) == false
# LS to NS with [:wormholes, :hi] - if no stargate exists, it's a wormhole connection
# With :wormholes enabled, wormhole connections are valid
assert ConnectionsImpl.is_connection_valid(scopes, @ls_system_id, @ns_system_id) == true
# HS to LS should not be valid with [:wormholes, :hi] (neither is WH, only HS matches)
assert ConnectionsImpl.is_connection_valid(scopes, @hs_system_id, @ls_system_id) == false
# HS to LS with [:wormholes, :hi] - if no stargate exists, it's a wormhole connection
assert ConnectionsImpl.is_connection_valid(scopes, @hs_system_id, @ls_system_id) == true
end
test "all scopes allows any connection" do
@@ -368,30 +369,32 @@ defmodule WandererApp.Map.Server.MapScopesTest do
true
end
test "K-SPACE ONLY: Hi-Sec->Hi-Sec with [:wormholes] is REJECTED" do
# No wormhole involved, neither matches :wormholes
assert ConnectionsImpl.is_connection_valid([:wormholes], @hs_system_id, 30_000_002) == false
test "K-SPACE ONLY: Hi-Sec->Hi-Sec with [:wormholes] is VALID when no stargate exists" do
# If no stargate exists between two k-space systems, it's a wormhole connection
# (The test systems don't have stargate data, so this is treated as a wormhole)
assert ConnectionsImpl.is_connection_valid([:wormholes], @hs_system_id, 30_000_002) == true
end
test "K-SPACE ONLY: Null->Hi-Sec with [:wormholes, :null] is REJECTED (no border for k-space)" do
# Neither system is a wormhole, so no border behavior
# Null matches :null, but Hi-Sec doesn't match any scope -> BOTH must match
test "K-SPACE ONLY: Null->Hi-Sec with [:wormholes, :null] is VALID when no stargate exists" do
# If no stargate exists, this is a wormhole connection through k-space
# With [:wormholes] enabled, wormhole connections are valid
assert ConnectionsImpl.is_connection_valid(
[:wormholes, :null],
@ns_system_id,
@hs_system_id
) ==
false
true
end
test "K-SPACE ONLY: Hi-Sec->Low-Sec with [:wormholes, :null] is REJECTED" do
# Neither Hi-Sec nor Low-Sec match [:wormholes, :null], no WH involved
test "K-SPACE ONLY: Hi-Sec->Low-Sec with [:wormholes, :null] is VALID when no stargate exists" do
# If no stargate exists, this is a wormhole connection
# With [:wormholes] enabled, wormhole connections are valid
assert ConnectionsImpl.is_connection_valid(
[:wormholes, :null],
@hs_system_id,
@ls_system_id
) ==
false
true
end
test "K-SPACE ONLY: Low-Sec->Hi-Sec with [:low] is REJECTED (no border for k-space)" do
@@ -437,29 +440,90 @@ defmodule WandererApp.Map.Server.MapScopesTest do
true
end
test "excluded path: k-space chain with [:wormholes] scope remains excluded" do
# If character moves within k-space (no WH involved), should be excluded
assert ConnectionsImpl.is_connection_valid([:wormholes], @hs_system_id, 30_000_002) == false
assert ConnectionsImpl.is_connection_valid([:wormholes], 30_000_002, @ls_system_id) == false
test "k-space chain with [:wormholes] scope is VALID when no stargates exist" do
# If no stargates exist between k-space systems, they're wormhole connections
# With [:wormholes] scope, these should be tracked
assert ConnectionsImpl.is_connection_valid([:wormholes], @hs_system_id, 30_000_002) == true
assert ConnectionsImpl.is_connection_valid([:wormholes], 30_000_002, @ls_system_id) == true
end
test "excluded path: Null->Hi-Sec->Low-Sec with [:wormholes, :null] - only Null tracked" do
# Character in Null (tracked) jumps to Hi-Sec (border - but NO wormhole!) -> REJECTED
# This is the key case: k-space to k-space should NOT add border systems
test "k-space chain with [:wormholes, :null] - wormhole connections are tracked" do
# If no stargates exist, these are wormhole connections through k-space
# With [:wormholes] enabled, all wormhole connections are tracked
assert ConnectionsImpl.is_connection_valid(
[:wormholes, :null],
@ns_system_id,
@hs_system_id
) ==
false
true
# Hi-Sec to Low-Sec also rejected (neither matches)
# Hi-Sec to Low-Sec is also a wormhole connection (no stargate in test data)
assert ConnectionsImpl.is_connection_valid(
[:wormholes, :null],
@hs_system_id,
@ls_system_id
) ==
false
true
end
end
describe "wormhole connections in k-space (unknown connections)" do
@moduledoc """
These tests verify the behavior for k-space to k-space connections that are
NOT known stargates. Such connections should be treated as wormhole connections.
Scenario: A player jumps from Low-Sec to Hi-Sec. If there's no stargate between
these systems, the jump must have been through a wormhole. With [:wormholes] scope,
this connection SHOULD be valid.
The connection TYPE (stargate vs wormhole) is determined separately in
maybe_add_connection using is_connection_valid(:stargates, ...).
"""
test "Low-Sec to Hi-Sec with [:wormholes] is valid when no stargate exists (wormhole connection)" do
# When there's no stargate between low-sec and hi-sec, the jump must be through a wormhole
# With [:wormholes] scope, this wormhole connection should be valid
#
# The test systems @ls_system_id and @hs_system_id don't have a known stargate between them
# (they're test systems not in the EVE jump database), so this should be treated as a wormhole
result = ConnectionsImpl.is_connection_valid([:wormholes], @ls_system_id, @hs_system_id)
# Connection is valid because no stargate exists - it's a wormhole connection
assert result == true,
"K-space to K-space with [:wormholes] should be valid when no stargate exists"
end
test "Hi-Sec to Low-Sec with [:wormholes] is valid when no stargate exists" do
# Test the reverse direction
result = ConnectionsImpl.is_connection_valid([:wormholes], @hs_system_id, @ls_system_id)
assert result == true,
"Hi-Sec to Low-Sec with [:wormholes] should be valid when no stargate exists"
end
test "Null-Sec to Hi-Sec with [:wormholes] is valid when no stargate exists" do
# Null to Hi-Sec through wormhole
result = ConnectionsImpl.is_connection_valid([:wormholes], @ns_system_id, @hs_system_id)
assert result == true,
"Null-Sec to Hi-Sec with [:wormholes] should be valid when no stargate exists"
end
test "Low-Sec to Null-Sec with [:wormholes] is valid when no stargate exists" do
# Low to Null through wormhole
result = ConnectionsImpl.is_connection_valid([:wormholes], @ls_system_id, @ns_system_id)
assert result == true,
"Low-Sec to Null-Sec with [:wormholes] should be valid when no stargate exists"
end
test "Pochven to Hi-Sec with [:wormholes] is valid when no stargate exists" do
# Pochven has special wormhole connections to k-space
result = ConnectionsImpl.is_connection_valid([:wormholes], @pochven_id, @hs_system_id)
assert result == true,
"Pochven to Hi-Sec with [:wormholes] should be valid when no stargate exists"
end
end
end