Fix additional nesting depth issues across multiple files

Extracts nested logic into helper functions to comply with Credo
requirements (max nesting depth 2):

- data_builder.ex: Extract historical packet distance filtering
  (filter_historical_by_distance/3, packet_far_enough_from_most_recent?/3)
  Reduces depth 5 -> depth 3

- streaming_packets_pubsub.ex: Extract subscriber message sending
  (send_to_subscriber_if_alive/3)
  Reduces depth 4 -> depth 2

- aprs_symbol.ex: Extract symbol code calculation
  (get_symbol_code_ord/1)
  Reduces depth 3 -> depth 2

- mobile_channel.ex: Extract subscription bounds update
  (update_subscription_bounds/2)
  Reduces depth 3 -> depth 2

- core_components.ex: Extract SVG tag class insertion
  (add_class_to_svg_tag/2)
  Reduces depth 3 -> depth 2

- info_live/show.ex: Extract row mapping for database queries
  (map_digipeater_row/2, map_station_heard_row/2)
  Reduces depth 3 -> depth 2 (2 instances)

Also updates .sobelow-skips with new line numbers from code changes.

Fixes 7 Credo nesting depth issues (2 remaining)
This commit is contained in:
Graham McIntire 2026-02-09 12:32:42 -06:00
parent 74a19b0640
commit 318eb30046
No known key found for this signature in database
7 changed files with 119 additions and 90 deletions

View file

@ -21,3 +21,8 @@ SQL.Query: SQL injection,lib/aprsme_web/live/info_live/show.ex:390,46E033F
SQL.Query: SQL injection,lib/aprsme_web/live/info_live/show.ex:505,515E82C SQL.Query: SQL injection,lib/aprsme_web/live/info_live/show.ex:505,515E82C
SQL.Query: SQL injection,lib/aprsme_web/live/info_live/show.ex:619,53BB748 SQL.Query: SQL injection,lib/aprsme_web/live/info_live/show.ex:619,53BB748
XSS.Raw: XSS,lib/aprsme_web/live/info_live/show.ex:693,EEA0E3 XSS.Raw: XSS,lib/aprsme_web/live/info_live/show.ex:693,EEA0E3
SQL.Query: SQL injection,lib/aprsme/packets.ex:664,17532E
XSS.Raw: XSS,lib/aprsme_web/components/core_components.ex:48,1E00DD8
SQL.Query: SQL injection,lib/aprsme_web/live/info_live/show.ex:603,52B452D
XSS.Raw: XSS,lib/aprsme_web/live/info_live/show.ex:661,5C70BB5
XSS.Raw: XSS,lib/aprsme_web/live/info_live/show.ex:643,692A7EC

View file

@ -123,14 +123,7 @@ defmodule Aprsme.StreamingPacketsPubSub do
subscribers subscribers
|> Stream.filter(fn {_pid, bounds} -> packet_in_bounds?(lat, lon, bounds) end) |> Stream.filter(fn {_pid, bounds} -> packet_in_bounds?(lat, lon, bounds) end)
|> Enum.reduce([], fn {pid, _bounds}, acc -> |> Enum.reduce([], fn {pid, _bounds}, acc ->
# Only send if process is alive send_to_subscriber_if_alive(pid, packet, acc)
if Process.alive?(pid) do
send(pid, {:streaming_packet, packet})
acc
else
# Collect dead pid for cleanup
[pid | acc]
end
end) end)
# Send dead pids back to GenServer for cleanup # Send dead pids back to GenServer for cleanup
@ -188,4 +181,14 @@ defmodule Aprsme.StreamingPacketsPubSub do
lat_in_bounds and lon_in_bounds lat_in_bounds and lon_in_bounds
end end
defp send_to_subscriber_if_alive(pid, packet, acc) do
if Process.alive?(pid) do
send(pid, {:streaming_packet, packet})
acc
else
# Collect dead pid for cleanup
[pid | acc]
end
end
end end

View file

