101 lines
2.9 KiB
Elixir
101 lines
2.9 KiB
Elixir
defmodule Towerops.Uisp.SiteSync do
|
|
@moduledoc """
|
|
Syncs UISP sites into the Towerops sites table.
|
|
|
|
Fetches all sites from the UISP API and upserts them by name within
|
|
the organization. GPS coordinates are mapped from the UISP response.
|
|
"""
|
|
|
|
import Ecto.Query
|
|
|
|
alias Towerops.Integrations.Integration
|
|
alias Towerops.Repo
|
|
alias Towerops.Sites.Site
|
|
alias Towerops.Uisp.Client
|
|
|
|
require Logger
|
|
|
|
@doc """
|
|
Fetches sites from UISP and upserts them into the local sites table.
|
|
|
|
Returns `{:ok, %{synced: count, site_map: map}}` where `site_map` maps
|
|
UISP site IDs to local Site records (used by device sync for site assignment).
|
|
"""
|
|
def sync_sites(%Integration{} = integration) do
|
|
base_url = integration.credentials["url"]
|
|
api_key = integration.credentials["api_key"]
|
|
org_id = integration.organization_id
|
|
|
|
case Client.list_sites(base_url, api_key) do
|
|
{:ok, sites} ->
|
|
{synced, site_map} = upsert_sites(org_id, sites)
|
|
{:ok, %{synced: synced, site_map: site_map}}
|
|
|
|
{:error, reason} ->
|
|
{:error, reason}
|
|
end
|
|
end
|
|
|
|
defp upsert_sites(org_id, sites) do
|
|
Enum.reduce(sites, {0, %{}}, fn site_data, {count, acc} ->
|
|
uisp_id = get_in(site_data, ["identification", "id"]) || site_data["id"]
|
|
name = get_in(site_data, ["identification", "name"]) || site_data["name"]
|
|
latitude = get_in(site_data, ["description", "gps", "latitude"])
|
|
longitude = get_in(site_data, ["description", "gps", "longitude"])
|
|
|
|
if is_nil(name) or name == "" do
|
|
{count, acc}
|
|
else
|
|
attrs = %{
|
|
organization_id: org_id,
|
|
name: name,
|
|
latitude: to_float(latitude),
|
|
longitude: to_float(longitude)
|
|
}
|
|
|
|
case upsert_site(attrs) do
|
|
{:ok, site} ->
|
|
new_acc = if uisp_id, do: Map.put(acc, uisp_id, site), else: acc
|
|
{count + 1, new_acc}
|
|
|
|
{:error, changeset} ->
|
|
Logger.warning("UISP site sync: failed to upsert site #{name}: #{inspect(changeset.errors)}")
|
|
{count, acc}
|
|
end
|
|
end
|
|
end)
|
|
end
|
|
|
|
defp upsert_site(attrs) do
|
|
%Site{}
|
|
|> Site.changeset(attrs)
|
|
|> Repo.insert(
|
|
on_conflict: {:replace, [:latitude, :longitude, :updated_at]},
|
|
conflict_target: [:organization_id, :name],
|
|
returning: true
|
|
)
|
|
end
|
|
|
|
@doc """
|
|
Builds a site_map from existing local sites, keyed by name.
|
|
|
|
Used after sync to allow device_sync to match UISP site names to local sites.
|
|
"""
|
|
def build_site_map_by_name(org_id) do
|
|
Site
|
|
|> where(organization_id: ^org_id)
|
|
|> Repo.all()
|
|
|> Map.new(fn site -> {site.name, site} end)
|
|
end
|
|
|
|
defp to_float(nil), do: nil
|
|
defp to_float(v) when is_float(v), do: v
|
|
defp to_float(v) when is_integer(v), do: v / 1
|
|
|
|
defp to_float(v) when is_binary(v) do
|
|
case Float.parse(v) do
|
|
{f, _} -> f
|
|
:error -> nil
|
|
end
|
|
end
|
|
end
|