- Fixed info page not receiving live updates by implementing callsign-specific PubSub topics - Added automatic time counter for "Last Position" and other timestamps using JavaScript hook - Optimized map updates to only move marker instead of reloading entire map - Fixed APRS parser to properly handle [000/000/A=altitude format in comments - Removed packet caching on info page for real-time updates The info page now properly subscribes to updates for the specific callsign being viewed, timestamps automatically count up without page refresh, and the map smoothly animates marker position changes instead of flickering with full reloads. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
77 lines
2.6 KiB
Elixir
77 lines
2.6 KiB
Elixir
defmodule Aprsme.PostgresNotifier do
|
|
@moduledoc """
|
|
Listens to PostgreSQL NOTIFY events on the "aprs_events" and "aprs_packets" channels and broadcasts
|
|
them via Phoenix.PubSub for reactive, event-driven updates.
|
|
"""
|
|
use GenServer
|
|
|
|
@event_channel "aprs_events"
|
|
@event_topic "postgres:aprsme_events"
|
|
@packet_channel "aprs_packets"
|
|
@packet_topic "postgres:aprsme_packets"
|
|
|
|
def start_link(_opts) do
|
|
GenServer.start_link(__MODULE__, nil, name: __MODULE__)
|
|
end
|
|
|
|
@impl true
|
|
def init(_) do
|
|
{:ok, conn} = Postgrex.Notifications.start_link(Aprsme.Repo.config())
|
|
{:ok, _ref1} = Postgrex.Notifications.listen(conn, @event_channel)
|
|
{:ok, _ref2} = Postgrex.Notifications.listen(conn, @packet_channel)
|
|
{:ok, %{conn: conn}}
|
|
end
|
|
|
|
@impl true
|
|
def handle_info({:notification, _conn, _pid, @event_channel, payload}, state) do
|
|
Phoenix.PubSub.broadcast(Aprsme.PubSub, @event_topic, {:postgres_notify, payload})
|
|
{:noreply, state}
|
|
end
|
|
|
|
def handle_info({:notification, _conn, _pid, @packet_channel, payload}, state) do
|
|
case Jason.decode(payload) do
|
|
{:ok, packet} ->
|
|
# Broadcast to the general packet topic
|
|
Phoenix.PubSub.broadcast(Aprsme.PubSub, @packet_topic, {:postgres_packet, packet})
|
|
|
|
# Broadcast to callsign-specific topic
|
|
callsign = Map.get(packet, "sender") || Map.get(packet, :sender)
|
|
|
|
if is_binary(callsign) and callsign != "" do
|
|
normalized_callsign = String.upcase(String.trim(callsign))
|
|
Phoenix.PubSub.broadcast(Aprsme.PubSub, "packets:#{normalized_callsign}", {:postgres_packet, packet})
|
|
end
|
|
|
|
# Also broadcast to callsign-specific weather topic if it's a weather packet
|
|
broadcast_weather_packet_if_relevant(packet)
|
|
|
|
_ ->
|
|
:noop
|
|
end
|
|
|
|
{:noreply, state}
|
|
end
|
|
|
|
def handle_info(_msg, state), do: {:noreply, state}
|
|
|
|
defp broadcast_weather_packet_if_relevant(packet) do
|
|
# Check if this is a weather packet using centralized weather fields
|
|
has_weather_data =
|
|
Enum.any?(Aprsme.EncodingUtils.weather_fields(), fn field ->
|
|
value = Map.get(packet, to_string(field))
|
|
not is_nil(value)
|
|
end)
|
|
|
|
if has_weather_data do
|
|
# Extract callsign from packet
|
|
callsign = Map.get(packet, "sender") || Map.get(packet, :sender)
|
|
|
|
if is_binary(callsign) and callsign != "" do
|
|
# Normalize callsign and broadcast to weather-specific topic
|
|
normalized_callsign = String.upcase(String.trim(callsign))
|
|
weather_topic = "weather:#{normalized_callsign}"
|
|
Phoenix.PubSub.broadcast(Aprsme.PubSub, weather_topic, {:weather_packet, packet})
|
|
end
|
|
end
|
|
end
|
|
end
|