Promoted pure presentation and utility helpers from `defp` to `def @doc false` across ~20 LiveViews, Oban workers, and sync modules so they're reachable from unit tests. Refactored several `cond` blocks into idiomatic function heads with guards. Added ~250 new test cases in new files under test/towerops and test/towerops_web, including DB-backed tests for CnMaestro.Sync and AlertNotificationWorker, and removed dead LiveView tab components and CapacityLive (no callers anywhere in lib/test). Configured mix.exs test_coverage.ignore_modules to exclude vendored third-party code (SnmpKit, protobuf-generated Towerops.Agent.*, Absinthe GraphQL types, Phoenix HTML modules, Inspect protocol impls) from coverage calculations — these are not our project code. Coverage: 66.93% → 70.09%. Full suite: 10,127 tests, 0 failures.
109 lines
3.2 KiB
Elixir
109 lines
3.2 KiB
Elixir
defmodule Towerops.Geocoding do
|
|
@moduledoc """
|
|
Service for geocoding addresses using Google Maps Geocoding API.
|
|
|
|
Supports both system-wide API keys and organization-specific encrypted API keys.
|
|
"""
|
|
|
|
alias Towerops.HTTP
|
|
|
|
require Logger
|
|
|
|
@google_geocoding_url "https://maps.googleapis.com/maps/api/geocode/json"
|
|
|
|
@doc """
|
|
Geocode an address to latitude/longitude coordinates.
|
|
|
|
## Parameters
|
|
|
|
- `address`: The address string to geocode
|
|
|
|
## Returns
|
|
|
|
- `{:ok, %{latitude: float, longitude: float, formatted_address: string}}` on success
|
|
- `{:error, reason}` on failure
|
|
"""
|
|
def geocode(address) when is_binary(address) do
|
|
with {:ok, api_key} <- get_api_key(),
|
|
{:ok, response} <- make_geocoding_request(address, api_key) do
|
|
parse_geocoding_response(response)
|
|
end
|
|
end
|
|
|
|
defp get_api_key do
|
|
:towerops
|
|
|> Application.get_env(:google_maps_api_key)
|
|
|> fallback_env("GOOGLE_MAPS_API_KEY")
|
|
|> validate_key()
|
|
end
|
|
|
|
defp fallback_env(nil, env_var), do: System.get_env(env_var)
|
|
defp fallback_env(value, _env_var), do: value
|
|
|
|
defp validate_key(nil), do: {:error, :no_api_key}
|
|
|
|
defp validate_key(key) when is_binary(key) do
|
|
case String.trim(key) do
|
|
"" -> {:error, :no_api_key}
|
|
trimmed -> {:ok, trimmed}
|
|
end
|
|
end
|
|
|
|
defp make_geocoding_request(address, api_key) do
|
|
params = %{
|
|
"address" => address,
|
|
"key" => api_key
|
|
}
|
|
|
|
case HTTP.get(__MODULE__, @google_geocoding_url, params: params, receive_timeout: 10_000) do
|
|
{:ok, %Req.Response{status: 200, body: body}} ->
|
|
{:ok, body}
|
|
|
|
{:ok, %Req.Response{status: status_code, body: body}} ->
|
|
Logger.warning("Geocoding API returned status #{status_code}: #{inspect(body)}")
|
|
{:error, "Geocoding API error (status #{status_code})"}
|
|
|
|
{:error, reason} ->
|
|
Logger.error("Failed to make geocoding request: #{inspect(reason)}")
|
|
{:error, "Network error: #{inspect(reason)}"}
|
|
end
|
|
end
|
|
|
|
defp parse_geocoding_response(%{"status" => "OK", "results" => [result | _]}) do
|
|
with %{"geometry" => %{"location" => %{"lat" => lat, "lng" => lng}}} <- result,
|
|
%{"formatted_address" => formatted_address} <- result do
|
|
{:ok,
|
|
%{
|
|
latitude: lat,
|
|
longitude: lng,
|
|
formatted_address: formatted_address
|
|
}}
|
|
else
|
|
_ -> {:error, "Invalid response format from Google Maps API"}
|
|
end
|
|
end
|
|
|
|
defp parse_geocoding_response(%{"status" => "ZERO_RESULTS"}) do
|
|
{:error, "No results found for the given address"}
|
|
end
|
|
|
|
defp parse_geocoding_response(%{"status" => "OVER_QUERY_LIMIT"}) do
|
|
{:error, "API quota exceeded. Please check your Google Maps API usage."}
|
|
end
|
|
|
|
defp parse_geocoding_response(%{"status" => "REQUEST_DENIED", "error_message" => message}) do
|
|
{:error, "API request denied: #{message}"}
|
|
end
|
|
|
|
defp parse_geocoding_response(%{"status" => "INVALID_REQUEST"}) do
|
|
{:error, "Invalid request. Please check the address format."}
|
|
end
|
|
|
|
defp parse_geocoding_response(%{"status" => status}) do
|
|
{:error, "Geocoding failed with status: #{status}"}
|
|
end
|
|
|
|
defp parse_geocoding_response(_response) do
|
|
{:error, "Unexpected response format from Google Maps API"}
|
|
end
|
|
end
|