towerops/lib/towerops/geoip.ex

99 lines
2.5 KiB
Elixir

defmodule Towerops.GeoIP do
@moduledoc """
IP geolocation lookup using PostgreSQL database.
Provides fast lookups for country and city information from IP addresses
using the MaxMind GeoLite2-City database stored in PostgreSQL.
"""
import Ecto.Query
alias Towerops.GeoIP.Block
alias Towerops.GeoIP.Location
alias Towerops.Repo
@doc """
Looks up the country code for an IP address.
Returns the 2-letter country code or nil if not found.
## Examples
iex> Towerops.GeoIP.lookup("8.8.8.8")
"US"
iex> Towerops.GeoIP.lookup("invalid")
nil
"""
def lookup(ip_string) when is_binary(ip_string) do
case lookup_full(ip_string) do
%{country_code: country_code} -> country_code
nil -> nil
end
end
@doc """
Looks up full location information for an IP address.
Returns a map with country_code, country_name, city_name, and region information,
or nil if not found.
## Examples
iex> Towerops.GeoIP.lookup_full("8.8.8.8")
%{
country_code: "US",
country_name: "United States",
city_name: "Mountain View",
subdivision_1_name: "California",
subdivision_2_name: nil,
latitude: 37.386,
longitude: -122.0838
}
iex> Towerops.GeoIP.lookup_full("invalid")
nil
"""
def lookup_full(ip_string) when is_binary(ip_string) do
case parse_ip(ip_string) do
{:ok, ip_int} ->
find_location(ip_int)
:error ->
nil
end
end
defp find_location(ip_int) do
# Query for IP block that contains this IP address
# Uses index on (start_ip_int, end_ip_int) for fast range lookup
query =
from b in Block,
join: l in Location,
on: b.geoname_id == l.geoname_id,
where: b.start_ip_int <= ^ip_int and b.end_ip_int >= ^ip_int,
select: %{
country_code: l.country_code,
country_name: l.country_name,
city_name: l.city_name,
subdivision_1_name: l.subdivision_1_name,
subdivision_2_name: l.subdivision_2_name,
latitude: l.latitude,
longitude: l.longitude
},
limit: 1
Repo.one(query)
end
defp parse_ip(ip_string) do
case :inet.parse_address(String.to_charlist(ip_string)) do
{:ok, {a, b, c, d}} ->
# Convert IP to integer for range comparison
ip_int = a * 256 * 256 * 256 + b * 256 * 256 + c * 256 + d
{:ok, ip_int}
_ ->
:error
end
end
end