towerops/lib/towerops/uisp/client.ex

136 lines
3.5 KiB
Elixir

defmodule Towerops.Uisp.Client do
@moduledoc """
HTTP client for the UISP API.
Uses Req with built-in test support via `Req.Test`.
Auth: x-auth-token header.
Base URL is configured per-integration (user provides their UISP URL).
"""
require Logger
@doc """
Tests the connection to the UISP API by fetching one site.
Returns `{:ok, body}` on success, `{:error, reason}` on failure.
"""
def test_connection(base_url, api_key) do
case request(:get, "#{base_url}/sites?count=1", api_key) do
{:ok, %{status: status, body: body}} when status in 200..299 ->
{:ok, body}
{:ok, %{status: 401}} ->
{:error, :unauthorized}
{:ok, %{status: 403}} ->
{:error, :forbidden}
{:ok, %{status: status, body: body}} ->
Logger.warning("UISP API unexpected status #{status}: #{inspect(body)}")
{:error, {:unexpected_status, status}}
{:error, reason} ->
Logger.error("UISP API connection error: #{inspect(reason)}")
{:error, reason}
end
end
@doc """
Lists all sites from the UISP API.
Returns `{:ok, [site]}` on success.
"""
def list_sites(base_url, api_key) do
case request(:get, "#{base_url}/sites", api_key) do
{:ok, %{status: status, body: body}} when status in 200..299 ->
{:ok, normalize_list(body)}
{:ok, %{status: 401}} ->
{:error, :unauthorized}
{:ok, %{status: 403}} ->
{:error, :forbidden}
{:ok, %{status: status, body: body}} ->
{:error, {:unexpected_status, status, body}}
{:error, reason} ->
{:error, reason}
end
end
@doc """
Lists all devices from the UISP API.
Returns `{:ok, [device]}` on success.
"""
def list_devices(base_url, api_key) do
case request(:get, "#{base_url}/devices", api_key) do
{:ok, %{status: status, body: body}} when status in 200..299 ->
{:ok, normalize_list(body)}
{:ok, %{status: 401}} ->
{:error, :unauthorized}
{:ok, %{status: 403}} ->
{:error, :forbidden}
{:ok, %{status: status, body: body}} ->
{:error, {:unexpected_status, status, body}}
{:error, reason} ->
{:error, reason}
end
end
@doc """
Makes a raw request to any UISP API endpoint.
Returns `{:ok, body}` or `{:error, reason}`.
"""
def request_raw(method, url, api_key) do
case request(method, url, api_key) do
{:ok, %{status: status, body: body}} when status in 200..299 ->
{:ok, body}
{:ok, %{status: 401}} ->
{:error, :unauthorized}
{:ok, %{status: 404}} ->
{:error, :not_found}
{:ok, %{status: status, body: body}} ->
{:error, {:unexpected_status, status, body}}
{:error, reason} ->
{:error, reason}
end
end
defp normalize_list(body) when is_list(body), do: body
defp normalize_list(%{"items" => items}) when is_list(items), do: items
defp normalize_list(_), do: []
defp request(method, url, api_key) do
opts = [
method: method,
url: url,
headers: [{"x-auth-token", api_key}, {"accept", "application/json"}]
]
# Only use Req.Test plug in test environment
# Also disable retry in tests to avoid 7s timeout (1s + 2s + 4s exponential backoff)
opts =
if Application.get_env(:towerops, :env) == :test do
opts
|> Keyword.put(:plug, {Req.Test, __MODULE__})
|> Keyword.put(:retry, false)
else
opts
end
Req.request(opts)
rescue
exception ->
{:error, Exception.message(exception)}
end
end