towerops/lib/towerops_web/graphql/subscriptions.ex
Graham McIntire 72f81c572d
add real-time GraphQL API with time-series queries and subscriptions
- add absinthe_phoenix for WebSocket subscription support
- create GraphQL WebSocket socket with API token auth at /socket/graphql
- add time-series queries: deviceSensors, sensorReadings, interfaceTraffic, checkResults
- add subscriptions: deviceStatusChanged, alertEvent, sensorReadingsUpdated
- wire subscription triggers into agent_channel and alerts contexts
- add org-scoped access control for sensors/interfaces via join queries
- add 17 tests covering queries, org isolation, empty data edge cases, and auth
- update GraphQL API docs page with time-series and subscription sections
2026-03-12 10:24:39 -05:00

71 lines
1.9 KiB
Elixir

defmodule ToweropsWeb.GraphQL.Subscriptions do
@moduledoc """
Publishes events to GraphQL subscriptions.
Each function builds the subscription payload and calls
`Absinthe.Subscription.publish/3` to push to connected subscribers.
"""
@doc """
Publishes a device status change event.
Publishes to both org-wide and device-specific subscription topics.
"""
def publish_device_status(device_id, org_id, %{device_name: device_name, status: status}) do
payload = %{
device_id: device_id,
device_name: device_name,
status: status,
changed_at: to_string(DateTime.utc_now())
}
Absinthe.Subscription.publish(
ToweropsWeb.Endpoint,
payload,
device_status_changed: "gql:device_status:#{org_id}",
device_status_changed: "gql:device_status:#{org_id}:#{device_id}"
)
end
@doc """
Publishes an alert lifecycle event (created, acknowledged, resolved).
Accepts both Alert structs and plain maps.
"""
def publish_alert_event(alert, event_type) do
payload = %{
alert_id: Map.get(alert, :id),
alert_type: Map.get(alert, :alert_type),
severity: Map.get(alert, :severity),
message: Map.get(alert, :message),
device_id: Map.get(alert, :device_id),
event_type: event_type,
triggered_at: to_string(Map.get(alert, :triggered_at))
}
org_id = Map.get(alert, :organization_id)
Absinthe.Subscription.publish(
ToweropsWeb.Endpoint,
payload,
alert_event: "gql:alerts:#{org_id}"
)
end
@doc """
Publishes a batch of sensor readings for a device.
"""
def publish_sensor_readings(device_id, org_id, device_name, readings) do
payload = %{
device_id: device_id,
device_name: device_name,
readings: readings
}
Absinthe.Subscription.publish(
ToweropsWeb.Endpoint,
payload,
sensor_readings_updated: "gql:sensor_readings:#{org_id}:#{device_id}"
)
end
end