87 lines
2.9 KiB
Elixir
87 lines
2.9 KiB
Elixir
defmodule Towerops.Sonar.Sync do
|
|
@moduledoc """
|
|
Orchestrates syncing data from the Sonar GraphQL API into the local database.
|
|
|
|
Pulls accounts, network sites, and inventory items from Sonar
|
|
and updates integration sync status.
|
|
"""
|
|
|
|
alias Towerops.Integrations
|
|
alias Towerops.Sonar.Client
|
|
|
|
require Logger
|
|
|
|
@doc """
|
|
Main entry point: syncs all entity types for the given integration.
|
|
|
|
Returns `{:ok, %{accounts: n, network_sites: n, inventory_items: n}}`
|
|
or `{:error, reason}`.
|
|
"""
|
|
def sync_organization(%Integrations.Integration{} = integration) do
|
|
instance_url = integration.credentials["instance_url"]
|
|
api_token = integration.credentials["api_token"]
|
|
|
|
with {:ok, accounts} <- Client.list_accounts(instance_url, api_token),
|
|
{:ok, sites} <- Client.list_network_sites(instance_url, api_token),
|
|
{:ok, items} <- Client.list_inventory_items(instance_url, api_token) do
|
|
accounts_count = length(accounts)
|
|
sites_count = length(sites)
|
|
items_count = length(items)
|
|
|
|
mrr = calculate_total_mrr(accounts)
|
|
|
|
message =
|
|
"Synced #{accounts_count} accounts, #{sites_count} sites, #{items_count} inventory items" <>
|
|
if(mrr > 0, do: " (MRR: $#{:erlang.float_to_binary(mrr / 1, decimals: 2)})", else: "")
|
|
|
|
Integrations.update_sync_status(integration, "success", message)
|
|
|
|
{:ok,
|
|
%{
|
|
accounts: accounts_count,
|
|
network_sites: sites_count,
|
|
inventory_items: items_count
|
|
}}
|
|
else
|
|
{:error, reason} ->
|
|
Integrations.update_sync_status(integration, "failed", humanize_sync_error(reason))
|
|
{:error, reason}
|
|
end
|
|
end
|
|
|
|
defp calculate_total_mrr(accounts) do
|
|
Enum.reduce(accounts, 0.0, fn account, total ->
|
|
services = account["account_services"] || []
|
|
|
|
service_mrr =
|
|
Enum.reduce(services, 0.0, fn service, acc ->
|
|
price = service["price_override"]
|
|
|
|
if is_number(price) do
|
|
acc + price
|
|
else
|
|
acc
|
|
end
|
|
end)
|
|
|
|
total + service_mrr
|
|
end)
|
|
end
|
|
|
|
defp humanize_sync_error(:unauthorized), do: "Authentication failed — check your API token"
|
|
defp humanize_sync_error(:forbidden), do: "Access denied — your API token may not have sufficient permissions"
|
|
defp humanize_sync_error({:rate_limited, retry_after}), do: "Rate limited by Sonar — retry after #{retry_after}s"
|
|
defp humanize_sync_error({:unexpected_status, status}), do: "Sonar returned unexpected HTTP #{status}"
|
|
|
|
defp humanize_sync_error({:graphql_errors, errors}) when is_list(errors) do
|
|
messages = Enum.map_join(errors, "; ", &(&1["message"] || inspect(&1)))
|
|
"GraphQL errors: #{messages}"
|
|
end
|
|
|
|
defp humanize_sync_error(reason) when is_binary(reason), do: reason
|
|
|
|
defp humanize_sync_error(reason) do
|
|
Logger.warning("Sonar sync failed with unexpected error: #{inspect(reason)}")
|
|
"An unexpected error occurred during sync: #{inspect(reason)}"
|
|
end
|
|
end
|