diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..ba6da839414cc78716789fca34a7e3b1741ea859 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,20 @@ +before_script: + - git config --global url.https://.insteadOf git:// + - mkdir -p ~/.hex + - printf "{key,<<\"$HEX_KEY\">>}.\n{username,<<\"$HEX_USERNAME\">>}.\n" > ~/.hex/hex.config + +stages: + - compile + - test + +compile: + stage: compile + script: + - mix deps.get + - mix compile + +test: + stage: test + script: + - mix deps.get + - mix test diff --git a/GeoLite2-City.mmdb.gz b/GeoLite2-City.mmdb.gz new file mode 100644 index 0000000000000000000000000000000000000000..4aa5b0a7a1873ba280ad0ad73755576ebd8f751d Binary files /dev/null and b/GeoLite2-City.mmdb.gz differ diff --git a/GeoLite2-Country.mmdb.gz b/GeoLite2-Country.mmdb.gz new file mode 100644 index 0000000000000000000000000000000000000000..d9dfb035e83bebdd3c19893df09d4802064e5e79 Binary files /dev/null and b/GeoLite2-Country.mmdb.gz differ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..20b9650eae79ef7d0ec123e0d6836d0d9ed76821 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Erik Hedenström <erik@erlang.ninja> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 9aef16fe05cb76eeb8c883c9cb4434774d647505..3a152702657abc500e45fdede559d6402eb11d4b 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,16 @@ -# Plug.GeoIP2 +# plug_geoip2 -**TODO: Add description** +[](https://hex.pm/packages/plug_geoip2) +[](https://gitlab.hedenstroem.com/ci/projects/7?ref=master) -## Installation +## Setup -If [available in Hex](https://hex.pm/docs/publish), the package can be installed as: +To use plug_geoip2 in your projects, edit your `mix.exs` file and add plug_geoip2 as a dependency: - 1. Add plug_geoip2 to your list of dependencies in `mix.exs`: - - def deps do - [{:plug_geoip2, "~> 0.0.1"}] - end - - 2. Ensure plug_geoip2 is started before your application: - - def application do - [applications: [:plug_geoip2]] - end +```elixir +defp deps do + [ + {:plug_geoip2, "~> 0.1"} + ] +end +``` diff --git a/config/config.exs b/config/config.exs index 36a0977c1f8d8e8d8e2b10dea5ac213eff032a6a..aa4d73b3e876b2c34689ef795b250c6fbc27f919 100644 --- a/config/config.exs +++ b/config/config.exs @@ -28,3 +28,8 @@ use Mix.Config # here (which is why it is important to import them last). # # import_config "#{Mix.env}.exs" +config :geolix, + databases: [ + { :city, "GeoLite2-City.mmdb.gz" }, + { :country, "GeoLite2-Country.mmdb.gz" } + ] diff --git a/lib/mix/tasks/update_geolite2.ex b/lib/mix/tasks/update_geolite2.ex new file mode 100644 index 0000000000000000000000000000000000000000..89d499b1d7a3e40dce31987af5b775776616e7ae --- /dev/null +++ b/lib/mix/tasks/update_geolite2.ex @@ -0,0 +1,14 @@ +defmodule Mix.Tasks.Update.GeoLite2 do + use Mix.Task + + @shortdoc "Fetches the latest GeoLite2 DBs from MaxMind." + + def run(_) do + File.rm('GeoLite2-City.mmdb.gz') + File.rm('GeoLite2-Country.mmdb.gz') + :inets.start + :httpc.request(:get, {'http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.mmdb.gz', []}, [], [{:stream, 'GeoLite2-City.mmdb.gz'}]) + :httpc.request(:get, {'http://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.mmdb.gz', []}, [], [{:stream, 'GeoLite2-Country.mmdb.gz'}]) + end + +end diff --git a/lib/plug_geoip2.ex b/lib/plug_geoip2.ex index 44e69f43d2bf654597a5bf65f823f7a52b927efd..96c95b99bdf1640f15da24c8ec7aa689f482b03c 100644 --- a/lib/plug_geoip2.ex +++ b/lib/plug_geoip2.ex @@ -1,2 +1,28 @@ defmodule Plug.GeoIP2 do + + @moduledoc """ + Adds geo location to a Plug connection based upon the client IP address by using MaxMind's GeoIP2 database. + + To use it, just plug it into the desired module. The lookup uses the remote_ip field of the connection. + + plug Plug.GeoIP2, as: :raw, where: :city + + ## Options + + * `:as` - Return the result as a `:struct` or `:raw` (plain map) + * `:locale` - Language (atom) to fetch information for. + Only affects "top level" struct values. + * `:where` - Lookup information in a single registered database + """ + + @doc "Callback implementation for Plug.init/1" + def init(options) do + options + end + + @doc "Callback implementation for Plug.call/2" + def call(conn, options) do + conn |> Plug.Conn.assign(:geolocation, Geolix.lookup(conn.remote_ip, options)) + end + end diff --git a/mix.exs b/mix.exs index b388d2470fe8f65fb1ea0be0e76f6e93b0846bdc..6ba3e0e6b4ddaafab48a502232e2dc14b551bf9b 100644 --- a/mix.exs +++ b/mix.exs @@ -7,26 +7,44 @@ defmodule Plug.GeoIP2.Mixfile do elixir: "~> 1.1", build_embedded: Mix.env == :prod, start_permanent: Mix.env == :prod, - deps: deps] + deps: deps, + package: package, + description: description] end - # Configuration for the OTP application - # - # Type "mix help compile.app" for more information def application do - [applications: [:logger]] + [applications: [ :geolix ]] end - # Dependencies can be Hex packages: - # - # {:mydep, "~> 0.3.0"} - # - # Or git/path repositories: - # - # {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"} - # - # Type "mix help deps" for more examples and options defp deps do - [] + [ + {:plug, "~> 1.0"}, + {:geolix, "~> 0.9"} + ] end + + defp description do + """ + Adds geo location to a Plug connection based upon the client IP address by using MaxMind's GeoIP2 database. + """ + end + + defp package do + %{ + maintainers: ["Erik Hedenström"], + files: [ + "GeoLite2-City.mmdb.gz", + "GeoLite2-Country.mmdb.gz", + "lib", + "mix.exs", + "LICENSE", + "README.md" + ], + licenses: ["MIT"], + links: %{ + "GitLab" => "https://gitlab.hedenstroem.com/phoenix/plug_geoip2" + } + } + end + end diff --git a/mix.lock b/mix.lock new file mode 100644 index 0000000000000000000000000000000000000000..f0f074b7fda548ae8834ffc686cbb76ee40017be --- /dev/null +++ b/mix.lock @@ -0,0 +1,11 @@ +%{"certifi": {:hex, :certifi, "0.3.0"}, + "coverex": {:hex, :coverex, "1.4.8"}, + "geolix": {:hex, :geolix, "0.9.0"}, + "hackney": {:hex, :hackney, "1.4.5"}, + "httpoison": {:hex, :httpoison, "0.8.0"}, + "idna": {:hex, :idna, "1.0.2"}, + "mimerl": {:hex, :mimerl, "1.0.0"}, + "plug": {:hex, :plug, "1.0.2"}, + "poison": {:hex, :poison, "1.5.0"}, + "poolboy": {:hex, :poolboy, "1.5.1"}, + "ssl_verify_hostname": {:hex, :ssl_verify_hostname, "1.0.5"}} diff --git a/test/plug_geoip2_test.exs b/test/plug_geoip2_test.exs index ac774bc53579cc0cbc475623c3f0df1fd30d2b31..7533930291c318dffa608977b6bab406a0c5735e 100644 --- a/test/plug_geoip2_test.exs +++ b/test/plug_geoip2_test.exs @@ -1,8 +1,45 @@ +defmodule TestRouter do + use Plug.Router + + plug :match + plug :dispatch + plug Plug.GeoIP2, as: :struct, locale: :en, where: :city + + get "/" do + send_resp(conn, 200, "test") + end + + match _, do: send_resp(conn, 404, "not found") + +end + defmodule Plug.GeoIP2Test do - use ExUnit.Case + use ExUnit.Case, async: true + use Plug.Test + doctest Plug.GeoIP2 - test "the truth" do - assert 1 + 1 == 2 + @opts TestRouter.init([]) + + test "sets location correctly" do + conn = conn(:get, "/") + conn = %Plug.Conn{conn | remote_ip: {92, 244, 7, 86}} + conn = check_http(conn) + assert conn.assigns.geolocation.city.name == "Solna" + end + + test "handles localhost correctly" do + conn = conn(:get, "/") + conn = check_http(conn) + assert conn.assigns.geolocation == nil end + + defp check_http(conn) do + conn = TestRouter.call(conn, @opts) + assert conn.state == :sent + assert conn.status == 200 + assert conn.resp_body == "test" + conn + end + end