From e736ccd316fbe7a76bfe64977a4f6cbe719ac9f3 Mon Sep 17 00:00:00 2001 From: ruslandoga <67764432+ruslandoga@users.noreply.github.com> Date: Sat, 22 Oct 2022 14:22:22 +0700 Subject: [PATCH] replace geolix with locus --- config/config.exs | 2 + config/runtime.exs | 31 +++-- config/test.exs | 39 +----- lib/plausible/application.ex | 6 + lib/plausible/geo.ex | 130 ++++++++++++++++++ lib/plausible/geo/adapter.ex | 6 + lib/plausible/geo/locus.ex | 48 +++++++ lib/plausible/ingestion/event.ex | 10 +- .../controllers/api/external_controller.ex | 9 +- .../controllers/stats_controller.ex | 13 +- mix.exs | 3 +- mix.lock | 4 +- .../api/external_controller_test.exs | 2 +- test/support/geo_stub.ex | 49 +++++++ 14 files changed, 278 insertions(+), 74 deletions(-) create mode 100644 lib/plausible/geo.ex create mode 100644 lib/plausible/geo/adapter.ex create mode 100644 lib/plausible/geo/locus.ex create mode 100644 test/support/geo_stub.ex diff --git a/config/config.exs b/config/config.exs index 369b11b00b264..43e3ab4204330 100644 --- a/config/config.exs +++ b/config/config.exs @@ -41,4 +41,6 @@ config :plausible, Plausible.Repo, connect_timeout: 300_000, handshake_timeout: 300_000 +config :plausible, Plausible.Geo, adapter: Plausible.Geo.Locus + import_config "#{config_env()}.exs" diff --git a/config/runtime.exs b/config/runtime.exs index 00002ac7bb6a9..811f8a78aea12 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -134,6 +134,8 @@ geolite2_country_db = ip_geolocation_db = get_var_from_path_or_env(config_dir, "IP_GEOLOCATION_DB", geolite2_country_db) geonames_source_file = get_var_from_path_or_env(config_dir, "GEONAMES_SOURCE_FILE") +maxmind_license_key = get_var_from_path_or_env(config_dir, "MAXMIND_LICENSE_KEY") +maxmind_edition = get_var_from_path_or_env(config_dir, "MAXMIND_EDITION") disable_auth = config_dir @@ -430,17 +432,24 @@ config :kaffy, ] ] -if config_env() != :test do - config :geolix, - databases: [ - %{ - id: :geolocation, - adapter: Geolix.Adapter.MMDB2, - source: ip_geolocation_db, - result_as: :raw - } - ] -end +geo_opts = + cond do + maxmind_license_key -> + [ + license_key: maxmind_license_key, + edition: maxmind_edition + ] + + ip_geolocation_db -> + [path: ip_geolocation_db] + + true -> + # adapter would fail on incorrect / missing opts anyway + # so leaving unhandled cases as empty opts + [] + end + +config :plausible, Plausible.Geo, geo_opts if geonames_source_file do config :location, :geonames_source_file, geonames_source_file diff --git a/config/test.exs b/config/test.exs index d148d4ca17de3..8013847fef16a 100644 --- a/config/test.exs +++ b/config/test.exs @@ -24,43 +24,8 @@ config :plausible, :google, config :bamboo, :refute_timeout, 10 -geolix_sample_lookup = %{ - city: %{geoname_id: 2_988_507, names: %{en: "Paris"}}, - continent: %{code: "EU", geoname_id: 6_255_148, names: %{en: "Europe"}}, - country: %{ - geoname_id: 3_017_382, - is_in_european_union: true, - iso_code: "FR", - names: %{en: "France"} - }, - ip_address: {2, 2, 2, 2}, - location: %{ - latitude: 48.8566, - longitude: 2.35222, - time_zone: "Europe/Paris", - weather_code: "FRXX0076" - }, - postal: %{code: "75000"}, - subdivisions: [ - %{geoname_id: 3_012_874, iso_code: "IDF", names: %{en: "Île-de-France"}}, - %{geoname_id: 2_968_815, iso_code: "75", names: %{en: "Paris"}} - ] -} - -config :geolix, - databases: [ - %{ - id: :geolocation, - adapter: Geolix.Adapter.Fake, - data: %{ - {1, 1, 1, 1} => %{country: %{iso_code: "US"}}, - {2, 2, 2, 2} => geolix_sample_lookup, - {1, 1, 1, 1, 1, 1, 1, 1} => %{country: %{iso_code: "US"}}, - {0, 0, 0, 0} => %{country: %{iso_code: "ZZ"}} - } - } - ] - config :plausible, session_timeout: 0, http_impl: Plausible.HTTPClient.Mock + +config :plausible, Plausible.Geo, adapter: Plausible.Geo.Stub diff --git a/lib/plausible/application.ex b/lib/plausible/application.ex index a267567f90f7f..53c0e7c92aafb 100644 --- a/lib/plausible/application.ex +++ b/lib/plausible/application.ex @@ -29,6 +29,7 @@ defmodule Plausible.Application do opts = [strategy: :one_for_one, name: Plausible.Supervisor] setup_sentry() setup_opentelemetry() + setup_geolocation() Location.load_all() Supervisor.start_link(children, opts) end @@ -116,4 +117,9 @@ defmodule Plausible.Application do OpentelemetryEcto.setup([:plausible, :clickhouse_repo]) OpentelemetryOban.setup() end + + defp setup_geolocation do + opts = Application.fetch_env!(:plausible, Plausible.Geo) + :ok = Plausible.Geo.load_db(opts) + end end diff --git a/lib/plausible/geo.ex b/lib/plausible/geo.ex new file mode 100644 index 0000000000000..e5762ddfa5bb2 --- /dev/null +++ b/lib/plausible/geo.ex @@ -0,0 +1,130 @@ +defmodule Plausible.Geo do + @moduledoc "Geolocation functions" + @adapter Application.compile_env!(:plausible, [__MODULE__, :adapter]) + + @doc """ + Looks up geo info about an ip address. + + Example: + + iex> lookup("8.7.6.5") + %{ + "city" => %{ + "geoname_id" => 5349755, + "names" => %{ + "de" => "Fontana", + "en" => "Fontana", + "ja" => "フォンタナ", + "ru" => "Фонтана" + } + }, + "continent" => %{ + "code" => "NA", + "geoname_id" => 6255149, + "names" => %{ + "de" => "Nordamerika", + "en" => "North America", + "es" => "Norteamérica", + "fr" => "Amérique du Nord", + "ja" => "北アメリカ", + "pt-BR" => "América do Norte", + "ru" => "Северная Америка", + "zh-CN" => "北美洲" + } + }, + "country" => %{ + "geoname_id" => 6252001, + "iso_code" => "US", + "names" => %{ + "de" => "Vereinigte Staaten", + "en" => "United States", + "es" => "Estados Unidos", + "fr" => "États Unis", + "ja" => "アメリカ", + "pt-BR" => "EUA", + "ru" => "США", + "zh-CN" => "美国" + } + }, + "location" => %{ + "accuracy_radius" => 50, + "latitude" => 34.1211, + "longitude" => -117.4362, + "metro_code" => 803, + "time_zone" => "America/Los_Angeles" + }, + "postal" => %{"code" => "92336"}, + "registered_country" => %{ + "geoname_id" => 6252001, + "iso_code" => "US", + "names" => %{ + "de" => "Vereinigte Staaten", + "en" => "United States", + "es" => "Estados Unidos", + "fr" => "États Unis", + "ja" => "アメリカ", + "pt-BR" => "EUA", + "ru" => "США", + "zh-CN" => "美国" + } + }, + "subdivisions" => [ + %{ + "geoname_id" => 5332921, + "iso_code" => "CA", + "names" => %{ + "de" => "Kalifornien", + "en" => "California", + "es" => "California", + "fr" => "Californie", + "ja" => "カリフォルニア州", + "pt-BR" => "Califórnia", + "ru" => "Калифорния", + "zh-CN" => "加州" + } + } + ] + } + + """ + def lookup(ip_address) do + @adapter.lookup(ip_address) + end + + @doc """ + Starts the geodatabase loading process. Two options are supported, local file and maxmind key. + + Loading a local file: + + iex> load_db(path: "/etc/plausible/dbip-city.mmdb") + :ok + + Loading a maxmind db: + + # this license key is no longer active + iex> load_db(license_key: "LNpsJCCKPis6XvBP", edition: "GeoLite2-City", async: true) + :ok + + """ + def load_db(opts \\ []) do + @adapter.load_db(opts) + end + + @doc """ + Returns geodatabase type. Used for deciding whether to show the DBIP disclaimer. + + Example: + + # in the case of a dbip db + iex> database_type() + "DBIP-City-Lite" + + # in the case of a maxmind db + iex> database_type() + "GeoLite2-City" + + """ + def database_type do + @adapter.database_type() + end +end diff --git a/lib/plausible/geo/adapter.ex b/lib/plausible/geo/adapter.ex new file mode 100644 index 0000000000000..d83398145a061 --- /dev/null +++ b/lib/plausible/geo/adapter.ex @@ -0,0 +1,6 @@ +defmodule Plausible.Geo.Adapter do + @moduledoc "Behaviour to be implemented by geo adapters" + @callback load_db(opts :: Keyword.t()) :: :ok | {:error, reason :: any} + @callback database_type :: String.t() | nil + @callback lookup(:inet.ip_address() | String.t()) :: entry :: map | nil +end diff --git a/lib/plausible/geo/locus.ex b/lib/plausible/geo/locus.ex new file mode 100644 index 0000000000000..64e039336b33b --- /dev/null +++ b/lib/plausible/geo/locus.ex @@ -0,0 +1,48 @@ +defmodule Plausible.Geo.Locus do + @moduledoc false + @behaviour Plausible.Geo.Adapter + @db :geolocation + + @impl true + def load_db(opts) do + cond do + license_key = opts[:license_key] -> + edition = opts[:edition] || "GeoLite2-City" + :ok = :locus.start_loader(@db, {:maxmind, edition}, license_key: license_key) + + path = opts[:path] -> + :ok = :locus.start_loader(@db, path) + + true -> + raise "failed to load geolocation db: need :path or :license_key to be provided" + end + + unless opts[:async] do + {:ok, _version} = :locus.await_loader(@db) + end + + :ok + end + + @impl true + def database_type do + case :locus.get_info(@db, :metadata) do + {:ok, %{database_type: type}} -> type + _other -> nil + end + end + + @impl true + def lookup(ip_address) do + case :locus.lookup(@db, ip_address) do + {:ok, entry} -> + entry + + :not_found -> + nil + + {:error, reason} -> + raise "failed to lookup ip address #{inspect(ip_address)}: " <> inspect(reason) + end + end +end diff --git a/lib/plausible/ingestion/event.ex b/lib/plausible/ingestion/event.ex index 2a706233566c4..5255d51a622fe 100644 --- a/lib/plausible/ingestion/event.ex +++ b/lib/plausible/ingestion/event.ex @@ -203,18 +203,18 @@ defmodule Plausible.Ingestion.Event do end defp put_geolocation(%{} = event, %Request{} = request) do - result = Geolix.lookup(request.remote_ip, where: :geolocation) + result = Plausible.Geo.lookup(request.remote_ip) country_code = - get_in(result, [:country, :iso_code]) + get_in(result, ["country", "iso_code"]) |> ignore_unknown_country() - city_geoname_id = get_in(result, [:city, :geoname_id]) + city_geoname_id = get_in(result, ["city", "geoname_id"]) city_geoname_id = Map.get(CityOverrides.get(), city_geoname_id, city_geoname_id) subdivision1_code = case result do - %{subdivisions: [%{iso_code: iso_code} | _rest]} -> + %{"subdivisions" => [%{"iso_code" => iso_code} | _rest]} -> country_code <> "-" <> iso_code _ -> @@ -223,7 +223,7 @@ defmodule Plausible.Ingestion.Event do subdivision2_code = case result do - %{subdivisions: [_first, %{iso_code: iso_code} | _rest]} -> + %{"subdivisions" => [_first, %{"iso_code" => iso_code} | _rest]} -> country_code <> "-" <> iso_code _ -> diff --git a/lib/plausible_web/controllers/api/external_controller.ex b/lib/plausible_web/controllers/api/external_controller.ex index d4e1f3bf9ceaa..1d4d588b5b327 100644 --- a/lib/plausible_web/controllers/api/external_controller.ex +++ b/lib/plausible_web/controllers/api/external_controller.ex @@ -72,14 +72,7 @@ defmodule PlausibleWeb.Api.ExternalController do |> Keyword.take([:version, :commit, :created, :tags]) |> Map.new() - geo_database = - case Geolix.metadata(where: :geolocation) do - %{database_type: type} -> - type - - _ -> - "(not configured)" - end + geo_database = Plausible.Geo.database_type() || "(not configured)" json(conn, %{ geo_database: geo_database, diff --git a/lib/plausible_web/controllers/stats_controller.ex b/lib/plausible_web/controllers/stats_controller.ex index 6eef88d3364ba..3541d11e5e72e 100644 --- a/lib/plausible_web/controllers/stats_controller.ex +++ b/lib/plausible_web/controllers/stats_controller.ex @@ -311,14 +311,13 @@ defmodule PlausibleWeb.StatsController do end defp is_dbip() do - if Application.get_env(:plausible, :is_selfhost) do - case Geolix.metadata(where: :geolocation) do - %{database_type: type} -> + is_or_nil = + if Application.get_env(:plausible, :is_selfhost) do + if type = Plausible.Geo.database_type() do String.starts_with?(type, "DBIP") - - _ -> - false + end end - end + + !!is_or_nil end end diff --git a/mix.exs b/mix.exs index 1eeb1d0dd504a..702b6fce41e81 100644 --- a/mix.exs +++ b/mix.exs @@ -80,8 +80,7 @@ defmodule Plausible.MixProject do {:floki, "~> 0.32.0", only: :test}, {:fun_with_flags, "~> 1.9.0"}, {:fun_with_flags_ui, "~> 0.8"}, - {:geolix, "~> 2.0"}, - {:geolix_adapter_mmdb2, "~> 0.6.0"}, + {:locus, "~> 2.3"}, {:hackney, "~> 1.8"}, {:hammer, "~> 6.0"}, {:httpoison, "~> 1.4"}, diff --git a/mix.lock b/mix.lock index 636d92a084329..2cbceba34a257 100644 --- a/mix.lock +++ b/mix.lock @@ -48,8 +48,6 @@ "fun_with_flags": {:hex, :fun_with_flags, "1.9.0", "0be8692727623af0c00e353f7bdcc9934dd6e1f87798ca6bfec9e739d42b63e6", [:mix], [{:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: true]}, {:redix, "~> 1.0", [hex: :redix, repo: "hexpm", optional: true]}], "hexpm", "8145dc009d549389f1b1915a715fb7e357fcd8fd7b91c74f04aae74912eef27a"}, "fun_with_flags_ui": {:hex, :fun_with_flags_ui, "0.8.0", "70587e344ba2035516a639e7bd8cb2ce8d54ee6c5f5dfd3e4e6f6a776e14ac1d", [:mix], [{:cowboy, ">= 2.0.0", [hex: :cowboy, repo: "hexpm", optional: true]}, {:fun_with_flags, "~> 1.8", [hex: :fun_with_flags, repo: "hexpm", optional: false]}, {:plug, "~> 1.12", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 2.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm", "95e1e5afae77e259a4a43f930f3da97ff3c388a72d4622f7848cb7ee34de6338"}, "gen_smtp": {:hex, :gen_smtp, "1.1.1", "bf9303c31735100631b1d708d629e4c65944319d1143b5c9952054f4a1311d85", [:rebar3], [{:hut, "1.3.0", [hex: :hut, repo: "hexpm", optional: false]}, {:ranch, ">= 1.7.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "51bc50cc017efd4a4248cbc39ea30fb60efa7d4a49688986fafad84434ff9ab7"}, - "geolix": {:hex, :geolix, "2.0.0", "7e65764bedfc98d08a3ddb24c417657c9d438eff163280b45fbb7de289626acd", [:mix], [], "hexpm", "8742bf588ed0bb7def2c443204d09d355990846c6efdff96ded66aac24c301df"}, - "geolix_adapter_mmdb2": {:hex, :geolix_adapter_mmdb2, "0.6.0", "6ab9dbf6ea395817aa1fd2597be25d0dda1853c7f664e62716e47728d18bc4f9", [:mix], [{:geolix, "~> 2.0", [hex: :geolix, repo: "hexpm", optional: false]}, {:mmdb2_decoder, "~> 3.0", [hex: :mmdb2_decoder, repo: "hexpm", optional: false]}], "hexpm", "06ff962feae8a310cffdf86b74bfcda6e2d0dccb439bb1f62df2b657b1c0269b"}, "gettext": {:hex, :gettext, "0.19.1", "564953fd21f29358e68b91634799d9d26989f8d039d7512622efb3c3b1c97892", [:mix], [], "hexpm", "10c656c0912b8299adba9b061c06947511e3f109ab0d18b44a866a4498e77222"}, "gproc": {:hex, :gproc, "0.8.0", "cea02c578589c61e5341fce149ea36ccef236cc2ecac8691fba408e7ea77ec2f", [:rebar3], [], "hexpm", "580adafa56463b75263ef5a5df4c86af321f68694e7786cb057fd805d1e2a7de"}, "grpcbox": {:hex, :grpcbox, "0.16.0", "b83f37c62d6eeca347b77f9b1ec7e9f62231690cdfeb3a31be07cd4002ba9c82", [:rebar3], [{:acceptor_pool, "~>1.0.0", [hex: :acceptor_pool, repo: "hexpm", optional: false]}, {:chatterbox, "~>0.13.0", [hex: :ts_chatterbox, repo: "hexpm", optional: false]}, {:ctx, "~>0.6.0", [hex: :ctx, repo: "hexpm", optional: false]}, {:gproc, "~>0.8.0", [hex: :gproc, repo: "hexpm", optional: false]}], "hexpm", "294df743ae20a7e030889f00644001370a4f7ce0121f3bbdaf13cf3169c62913"}, @@ -66,6 +64,7 @@ "jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"}, "kaffy": {:hex, :kaffy, "0.9.0", "bef34c9729f6a3af4d0dea8eede8bcb9e11371a83ac9a8b393991bce81839517", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.11", [hex: :phoenix_html, repo: "hexpm", optional: false]}], "hexpm", "d18ff57b8e68feb433aed11e71510cd357abc7034e75358af5deff7d0d4c6ed3"}, "location": {:git, "https://github.com/plausible/location.git", "8faf4f08b06905adde43554dc1d9d35675654816", []}, + "locus": {:hex, :locus, "2.3.6", "c9f53fd5df872fca66a54dc0aa2f8b2d3640388e56a0c39a741be0df6d8854bf", [:rebar3], [{:tls_certificate_check, "~> 1.9", [hex: :tls_certificate_check, repo: "hexpm", optional: false]}], "hexpm", "6087aa9a69673e7011837fb4b3d7f756560adde76892c32f5f93904ee30064e2"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, @@ -74,7 +73,6 @@ "mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, "mint": {:hex, :mint, "1.4.2", "50330223429a6e1260b2ca5415f69b0ab086141bc76dc2fbf34d7c389a6675b2", [:mix], [{:castore, "~> 0.1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "ce75a5bbcc59b4d7d8d70f8b2fc284b1751ffb35c7b6a6302b5192f8ab4ddd80"}, - "mmdb2_decoder": {:hex, :mmdb2_decoder, "3.0.1", "78e3aedde88035c6873ada5ceaf41b7f15a6259ed034e0eaca72ccfa937798f0", [:mix], [], "hexpm", "316af0f388fac824782d944f54efe78e7c9691bbbdb0afd5cccdd0510adf559d"}, "mox": {:hex, :mox, "1.0.2", "dc2057289ac478b35760ba74165b4b3f402f68803dd5aecd3bfd19c183815d64", [:mix], [], "hexpm", "f9864921b3aaf763c8741b5b8e6f908f44566f1e427b2630e89e9a73b981fef2"}, "nanoid": {:hex, :nanoid, "2.0.5", "1d2948d8967ef2d948a58c3fef02385040bd9823fc6394bd604b8d98e5516b22", [:mix], [], "hexpm", "956e8876321104da72aa48770539ff26b36b744cd26753ec8e7a8a37e53d5f58"}, "nimble_options": {:hex, :nimble_options, "0.4.0", "c89babbab52221a24b8d1ff9e7d838be70f0d871be823165c94dd3418eea728f", [:mix], [], "hexpm", "e6701c1af326a11eea9634a3b1c62b475339ace9456c1a23ec3bc9a847bca02d"}, diff --git a/test/plausible_web/controllers/api/external_controller_test.exs b/test/plausible_web/controllers/api/external_controller_test.exs index a9f427fa5be56..bc7d97d7d75de 100644 --- a/test/plausible_web/controllers/api/external_controller_test.exs +++ b/test/plausible_web/controllers/api/external_controller_test.exs @@ -967,7 +967,7 @@ defmodule PlausibleWeb.Api.ExternalControllerTest do conn |> put_req_header("user-agent", @user_agent) - |> put_req_header("x-forwarded-for", "982.32.12.1") + |> put_req_header("x-forwarded-for", "82.32.12.1") |> post("/api/event", params) [one, two] = get_events("user-id-test-domain-2.com") diff --git a/test/support/geo_stub.ex b/test/support/geo_stub.ex new file mode 100644 index 0000000000000..5d81226a719bb --- /dev/null +++ b/test/support/geo_stub.ex @@ -0,0 +1,49 @@ +defmodule Plausible.Geo.Stub do + @moduledoc false + @behaviour Plausible.Geo.Adapter + + sample_lookup = %{ + "city" => %{"geoname_id" => 2_988_507, "names" => %{"en" => "Paris"}}, + "continent" => %{"code" => "EU", "geoname_id" => 6_255_148, "names" => %{"en" => "Europe"}}, + "country" => %{ + "geoname_id" => 3_017_382, + "is_in_european_union" => true, + "iso_code" => "FR", + "names" => %{"en" => "France"} + }, + "location" => %{ + "latitude" => 48.8566, + "longitude" => 2.35222, + "time_zone" => "Europe/Paris", + "weather_code" => "FRXX0076" + }, + "postal" => %{"code" => "75000"}, + "subdivisions" => [ + %{"geoname_id" => 3_012_874, "iso_code" => "IDF", "names" => %{"en" => "Île-de-France"}}, + %{"geoname_id" => 2_968_815, "iso_code" => "75", "names" => %{"en" => "Paris"}} + ] + } + + @lut %{ + {1, 1, 1, 1} => %{"country" => %{"iso_code" => "US"}}, + {2, 2, 2, 2} => sample_lookup, + {1, 1, 1, 1, 1, 1, 1, 1} => %{"country" => %{"iso_code" => "US"}}, + {0, 0, 0, 0} => %{"country" => %{"iso_code" => "ZZ"}} + } + + @impl true + def lookup(ip_address) when is_tuple(ip_address) do + Map.get(@lut, ip_address) + end + + def lookup(ip_address) when is_binary(ip_address) do + {:ok, ip_address} = :inet.parse_address(to_charlist(ip_address)) + lookup(ip_address) + end + + @impl true + def load_db(_opts), do: :ok + + @impl true + def database_type, do: "DBIP-City-Lite" +end