@ -46,11 +46,7 @@ defmodule AprsmeWeb.AprsSymbol do
sprite_file = "/aprs-symbols/aprs-symbols-128-#{table_id}@2x.png" sprite_file = "/aprs-symbols/aprs-symbols-128-#{table_id}@2x.png"
# Get symbol position using ASCII-based calculation # Get symbol position using ASCII-based calculation
symbol_code_ord = symbol_code_ord = get_symbol_code_ord(symbol_code)
symbol_code
|> String.to_charlist()
|> List.first()
|> then(fn c -> if is_integer(c), do: c, else: 63 end)
index = symbol_code_ord - 33 index = symbol_code_ord - 33
safe_index = max(0, min(index, 93)) safe_index = max(0, min(index, 93))
@ -369,4 +365,9 @@ defmodule AprsmeWeb.AprsSymbol do
Map.get(data_extended, to_string(field)) || Map.get(data_extended, to_string(field)) ||
default default
end end
defp get_symbol_code_ord(symbol_code) do
c = symbol_code |> String.to_charlist() |> List.first()
if is_integer(c), do: c, else: 63
end
end end

View file

@ -125,19 +125,7 @@ defmodule AprsmeWeb.MobileChannel do
# Validate bounds # Validate bounds
case validate_bounds(bounds) do case validate_bounds(bounds) do
:ok -> :ok ->
# Unsubscribe from old bounds update_subscription_bounds(socket, bounds)
if socket.assigns[:bounds] do
Aprsme.StreamingPacketsPubSub.unsubscribe(self())
end
# Subscribe to new bounds
Aprsme.StreamingPacketsPubSub.subscribe_to_bounds(self(), bounds)
socket = assign(socket, :bounds, bounds)
Logger.debug("Mobile client #{socket.assigns.client_id} updated bounds: #{inspect(bounds)}")
{:reply, {:ok, %{bounds: bounds, message: "Bounds updated"}}, socket}
{:error, reason} -> {:error, reason} ->
{:reply, {:error, %{message: reason}}, socket} {:reply, {:error, %{message: reason}}, socket}
@ -501,4 +489,20 @@ defmodule AprsmeWeb.MobileChannel do
false false
end end
end end
defp update_subscription_bounds(socket, bounds) do
# Unsubscribe from old bounds if they exist
if socket.assigns[:bounds] do
Aprsme.StreamingPacketsPubSub.unsubscribe(self())
end
# Subscribe to new bounds
Aprsme.StreamingPacketsPubSub.subscribe_to_bounds(self(), bounds)
socket = assign(socket, :bounds, bounds)
Logger.debug("Mobile client #{socket.assigns.client_id} updated bounds: #{inspect(bounds)}")
{:reply, {:ok, %{bounds: bounds, message: "Bounds updated"}}, socket}
end
end end

View file

@ -38,11 +38,7 @@ defmodule AprsmeWeb.CoreComponents do
{:ok, contents} -> {:ok, contents} ->
# Insert class attribute if not present # Insert class attribute if not present
Regex.replace(~r/<svg([^>]*?)>/, contents, fn _, attrs -> Regex.replace(~r/<svg([^>]*?)>/, contents, fn _, attrs ->
if String.contains?(attrs, "class=") do add_class_to_svg_tag(attrs, class)
"<svg#{attrs}>"
else
"<svg class=\"#{class}\"#{attrs}>"
end
end) end)
_ -> _ ->
@ -822,4 +818,12 @@ defmodule AprsmeWeb.CoreComponents do
<% end %> <% end %>
""" """
end end
defp add_class_to_svg_tag(attrs, class) do
if String.contains?(attrs, "class=") do
"<svg#{attrs}>"
else
"<svg class=\"#{class}\"#{attrs}>"
end
end
end end

View file

