defmodule ToweropsWeb.GraphQL.Resolvers.Device do @moduledoc "GraphQL resolvers for device queries and mutations." alias Towerops.Devices alias Towerops.Devices.Device alias Towerops.Monitoring alias Towerops.Repo alias Towerops.Snmp alias ToweropsWeb.GraphQL.Resolvers.Helpers alias ToweropsWeb.ScopedResource def list(_parent, args, %{context: %{organization_id: org_id}}) do params = Map.new(args, fn {k, v} -> {to_string(k), v} end) devices = Devices.list_organization_devices(org_id, params) {:ok, devices} end def list(_parent, _args, _resolution), do: Helpers.authentication_error() def get(_parent, %{id: id}, %{context: %{organization_id: org_id}}) do with {:ok, device} <- fetch_org_device(id, org_id) do {:ok, Repo.preload(device, :site)} end end def get(_parent, _args, _resolution), do: Helpers.authentication_error() def create(_parent, %{input: input}, %{context: %{organization_id: org_id}}) do attrs = input |> Map.new(fn {k, v} -> {to_string(k), v} end) |> Map.put("organization_id", org_id) case Devices.create_device(attrs) do {:ok, device} -> {:ok, Repo.preload(device, :site)} {:error, changeset} -> {:error, Helpers.format_changeset_errors(changeset)} end end def create(_parent, _args, _resolution), do: Helpers.authentication_error() def update(_parent, %{id: id, input: input}, %{context: %{organization_id: org_id}}) do with {:ok, device} <- fetch_org_device(id, org_id) do attrs = Map.new(input, fn {k, v} -> {to_string(k), v} end) case Devices.update_device(device, attrs) do {:ok, updated} -> {:ok, Repo.preload(updated, :site, force: true)} {:error, changeset} -> {:error, Helpers.format_changeset_errors(changeset)} end end end def update(_parent, _args, _resolution), do: Helpers.authentication_error() def delete(_parent, %{id: id}, %{context: %{organization_id: org_id}}) do with {:ok, device} <- fetch_org_device(id, org_id) do case Devices.delete_device(device) do {:ok, _} -> {:ok, %{success: true, message: "Device deleted"}} {:error, _} -> {:ok, %{success: false, message: "Could not delete device"}} end end end def delete(_parent, _args, _resolution), do: Helpers.authentication_error() def metrics(_parent, %{device_id: device_id} = args, %{context: %{organization_id: org_id}}) do with {:ok, device} <- fetch_org_device(device_id, org_id) do {:ok, build_device_metrics(device, org_id, args)} end end def metrics(_parent, _args, _resolution), do: Helpers.authentication_error() def interfaces(_parent, %{device_id: device_id}, %{context: %{organization_id: org_id}}) do with {:ok, device} <- fetch_org_device(device_id, org_id) do device = Repo.preload(device, :snmp_device) interfaces = case device.snmp_device do nil -> [] snmp_device -> Snmp.list_interfaces(snmp_device.id) end {:ok, interfaces} end end def interfaces(_parent, _args, _resolution), do: Helpers.authentication_error() defp fetch_org_device(id, org_id) do case ScopedResource.fetch(Device, id, org_id) do {:ok, device} -> {:ok, device} {:error, _reason} -> {:error, "Device not found"} end end defp build_device_metrics(device, org_id, args) do hours = parse_time_range(Map.get(args, :time_range, "24h")) from_time = DateTime.add(DateTime.utc_now(), -hours * 3600, :second) check_type = args[:sensor_type] checks = Monitoring.list_checks(org_id, device_id: device.id, check_type: check_type) Enum.flat_map(checks, fn check -> check.id |> Monitoring.get_check_results(from: from_time) |> Enum.map(&format_check_result(&1, check)) end) end defp format_check_result(result, check) do %{ timestamp: to_string(result.checked_at), value: result.value, status: result.status, check_name: check.name, check_type: check.check_type } end defp parse_time_range(range) do case Integer.parse(String.replace(range, "h", "")) do {hours, _} -> hours :error -> 24 end end end