towerops/lib/towerops_web/channels/mobile_channel.ex
Graham McIntire 91e3181bbc dialyzer: fix all unmatched_return warnings (154 → 0)
Prefix fire-and-forget calls (PubSub.broadcast, Oban enqueue, Logger,
update_*, etc.) with `_ =` so dialyzer knows the return value is
intentionally discarded. Wrap a few if/case expressions whose
branches produce mixed types the same way.

No behavior changes — only explicit acknowledgement of discarded
returns.

Warnings: 242 → 88.
2026-04-21 10:03:55 -05:00

97 lines
2.8 KiB
Elixir

defmodule ToweropsWeb.MobileChannel do
@moduledoc """
Phoenix channel for real-time mobile app communication.
Supports two topic patterns:
- `mobile:org:<org_id>` — org-level alerts and device status events
- `mobile:device:<device_id>` — device-level sensor and interface events
"""
use ToweropsWeb, :channel
alias Towerops.Devices
alias Towerops.Organizations
@impl true
def join("mobile:org:" <> org_id, _params, socket) do
user_id = socket.assigns.user_id
if Organizations.user_has_access?(user_id, org_id) do
_ = Phoenix.PubSub.subscribe(Towerops.PubSub, "organization:#{org_id}:alerts")
_ = Phoenix.PubSub.subscribe(Towerops.PubSub, "devices:org:#{org_id}")
{:ok, socket}
else
{:error, %{reason: "unauthorized"}}
end
end
def join("mobile:device:" <> device_id, _params, socket) do
user_id = socket.assigns.user_id
case Devices.get_device(device_id) do
nil ->
{:error, %{reason: "not_found"}}
device ->
if Organizations.user_has_access?(user_id, device.organization_id) do
_ = Phoenix.PubSub.subscribe(Towerops.PubSub, "device:#{device_id}")
{:ok, socket}
else
{:error, %{reason: "unauthorized"}}
end
end
end
# Org-level: alert changed
@impl true
def handle_info({:alert_changed, org_id}, socket) do
push(socket, "alert:changed", %{organization_id: org_id})
{:noreply, socket}
end
# Org-level: device events
def handle_info({event, org_id}, socket)
when event in [:device_created, :device_updated, :device_status_changed, :device_deleted] do
push(socket, "device:changed", %{
event: to_string(event),
organization_id: org_id
})
{:noreply, socket}
end
# Device-level: sensor and interface events from SensorChangeDetector/DevicePollerWorker
def handle_info({:device_event, event}, socket) when is_map(event) do
push(socket, "device:event", %{
device_id: event.device_id,
event_type: event.event_type,
severity: event.severity,
message: event.message,
metadata: event.metadata,
occurred_at: format_datetime(event.occurred_at)
})
{:noreply, socket}
end
# Device-level: bulk data updates
def handle_info({update_type, device_id}, socket)
when update_type in [:sensors_updated, :state_sensors_updated, :interfaces_updated, :neighbors_updated] and
is_binary(device_id) do
push(socket, "device:updated", %{
device_id: device_id,
update_type: to_string(update_type)
})
{:noreply, socket}
end
# Catch-all for unhandled PubSub messages
def handle_info(_msg, socket) do
{:noreply, socket}
end
defp format_datetime(%DateTime{} = dt), do: DateTime.to_iso8601(dt)
defp format_datetime(other), do: to_string(other)
end