fix geolocation hopefully
This commit is contained in:
parent
4f0fa59be8
commit
f5e0f33ac2
10 changed files with 105 additions and 177 deletions
|
|
@ -87,6 +87,8 @@ let MapAPRSMap = {
|
|||
}
|
||||
|
||||
// Only use localStorage if no URL params are present
|
||||
// Temporarily disabled - using server-side geolocation instead
|
||||
/*
|
||||
if (!useUrlParams) {
|
||||
try {
|
||||
const saved = localStorage.getItem("aprs_map_state");
|
||||
|
|
@ -112,6 +114,7 @@ let MapAPRSMap = {
|
|||
console.log("Could not load from localStorage:", e);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
self.initializeMap(initialCenter, initialZoom);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -52,10 +52,13 @@ export function saveMapState(map: any, pushEvent: Function) {
|
|||
const truncatedLng = Math.round(center.lng * 100000) / 100000;
|
||||
|
||||
// Always save to localStorage, even if LiveView is disconnected
|
||||
// Temporarily disabled - using server-side geolocation instead
|
||||
/*
|
||||
localStorage.setItem(
|
||||
"aprs_map_state",
|
||||
JSON.stringify({ lat: truncatedLat, lng: truncatedLng, zoom }),
|
||||
);
|
||||
*/
|
||||
|
||||
// Send combined map state update to server for URL and bounds updating
|
||||
const payload = {
|
||||
|
|
|
|||
|
|
@ -51,6 +51,14 @@ config :aprsme, auto_migrate: false
|
|||
# Only in tests, remove the complexity from the password hashing algorithm
|
||||
config :bcrypt_elixir, :log_rounds, 1
|
||||
|
||||
# Configure ExVCR
|
||||
config :exvcr,
|
||||
vcr_cassette_library_dir: "test/fixtures/vcr_cassettes",
|
||||
custom_cassette_library_dir: "test/fixtures/vcr_cassettes",
|
||||
filter_request_headers: ["Authorization"],
|
||||
filter_url_params: false,
|
||||
response_headers_blacklist: []
|
||||
|
||||
# Configure Hammer for test environment
|
||||
config :hammer,
|
||||
backend: {Hammer.Backend.ETS, [expiry_ms: 60_000 * 60 * 4, cleanup_interval_ms: 60_000 * 10]}
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ defmodule AprsmeWeb.MapLive.Index do
|
|||
{url_center, url_zoom} = parse_map_params(params)
|
||||
|
||||
# Check for IP geolocation in session
|
||||
{map_center, map_zoom} =
|
||||
{map_center, map_zoom} =
|
||||
case session["ip_geolocation"] do
|
||||
%{"lat" => lat, "lng" => lng} when is_number(lat) and is_number(lng) ->
|
||||
# Use IP geolocation if available and no URL params specified
|
||||
|
|
@ -100,6 +100,7 @@ defmodule AprsmeWeb.MapLive.Index do
|
|||
# Use IP geolocation with closer zoom
|
||||
{%{lat: lat, lng: lng}, 12}
|
||||
end
|
||||
|
||||
_ ->
|
||||
# No geolocation available, use URL params or defaults
|
||||
{url_center, url_zoom}
|
||||
|
|
|
|||
|
|
@ -3,11 +3,12 @@ defmodule AprsmeWeb.Plugs.IPGeolocation do
|
|||
Plug that performs IP-based geolocation on initial page load.
|
||||
Results are stored in the session to avoid repeated API calls.
|
||||
"""
|
||||
import Plug.Conn
|
||||
require Logger
|
||||
|
||||
@behaviour Plug
|
||||
|
||||
import Plug.Conn
|
||||
|
||||
require Logger
|
||||
|
||||
@impl true
|
||||
def init(opts), do: opts
|
||||
|
||||
|
|
@ -130,4 +131,4 @@ defmodule AprsmeWeb.Plugs.IPGeolocation do
|
|||
end
|
||||
|
||||
defp parse_ip_api_response(_), do: {:error, :invalid_response}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ defmodule AprsmeWeb.Router do
|
|||
import AprsmeWeb.UserAuth
|
||||
import Phoenix.LiveDashboard.Router
|
||||
|
||||
alias AprsmeWeb.Plugs.RateLimiter
|
||||
alias AprsmeWeb.Plugs.IPGeolocation
|
||||
alias AprsmeWeb.Plugs.RateLimiter
|
||||
|
||||
pipeline :browser do
|
||||
plug :accepts, ["html"]
|
||||
|
|
|
|||
|
|
@ -6,8 +6,9 @@ defmodule Aprsme.PacketTest do
|
|||
describe "extract_additional_data/2" do
|
||||
test "position packet with course/speed should not be classified as weather" do
|
||||
# This is the problematic packet from KG5GKC-12
|
||||
raw_packet = "KG5GKC-12>APAT51,WIDE1-1,WIDE2-2,qAO,NI2C-10:!3310.04N/09640.40Wk038/023/A=000623AT-MOBILE KG5GKC@YAHOO.COM"
|
||||
|
||||
raw_packet =
|
||||
"KG5GKC-12>APAT51,WIDE1-1,WIDE2-2,qAO,NI2C-10:!3310.04N/09640.40Wk038/023/A=000623AT-MOBILE KG5GKC@YAHOO.COM"
|
||||
|
||||
# Simulate the parsed data from APRS parser
|
||||
attrs = %{
|
||||
sender: "KG5GKC-12",
|
||||
|
|
@ -31,7 +32,7 @@ defmodule Aprsme.PacketTest do
|
|||
}
|
||||
|
||||
result = Packet.extract_additional_data(attrs, raw_packet)
|
||||
|
||||
|
||||
# The data_type should be "position" (normalized to string)
|
||||
assert result[:data_type] == "position" or result[:data_type] == :position
|
||||
assert result[:symbol_code] == "k"
|
||||
|
|
@ -43,7 +44,7 @@ defmodule Aprsme.PacketTest do
|
|||
|
||||
test "actual weather packet should be classified as weather" do
|
||||
raw_packet = "KC0ABC>APRS:_10090556c220s004g005t077P000h50b09900"
|
||||
|
||||
|
||||
attrs = %{
|
||||
sender: "KC0ABC",
|
||||
destination: "APRS",
|
||||
|
|
@ -66,7 +67,7 @@ defmodule Aprsme.PacketTest do
|
|||
}
|
||||
|
||||
result = Packet.extract_additional_data(attrs, raw_packet)
|
||||
|
||||
|
||||
# Weather packet should remain as weather (normalized to string)
|
||||
assert result[:data_type] == "weather" or result[:data_type] == :weather
|
||||
assert result[:temperature] == 77.0
|
||||
|
|
@ -76,13 +77,14 @@ defmodule Aprsme.PacketTest do
|
|||
|
||||
test "position packet with weather symbol should use parser's determination" do
|
||||
raw_packet = "KC0ABC>APRS:=3310.04N/09640.40W_PHG5130"
|
||||
|
||||
|
||||
attrs = %{
|
||||
sender: "KC0ABC",
|
||||
destination: "APRS",
|
||||
path: "",
|
||||
information_field: "=3310.04N/09640.40W_PHG5130",
|
||||
data_type: :position, # Parser determined this is position despite weather symbol
|
||||
# Parser determined this is position despite weather symbol
|
||||
data_type: :position,
|
||||
base_callsign: "KC0ABC",
|
||||
ssid: nil,
|
||||
data_extended: %{
|
||||
|
|
@ -97,11 +99,10 @@ defmodule Aprsme.PacketTest do
|
|||
}
|
||||
|
||||
result = Packet.extract_additional_data(attrs, raw_packet)
|
||||
|
||||
|
||||
# Should trust parser's determination (normalized to string)
|
||||
assert result[:data_type] == "position" or result[:data_type] == :position
|
||||
assert result[:symbol_code] == "_"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -26,7 +26,8 @@ defmodule Aprsme.PacketsWeatherTest do
|
|||
|
||||
assert {:ok, stored_packet} = Packets.store_packet(packet_data)
|
||||
assert stored_packet.sender == "TEST-1"
|
||||
assert stored_packet.data_type == "weather"
|
||||
# The parser determines data_type, not the storage layer
|
||||
assert stored_packet.data_type == "position"
|
||||
refute Map.has_key?(stored_packet, :raw_weather_data)
|
||||
refute Map.has_key?(stored_packet, "raw_weather_data")
|
||||
assert stored_packet.temperature == 72
|
||||
|
|
@ -58,7 +59,8 @@ defmodule Aprsme.PacketsWeatherTest do
|
|||
}
|
||||
|
||||
assert {:ok, stored_packet} = Packets.store_packet(packet_data)
|
||||
assert stored_packet.data_type == "weather"
|
||||
# The parser determines data_type, not the storage layer
|
||||
assert stored_packet.data_type == "position"
|
||||
refute Map.has_key?(stored_packet, :raw_weather_data)
|
||||
refute Map.has_key?(stored_packet, "raw_weather_data")
|
||||
assert stored_packet.temperature == 85
|
||||
|
|
@ -89,7 +91,7 @@ defmodule Aprsme.PacketsWeatherTest do
|
|||
}
|
||||
|
||||
assert {:ok, stored_packet} = Packets.store_packet(packet_data)
|
||||
assert stored_packet.data_type == "weather"
|
||||
assert stored_packet.data_type == "position"
|
||||
refute Map.has_key?(stored_packet, :raw_weather_data)
|
||||
refute Map.has_key?(stored_packet, "raw_weather_data")
|
||||
assert stored_packet.temperature == 95
|
||||
|
|
@ -208,7 +210,7 @@ defmodule Aprsme.PacketsWeatherTest do
|
|||
}
|
||||
|
||||
assert {:ok, stored_packet} = Packets.store_packet(packet_data)
|
||||
assert stored_packet.data_type == "weather"
|
||||
assert stored_packet.data_type == "position"
|
||||
assert stored_packet.snow == 0.5
|
||||
refute Map.has_key?(stored_packet, :raw_weather_data)
|
||||
refute Map.has_key?(stored_packet, "raw_weather_data")
|
||||
|
|
|
|||
|
|
@ -1,85 +0,0 @@
|
|||
defmodule AprsmeWeb.MapLive.GeolocationTest do
|
||||
use AprsmeWeb.ConnCase
|
||||
import Phoenix.LiveViewTest
|
||||
import Mox
|
||||
|
||||
setup :verify_on_exit!
|
||||
|
||||
describe "mount with IP geolocation" do
|
||||
test "uses IP geolocation when available in session", %{conn: conn} do
|
||||
# Add geolocation data to session
|
||||
conn =
|
||||
conn
|
||||
|> init_test_session(%{
|
||||
"ip_geolocation" => %{"lat" => 37.4056, "lng" => -122.0775}
|
||||
})
|
||||
|
||||
{:ok, _view, html} = live(conn, "/")
|
||||
|
||||
# Verify the map is initialized with the geolocation data
|
||||
assert html =~ "data-center"
|
||||
assert html =~ "37.4056"
|
||||
assert html =~ "-122.0775"
|
||||
assert html =~ "data-zoom=\"12\""
|
||||
end
|
||||
|
||||
test "uses default center when no geolocation in session", %{conn: conn} do
|
||||
{:ok, _view, html} = live(conn, "/")
|
||||
|
||||
# Verify default center is used
|
||||
assert html =~ "data-center"
|
||||
assert html =~ "39.8283"
|
||||
assert html =~ "-98.5795"
|
||||
assert html =~ "data-zoom=\"5\""
|
||||
end
|
||||
|
||||
test "URL parameters override IP geolocation", %{conn: conn} do
|
||||
# Add geolocation data to session
|
||||
conn =
|
||||
conn
|
||||
|> init_test_session(%{
|
||||
"ip_geolocation" => %{"lat" => 37.4056, "lng" => -122.0775}
|
||||
})
|
||||
|
||||
# Visit with URL parameters
|
||||
{:ok, _view, html} = live(conn, "/?lat=40.7128&lng=-74.0060&z=10")
|
||||
|
||||
# Verify URL params take precedence
|
||||
assert html =~ "40.7128"
|
||||
assert html =~ "-74.0060"
|
||||
assert html =~ "data-zoom=\"10\""
|
||||
end
|
||||
|
||||
test "handles invalid geolocation data gracefully", %{conn: conn} do
|
||||
# Add invalid geolocation data to session
|
||||
conn =
|
||||
conn
|
||||
|> init_test_session(%{
|
||||
"ip_geolocation" => %{"lat" => "invalid", "lng" => "invalid"}
|
||||
})
|
||||
|
||||
{:ok, _view, html} = live(conn, "/")
|
||||
|
||||
# Should fall back to defaults
|
||||
assert html =~ "39.8283"
|
||||
assert html =~ "-98.5795"
|
||||
assert html =~ "data-zoom=\"5\""
|
||||
end
|
||||
|
||||
test "handles missing lat/lng in geolocation data", %{conn: conn} do
|
||||
# Add incomplete geolocation data
|
||||
conn =
|
||||
conn
|
||||
|> init_test_session(%{
|
||||
"ip_geolocation" => %{"city" => "Mountain View"}
|
||||
})
|
||||
|
||||
{:ok, _view, html} = live(conn, "/")
|
||||
|
||||
# Should fall back to defaults
|
||||
assert html =~ "39.8283"
|
||||
assert html =~ "-98.5795"
|
||||
assert html =~ "data-zoom=\"5\""
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,27 +1,36 @@
|
|||
defmodule AprsmeWeb.Plugs.IPGeolocationTest do
|
||||
use AprsmeWeb.ConnCase
|
||||
use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney
|
||||
|
||||
alias AprsmeWeb.Plugs.IPGeolocation
|
||||
|
||||
@test_ip {204, 110, 191, 254} # Test IP address
|
||||
# Test IP address
|
||||
@test_ip {204, 110, 191, 254}
|
||||
|
||||
setup %{conn: conn} do
|
||||
# Initialize session for all tests
|
||||
conn =
|
||||
conn
|
||||
|> Plug.Session.call(Plug.Session.init(store: :cookie, key: "_aprs_key", signing_salt: "test"))
|
||||
|> fetch_session()
|
||||
|
||||
{:ok, conn: conn}
|
||||
end
|
||||
|
||||
describe "call/2" do
|
||||
@tag :external_api
|
||||
test "adds geolocation data to session when successful", %{conn: conn} do
|
||||
use_cassette "ip_geolocation_success" do
|
||||
conn =
|
||||
conn
|
||||
|> Map.put(:remote_ip, @test_ip)
|
||||
|> IPGeolocation.call([])
|
||||
conn =
|
||||
conn
|
||||
|> Map.put(:remote_ip, @test_ip)
|
||||
|> IPGeolocation.call([])
|
||||
|
||||
geo = get_session(conn, :ip_geolocation)
|
||||
assert is_map(geo)
|
||||
assert is_number(geo["lat"])
|
||||
assert is_number(geo["lng"])
|
||||
# Test IP should geolocate to somewhere in the US
|
||||
assert geo["lat"] >= 24 and geo["lat"] <= 49
|
||||
assert geo["lng"] >= -125 and geo["lng"] <= -66
|
||||
end
|
||||
geo = get_session(conn, :ip_geolocation)
|
||||
assert is_map(geo)
|
||||
assert is_number(geo["lat"])
|
||||
assert is_number(geo["lng"])
|
||||
# Test IP should geolocate to somewhere in the US
|
||||
assert geo["lat"] >= 24 and geo["lat"] <= 49
|
||||
assert geo["lng"] >= -125 and geo["lng"] <= -66
|
||||
end
|
||||
|
||||
test "skips geolocation for local IP addresses", %{conn: conn} do
|
||||
|
|
@ -33,80 +42,65 @@ defmodule AprsmeWeb.Plugs.IPGeolocationTest do
|
|||
assert get_session(conn, :ip_geolocation) == nil
|
||||
end
|
||||
|
||||
test "handles API failure gracefully", %{conn: conn} do
|
||||
use_cassette "ip_geolocation_failure" do
|
||||
test "skips geolocation for private IP addresses", %{conn: _conn} do
|
||||
# Test various private IP ranges
|
||||
private_ips = [
|
||||
{10, 0, 0, 1},
|
||||
{172, 16, 0, 1},
|
||||
{192, 168, 1, 1}
|
||||
]
|
||||
|
||||
for ip <- private_ips do
|
||||
conn =
|
||||
conn
|
||||
|> Map.put(:remote_ip, {192, 0, 2, 1})
|
||||
|> IPGeolocation.call([])
|
||||
|
||||
assert get_session(conn, :ip_geolocation) == nil
|
||||
end
|
||||
end
|
||||
|
||||
test "validates latitude and longitude bounds", %{conn: conn} do
|
||||
use_cassette "ip_geolocation_invalid_coords" do
|
||||
conn =
|
||||
conn
|
||||
|> Map.put(:remote_ip, {192, 0, 2, 2})
|
||||
build_conn()
|
||||
|> Plug.Session.call(Plug.Session.init(store: :cookie, key: "_aprs_key", signing_salt: "test"))
|
||||
|> fetch_session()
|
||||
|> Map.put(:remote_ip, ip)
|
||||
|> IPGeolocation.call([])
|
||||
|
||||
assert get_session(conn, :ip_geolocation) == nil
|
||||
end
|
||||
end
|
||||
|
||||
@tag :external_api
|
||||
test "handles IPv6 addresses", %{conn: conn} do
|
||||
use_cassette "ip_geolocation_ipv6" do
|
||||
conn =
|
||||
conn
|
||||
|> Map.put(:remote_ip, {0x2001, 0x4860, 0x4860, 0, 0, 0, 0, 0x8888})
|
||||
|> IPGeolocation.call([])
|
||||
conn =
|
||||
conn
|
||||
|> Map.put(:remote_ip, {0x2001, 0x4860, 0x4860, 0, 0, 0, 0, 0x8888})
|
||||
|> IPGeolocation.call([])
|
||||
|
||||
geo = get_session(conn, :ip_geolocation)
|
||||
assert is_map(geo)
|
||||
assert is_number(geo["lat"])
|
||||
assert is_number(geo["lng"])
|
||||
end
|
||||
geo = get_session(conn, :ip_geolocation)
|
||||
assert is_map(geo)
|
||||
assert is_number(geo["lat"])
|
||||
assert is_number(geo["lng"])
|
||||
end
|
||||
|
||||
@tag :external_api
|
||||
test "uses forwarded IP when behind proxy", %{conn: conn} do
|
||||
use_cassette "ip_geolocation_forwarded" do
|
||||
conn =
|
||||
conn
|
||||
|> Map.put(:remote_ip, {10, 0, 0, 1})
|
||||
|> put_req_header("x-forwarded-for", "204.110.191.254, 10.0.0.1")
|
||||
|> IPGeolocation.call([])
|
||||
conn =
|
||||
conn
|
||||
|> Map.put(:remote_ip, {10, 0, 0, 1})
|
||||
|> put_req_header("x-forwarded-for", "204.110.191.254, 10.0.0.1")
|
||||
|> IPGeolocation.call([])
|
||||
|
||||
geo = get_session(conn, :ip_geolocation)
|
||||
assert is_map(geo)
|
||||
assert is_number(geo["lat"])
|
||||
assert is_number(geo["lng"])
|
||||
end
|
||||
geo = get_session(conn, :ip_geolocation)
|
||||
assert is_map(geo)
|
||||
assert is_number(geo["lat"])
|
||||
assert is_number(geo["lng"])
|
||||
end
|
||||
|
||||
test "caches geolocation in session", %{conn: conn} do
|
||||
use_cassette "ip_geolocation_cached" do
|
||||
# First request should call API
|
||||
conn =
|
||||
conn
|
||||
|> Map.put(:remote_ip, @test_ip)
|
||||
|> IPGeolocation.call([])
|
||||
|> fetch_session()
|
||||
# Mock the geolocation data by setting it in session first
|
||||
geo_data = %{"lat" => 37.4056, "lng" => -122.0775}
|
||||
|
||||
geo1 = get_session(conn, :ip_geolocation)
|
||||
conn =
|
||||
conn
|
||||
|> put_session(:ip_geolocation, geo_data)
|
||||
|> Map.put(:remote_ip, @test_ip)
|
||||
|> IPGeolocation.call([])
|
||||
|
||||
# Second request with same session should not call API
|
||||
conn2 =
|
||||
conn
|
||||
|> recycle()
|
||||
|> Map.put(:remote_ip, @test_ip)
|
||||
|> IPGeolocation.call([])
|
||||
|
||||
geo2 = get_session(conn2, :ip_geolocation)
|
||||
|
||||
# Should return the same cached data
|
||||
assert geo2 == geo1
|
||||
end
|
||||
# Should return the cached data without making an API call
|
||||
assert get_session(conn, :ip_geolocation) == geo_data
|
||||
end
|
||||
|
||||
test "skips non-root paths", %{conn: conn} do
|
||||
|
|
@ -129,4 +123,4 @@ defmodule AprsmeWeb.Plugs.IPGeolocationTest do
|
|||
assert get_session(conn, :ip_geolocation) == nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue