towerops/lib/towerops_web/live/alert_live/index.ex
Graham McIntire 6aab59dcf5
refactor: centralize LiveView access control checks
Extract duplicate access control logic from multiple LiveView files into
a reusable AccessControl helper module.

**Changes:**
- NEW: lib/towerops_web/live/helpers/access_control.ex
- NEW: test/towerops_web/live/helpers/access_control_test.exs
- Refactor device_live/index.ex to use AccessControl.verify_site_access/2
  and verify_device_access/2
- Refactor device_live/form.ex to use AccessControl.verify_device_access/2
- Refactor alert_live/index.ex to use AccessControl.verify_alert_access/2
- Refactor device_live/show.ex to use AccessControl.verify_device_access/2
- Remove unused Repo aliases from refactored files

**Benefits:**
- Reduces code duplication across 7+ locations
- Centralizes security-critical access checks
- Improves testability (helper module has >90% coverage)
- Consistent error handling across all LiveViews

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-04 17:51:05 -06:00

88 lines
2.5 KiB
Elixir

defmodule ToweropsWeb.AlertLive.Index do
@moduledoc false
use ToweropsWeb, :live_view
alias Towerops.Alerts
alias ToweropsWeb.Live.Helpers.AccessControl
@impl true
def mount(_params, _session, socket) do
organization = socket.assigns.current_scope.organization
# Subscribe to alert events
_ =
if connected?(socket) do
_ = Phoenix.PubSub.subscribe(Towerops.PubSub, "alerts:new")
Phoenix.PubSub.subscribe(Towerops.PubSub, "alerts:resolved")
end
{:ok,
socket
|> assign(:page_title, "Alerts")
|> assign(:filter, "active")
|> load_alerts(organization.id)}
end
@impl true
def handle_params(params, _url, socket) do
filter = Map.get(params, "filter", "active")
{:noreply,
socket
|> assign(:filter, filter)
|> load_alerts(socket.assigns.current_scope.organization.id)}
end
@impl true
def handle_event("acknowledge", %{"id" => id}, socket) do
organization = socket.assigns.current_scope.organization
user_id = socket.assigns.current_scope.user.id
case AccessControl.verify_alert_access(id, organization.id) do
{:ok, alert} ->
perform_acknowledge_alert(socket, alert, user_id, organization.id)
{:error, :not_found} ->
{:noreply, put_flash(socket, :error, t_equipment("Alert not found"))}
{:error, :unauthorized} ->
{:noreply, put_flash(socket, :error, t_equipment("You don't have access to this alert"))}
end
end
defp perform_acknowledge_alert(socket, alert, user_id, organization_id) do
case Alerts.acknowledge_alert(alert, user_id) do
{:ok, _alert} ->
{:noreply,
socket
|> put_flash(:info, t_equipment("Alert acknowledged"))
|> load_alerts(organization_id)}
{:error, _changeset} ->
{:noreply, put_flash(socket, :error, t_equipment("Unable to acknowledge alert"))}
end
end
@impl true
def handle_info({:new_alert, _device_id, _alert_type}, socket) do
{:noreply, load_alerts(socket, socket.assigns.current_scope.organization.id)}
end
@impl true
def handle_info({:alert_resolved, _device_id, _alert_type}, socket) do
{:noreply, load_alerts(socket, socket.assigns.current_scope.organization.id)}
end
defp load_alerts(socket, organization_id) do
alerts =
case socket.assigns.filter do
"active" ->
Alerts.list_organization_active_alerts(organization_id)
_ ->
Alerts.list_organization_alerts(organization_id, 100)
end
assign(socket, :alerts, alerts)
end
end