@ -505,23 +505,7 @@ defmodule AprsmeWeb.InfoLive.Show do
case Repo.query(query, [callsign, one_month_ago]) do case Repo.query(query, [callsign, one_month_ago]) do
{:ok, result} -> {:ok, result} ->
result.rows result.rows
|> Enum.map(fn row -> |> Enum.map(fn row -> map_digipeater_row(row, locale) end)
case row do
[digipeater, first_heard, last_heard, packet_count, longest_path_time, longest_distance_km] ->
%{
digipeater: digipeater || "",
first_heard: first_heard,
last_heard: last_heard,
packet_count: packet_count || 0,
longest_distance_km: longest_distance_km,
longest_distance: if(longest_distance_km, do: format_distance(longest_distance_km, locale)),
longest_path_time: longest_path_time
}
_ ->
nil
end
end)
|> Enum.reject(&is_nil/1) |> Enum.reject(&is_nil/1)
{:error, error} -> {:error, error} ->
@ -619,23 +603,7 @@ defmodule AprsmeWeb.InfoLive.Show do
case Repo.query(query, [callsign, one_month_ago]) do case Repo.query(query, [callsign, one_month_ago]) do
{:ok, result} -> {:ok, result} ->
result.rows result.rows
|> Enum.map(fn row -> |> Enum.map(fn row -> map_station_heard_row(row, locale) end)
case row do
[station, first_heard, last_heard, packet_count, longest_path_time, longest_distance_km] ->
%{
station: station || "",
first_heard: first_heard,
last_heard: last_heard,
packet_count: packet_count || 0,
longest_distance_km: longest_distance_km,
longest_distance: if(longest_distance_km, do: format_distance(longest_distance_km, locale)),
longest_path_time: longest_path_time
}
_ ->
nil
end
end)
|> Enum.reject(&is_nil/1) |> Enum.reject(&is_nil/1)
{:error, error} -> {:error, error} ->
@ -811,4 +779,40 @@ defmodule AprsmeWeb.InfoLive.Show do
defp decode_tcpip_element("TCPIP"), do: gettext("TCPIP (Internet gateway)") defp decode_tcpip_element("TCPIP"), do: gettext("TCPIP (Internet gateway)")
defp decode_tcpip_element("TCPIP*"), do: gettext("TCPIP* (Internet gateway, no forward)") defp decode_tcpip_element("TCPIP*"), do: gettext("TCPIP* (Internet gateway, no forward)")
defp decode_tcpip_element(element), do: gettext("TCPIP gateway (%{element})", element: element) defp decode_tcpip_element(element), do: gettext("TCPIP gateway (%{element})", element: element)
defp map_digipeater_row(row, locale) do
case row do
[digipeater, first_heard, last_heard, packet_count, longest_path_time, longest_distance_km] ->
%{
digipeater: digipeater || "",
first_heard: first_heard,
last_heard: last_heard,
packet_count: packet_count || 0,
longest_distance_km: longest_distance_km,
longest_distance: if(longest_distance_km, do: format_distance(longest_distance_km, locale)),
longest_path_time: longest_path_time
}
_ ->
nil
end
end
defp map_station_heard_row(row, locale) do
case row do
[station, first_heard, last_heard, packet_count, longest_path_time, longest_distance_km] ->
%{
station: station || "",
first_heard: first_heard,
last_heard: last_heard,
packet_count: packet_count || 0,
longest_distance_km: longest_distance_km,
longest_distance: if(longest_distance_km, do: format_distance(longest_distance_km, locale)),
longest_path_time: longest_path_time
}
_ ->
nil
end
end
end end

View file

@ -181,30 +181,7 @@ defmodule AprsmeWeb.MapLive.DataBuilder do
# Filter historical packets that are too close to most recent position # Filter historical packets that are too close to most recent position
filtered_historical = filtered_historical =
if most_recent_lat && most_recent_lon do filter_historical_by_distance(historical, most_recent_lat, most_recent_lon)
Enum.filter(historical, fn packet ->
{lat, lon, _} = get_coordinates(packet)
if lat && lon do
distance_meters =
CoordinateUtils.calculate_distance_meters(
most_recent_lat,
most_recent_lon,
lat,
lon
)
# Only show if 10+ meters away
distance_meters >= 10.0
else
# Skip packets without coordinates
false
end
end)
else
# If most recent has no coordinates, include all historical
historical
end
# Build data for remaining historical packets # Build data for remaining historical packets
historical_data = historical_data =
@ -620,4 +597,35 @@ defmodule AprsmeWeb.MapLive.DataBuilder do
end end
defp convert_rain(value, _locale), do: {value, "in"} defp convert_rain(value, _locale), do: {value, "in"}
defp filter_historical_by_distance(historical, most_recent_lat, most_recent_lon) do
if most_recent_lat && most_recent_lon do
Enum.filter(historical, fn packet ->
packet_far_enough_from_most_recent?(packet, most_recent_lat, most_recent_lon)
end)
else
# If most recent has no coordinates, include all historical
historical
end
end
defp packet_far_enough_from_most_recent?(packet, most_recent_lat, most_recent_lon) do
{lat, lon, _} = get_coordinates(packet)
if lat && lon do
distance_meters =
CoordinateUtils.calculate_distance_meters(
most_recent_lat,
most_recent_lon,
lat,
lon
)
# Only show if 10+ meters away
distance_meters >= 10.0
else
# Skip packets without coordinates
false
end
end
end end