fix: resolve all credo issues — 0 warnings, 0 refactoring, 0 readability issues
- Add ex_slop credo plugin with all 40 checks - Remove DualKeyAccess patterns across codebase — atom-only access - Fix LengthInGuard, LengthComparison, ListLast, ReduceMapPut in lib/ - Disable LengthComparison for test files - Remove obvious comments and narrator comments (~50) - Add missing aliases for fully-qualified modules - Rewrite boilerplate docs in test/support - Add normalize_keys helpers at API boundaries
This commit is contained in:
parent
8b62afa36b
commit
2f09b590e2
64 changed files with 237 additions and 230 deletions
51
.credo.exs
51
.credo.exs
|
|
@ -36,7 +36,7 @@
|
|||
#
|
||||
# Load and configure plugins here:
|
||||
#
|
||||
plugins: [],
|
||||
plugins: [{ExSlop, []}],
|
||||
#
|
||||
# If you create your own checks, you must specify the source files for
|
||||
# them here, so they can be loaded by Credo before running the analysis.
|
||||
|
|
@ -158,6 +158,55 @@
|
|||
{Credo.Check.Warning.UnsafeExec, []},
|
||||
|
||||
#
|
||||
## ExSlop Checks
|
||||
#
|
||||
# Readability
|
||||
{ExSlop.Check.Readability.BoilerplateDocParams, []},
|
||||
{ExSlop.Check.Readability.DocFalseOnPublicFunction, []},
|
||||
{ExSlop.Check.Readability.NarratorComment, []},
|
||||
{ExSlop.Check.Readability.NarratorDoc, []},
|
||||
{ExSlop.Check.Readability.ObviousComment, []},
|
||||
{ExSlop.Check.Readability.StepComment, []},
|
||||
{ExSlop.Check.Readability.UnaliasedModuleUse, []},
|
||||
|
||||
# Refactor
|
||||
{ExSlop.Check.Refactor.CaseTrueFalse, []},
|
||||
{ExSlop.Check.Refactor.ExplicitSumReduce, []},
|
||||
{ExSlop.Check.Refactor.FilterNil, []},
|
||||
{ExSlop.Check.Refactor.FlatMapFilter, []},
|
||||
{ExSlop.Check.Refactor.GraphemesLength, []},
|
||||
{ExSlop.Check.Refactor.IdentityMap, []},
|
||||
{ExSlop.Check.Refactor.IdentityPassthrough, []},
|
||||
{ExSlop.Check.Refactor.LengthComparison, [files: %{excluded: [~r|test/|]}]},
|
||||
{ExSlop.Check.Refactor.LengthInGuard, []},
|
||||
{ExSlop.Check.Refactor.ListFold, []},
|
||||
{ExSlop.Check.Refactor.ListLast, []},
|
||||
{ExSlop.Check.Refactor.ManualStringReverse, []},
|
||||
{ExSlop.Check.Refactor.MapIntoLiteral, []},
|
||||
{ExSlop.Check.Refactor.PreferEnumSlice, []},
|
||||
{ExSlop.Check.Refactor.ReduceAsMap, []},
|
||||
{ExSlop.Check.Refactor.ReduceMapPut, []},
|
||||
{ExSlop.Check.Refactor.RedundantBooleanIf, []},
|
||||
{ExSlop.Check.Refactor.RedundantEnumJoinSeparator, []},
|
||||
{ExSlop.Check.Refactor.RejectNil, []},
|
||||
{ExSlop.Check.Refactor.SortForTopK, []},
|
||||
{ExSlop.Check.Refactor.SortThenAt, []},
|
||||
{ExSlop.Check.Refactor.SortThenReverse, []},
|
||||
{ExSlop.Check.Refactor.StringConcatInReduce, []},
|
||||
{ExSlop.Check.Refactor.TryRescueWithSafeAlternative, []},
|
||||
{ExSlop.Check.Refactor.UseMapJoin, []},
|
||||
{ExSlop.Check.Refactor.WithIdentityDo, []},
|
||||
{ExSlop.Check.Refactor.WithIdentityElse, []},
|
||||
|
||||
# Warning
|
||||
{ExSlop.Check.Warning.BlanketRescue, []},
|
||||
{ExSlop.Check.Warning.DualKeyAccess, []},
|
||||
{ExSlop.Check.Warning.GenserverAsKvStore, []},
|
||||
{ExSlop.Check.Warning.PathExpandPriv, []},
|
||||
{ExSlop.Check.Warning.QueryInEnumMap, []},
|
||||
{ExSlop.Check.Warning.RepoAllThenFilter, []},
|
||||
{ExSlop.Check.Warning.RescueWithoutReraise, []},
|
||||
|
||||
## Jump.CredoChecks
|
||||
#
|
||||
{Jump.CredoChecks.AssertElementSelectorCanNeverFail, []},
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ defmodule Aprsme.Accounts do
|
|||
alias Aprsme.Accounts.UserNotifier
|
||||
alias Aprsme.Accounts.UserToken
|
||||
alias Aprsme.Repo
|
||||
alias Ecto.Multi
|
||||
|
||||
## Database getters
|
||||
|
||||
|
|
@ -168,9 +169,9 @@ defmodule Aprsme.Accounts do
|
|||
|> User.email_changeset(%{email: email})
|
||||
|> User.confirm_changeset()
|
||||
|
||||
Ecto.Multi.new()
|
||||
|> Ecto.Multi.update(:user, changeset)
|
||||
|> Ecto.Multi.delete_all(:tokens, UserToken.user_and_contexts_query(user, [context]))
|
||||
Multi.new()
|
||||
|> Multi.update(:user, changeset)
|
||||
|> Multi.delete_all(:tokens, UserToken.user_and_contexts_query(user, [context]))
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
|
|
@ -225,8 +226,8 @@ defmodule Aprsme.Accounts do
|
|||
|> User.callsign_changeset(attrs)
|
||||
|> User.validate_current_password(password)
|
||||
|
||||
Ecto.Multi.new()
|
||||
|> Ecto.Multi.update(:user, changeset)
|
||||
Multi.new()
|
||||
|> Multi.update(:user, changeset)
|
||||
|> Repo.transaction()
|
||||
|> transaction_unwrap()
|
||||
end
|
||||
|
|
@ -265,9 +266,9 @@ defmodule Aprsme.Accounts do
|
|||
|> User.password_changeset(attrs)
|
||||
|> User.validate_current_password(password)
|
||||
|
||||
Ecto.Multi.new()
|
||||
|> Ecto.Multi.update(:user, changeset)
|
||||
|> Ecto.Multi.delete_all(:tokens, UserToken.user_and_contexts_query(user, :all))
|
||||
Multi.new()
|
||||
|> Multi.update(:user, changeset)
|
||||
|> Multi.delete_all(:tokens, UserToken.user_and_contexts_query(user, :all))
|
||||
|> Repo.transaction()
|
||||
|> transaction_unwrap()
|
||||
end
|
||||
|
|
@ -344,9 +345,9 @@ defmodule Aprsme.Accounts do
|
|||
end
|
||||
|
||||
defp confirm_user_multi(user) do
|
||||
Ecto.Multi.new()
|
||||
|> Ecto.Multi.update(:user, User.confirm_changeset(user))
|
||||
|> Ecto.Multi.delete_all(:tokens, UserToken.user_and_contexts_query(user, ["confirm"]))
|
||||
Multi.new()
|
||||
|> Multi.update(:user, User.confirm_changeset(user))
|
||||
|> Multi.delete_all(:tokens, UserToken.user_and_contexts_query(user, ["confirm"]))
|
||||
end
|
||||
|
||||
## Reset password
|
||||
|
|
@ -404,9 +405,9 @@ defmodule Aprsme.Accounts do
|
|||
|
||||
"""
|
||||
def reset_user_password(user, attrs) do
|
||||
Ecto.Multi.new()
|
||||
|> Ecto.Multi.update(:user, User.password_changeset(user, attrs))
|
||||
|> Ecto.Multi.delete_all(:tokens, UserToken.user_and_contexts_query(user, :all))
|
||||
Multi.new()
|
||||
|> Multi.update(:user, User.password_changeset(user, attrs))
|
||||
|> Multi.delete_all(:tokens, UserToken.user_and_contexts_query(user, :all))
|
||||
|> Repo.transaction()
|
||||
|> transaction_unwrap()
|
||||
end
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ defmodule Aprsme.BroadcastTaskSupervisor do
|
|||
pool_size = System.schedulers_online() * 2
|
||||
|
||||
children = [
|
||||
# Create a Task.Supervisor with a specific name
|
||||
{Task.Supervisor, name: @pool_name}
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -151,9 +151,7 @@ defmodule Aprsme.EncodingUtils do
|
|||
def sanitize_packet_strings(list) when is_list(list), do: Enum.map(list, &sanitize_packet_strings/1)
|
||||
|
||||
def sanitize_packet_strings(map) when is_map(map) do
|
||||
Enum.reduce(map, %{}, fn {key, value}, acc ->
|
||||
Map.put(acc, key, sanitize_packet_strings(value))
|
||||
end)
|
||||
Map.new(map, fn {key, value} -> {key, sanitize_packet_strings(value)} end)
|
||||
end
|
||||
|
||||
def sanitize_packet_strings(binary) when is_binary(binary) do
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ defmodule Aprsme.ErrorNotifier do
|
|||
|
||||
import Swoosh.Email
|
||||
|
||||
alias Phoenix.HTML
|
||||
|
||||
require Logger
|
||||
|
||||
@doc """
|
||||
|
|
@ -90,19 +92,18 @@ defmodule Aprsme.ErrorNotifier do
|
|||
view = occurrence.context["live_view.view"] || "Unknown view"
|
||||
path = occurrence.context["request.path"] || "Unknown path"
|
||||
|
||||
# Get the error URL
|
||||
error_url = "https://aprs.me/dev/error-tracker/errors/#{occurrence.error_id}"
|
||||
|
||||
# Escape all user-controlled fields to prevent XSS
|
||||
escaped_error_id = occurrence.error_id |> Phoenix.HTML.html_escape() |> Phoenix.HTML.safe_to_string()
|
||||
escaped_error_id = occurrence.error_id |> HTML.html_escape() |> HTML.safe_to_string()
|
||||
|
||||
escaped_reason =
|
||||
occurrence.reason |> String.slice(0..199) |> Phoenix.HTML.html_escape() |> Phoenix.HTML.safe_to_string()
|
||||
occurrence.reason |> String.slice(0..199) |> HTML.html_escape() |> HTML.safe_to_string()
|
||||
|
||||
escaped_location = error_location |> Phoenix.HTML.html_escape() |> Phoenix.HTML.safe_to_string()
|
||||
escaped_view = view |> Phoenix.HTML.html_escape() |> Phoenix.HTML.safe_to_string()
|
||||
escaped_path = path |> Phoenix.HTML.html_escape() |> Phoenix.HTML.safe_to_string()
|
||||
escaped_url = error_url |> Phoenix.HTML.html_escape() |> Phoenix.HTML.safe_to_string()
|
||||
escaped_location = error_location |> HTML.html_escape() |> HTML.safe_to_string()
|
||||
escaped_view = view |> HTML.html_escape() |> HTML.safe_to_string()
|
||||
escaped_path = path |> HTML.html_escape() |> HTML.safe_to_string()
|
||||
escaped_url = error_url |> HTML.html_escape() |> HTML.safe_to_string()
|
||||
|
||||
"""
|
||||
<div style="max-width: 600px; margin: 0 auto; padding: 20px; font-family: system-ui, -apple-system, sans-serif;">
|
||||
|
|
|
|||
|
|
@ -598,7 +598,6 @@ defmodule Aprsme.Is do
|
|||
def dispatch(message) do
|
||||
case Aprs.parse(message) do
|
||||
{:ok, parsed_message} ->
|
||||
# Store the packet in the database for future replay
|
||||
# Use GenStage pipeline for efficient batch processing
|
||||
try do
|
||||
# Set received_at and convert struct to map — PacketConsumer.prepare_packet_for_insert
|
||||
|
|
|
|||
|
|
@ -330,7 +330,7 @@ defmodule Aprsme.Packet do
|
|||
"""
|
||||
@spec extract_additional_data(map(), String.t() | nil) :: map()
|
||||
def extract_additional_data(attrs, raw_packet \\ nil) do
|
||||
data_extended = attrs[:data_extended] || attrs["data_extended"] || %{}
|
||||
data_extended = attrs[:data_extended] || %{}
|
||||
|
||||
# Start with the base attributes and add the raw packet
|
||||
base_attrs = Map.put(attrs, :raw_packet, raw_packet)
|
||||
|
|
@ -373,8 +373,8 @@ defmodule Aprsme.Packet do
|
|||
|
||||
defp add_symbol_data(base_attrs, attrs) do
|
||||
base_attrs
|
||||
|> maybe_put(:symbol_code, attrs[:symbol_code] || attrs["symbol_code"])
|
||||
|> maybe_put(:symbol_table_id, attrs[:symbol_table_id] || attrs["symbol_table_id"])
|
||||
|> maybe_put(:symbol_code, attrs[:symbol_code])
|
||||
|> maybe_put(:symbol_table_id, attrs[:symbol_table_id])
|
||||
end
|
||||
|
||||
defp merge_extracted_data(base_attrs, additional_data, attrs) do
|
||||
|
|
@ -439,7 +439,7 @@ defmodule Aprsme.Packet do
|
|||
data_extended = Map.delete(data_extended, "raw_weather_data")
|
||||
|
||||
# Handle nested data_extended structures
|
||||
nested_data_extended = data_extended[:data_extended] || data_extended["data_extended"]
|
||||
nested_data_extended = data_extended[:data_extended]
|
||||
|
||||
# Merge data from both levels
|
||||
combined_data =
|
||||
|
|
@ -469,24 +469,24 @@ defmodule Aprsme.Packet do
|
|||
|
||||
defp put_symbol_fields(map, data_extended) do
|
||||
# First try to get symbol data from the data_extended map
|
||||
symbol_code = data_extended[:symbol_code] || data_extended["symbol_code"]
|
||||
symbol_table_id = data_extended[:symbol_table_id] || data_extended["symbol_table_id"]
|
||||
symbol_code = data_extended[:symbol_code]
|
||||
symbol_table_id = data_extended[:symbol_table_id]
|
||||
|
||||
# Clean comment by removing altitude and PHG data
|
||||
comment = clean_comment(data_extended[:comment] || data_extended["comment"])
|
||||
comment = clean_comment(data_extended[:comment])
|
||||
|
||||
map
|
||||
|> maybe_put(:symbol_code, symbol_code)
|
||||
|> maybe_put(:symbol_table_id, symbol_table_id)
|
||||
|> maybe_put(:comment, comment)
|
||||
|> maybe_put(:timestamp, data_extended[:timestamp] || data_extended["timestamp"])
|
||||
|> maybe_put(:timestamp, data_extended[:timestamp])
|
||||
|> maybe_put(
|
||||
:aprs_messaging,
|
||||
data_extended[:aprs_messaging?] || data_extended["aprs_messaging?"]
|
||||
data_extended[:aprs_messaging?]
|
||||
)
|
||||
|> maybe_put(:position_ambiguity, data_extended[:position_ambiguity] || data_extended["position_ambiguity"])
|
||||
|> maybe_put(:posresolution, data_extended[:posresolution] || data_extended["posresolution"])
|
||||
|> maybe_put(:format, normalize_format_field(data_extended[:format] || data_extended["format"]))
|
||||
|> maybe_put(:position_ambiguity, data_extended[:position_ambiguity])
|
||||
|> maybe_put(:posresolution, data_extended[:posresolution])
|
||||
|> maybe_put(:format, normalize_format_field(data_extended[:format]))
|
||||
end
|
||||
|
||||
defp put_weather_fields(map, data_extended) do
|
||||
|
|
@ -561,25 +561,25 @@ defmodule Aprsme.Packet do
|
|||
|
||||
defp put_message_fields(map, data_extended) do
|
||||
map
|
||||
|> maybe_put(:addressee, data_extended[:addressee] || data_extended["addressee"])
|
||||
|> maybe_put(:message_text, data_extended[:message_text] || data_extended["message_text"])
|
||||
|> maybe_put(:addressee, data_extended[:addressee])
|
||||
|> maybe_put(:message_text, data_extended[:message_text])
|
||||
|> maybe_put(
|
||||
:message_number,
|
||||
data_extended[:message_number] || data_extended["message_number"]
|
||||
data_extended[:message_number]
|
||||
)
|
||||
end
|
||||
|
||||
defp put_telemetry_fields(map, data_extended) do
|
||||
telemetry = data_extended[:telemetry] || data_extended["telemetry"] || data_extended
|
||||
telemetry = data_extended[:telemetry] || data_extended
|
||||
|
||||
map
|
||||
|> maybe_put(:telemetry_seq, parse_telemetry_seq(telemetry))
|
||||
|> maybe_put(:telemetry_vals, parse_telemetry_vals(telemetry))
|
||||
|> maybe_put(:telemetry_bits, telemetry[:bits] || telemetry["bits"])
|
||||
|> maybe_put(:telemetry_bits, telemetry[:bits])
|
||||
end
|
||||
|
||||
defp parse_telemetry_seq(telemetry) do
|
||||
case telemetry[:seq] || telemetry["seq"] do
|
||||
case telemetry[:seq] do
|
||||
nil -> nil
|
||||
seq when is_integer(seq) -> seq
|
||||
seq when is_binary(seq) -> parse_integer_string(seq)
|
||||
|
|
@ -588,7 +588,7 @@ defmodule Aprsme.Packet do
|
|||
end
|
||||
|
||||
defp parse_telemetry_vals(telemetry) do
|
||||
case telemetry[:vals] || telemetry["vals"] do
|
||||
case telemetry[:vals] do
|
||||
nil -> nil
|
||||
vals when is_list(vals) -> Enum.map(vals, &normalize_telemetry_value/1)
|
||||
_ -> nil
|
||||
|
|
@ -637,8 +637,8 @@ defmodule Aprsme.Packet do
|
|||
|> maybe_put(:course, mic_e_map[:heading])
|
||||
|> maybe_put(:speed, mic_e_map[:speed])
|
||||
# Use symbol data from MicE if available, otherwise use default car symbol
|
||||
|> maybe_put(:symbol_code, mic_e_map[:symbol_code] || mic_e_map["symbol_code"] || ">")
|
||||
|> maybe_put(:symbol_table_id, mic_e_map[:symbol_table_id] || mic_e_map["symbol_table_id"] || "/")
|
||||
|> maybe_put(:symbol_code, mic_e_map[:symbol_code] || ">")
|
||||
|> maybe_put(:symbol_table_id, mic_e_map[:symbol_table_id] || "/")
|
||||
end
|
||||
|
||||
# Extract weather data from various formats, including new wx field
|
||||
|
|
@ -646,16 +646,16 @@ defmodule Aprsme.Packet do
|
|||
weather_data = find_weather_data(data_extended)
|
||||
|
||||
# Check for dedicated wx field from improved parser
|
||||
wx_data = data_extended[:wx] || data_extended["wx"]
|
||||
wx_data = data_extended[:wx]
|
||||
|
||||
final_weather_data = wx_data || weather_data
|
||||
process_weather_data(attrs, final_weather_data)
|
||||
end
|
||||
|
||||
defp find_weather_data(data_extended) do
|
||||
data_extended[:weather] || data_extended["weather"] ||
|
||||
data_extended[:weather_report] || data_extended["weather_report"] ||
|
||||
data_extended[:raw_weather_data] || data_extended["raw_weather_data"]
|
||||
data_extended[:weather] ||
|
||||
data_extended[:weather_report] ||
|
||||
data_extended[:raw_weather_data]
|
||||
end
|
||||
|
||||
defp process_weather_data(attrs, weather_data) do
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ defmodule Aprsme.PacketConsumer do
|
|||
alias Aprsme.Cluster.PacketDistributor
|
||||
alias Aprsme.LogSanitizer
|
||||
alias Aprsme.Repo
|
||||
alias Phoenix.PubSub
|
||||
|
||||
require Logger
|
||||
|
||||
|
|
@ -386,14 +387,14 @@ defmodule Aprsme.PacketConsumer do
|
|||
# Replaces the `aprs_packets` pg_notify path (removed as a DB trigger).
|
||||
defp broadcast_legacy_topics(packet, routing_callsign, has_weather?) do
|
||||
# Hot path: PubSub.broadcast failures tolerated (logged by PubSub itself).
|
||||
_ = Phoenix.PubSub.broadcast(Aprsme.PubSub, "postgres:aprsme_packets", {:postgres_packet, packet})
|
||||
_ = PubSub.broadcast(Aprsme.PubSub, "postgres:aprsme_packets", {:postgres_packet, packet})
|
||||
|
||||
_ =
|
||||
if is_binary(routing_callsign) and routing_callsign != "" do
|
||||
_ = Phoenix.PubSub.broadcast(Aprsme.PubSub, "packets:#{routing_callsign}", {:postgres_packet, packet})
|
||||
_ = PubSub.broadcast(Aprsme.PubSub, "packets:#{routing_callsign}", {:postgres_packet, packet})
|
||||
|
||||
if has_weather? do
|
||||
Phoenix.PubSub.broadcast(Aprsme.PubSub, "weather:#{routing_callsign}", {:weather_packet, packet})
|
||||
PubSub.broadcast(Aprsme.PubSub, "weather:#{routing_callsign}", {:weather_packet, packet})
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -550,7 +551,7 @@ defmodule Aprsme.PacketConsumer do
|
|||
end
|
||||
|
||||
defp apply_item_fields(attrs) do
|
||||
item_name = Map.get(attrs, :itemname) || Map.get(attrs, "itemname")
|
||||
item_name = Map.get(attrs, :itemname)
|
||||
|
||||
attrs
|
||||
|> Map.put(:item_name, item_name)
|
||||
|
|
@ -583,7 +584,7 @@ defmodule Aprsme.PacketConsumer do
|
|||
attrs
|
||||
|> then(fn a ->
|
||||
# Convert aprs_messaging? to aprs_messaging
|
||||
case Map.get(a, :aprs_messaging?) || Map.get(a, "aprs_messaging?") do
|
||||
case Map.get(a, :aprs_messaging?) do
|
||||
nil ->
|
||||
a
|
||||
|
||||
|
|
@ -691,19 +692,13 @@ defmodule Aprsme.PacketConsumer do
|
|||
end
|
||||
|
||||
defp extract_lat_from_ext_map(ext_map) do
|
||||
ext_map[:latitude] || ext_map["latitude"] ||
|
||||
(Map.has_key?(ext_map, :position) &&
|
||||
(ext_map[:position][:latitude] || ext_map[:position]["latitude"])) ||
|
||||
(Map.has_key?(ext_map, "position") &&
|
||||
(ext_map["position"][:latitude] || ext_map["position"]["latitude"]))
|
||||
ext_map[:latitude] ||
|
||||
(Map.has_key?(ext_map, :position) && ext_map[:position][:latitude])
|
||||
end
|
||||
|
||||
defp extract_lon_from_ext_map(ext_map) do
|
||||
ext_map[:longitude] || ext_map["longitude"] ||
|
||||
(Map.has_key?(ext_map, :position) &&
|
||||
(ext_map[:position][:longitude] || ext_map[:position]["longitude"])) ||
|
||||
(Map.has_key?(ext_map, "position") &&
|
||||
(ext_map["position"][:longitude] || ext_map["position"]["longitude"]))
|
||||
ext_map[:longitude] ||
|
||||
(Map.has_key?(ext_map, :position) && ext_map[:position][:longitude])
|
||||
end
|
||||
|
||||
# extract_position already ran to_float; lat/lon are floats or nil here.
|
||||
|
|
@ -719,7 +714,6 @@ defmodule Aprsme.PacketConsumer do
|
|||
defp round_coord(n) when is_float(n), do: Float.round(n, 6)
|
||||
defp round_coord(_), do: nil
|
||||
|
||||
# Build the PostGIS point only for in-range coordinates.
|
||||
# Must run AFTER sanitize_packet_strings so the struct survives intact.
|
||||
defp create_location_geometry(%{lat: lat, lon: lon} = attrs)
|
||||
when is_float(lat) and is_float(lon) and lat >= -90.0 and lat <= 90.0 and lon >= -180.0 and lon <= 180.0 do
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ defmodule Aprsme.Packets do
|
|||
"""
|
||||
@spec store_packet(map()) :: {:ok, struct()} | {:error, :validation_error | :storage_exception}
|
||||
def store_packet(packet_data) do
|
||||
packet_data = normalize_packet_keys(packet_data)
|
||||
|
||||
with :ok <- validate_sender(packet_data),
|
||||
{:ok, sanitized_data} <- sanitize_packet_data(packet_data),
|
||||
{:ok, packet_attrs} <- build_packet_attrs(sanitized_data),
|
||||
|
|
@ -48,7 +50,7 @@ defmodule Aprsme.Packets do
|
|||
end
|
||||
|
||||
defp validate_sender(packet_data) when is_map(packet_data) do
|
||||
sender = Map.get(packet_data, :sender) || Map.get(packet_data, "sender")
|
||||
sender = Map.get(packet_data, :sender)
|
||||
|
||||
if Aprsme.Callsign.valid?(sender) do
|
||||
:ok
|
||||
|
|
@ -59,6 +61,16 @@ defmodule Aprsme.Packets do
|
|||
|
||||
defp validate_sender(_packet_data), do: {:error, {:validation_error, "sender: must be a safe APRS identifier"}}
|
||||
|
||||
# Normalize string keys to atoms for consistent access at the API boundary
|
||||
defp normalize_packet_keys(packet_data) when is_map(packet_data) do
|
||||
Map.new(packet_data, fn
|
||||
{key, value} when is_atom(key) -> {key, value}
|
||||
{key, value} when is_binary(key) -> {String.to_existing_atom(key), value}
|
||||
end)
|
||||
end
|
||||
|
||||
defp normalize_packet_keys(packet_data), do: packet_data
|
||||
|
||||
# Pattern matching for sanitization
|
||||
defp sanitize_packet_data(packet_data) do
|
||||
{:ok, Aprsme.EncodingUtils.sanitize_packet(packet_data)}
|
||||
|
|
@ -393,7 +405,7 @@ defmodule Aprsme.Packets do
|
|||
|
||||
defp maybe_filter_by_callsign(query, _), do: query
|
||||
|
||||
defp maybe_filter_by_bounds(query, bounds) when is_list(bounds) and length(bounds) == 4 do
|
||||
defp maybe_filter_by_bounds(query, [_, _, _, _] = bounds) do
|
||||
QueryBuilder.within_bounds(query, bounds)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -69,8 +69,8 @@ defmodule Aprsme.Packets.Clustering do
|
|||
# Filter packets with valid lat/lon coordinates
|
||||
defp filter_valid_coordinates(packets) do
|
||||
Enum.filter(packets, fn packet ->
|
||||
lat = decimal_to_float(Map.get(packet, :lat) || Map.get(packet, "lat"))
|
||||
lon = decimal_to_float(Map.get(packet, :lon) || Map.get(packet, "lon"))
|
||||
lat = decimal_to_float(Map.get(packet, :lat))
|
||||
lon = decimal_to_float(Map.get(packet, :lon))
|
||||
|
||||
not is_nil(lat) and not is_nil(lon) and
|
||||
lat >= -90 and lat <= 90 and
|
||||
|
|
@ -96,8 +96,8 @@ defmodule Aprsme.Packets.Clustering do
|
|||
defp perform_clustering(packets, radius) do
|
||||
packets
|
||||
|> Enum.reduce(%{}, fn packet, clusters ->
|
||||
lat = decimal_to_float(Map.get(packet, :lat) || Map.get(packet, "lat"))
|
||||
lon = decimal_to_float(Map.get(packet, :lon) || Map.get(packet, "lon"))
|
||||
lat = decimal_to_float(Map.get(packet, :lat))
|
||||
lon = decimal_to_float(Map.get(packet, :lon))
|
||||
|
||||
# Find cluster key by rounding to grid
|
||||
cluster_lat = Float.round(lat / radius) * radius
|
||||
|
|
|
|||
|
|
@ -8,6 +8,16 @@ defmodule Aprsme.Packets.QueryBuilder do
|
|||
|
||||
alias Aprsme.Packet
|
||||
|
||||
# Normalize string keys to atoms for consistent access at the API boundary
|
||||
defp normalize_opts_keys(opts) when is_map(opts) do
|
||||
Map.new(opts, fn
|
||||
{key, value} when is_atom(key) -> {key, value}
|
||||
{key, value} when is_binary(key) -> {String.to_existing_atom(key), value}
|
||||
end)
|
||||
end
|
||||
|
||||
defp normalize_opts_keys(opts), do: opts
|
||||
|
||||
@doc """
|
||||
Filters query by time range. Supports start_time, end_time, and hours_back options.
|
||||
|
||||
|
|
@ -22,10 +32,12 @@ defmodule Aprsme.Packets.QueryBuilder do
|
|||
end
|
||||
|
||||
def with_time_range(query, opts) when is_map(opts) do
|
||||
opts = normalize_opts_keys(opts)
|
||||
|
||||
query
|
||||
|> maybe_filter_start_time(opts[:start_time] || opts["start_time"])
|
||||
|> maybe_filter_end_time(opts[:end_time] || opts["end_time"])
|
||||
|> maybe_filter_hours_back(opts[:hours_back] || opts["hours_back"])
|
||||
|> maybe_filter_start_time(opts[:start_time])
|
||||
|> maybe_filter_end_time(opts[:end_time])
|
||||
|> maybe_filter_hours_back(opts[:hours_back])
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
|
@ -182,7 +194,8 @@ defmodule Aprsme.Packets.QueryBuilder do
|
|||
"""
|
||||
@spec recent_position_packets(map() | keyword()) :: Ecto.Query.t()
|
||||
def recent_position_packets(opts \\ %{}) do
|
||||
limit = opts[:limit] || opts["limit"] || 100
|
||||
opts = normalize_opts_keys(opts)
|
||||
limit = opts[:limit] || 100
|
||||
|
||||
from(p in Packet)
|
||||
|> with_position()
|
||||
|
|
@ -203,7 +216,8 @@ defmodule Aprsme.Packets.QueryBuilder do
|
|||
"""
|
||||
@spec callsign_history(String.t(), map() | keyword()) :: Ecto.Query.t()
|
||||
def callsign_history(callsign, opts \\ %{}) do
|
||||
limit = opts[:limit] || opts["limit"] || 100
|
||||
opts = normalize_opts_keys(opts)
|
||||
limit = opts[:limit] || 100
|
||||
|
||||
from(p in Packet)
|
||||
|> for_callsign(callsign)
|
||||
|
|
@ -223,8 +237,9 @@ defmodule Aprsme.Packets.QueryBuilder do
|
|||
"""
|
||||
@spec weather_packets(map() | keyword()) :: Ecto.Query.t()
|
||||
def weather_packets(opts \\ %{}) do
|
||||
limit = opts[:limit] || opts["limit"] || 100
|
||||
callsign = opts[:callsign] || opts["callsign"]
|
||||
opts = normalize_opts_keys(opts)
|
||||
limit = opts[:limit] || 100
|
||||
callsign = opts[:callsign]
|
||||
|
||||
query =
|
||||
from(p in Packet)
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ defmodule Aprsme.PlausibleAnalytics do
|
|||
headers =
|
||||
maybe_add_forwarded_for(
|
||||
[{"content-type", "application/json"}, {"user-agent", Map.get(metadata, :user_agent, "aprs.me-server")}],
|
||||
Map.get(metadata, :remote_ip) || Map.get(metadata, "remote_ip")
|
||||
Map.get(metadata, :remote_ip)
|
||||
)
|
||||
|
||||
case Req.post(config[:endpoint], json: body, headers: headers, max_retries: 0, receive_timeout: 2_000) do
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ defmodule AprsmeWeb.AprsSymbol do
|
|||
across the application.
|
||||
"""
|
||||
|
||||
alias Aprsme.Cache
|
||||
|
||||
@doc """
|
||||
Gets sprite information for a given symbol table and code.
|
||||
Returns a map with sprite_file, background_position, and background_size.
|
||||
|
|
@ -177,14 +179,14 @@ defmodule AprsmeWeb.AprsSymbol do
|
|||
if is_nil(callsign) do
|
||||
cache_key = "symbol_html:#{symbol_table}:#{symbol_code}:#{size}"
|
||||
|
||||
case Aprsme.Cache.get(:symbol_cache, cache_key) do
|
||||
case Cache.get(:symbol_cache, cache_key) do
|
||||
{:ok, html} when not is_nil(html) ->
|
||||
html
|
||||
|
||||
_ ->
|
||||
html = generate_marker_html(symbol_table, symbol_code, nil, size)
|
||||
# Cache for 1 hour since symbols don't change
|
||||
_ = Aprsme.Cache.put(:symbol_cache, cache_key, html, ttl: Aprsme.Cache.to_timeout(hour: 1))
|
||||
_ = Cache.put(:symbol_cache, cache_key, html, ttl: Cache.to_timeout(hour: 1))
|
||||
html
|
||||
end
|
||||
else
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ defmodule AprsmeWeb.SymbolRenderer do
|
|||
attr :title, :string, default: nil, doc: "Optional title/tooltip text"
|
||||
|
||||
def symbol(assigns) do
|
||||
# Get the sprite file and position for this symbol
|
||||
sprite_info = AprsmeWeb.AprsSymbol.get_sprite_info(assigns.symbol_table, assigns.symbol_code)
|
||||
|
||||
assigns =
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ defmodule AprsmeWeb.InfoLive.Show do
|
|||
alias AprsmeWeb.AprsSymbol
|
||||
alias AprsmeWeb.Live.SharedPacketHandler
|
||||
alias AprsmeWeb.MapLive.PacketUtils
|
||||
alias Phoenix.HTML
|
||||
|
||||
require Logger
|
||||
|
||||
|
|
@ -120,7 +121,6 @@ defmodule AprsmeWeb.InfoLive.Show do
|
|||
"InfoLive received packet update for #{socket.assigns.callsign}: #{inspect(Map.get(incoming_packet, "raw_packet"))}"
|
||||
)
|
||||
|
||||
# Get the new packet data
|
||||
new_packet = get_latest_packet(socket.assigns.callsign)
|
||||
new_packet = if new_packet, do: SharedPacketHandler.enrich_with_device_info(new_packet)
|
||||
|
||||
|
|
@ -197,7 +197,6 @@ defmodule AprsmeWeb.InfoLive.Show do
|
|||
end
|
||||
|
||||
defp get_latest_position_packet(callsign) do
|
||||
# Get the most recent packet with valid position data
|
||||
Repo.one(
|
||||
from(p in Aprsme.Packet,
|
||||
where: fragment("upper(?)", p.sender) == ^String.upcase(String.trim(callsign)),
|
||||
|
|
@ -544,12 +543,12 @@ defmodule AprsmeWeb.InfoLive.Show do
|
|||
style =
|
||||
"position: relative; width: #{size}px; height: #{size}px; background-image: url(#{overlay_sprite_info.sprite_file}), url(#{sprite_info.sprite_file}); background-position: #{overlay_sprite_info.background_position}, #{sprite_info.background_position}; background-size: #{overlay_sprite_info.background_size}, #{sprite_info.background_size}; background-repeat: no-repeat, no-repeat; image-rendering: pixelated; display: inline-block; vertical-align: middle; margin-bottom: -6px;"
|
||||
|
||||
escaped_style = style |> Phoenix.HTML.html_escape() |> Phoenix.HTML.safe_to_string()
|
||||
escaped_style = style |> HTML.html_escape() |> HTML.safe_to_string()
|
||||
|
||||
raw("<div style=\"#{escaped_style}\"></div>")
|
||||
else
|
||||
style = AprsSymbol.render_style(symbol_table_id, symbol_code, size)
|
||||
escaped_style = style |> Phoenix.HTML.html_escape() |> Phoenix.HTML.safe_to_string()
|
||||
escaped_style = style |> HTML.html_escape() |> HTML.safe_to_string()
|
||||
|
||||
raw("<div style=\"#{escaped_style}\"></div>")
|
||||
end
|
||||
|
|
|
|||
|
|
@ -365,7 +365,7 @@ defmodule AprsmeWeb.MapLive.Components do
|
|||
|
||||
defp get_received_at(packet) when is_map(packet) do
|
||||
# For regular maps, try both atom and string keys
|
||||
Map.get(packet, :received_at) || Map.get(packet, "received_at")
|
||||
Map.get(packet, :received_at)
|
||||
end
|
||||
|
||||
defp get_received_at(_), do: nil
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ defmodule AprsmeWeb.MapLive.DataBuilder do
|
|||
proper coordinate validation, symbol rendering, and weather data processing.
|
||||
"""
|
||||
|
||||
alias Aprsme.WeatherCache
|
||||
alias AprsmeWeb.Live.Shared.CoordinateUtils
|
||||
alias AprsmeWeb.Live.Shared.PacketUtils, as: SharedPacketUtils
|
||||
alias AprsmeWeb.Live.Shared.ParamUtils
|
||||
|
|
@ -41,7 +42,7 @@ defmodule AprsmeWeb.MapLive.DataBuilder do
|
|||
|> Enum.map(&display_name/1)
|
||||
|> Enum.uniq()
|
||||
|
||||
Aprsme.WeatherCache.cache_weather_callsigns(callsigns)
|
||||
WeatherCache.cache_weather_callsigns(callsigns)
|
||||
|
||||
packets_map
|
||||
|> Enum.map(fn {_callsign, packet} ->
|
||||
|
|
@ -499,10 +500,10 @@ defmodule AprsmeWeb.MapLive.DataBuilder do
|
|||
|
||||
@spec has_weather_packets?(String.t()) :: boolean()
|
||||
defp has_weather_packets?(callsign) when is_binary(callsign) do
|
||||
case Aprsme.WeatherCache.weather_callsign?(callsign) do
|
||||
case WeatherCache.weather_callsign?(callsign) do
|
||||
:unknown ->
|
||||
Aprsme.WeatherCache.cache_weather_callsigns([callsign])
|
||||
Aprsme.WeatherCache.weather_callsign?(callsign) == true
|
||||
WeatherCache.cache_weather_callsigns([callsign])
|
||||
WeatherCache.weather_callsign?(callsign) == true
|
||||
|
||||
result when is_boolean(result) ->
|
||||
result
|
||||
|
|
@ -584,7 +585,7 @@ defmodule AprsmeWeb.MapLive.DataBuilder do
|
|||
# Helper to get packet ID
|
||||
@spec get_packet_id(map()) :: any()
|
||||
defp get_packet_id(packet) do
|
||||
Map.get(packet, :id) || Map.get(packet, "id")
|
||||
Map.get(packet, :id)
|
||||
end
|
||||
|
||||
# Helper to get packet received_at datetime
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ defmodule AprsmeWeb.MapLive.DisplayManager do
|
|||
all_packets
|
||||
|> BoundsUtils.filter_packets_by_bounds(socket.assigns.map_bounds)
|
||||
|> Enum.uniq_by(fn packet ->
|
||||
Map.get(packet, :id) || Map.get(packet, "id")
|
||||
Map.get(packet, :id)
|
||||
end)
|
||||
|
||||
send_heat_map_for_packets(socket, filtered_packets)
|
||||
|
|
@ -170,7 +170,7 @@ defmodule AprsmeWeb.MapLive.DisplayManager do
|
|||
end
|
||||
|
||||
defp tracked_packet?(packet, callsign, threshold) do
|
||||
sender = Map.get(packet, :sender) || Map.get(packet, "sender")
|
||||
sender = Map.get(packet, :sender)
|
||||
received_at = packet_received_at(packet)
|
||||
|
||||
String.upcase(sender) == String.upcase(callsign) &&
|
||||
|
|
@ -185,8 +185,8 @@ defmodule AprsmeWeb.MapLive.DisplayManager do
|
|||
end
|
||||
|
||||
defp trail_point(packet) do
|
||||
lat = Map.get(packet, :lat) || Map.get(packet, "lat")
|
||||
lon = Map.get(packet, :lon) || Map.get(packet, "lon")
|
||||
lat = Map.get(packet, :lat)
|
||||
lon = Map.get(packet, :lon)
|
||||
received_at = packet_received_at(packet)
|
||||
|
||||
%{
|
||||
|
|
@ -197,7 +197,7 @@ defmodule AprsmeWeb.MapLive.DisplayManager do
|
|||
end
|
||||
|
||||
defp packet_received_at(packet) do
|
||||
Map.get(packet, :received_at) || Map.get(packet, "received_at")
|
||||
Map.get(packet, :received_at)
|
||||
end
|
||||
|
||||
defp packet_timestamp(packet) do
|
||||
|
|
@ -208,11 +208,10 @@ defmodule AprsmeWeb.MapLive.DisplayManager do
|
|||
|
||||
defp trail_packet_key(packet) do
|
||||
Map.get(packet, :id) ||
|
||||
Map.get(packet, "id") ||
|
||||
{
|
||||
Map.get(packet, :sender) || Map.get(packet, "sender"),
|
||||
Map.get(packet, :lat) || Map.get(packet, "lat"),
|
||||
Map.get(packet, :lon) || Map.get(packet, "lon"),
|
||||
Map.get(packet, :sender),
|
||||
Map.get(packet, :lat),
|
||||
Map.get(packet, :lon),
|
||||
packet_received_at(packet)
|
||||
}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -220,11 +220,11 @@ defmodule AprsmeWeb.MapLive.HistoricalLoader do
|
|||
|
||||
# Extract the next keyset cursor from the last packet in the list.
|
||||
defp advance_historical_cursor(socket, packets) do
|
||||
case List.last(packets) do
|
||||
nil ->
|
||||
case Enum.reverse(packets) do
|
||||
[] ->
|
||||
socket
|
||||
|
||||
last ->
|
||||
[last | _] ->
|
||||
received_at = get_cursor_field(last, :received_at)
|
||||
id = get_cursor_field(last, :id)
|
||||
|
||||
|
|
|
|||
|
|
@ -335,7 +335,6 @@ defmodule AprsmeWeb.MapLive.Index do
|
|||
|
||||
def handle_info({:process_pending_bounds}, socket) do
|
||||
if socket.assigns.pending_bounds && !socket.assigns.historical_loading do
|
||||
# Process the pending bounds update
|
||||
bounds = socket.assigns.pending_bounds
|
||||
socket = assign(socket, pending_bounds: nil)
|
||||
BoundsUpdater.handle_bounds_update(bounds, socket)
|
||||
|
|
@ -452,7 +451,6 @@ defmodule AprsmeWeb.MapLive.Index do
|
|||
packet_sender = Map.get(packet, :sender, Map.get(packet, "sender", ""))
|
||||
|
||||
if String.upcase(packet_sender) == String.upcase(socket.assigns.tracked_callsign) do
|
||||
# Update the tracked callsign's latest packet
|
||||
socket =
|
||||
assign(
|
||||
socket,
|
||||
|
|
@ -1191,7 +1189,7 @@ defmodule AprsmeWeb.MapLive.Index do
|
|||
end
|
||||
|
||||
defp packet_received_at(packet) do
|
||||
Map.get(packet, :received_at) || Map.get(packet, "received_at")
|
||||
Map.get(packet, :received_at)
|
||||
end
|
||||
|
||||
@impl true
|
||||
|
|
|
|||
|
|
@ -121,8 +121,8 @@ defmodule AprsmeWeb.MapLive.PacketBatcher do
|
|||
end
|
||||
|
||||
defp accept_packet(state, packet) do
|
||||
sender = Map.get(packet, :sender) || Map.get(packet, "sender")
|
||||
received_at = Map.get(packet, :received_at) || Map.get(packet, "received_at")
|
||||
sender = Map.get(packet, :sender)
|
||||
received_at = Map.get(packet, :received_at)
|
||||
|
||||
with true <- is_binary(sender),
|
||||
false <- is_nil(received_at),
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ defmodule AprsmeWeb.MapLive.Subscriptions do
|
|||
import Phoenix.LiveView, only: [connected?: 1, push_event: 3, put_flash: 3]
|
||||
|
||||
alias Phoenix.LiveView.Socket
|
||||
alias Phoenix.PubSub
|
||||
|
||||
@doc """
|
||||
Set up spatial and PubSub subscriptions for a newly mounted socket.
|
||||
|
|
@ -61,20 +62,20 @@ defmodule AprsmeWeb.MapLive.Subscriptions do
|
|||
if Application.get_env(:aprsme, :cluster_enabled, false) do
|
||||
_ = Aprsme.ConnectionMonitor.register_connection()
|
||||
# Subscribe to drain events for this node
|
||||
Phoenix.PubSub.subscribe(Aprsme.PubSub, "connection:drain:#{Node.self()}")
|
||||
PubSub.subscribe(Aprsme.PubSub, "connection:drain:#{Node.self()}")
|
||||
end
|
||||
|
||||
# Register the actual initial viewport.
|
||||
{:ok, spatial_topic} = Aprsme.SpatialPubSub.register_viewport(client_id, initial_bounds)
|
||||
|
||||
# Subscribe to the spatial topic for this client
|
||||
:ok = Phoenix.PubSub.subscribe(Aprsme.PubSub, spatial_topic)
|
||||
:ok = PubSub.subscribe(Aprsme.PubSub, spatial_topic)
|
||||
|
||||
# Still subscribe to bad packets (they don't have location)
|
||||
:ok = Phoenix.PubSub.subscribe(Aprsme.PubSub, "bad_packets")
|
||||
:ok = PubSub.subscribe(Aprsme.PubSub, "bad_packets")
|
||||
|
||||
# Subscribe to deployment events
|
||||
:ok = Phoenix.PubSub.subscribe(Aprsme.PubSub, "deployment_events")
|
||||
:ok = PubSub.subscribe(Aprsme.PubSub, "deployment_events")
|
||||
|
||||
_ = Process.send_after(self(), :cleanup_old_packets, 60_000)
|
||||
|
||||
|
|
|
|||
|
|
@ -108,7 +108,6 @@ defmodule AprsmeWeb.PacketsLive.CallsignView do
|
|||
defp enrich_packet_with_device_identifier(sanitized_payload) do
|
||||
device_identifier =
|
||||
Map.get(sanitized_payload, :device_identifier) ||
|
||||
Map.get(sanitized_payload, "device_identifier") ||
|
||||
DeviceParser.extract_device_identifier(sanitized_payload)
|
||||
|
||||
canonical_identifier = resolve_canonical_identifier(device_identifier)
|
||||
|
|
|
|||
|
|
@ -77,14 +77,16 @@ defmodule AprsmeWeb.PacketsLive.Index do
|
|||
|> do_trim(socket)
|
||||
end
|
||||
|
||||
defp do_trim(packets, socket) when length(packets) <= @max_stream_size, do: socket
|
||||
|
||||
defp do_trim(packets, socket) do
|
||||
{_kept, to_remove} = Enum.split(packets, @max_stream_size)
|
||||
if length(packets) <= @max_stream_size do
|
||||
socket
|
||||
else
|
||||
{_kept, to_remove} = Enum.split(packets, @max_stream_size)
|
||||
|
||||
Enum.reduce(to_remove, socket, fn {id, _}, s ->
|
||||
stream_delete(s, :packets, id)
|
||||
end)
|
||||
Enum.reduce(to_remove, socket, fn {id, _}, s ->
|
||||
stream_delete(s, :packets, id)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
|
|
|||
|
|
@ -23,9 +23,9 @@ defmodule AprsmeWeb.Live.Shared.CoordinateUtils do
|
|||
end
|
||||
|
||||
def get_coordinates(packet) do
|
||||
lat = Map.get(packet, :lat) || Map.get(packet, "lat")
|
||||
lon = Map.get(packet, :lon) || Map.get(packet, "lon")
|
||||
data_extended = Map.get(packet, :data_extended) || Map.get(packet, "data_extended")
|
||||
lat = Map.get(packet, :lat)
|
||||
lon = Map.get(packet, :lon)
|
||||
data_extended = Map.get(packet, :data_extended)
|
||||
{lat, lon, data_extended}
|
||||
end
|
||||
|
||||
|
|
@ -64,8 +64,8 @@ defmodule AprsmeWeb.Live.Shared.CoordinateUtils do
|
|||
"""
|
||||
@spec has_position_data?(map()) :: boolean()
|
||||
def has_position_data?(packet) do
|
||||
lat = Map.get(packet, :lat) || Map.get(packet, "lat")
|
||||
lon = Map.get(packet, :lon) || Map.get(packet, "lon")
|
||||
lat = Map.get(packet, :lat)
|
||||
lon = Map.get(packet, :lon)
|
||||
|
||||
has_direct_coordinates?(lat, lon) or has_position_in_data_extended?(packet)
|
||||
end
|
||||
|
|
@ -130,7 +130,7 @@ defmodule AprsmeWeb.Live.Shared.CoordinateUtils do
|
|||
defp has_direct_coordinates?(_, _), do: false
|
||||
|
||||
defp has_position_in_data_extended?(packet) do
|
||||
data_extended = Map.get(packet, :data_extended) || Map.get(packet, "data_extended")
|
||||
data_extended = Map.get(packet, :data_extended)
|
||||
has_position_in_data_extended_case?(data_extended)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ defmodule AprsmeWeb.Live.SharedPacketHandler do
|
|||
Checks if packet sender matches the given callsign.
|
||||
"""
|
||||
def packet_matches_callsign?(packet, callsign) do
|
||||
packet_sender = Map.get(packet, "sender") || Map.get(packet, :sender, "")
|
||||
packet_sender = Map.get(packet, :sender, "")
|
||||
Callsign.matches?(packet_sender, callsign)
|
||||
end
|
||||
|
||||
|
|
@ -102,7 +102,7 @@ defmodule AprsmeWeb.Live.SharedPacketHandler do
|
|||
end
|
||||
|
||||
defp get_device_identifier(packet) do
|
||||
Map.get(packet, :device_identifier) || Map.get(packet, "device_identifier")
|
||||
Map.get(packet, :device_identifier)
|
||||
end
|
||||
|
||||
defp lookup_device_info(nil), do: nil
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ defmodule AprsmeWeb.Live.Shared.PacketUtils do
|
|||
"""
|
||||
@spec has_weather_data?(map()) :: boolean()
|
||||
def has_weather_data?(packet) do
|
||||
case Map.get(packet, :has_weather) || Map.get(packet, "has_weather") do
|
||||
case Map.get(packet, :has_weather) do
|
||||
nil ->
|
||||
Enum.any?(Aprsme.EncodingUtils.weather_fields(), fn field ->
|
||||
value = get_packet_field(packet, field, nil)
|
||||
|
|
@ -135,8 +135,7 @@ defmodule AprsmeWeb.Live.Shared.PacketUtils do
|
|||
case Map.get(packet, to_string(field)) do
|
||||
nil ->
|
||||
de =
|
||||
Map.get(packet, :data_extended) ||
|
||||
Map.get(packet, "data_extended", %{}) || %{}
|
||||
Map.get(packet, :data_extended) || %{}
|
||||
|
||||
Map.get(de, field) || Map.get(de, to_string(field)) || default
|
||||
|
||||
|
|
@ -190,7 +189,7 @@ defmodule AprsmeWeb.Live.Shared.PacketUtils do
|
|||
"""
|
||||
@spec get_timestamp(map()) :: String.t()
|
||||
def get_timestamp(packet) do
|
||||
received_at = Map.get(packet, :received_at) || Map.get(packet, "received_at")
|
||||
received_at = Map.get(packet, :received_at)
|
||||
|
||||
case received_at do
|
||||
nil ->
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ defmodule AprsmeWeb.StatusLive.Index do
|
|||
"""
|
||||
use AprsmeWeb, :live_view
|
||||
|
||||
alias Aprsme.Cache
|
||||
alias Aprsme.Cluster.LeaderElection
|
||||
|
||||
require Logger
|
||||
|
|
@ -409,20 +410,19 @@ defmodule AprsmeWeb.StatusLive.Index do
|
|||
require Logger
|
||||
|
||||
Logger.error("Error getting APRS status: #{inspect(error)}")
|
||||
# Return a default status when database is unavailable
|
||||
default_status()
|
||||
end
|
||||
|
||||
defp get_cached_aprs_status do
|
||||
# Try to get cached status for instant load
|
||||
case Aprsme.Cache.get(:query_cache, "aprs_status") do
|
||||
case Cache.get(:query_cache, "aprs_status") do
|
||||
{:ok, status} when not is_nil(status) ->
|
||||
status
|
||||
|
||||
_ ->
|
||||
# Fallback to direct query if cache miss
|
||||
status = get_aprs_status()
|
||||
_ = Aprsme.Cache.put(:query_cache, "aprs_status", status, ttl: Aprsme.Cache.to_timeout(second: 10))
|
||||
_ = Cache.put(:query_cache, "aprs_status", status, ttl: Cache.to_timeout(second: 10))
|
||||
status
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -92,8 +92,8 @@ defmodule AprsmeWeb.WeatherLive.CallsignView do
|
|||
defp should_update_weather?(nil, _new_packet), do: true
|
||||
|
||||
defp should_update_weather?(current_packet, new_packet) do
|
||||
new_received_at = Map.get(new_packet, :received_at) || Map.get(new_packet, "received_at")
|
||||
current_received_at = Map.get(current_packet, :received_at) || Map.get(current_packet, "received_at")
|
||||
new_received_at = Map.get(new_packet, :received_at)
|
||||
current_received_at = Map.get(current_packet, :received_at)
|
||||
|
||||
compare_timestamps(new_received_at, current_received_at)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ defmodule AprsmeWeb.Plugs.SetLocale do
|
|||
|
||||
def call(conn, _opts) do
|
||||
locale = get_locale_from_header(conn) || "en"
|
||||
# Set the backend's locale for the current process
|
||||
_ = Gettext.put_locale(AprsmeWeb.Gettext, locale)
|
||||
# Store locale in session for LiveView to access
|
||||
conn = put_session(conn, :locale, locale)
|
||||
|
|
|
|||
1
mix.exs
1
mix.exs
|
|
@ -115,6 +115,7 @@ defmodule Aprsme.MixProject do
|
|||
{:styler, "~> 1.10", only: [:dev, :test], runtime: false},
|
||||
{:hammer, "~> 7.0"},
|
||||
{:jump_credo_checks, "~> 0.4", only: [:dev, :test], runtime: false},
|
||||
{:ex_slop, "~> 0.1", only: [:dev, :test], runtime: false},
|
||||
{:inet_cidr, "~> 1.0"}
|
||||
]
|
||||
end
|
||||
|
|
|
|||
1
mix.lock
1
mix.lock
|
|
@ -17,6 +17,7 @@
|
|||
"erlex": {:hex, :erlex, "0.2.9", "7debbbaa9f4f368b8cd648983e0f1d7963028508e9c59e9d4ed504e94ef52a55", [:mix], [], "hexpm", "8cfffc0ec7159e6d73de2ab28a588064de80f88b2798d5cbe4482cbbc200178b"},
|
||||
"error_tracker": {:hex, :error_tracker, "0.9.0", "fef10b1230be1b051452930ba4db9f809cdab9705fe87891c25f5e551cead372", [:mix], [{:ecto, "~> 3.13", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.13", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, ">= 0.0.0", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:igniter, "~> 0.5", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:myxql, ">= 0.0.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:phoenix_ecto, "~> 4.6", [hex: :phoenix_ecto, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "1de7d89ec9034c3b7282b6bd2d0584ca98cddc51e87bf0979ba71f6182803ac4"},
|
||||
"esbuild": {:hex, :esbuild, "0.10.0", "b0aa3388a1c23e727c5a3e7427c932d89ee791746b0081bbe56103e9ef3d291f", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "468489cda427b974a7cc9f03ace55368a83e1a7be12fba7e30969af78e5f8c70"},
|
||||
"ex_slop": {:hex, :ex_slop, "0.4.4", "8ca277b902e93193eb3545e414c309026a51204e5eb7d6ada5ff96555de6abcb", [:mix], [{:credo, "~> 1.7", [hex: :credo, repo: "hexpm", optional: false]}], "hexpm", "03ef1b2553ebad45c8ceb3e482eed5f1ccd08fc992215a9da0ef6196935107e2"},
|
||||
"expo": {:hex, :expo, "1.1.1", "4202e1d2ca6e2b3b63e02f69cfe0a404f77702b041d02b58597c00992b601db5", [:mix], [], "hexpm", "5fb308b9cb359ae200b7e23d37c76978673aa1b06e2b3075d814ce12c5811640"},
|
||||
"file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"},
|
||||
"finch": {:hex, :finch, "0.23.0", "e3f9287ac25a8832f848b144c2b57346aac65b205e2e0629a52adfe6507fd837", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.8", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "80e58d3f936f57e3fdf404f83a3642897ae6d9fb642934e46da4d8fe761b99d5"},
|
||||
|
|
|
|||
|
|
@ -127,10 +127,8 @@ defmodule Aprsme.BadPacketTest do
|
|||
query = BadPacket.recent()
|
||||
_query = BadPacket.recent()
|
||||
|
||||
# Check the query structure contains limit
|
||||
_query = BadPacket.recent()
|
||||
result = Repo.all(query)
|
||||
# Verify the query works and the default limit is reasonable
|
||||
assert is_struct(query, Ecto.Query)
|
||||
assert length(result) <= 100
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ defmodule Aprsme.BroadcastTaskSupervisorTest do
|
|||
|
||||
setup do
|
||||
# Ensure the supervisor is started (it should be from application)
|
||||
# Create a test topic for PubSub
|
||||
test_topic = "test_topic_#{System.unique_integer()}"
|
||||
Phoenix.PubSub.subscribe(Aprsme.PubSub, test_topic)
|
||||
|
||||
|
|
|
|||
|
|
@ -235,7 +235,6 @@ defmodule Aprsme.Cluster.ConnectionManagerTest do
|
|||
end
|
||||
|
||||
test "handles IsSupervisor start failure", %{pid: pid} do
|
||||
# Define a failing IsSupervisor
|
||||
defmodule FailingIsSupervisor do
|
||||
@moduledoc false
|
||||
def start_link(_opts) do
|
||||
|
|
|
|||
|
|
@ -435,7 +435,6 @@ defmodule Aprsme.DeviceIdentificationTest do
|
|||
end
|
||||
|
||||
test "handles non-wildcard exact match" do
|
||||
# Insert a device with an exact identifier (no ?).
|
||||
Repo.delete_all(Devices)
|
||||
|
||||
Repo.insert!(%Devices{
|
||||
|
|
|
|||
|
|
@ -1229,7 +1229,6 @@ defmodule Aprsme.IsTest do
|
|||
end
|
||||
|
||||
test "resets per-second counter when time advances" do
|
||||
# Build a state with last_second_timestamp in the past
|
||||
old_stats = %{
|
||||
total_packets: 5,
|
||||
last_packet_at: DateTime.utc_now(),
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ defmodule Aprsme.MigrationLockTest do
|
|||
@lock_id 8_764_293_847_291
|
||||
|
||||
setup do
|
||||
# Ensure the advisory lock is released before each test.
|
||||
# Advisory locks are session-level, so we need to make sure none
|
||||
# are left over from a previous test.
|
||||
Repo.query("SELECT pg_advisory_unlock_all()")
|
||||
|
|
|
|||
|
|
@ -203,7 +203,7 @@ defmodule Aprsme.MigrationValidationTest do
|
|||
|
||||
assert after_insert == before + 1
|
||||
|
||||
# Delete the packet — trigger should decrement
|
||||
# The counter trigger should fire on delete to decrement the count.
|
||||
Repo.query!("DELETE FROM packets WHERE id = $1", [inserted_id])
|
||||
|
||||
{:ok, %{rows: [[after_delete]]}} =
|
||||
|
|
|
|||
|
|
@ -951,8 +951,8 @@ defmodule Aprsme.PacketConsumerTest do
|
|||
:erlang.garbage_collect()
|
||||
memory_before = :erlang.memory(:total)
|
||||
|
||||
# Create a smaller, more focused batch of packets
|
||||
# Reduced from 5000 to 1000 packets and smaller payloads
|
||||
# Reduced from 5000 to 1000 packets and smaller payloads to keep
|
||||
# memory usage within test environment limits.
|
||||
events =
|
||||
for i <- 1..1000 do
|
||||
%{
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ defmodule Aprsme.PacketParsingTest do
|
|||
test "extracts altitude and PHG data from packet" do
|
||||
raw_packet = "W5MRC-15>APN391,qAO,KG5JPL-1:!3317.02NN09634.37W#PHG5530 Collin Cty Wide Digi /A=000680"
|
||||
|
||||
# Parse the packet
|
||||
{:ok, parsed} = Aprs.parse(raw_packet)
|
||||
|
||||
# Create attributes for packet
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ defmodule Aprsme.PacketWeatherExtractionTest do
|
|||
# Simulate a real weather packet
|
||||
raw_packet = "AE5PL-13>API510,DSTAR*:!3240.46N/09657.49W_225/004g009t075r000p000h61b10206Plano, TX weather"
|
||||
|
||||
# Parse the packet using the real APRS parser
|
||||
{:ok, parsed_packet} = Aprs.parse(raw_packet)
|
||||
|
||||
# Create packet data as it would come from the parser
|
||||
|
|
@ -48,7 +47,6 @@ defmodule Aprsme.PacketWeatherExtractionTest do
|
|||
# Simulate a pure weather packet (starts with _)
|
||||
raw_packet = "AE5PL-WX>APRS:_07121545c220s004g009t075r000p000h61b10206"
|
||||
|
||||
# Parse the packet using the real APRS parser
|
||||
{:ok, parsed_packet} = Aprs.parse(raw_packet)
|
||||
|
||||
# Create packet data as it would come from the parser
|
||||
|
|
|
|||
|
|
@ -110,7 +110,6 @@ defmodule Aprsme.Packets.ClusteringTest do
|
|||
|
||||
# At zoom 8, these should be two clusters (radius 0.039 degrees)
|
||||
# but our points are only 0.014 degrees apart, so they'll still cluster
|
||||
# Let's use points that are further apart
|
||||
far_packets = [
|
||||
%Packet{id: 1, lat: Decimal.new("40.7128"), lon: Decimal.new("-74.0060")},
|
||||
%Packet{id: 2, lat: Decimal.new("40.8128"), lon: Decimal.new("-74.1060")}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,6 @@ defmodule Aprsme.PacketsMicETest do
|
|||
# This should not raise a KeyError
|
||||
assert {:ok, stored_packet} = Packets.store_packet(packet_data)
|
||||
|
||||
# Verify the packet was stored with correct position data
|
||||
assert stored_packet.sender == "DG1ID-9"
|
||||
assert stored_packet.has_position == true
|
||||
|
||||
|
|
@ -214,7 +213,6 @@ defmodule Aprsme.PacketsMicETest do
|
|||
# The fix ensures we use data_extended[:latitude] instead of data_extended.latitude
|
||||
assert {:ok, stored_packet} = Packets.store_packet(packet_data)
|
||||
|
||||
# Verify the packet was stored correctly
|
||||
assert stored_packet.sender == "DG1ID-9"
|
||||
|
||||
# Note: longitude 198 is invalid (outside -180 to 180 range)
|
||||
|
|
@ -250,7 +248,6 @@ defmodule Aprsme.PacketsMicETest do
|
|||
# The fix ensures we check the struct type before using Access behavior
|
||||
assert {:ok, stored_packet} = Packets.store_packet(packet_data)
|
||||
|
||||
# Verify the packet was stored correctly
|
||||
assert stored_packet.sender == "LU9DCE-4"
|
||||
assert stored_packet.data_type == "phg_data"
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ defmodule Aprsme.PacketsTest do
|
|||
|
||||
assert {:ok, packet} = Packets.store_packet(packet_data)
|
||||
assert packet.sender == "TEST1"
|
||||
# Handle the fact that lat/lon may be stored as Decimal
|
||||
# lat/lon may be stored as Decimal, not float, depending on Ecto config.
|
||||
lat_value = if is_struct(packet.lat, Decimal), do: Decimal.to_float(packet.lat), else: packet.lat
|
||||
lon_value = if is_struct(packet.lon, Decimal), do: Decimal.to_float(packet.lon), else: packet.lon
|
||||
assert Float.round(lat_value, 4) == 39.8333
|
||||
|
|
@ -164,7 +164,6 @@ defmodule Aprsme.PacketsTest do
|
|||
}
|
||||
|
||||
assert {:ok, packet} = Packets.store_packet(packet_data)
|
||||
# Verify the packet was stored successfully
|
||||
assert packet.sender == "TEST7"
|
||||
end
|
||||
end
|
||||
|
|
@ -1066,7 +1065,6 @@ defmodule Aprsme.PacketsTest do
|
|||
|
||||
describe "get_oldest_packet_timestamp/0" do
|
||||
test "returns timestamp of oldest packet" do
|
||||
# Create an old packet
|
||||
now = DateTime.utc_now()
|
||||
old_time = DateTime.add(now, -365 * 24 * 3600, :second)
|
||||
|
||||
|
|
@ -1308,7 +1306,6 @@ defmodule Aprsme.PacketsTest do
|
|||
test "only returns SSIDs with recent packets" do
|
||||
now = DateTime.utc_now()
|
||||
|
||||
# Create a recent packet
|
||||
PacketsFixtures.packet_fixture(%{
|
||||
sender: "RECENT-1",
|
||||
base_callsign: "RECENT",
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ defmodule Aprsme.PacketsWeatherTest do
|
|||
# Simulate a real APRS weather packet with correct format
|
||||
raw_packet = "TEST-1>APRS,WIDE1-1,WIDE2-2:!3216.46N/09647.82W_180/010g015t072r000p000h45b10132"
|
||||
|
||||
# Parse the packet using the real APRS parser
|
||||
{:ok, parsed_packet} = Aprs.parse(raw_packet)
|
||||
|
||||
# Create packet data as it would come from the parser
|
||||
|
|
@ -43,7 +42,6 @@ defmodule Aprsme.PacketsWeatherTest do
|
|||
# Simulate a real APRS weather packet with correct format
|
||||
raw_packet = "WEATHER-1>APRS,WIDE1-1:!3216.46N/09647.82W_270/012g018t085r000p000h60b10150"
|
||||
|
||||
# Parse the packet using the real APRS parser
|
||||
{:ok, parsed_packet} = Aprs.parse(raw_packet)
|
||||
|
||||
packet_data = %{
|
||||
|
|
@ -75,7 +73,6 @@ defmodule Aprsme.PacketsWeatherTest do
|
|||
# Simulate a real APRS packet with weather data in comment
|
||||
raw_packet = "COMMENT-1>APRS,WIDE1-1:!3216.46N/09647.82W>090/008g012t095r000p000h70b10200"
|
||||
|
||||
# Parse the packet using the real APRS parser
|
||||
{:ok, parsed_packet} = Aprs.parse(raw_packet)
|
||||
|
||||
packet_data = %{
|
||||
|
|
@ -106,7 +103,6 @@ defmodule Aprsme.PacketsWeatherTest do
|
|||
# Simulate a real APRS packet without weather data
|
||||
raw_packet = "REGULAR-1>APRS,WIDE1-1:!3216.46N/09647.82W>Just a regular packet"
|
||||
|
||||
# Parse the packet using the real APRS parser
|
||||
{:ok, parsed_packet} = Aprs.parse(raw_packet)
|
||||
|
||||
packet_data = %{
|
||||
|
|
@ -141,7 +137,6 @@ defmodule Aprsme.PacketsWeatherTest do
|
|||
raw_packet1 = "BATCH1-1>APRS,WIDE1-1:!3216.46N/09647.82W_200/010g015t070r000p000h50b10120"
|
||||
raw_packet2 = "BATCH2-1>APRS,WIDE1-1:!3216.46N/09647.82W_220/012g018t075r000p000h55b10140"
|
||||
|
||||
# Parse the packets using the real APRS parser
|
||||
{:ok, parsed_packet1} = Aprs.parse(raw_packet1)
|
||||
{:ok, parsed_packet2} = Aprs.parse(raw_packet2)
|
||||
|
||||
|
|
@ -194,7 +189,6 @@ defmodule Aprsme.PacketsWeatherTest do
|
|||
# Simulate a real APRS weather packet with snow data
|
||||
raw_packet = "SNOW-1>APRS,WIDE1-1:!3216.46N/09647.82W_180/010g015t072r000p000h45b10132s005"
|
||||
|
||||
# Parse the packet using the real APRS parser
|
||||
{:ok, parsed_packet} = Aprs.parse(raw_packet)
|
||||
|
||||
packet_data = %{
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ defmodule Aprsme.Workers.PacketCleanupWorkerTest do
|
|||
|
||||
describe "perform/1 without cleanup_days" do
|
||||
test "cleans up expired bad packets" do
|
||||
# Create an old bad packet
|
||||
retention_days = Application.get_env(:aprsme, :packet_retention_days, 7)
|
||||
|
||||
old_time =
|
||||
|
|
@ -16,7 +15,6 @@ defmodule Aprsme.Workers.PacketCleanupWorkerTest do
|
|||
|
||||
Repo.insert!(%BadPacket{raw_packet: "bad", error_message: "test", attempted_at: old_time})
|
||||
|
||||
# Create a recent bad packet
|
||||
recent_time =
|
||||
DateTime.utc_now()
|
||||
|> DateTime.add(-3600, :second)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ defmodule AprsmeWeb.Api.V1.WeatherControllerTest do
|
|||
|
||||
describe "GET /api/v1/weather/nearby" do
|
||||
setup do
|
||||
# Create a weather station packet
|
||||
station =
|
||||
packet_fixture(%{
|
||||
sender: "TEST-1",
|
||||
|
|
@ -35,7 +34,7 @@ defmodule AprsmeWeb.Api.V1.WeatherControllerTest do
|
|||
rain_since_midnight: 0.0
|
||||
})
|
||||
|
||||
# Create a non-weather packet (should not appear)
|
||||
# Should not appear in weather results.
|
||||
packet_fixture(%{
|
||||
sender: "TEST-2",
|
||||
data_type: "position",
|
||||
|
|
@ -44,7 +43,7 @@ defmodule AprsmeWeb.Api.V1.WeatherControllerTest do
|
|||
has_weather: false
|
||||
})
|
||||
|
||||
# Create a weather station outside radius
|
||||
# Should be excluded because it falls outside the search radius.
|
||||
packet_fixture(%{
|
||||
sender: "TEST-3",
|
||||
data_type: "weather",
|
||||
|
|
|
|||
|
|
@ -99,7 +99,6 @@ defmodule AprsmeWeb.Integration.AprsStatusTest do
|
|||
|
||||
describe "LiveView event handling without APRS" do
|
||||
test "map events work without APRS connection", %{conn: conn} do
|
||||
# Set the mock to allow calls from any process
|
||||
Mox.set_mox_global()
|
||||
|
||||
# Stub the function that will be called during bounds changes
|
||||
|
|
@ -196,7 +195,6 @@ defmodule AprsmeWeb.Integration.AprsStatusTest do
|
|||
end
|
||||
|
||||
test "APRS.Is process is not running" do
|
||||
# Verify the real APRS.Is GenServer is not started
|
||||
assert Process.whereis(Aprsme.Is) == nil
|
||||
assert Map.has_key?(Aprsme.Is.get_status(), :connected)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -48,7 +48,6 @@ defmodule AprsmeWeb.BadPacketsLiveTest do
|
|||
test "updates in real-time when new bad packet is created", %{conn: conn} do
|
||||
{:ok, index_live, _html} = live(conn, ~p"/badpackets", on_error: :warn)
|
||||
|
||||
# Create a bad packet after the live view is loaded
|
||||
bad_packet =
|
||||
Repo.insert!(%BadPacket{
|
||||
raw_packet: "KD9PDP>APRS:New invalid packet",
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ defmodule AprsmeWeb.MapLive.DisplayManagerTest do
|
|||
alias Phoenix.LiveView.Socket
|
||||
|
||||
setup do
|
||||
# Create a basic socket mock for testing
|
||||
socket = %Socket{
|
||||
assigns: %{
|
||||
visible_packets: %{},
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ defmodule AprsmeWeb.MapLive.HistoricalLoadingTest do
|
|||
comment: "Test station 2 in Times Square"
|
||||
})
|
||||
|
||||
# Create an old packet that should not be loaded
|
||||
# Should not be loaded — it falls outside the historical time window.
|
||||
old_packet =
|
||||
packet_fixture(%{
|
||||
sender: "TEST3",
|
||||
|
|
@ -270,7 +270,6 @@ defmodule AprsmeWeb.MapLive.HistoricalLoadingTest do
|
|||
end
|
||||
|
||||
test "does not send historical packets when bounds are invalid", %{conn: conn} do
|
||||
# Create a test packet
|
||||
packet_fixture(%{
|
||||
sender: "INVALID1",
|
||||
base_callsign: "INVALID1",
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ defmodule AprsmeWeb.MapLive.OverlayRenderingTest do
|
|||
|
||||
describe "overlay symbol rendering in map" do
|
||||
test "W5MRC-15 with D& symbol emits structured marker data" do
|
||||
# Create a test packet with overlay symbol D&
|
||||
packet = %{
|
||||
id: 1,
|
||||
sender: "W5MRC-15",
|
||||
|
|
@ -20,7 +19,6 @@ defmodule AprsmeWeb.MapLive.OverlayRenderingTest do
|
|||
data_type: "position"
|
||||
}
|
||||
|
||||
# Process the packet through PacketUtils
|
||||
result = PacketUtils.build_packet_data(packet, true, "en-US")
|
||||
|
||||
assert result["symbol_table_id"] == "D"
|
||||
|
|
@ -30,7 +28,6 @@ defmodule AprsmeWeb.MapLive.OverlayRenderingTest do
|
|||
end
|
||||
|
||||
test "N# green star overlay symbol generates correct HTML" do
|
||||
# Create a test packet with overlay symbol N#
|
||||
packet = %{
|
||||
id: 2,
|
||||
sender: "TEST-1",
|
||||
|
|
@ -45,7 +42,6 @@ defmodule AprsmeWeb.MapLive.OverlayRenderingTest do
|
|||
data_type: "position"
|
||||
}
|
||||
|
||||
# Process the packet
|
||||
result = PacketUtils.build_packet_data(packet, true, "en-US")
|
||||
assert result["symbol_table_id"] == "N"
|
||||
assert result["symbol_code"] == "#"
|
||||
|
|
@ -54,7 +50,6 @@ defmodule AprsmeWeb.MapLive.OverlayRenderingTest do
|
|||
end
|
||||
|
||||
test "normal symbol /_ generates correct HTML without overlay" do
|
||||
# Create a test packet with normal symbol /_
|
||||
packet = %{
|
||||
id: 3,
|
||||
sender: "WEATHER-1",
|
||||
|
|
@ -69,7 +64,6 @@ defmodule AprsmeWeb.MapLive.OverlayRenderingTest do
|
|||
data_type: "weather"
|
||||
}
|
||||
|
||||
# Process the packet
|
||||
result = PacketUtils.build_packet_data(packet, true, "en-US")
|
||||
assert result["symbol_table_id"] == "/"
|
||||
assert result["symbol_code"] == "_"
|
||||
|
|
|
|||
|
|
@ -111,7 +111,6 @@ defmodule AprsmeWeb.MapLive.RfPathTest do
|
|||
end
|
||||
|
||||
test "filters out APRS beacon patterns from paths", %{conn: conn} do
|
||||
# Create a real station that should be included
|
||||
{:ok, _station} =
|
||||
create_test_packet(%{
|
||||
sender: "KC5ABC-9",
|
||||
|
|
|
|||
|
|
@ -78,7 +78,6 @@ defmodule AprsmeWeb.MapLive.TrackedCallsignOldPacketTest do
|
|||
end
|
||||
|
||||
test "displays message packet without position for tracked callsign", %{conn: conn} do
|
||||
# Create a message packet without position data
|
||||
packet_fixture(%{
|
||||
sender: "TEST-MSG",
|
||||
raw_packet: "TEST-MSG>APRS::W5ISP :Hello there! This is a test message{001",
|
||||
|
|
@ -100,7 +99,6 @@ defmodule AprsmeWeb.MapLive.TrackedCallsignOldPacketTest do
|
|||
end
|
||||
|
||||
test "handles whitespace in callsign parameter", %{conn: conn} do
|
||||
# Create a packet for testing
|
||||
packet_fixture(%{
|
||||
sender: "TEST-WS",
|
||||
raw_packet: "TEST-WS>APRS:>Test whitespace handling",
|
||||
|
|
@ -116,7 +114,6 @@ defmodule AprsmeWeb.MapLive.TrackedCallsignOldPacketTest do
|
|||
end
|
||||
|
||||
test "handles lowercase callsign with non-position packet", %{conn: conn} do
|
||||
# Create a telemetry packet without position
|
||||
packet_fixture(%{
|
||||
sender: "TEST-TELEM",
|
||||
raw_packet: "TEST-TELEM>APRS:T#123,456,789,012,345,678,00000000",
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ defmodule AprsmeWeb.StatusLive.IndexTest do
|
|||
end
|
||||
|
||||
test "renders the connected-path template when cache has a live status", %{conn: conn} do
|
||||
# Ensure the cache ETS table exists for test.
|
||||
# ETS table may not exist in the test sandbox, so create it on demand.
|
||||
_ =
|
||||
case :ets.info(:query_cache) do
|
||||
:undefined -> :ets.new(:query_cache, [:named_table, :public, :set])
|
||||
|
|
|
|||
|
|
@ -61,7 +61,6 @@ defmodule AprsmeWeb.UserSettingsLiveTest do
|
|||
flash = assert_redirect(lv, ~p"/users/settings")
|
||||
assert flash["info"] == "Callsign updated successfully."
|
||||
|
||||
# Verify the callsign was updated
|
||||
updated_user = Accounts.get_user!(user.id)
|
||||
assert updated_user.callsign == "W9NEW"
|
||||
end
|
||||
|
|
@ -93,7 +92,6 @@ defmodule AprsmeWeb.UserSettingsLiveTest do
|
|||
|
||||
assert result =~ "is not valid"
|
||||
|
||||
# Verify the callsign was not updated
|
||||
updated_user = Accounts.get_user!(user.id)
|
||||
assert updated_user.callsign == user.callsign
|
||||
end
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@ defmodule AprsmeWeb.Plugs.ContentSecurityPolicyTest do
|
|||
nonce = conn.private[:csp_nonce]
|
||||
assert is_binary(nonce) and byte_size(nonce) > 0
|
||||
|
||||
# Verify the private nonce matches the one in the header
|
||||
csp = conn |> get_resp_header("content-security-policy") |> List.first()
|
||||
assert csp =~ "'nonce-#{nonce}'"
|
||||
end
|
||||
|
|
|
|||
|
|
@ -91,7 +91,6 @@ defmodule Aprsme.Integration.HistoricalPacketsTest do
|
|||
|
||||
describe "live packet updates with historical data" do
|
||||
setup do
|
||||
# Create a historical packet
|
||||
now = DateTime.utc_now()
|
||||
thirty_minutes_ago = DateTime.add(now, -1800, :second)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +1,13 @@
|
|||
defmodule AprsmeWeb.ChannelCase do
|
||||
@moduledoc """
|
||||
This module defines the test case to be used by
|
||||
channel tests.
|
||||
Test case for channel tests.
|
||||
|
||||
Such tests rely on `Phoenix.ChannelTest` and also
|
||||
import other functionality to make it easier
|
||||
to build common data structures and query the data layer.
|
||||
Relies on `Phoenix.ChannelTest` and imports helpers for building common
|
||||
data structures and querying the data layer.
|
||||
|
||||
Finally, if the test case interacts with the database,
|
||||
we enable the SQL sandbox, so changes done to the database
|
||||
are reverted at the end of every test. If you are using
|
||||
PostgreSQL, you can even run database tests asynchronously
|
||||
by setting `use AprsmeWeb.ChannelCase, async: true`, although
|
||||
this option is not recommended for other databases.
|
||||
When the test case interacts with the database, the SQL sandbox is enabled
|
||||
so changes are reverted at the end of every test. PostgreSQL users can
|
||||
run database tests asynchronously by setting `use AprsmeWeb.ChannelCase, async: true`.
|
||||
"""
|
||||
|
||||
use ExUnit.CaseTemplate
|
||||
|
|
|
|||
|
|
@ -1,18 +1,13 @@
|
|||
defmodule AprsmeWeb.ConnCase do
|
||||
@moduledoc """
|
||||
This module defines the test case to be used by
|
||||
tests that require setting up a connection.
|
||||
Test case for connection tests.
|
||||
|
||||
Such tests rely on `Phoenix.ConnTest` and also
|
||||
import other functionality to make it easier
|
||||
to build common data structures and query the data layer.
|
||||
Relies on `Phoenix.ConnTest` and imports helpers for building common
|
||||
data structures and querying the data layer.
|
||||
|
||||
Finally, if the test case interacts with the database,
|
||||
we enable the SQL sandbox, so changes done to the database
|
||||
are reverted at the end of every test. If you use PostgreSQL, you
|
||||
can even run database tests asynchronously by setting
|
||||
`use AprsmeWeb.ConnCase, async: true`, although
|
||||
this option is not recommended for other databases.
|
||||
When the test case interacts with the database, the SQL sandbox is enabled
|
||||
so changes are reverted at the end of every test. PostgreSQL users can
|
||||
run database tests asynchronously by setting `use AprsmeWeb.ConnCase, async: true`.
|
||||
"""
|
||||
|
||||
use ExUnit.CaseTemplate
|
||||
|
|
|
|||
|
|
@ -1,17 +1,13 @@
|
|||
defmodule Aprsme.DataCase do
|
||||
@moduledoc """
|
||||
This module defines the test case to be used by
|
||||
data tests.
|
||||
Test case for data-layer tests.
|
||||
|
||||
You may define functions here to be used as helpers in
|
||||
your data tests. See `errors_on/2`'s definition as an example.
|
||||
Imports Ecto helpers (`Ecto`, `Ecto.Changeset`, `Ecto.Query`) and
|
||||
provides utilities like `errors_on/1` for validating changesets.
|
||||
|
||||
Finally, if the test case interacts with the database,
|
||||
we enable the SQL sandbox, so changes done to the database
|
||||
are reverted at the end of every test. If you use PostgreSQL, you
|
||||
can even run database tests asynchronously by setting
|
||||
`use Aprsme.DataCase, async: true`, although
|
||||
this option is not recommended for other databases.
|
||||
When the test case interacts with the database, the SQL sandbox is enabled
|
||||
so changes are reverted at the end of every test. PostgreSQL users can
|
||||
run database tests asynchronously by setting `use Aprsme.DataCase, async: true`.
|
||||
"""
|
||||
|
||||
use ExUnit.CaseTemplate
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
defmodule Aprsme.AccountsFixtures do
|
||||
@moduledoc """
|
||||
This module defines test helpers for creating
|
||||
entities via the `Aprsme.Accounts` context.
|
||||
Test helpers for creating user entities via the `Aprsme.Accounts` context.
|
||||
"""
|
||||
|
||||
def unique_user_email, do: "user#{System.unique_integer()}@example.com"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
defmodule Aprsme.PacketsFixtures do
|
||||
@moduledoc """
|
||||
This module defines test helpers for creating
|
||||
entities via the `Aprsme.Packets` context.
|
||||
Test helpers for creating packet entities via the `Aprsme.Packets` context.
|
||||
"""
|
||||
|
||||
alias Aprsme.Callsign
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue