137 lines
3.6 KiB
Elixir
137 lines
3.6 KiB
Elixir
defmodule ToweropsWeb.Api.V1.GeoipController do
|
|
@moduledoc """
|
|
API controller for managing GeoIP database imports.
|
|
|
|
Allows superusers to trigger GeoIP database imports from MaxMind CSV files.
|
|
|
|
All endpoints require superuser API token authentication.
|
|
"""
|
|
|
|
use ToweropsWeb, :controller
|
|
|
|
alias Mix.Tasks.Geoip.Import
|
|
|
|
require Logger
|
|
|
|
@doc """
|
|
Import GeoIP database from MaxMind GeoLite2-City CSV files.
|
|
|
|
Requires superuser API token.
|
|
|
|
## Request
|
|
|
|
POST /api/v1/geoip/import
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"directory": "/path/to/GeoLite2-City-CSV_20260127"
|
|
}
|
|
|
|
## Response
|
|
|
|
202 Accepted
|
|
{
|
|
"status": "ok",
|
|
"message": "GeoIP import started in background"
|
|
}
|
|
|
|
Or:
|
|
|
|
200 OK
|
|
{
|
|
"status": "ok",
|
|
"message": "GeoIP database imported successfully",
|
|
"locations_count": 130000,
|
|
"blocks_count": 3500000
|
|
}
|
|
"""
|
|
def import_database(conn, %{"directory" => directory}) do
|
|
with :ok <- require_superuser(conn) do
|
|
Logger.info("GeoIP import requested by #{conn.assigns[:current_user].email} for directory: #{directory}")
|
|
|
|
# Expand the path to handle ~ and relative paths
|
|
expanded_directory = Path.expand(directory)
|
|
|
|
# Verify directory exists
|
|
if File.dir?(expanded_directory) do
|
|
# Run import synchronously (it's fast enough with batching)
|
|
case run_import(expanded_directory) do
|
|
{:ok, locations_count, blocks_count} ->
|
|
Logger.info("GeoIP import completed: #{locations_count} locations, #{blocks_count} blocks")
|
|
|
|
conn
|
|
|> put_status(:ok)
|
|
|> json(%{
|
|
status: "ok",
|
|
message: "GeoIP database imported successfully",
|
|
locations_count: locations_count,
|
|
blocks_count: blocks_count
|
|
})
|
|
|
|
{:error, reason} ->
|
|
Logger.error("GeoIP import failed: #{inspect(reason)}")
|
|
|
|
conn
|
|
|> put_status(:internal_server_error)
|
|
|> json(%{error: "Import failed: #{inspect(reason)}"})
|
|
end
|
|
else
|
|
conn
|
|
|> put_status(:bad_request)
|
|
|> json(%{error: "Directory does not exist: #{directory}"})
|
|
end
|
|
end
|
|
end
|
|
|
|
def import_database(conn, _params) do
|
|
conn
|
|
|> put_status(:bad_request)
|
|
|> json(%{error: "Missing required parameter: directory"})
|
|
end
|
|
|
|
defp require_superuser(conn) do
|
|
user = conn.assigns[:current_user]
|
|
|
|
if !user || !user.is_superuser do
|
|
Logger.warning("GeoIP import attempted by non-superuser: #{inspect(user && user.email)}")
|
|
|
|
conn
|
|
|> put_status(:forbidden)
|
|
|> json(%{
|
|
error: "Superuser access required. Only API tokens created by superusers can import GeoIP data."
|
|
})
|
|
|> halt()
|
|
else
|
|
:ok
|
|
end
|
|
end
|
|
|
|
defp run_import(directory) do
|
|
alias Towerops.GeoIP.Block
|
|
alias Towerops.GeoIP.Location
|
|
alias Towerops.Repo
|
|
|
|
blocks_file = Path.join(directory, "GeoLite2-City-Blocks-IPv4.csv")
|
|
locations_file = Path.join(directory, "GeoLite2-City-Locations-en.csv")
|
|
|
|
# Verify files exist
|
|
if File.exists?(blocks_file) && File.exists?(locations_file) do
|
|
# Truncate existing data
|
|
Repo.delete_all(Block)
|
|
Repo.delete_all(Location)
|
|
|
|
# Load locations
|
|
{locations_count, valid_geoname_ids} = Import.load_locations(locations_file)
|
|
|
|
# Load blocks
|
|
blocks_count = Import.load_blocks(blocks_file, valid_geoname_ids)
|
|
|
|
{:ok, locations_count, blocks_count}
|
|
else
|
|
{:error, "Required CSV files not found in directory"}
|
|
end
|
|
rescue
|
|
e ->
|
|
{:error, Exception.message(e)}
|
|
end
|
|
end
|