forked from potsda.mn/mobilizon
Add GraphQL methods and test
Signed-off-by: Thomas Citharel <tcit@tcit.fr> Finish Signed-off-by: Thomas Citharel <tcit@tcit.fr> Fix tests Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
parent
98b7618338
commit
6ca0b5f915
|
@ -108,6 +108,7 @@ defmodule Mobilizon.Addresses do
|
||||||
@doc """
|
@doc """
|
||||||
Processes raw geo data informations and return a `Geo` geometry which can be one of `Geo.Point`.
|
Processes raw geo data informations and return a `Geo` geometry which can be one of `Geo.Point`.
|
||||||
"""
|
"""
|
||||||
|
# TODO: Unused, remove me
|
||||||
def process_geom(%{"type" => type_input, "data" => data}) do
|
def process_geom(%{"type" => type_input, "data" => data}) do
|
||||||
type =
|
type =
|
||||||
if !is_atom(type_input) && type_input != nil do
|
if !is_atom(type_input) && type_input != nil do
|
||||||
|
@ -145,4 +146,62 @@ defmodule Mobilizon.Addresses do
|
||||||
defp process_point(_, _) do
|
defp process_point(_, _) do
|
||||||
{:error, "Latitude and longitude must be numbers"}
|
{:error, "Latitude and longitude must be numbers"}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Search addresses in our database
|
||||||
|
|
||||||
|
We only look at the description for now, and eventually order by object distance
|
||||||
|
"""
|
||||||
|
@spec search_addresses(String.t(), list()) :: list(Address.t())
|
||||||
|
def search_addresses(search, options) do
|
||||||
|
limit = Keyword.get(options, :limit, 5)
|
||||||
|
|
||||||
|
query = from(a in Address, where: ilike(a.description, ^"%#{search}%"), limit: ^limit)
|
||||||
|
|
||||||
|
query =
|
||||||
|
if coords = Keyword.get(options, :coords, false),
|
||||||
|
do:
|
||||||
|
from(a in query,
|
||||||
|
order_by: [fragment("? <-> ?", a.geom, ^"POINT(#{coords.lon} #{coords.lat})'")]
|
||||||
|
),
|
||||||
|
else: query
|
||||||
|
|
||||||
|
query =
|
||||||
|
if country = Keyword.get(options, :country, nil),
|
||||||
|
do: from(a in query, where: ilike(a.addressCountry, ^"%#{country}%")),
|
||||||
|
else: query
|
||||||
|
|
||||||
|
Repo.all(query)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Reverse geocode from coordinates in our database
|
||||||
|
|
||||||
|
We only take addresses 50km around and sort them by distance
|
||||||
|
"""
|
||||||
|
@spec reverse_geocode(number(), number(), list()) :: list(Address.t())
|
||||||
|
def reverse_geocode(lon, lat, options) do
|
||||||
|
limit = Keyword.get(options, :limit, 5)
|
||||||
|
radius = Keyword.get(options, :radius, 50_000)
|
||||||
|
country = Keyword.get(options, :country, nil)
|
||||||
|
srid = Keyword.get(options, :srid, 4326)
|
||||||
|
|
||||||
|
import Geo.PostGIS
|
||||||
|
|
||||||
|
with {:ok, point} <- Geo.WKT.decode("SRID=#{srid};POINT(#{lon} #{lat})") do
|
||||||
|
query =
|
||||||
|
from(a in Address,
|
||||||
|
order_by: [fragment("? <-> ?", a.geom, ^point)],
|
||||||
|
limit: ^limit,
|
||||||
|
where: st_dwithin_in_meters(^point, a.geom, ^radius)
|
||||||
|
)
|
||||||
|
|
||||||
|
query =
|
||||||
|
if country,
|
||||||
|
do: from(a in query, where: ilike(a.addressCountry, ^"%#{country}%")),
|
||||||
|
else: query
|
||||||
|
|
||||||
|
Repo.all(query)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -12,11 +12,17 @@ defmodule MobilizonWeb.Context do
|
||||||
end
|
end
|
||||||
|
|
||||||
def call(conn, _) do
|
def call(conn, _) do
|
||||||
with %User{} = user <- Guardian.Plug.current_resource(conn) do
|
context = %{ip: to_string(:inet_parse.ntoa(conn.remote_ip))}
|
||||||
put_private(conn, :absinthe, %{context: %{current_user: user}})
|
|
||||||
else
|
context =
|
||||||
|
case Guardian.Plug.current_resource(conn) do
|
||||||
|
%User{} = user ->
|
||||||
|
Map.put(context, :current_user, user)
|
||||||
|
|
||||||
nil ->
|
nil ->
|
||||||
conn
|
context
|
||||||
end
|
end
|
||||||
|
|
||||||
|
put_private(conn, :absinthe, %{context: context})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
33
lib/mobilizon_web/resolvers/address.ex
Normal file
33
lib/mobilizon_web/resolvers/address.ex
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
defmodule MobilizonWeb.Resolvers.Address do
|
||||||
|
@moduledoc """
|
||||||
|
Handles the comment-related GraphQL calls
|
||||||
|
"""
|
||||||
|
require Logger
|
||||||
|
alias Mobilizon.Addresses
|
||||||
|
alias Mobilizon.Service.Geospatial
|
||||||
|
|
||||||
|
def search(_parent, %{query: query}, %{context: %{ip: ip}}) do
|
||||||
|
country = Geolix.lookup(ip) |> Map.get(:country, nil)
|
||||||
|
|
||||||
|
local_addresses = Task.async(fn -> Addresses.search_addresses(query, country: country) end)
|
||||||
|
|
||||||
|
remote_addresses = Task.async(fn -> Geospatial.service().search(query) end)
|
||||||
|
|
||||||
|
addresses = Task.await(local_addresses) ++ Task.await(remote_addresses)
|
||||||
|
|
||||||
|
{:ok, addresses}
|
||||||
|
end
|
||||||
|
|
||||||
|
def reverse_geocode(_parent, %{longitude: longitude, latitude: latitude}, %{context: %{ip: ip}}) do
|
||||||
|
country = Geolix.lookup(ip) |> Map.get(:country, nil)
|
||||||
|
|
||||||
|
local_addresses =
|
||||||
|
Task.async(fn -> Addresses.reverse_geocode(longitude, latitude, country: country) end)
|
||||||
|
|
||||||
|
remote_addresses = Task.async(fn -> Geospatial.service().geocode(longitude, latitude) end)
|
||||||
|
|
||||||
|
addresses = Task.await(local_addresses) ++ Task.await(remote_addresses)
|
||||||
|
|
||||||
|
{:ok, addresses}
|
||||||
|
end
|
||||||
|
end
|
|
@ -132,6 +132,7 @@ defmodule MobilizonWeb.Schema do
|
||||||
import_fields(:event_queries)
|
import_fields(:event_queries)
|
||||||
import_fields(:participant_queries)
|
import_fields(:participant_queries)
|
||||||
import_fields(:tag_queries)
|
import_fields(:tag_queries)
|
||||||
|
import_fields(:address_queries)
|
||||||
end
|
end
|
||||||
|
|
||||||
@desc """
|
@desc """
|
||||||
|
|
|
@ -3,6 +3,7 @@ defmodule MobilizonWeb.Schema.AddressType do
|
||||||
Schema representation for Address
|
Schema representation for Address
|
||||||
"""
|
"""
|
||||||
use Absinthe.Schema.Notation
|
use Absinthe.Schema.Notation
|
||||||
|
alias MobilizonWeb.Resolvers
|
||||||
|
|
||||||
object :physical_address do
|
object :physical_address do
|
||||||
field(:type, :address_type)
|
field(:type, :address_type)
|
||||||
|
@ -36,4 +37,21 @@ defmodule MobilizonWeb.Schema.AddressType do
|
||||||
value(:phone, description: "The address is a phone number for a conference")
|
value(:phone, description: "The address is a phone number for a conference")
|
||||||
value(:other, description: "The address is something else")
|
value(:other, description: "The address is something else")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
object :address_queries do
|
||||||
|
@desc "Search for an address"
|
||||||
|
field :search_address, type: list_of(:physical_address) do
|
||||||
|
arg(:query, non_null(:string))
|
||||||
|
|
||||||
|
resolve(&Resolvers.Address.search/3)
|
||||||
|
end
|
||||||
|
|
||||||
|
@desc "Reverse geocode coordinates"
|
||||||
|
field :reverse_geocode, type: list_of(:physical_address) do
|
||||||
|
arg(:longitude, non_null(:float))
|
||||||
|
arg(:latitude, non_null(:float))
|
||||||
|
|
||||||
|
resolve(&Resolvers.Address.reverse_geocode/3)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
65
test/mobilizon_web/resolvers/address_resolver_test.exs
Normal file
65
test/mobilizon_web/resolvers/address_resolver_test.exs
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
defmodule MobilizonWeb.Resolvers.AddressResolverTest do
|
||||||
|
use MobilizonWeb.ConnCase
|
||||||
|
alias MobilizonWeb.AbsintheHelpers
|
||||||
|
import Mobilizon.Factory
|
||||||
|
|
||||||
|
describe "Address Resolver" do
|
||||||
|
test "search/3 search for addresses", %{conn: conn} do
|
||||||
|
address = insert(:address, description: "10 rue Jangot, Lyon")
|
||||||
|
|
||||||
|
query = """
|
||||||
|
{
|
||||||
|
searchAddress(query: "10 Rue Jangot") {
|
||||||
|
description,
|
||||||
|
geom
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
res =
|
||||||
|
conn
|
||||||
|
|> get("/api", AbsintheHelpers.query_skeleton(query, "address"))
|
||||||
|
|
||||||
|
json_response(res, 200)["data"]["searchAddress"]
|
||||||
|
|> Enum.each(fn addr -> assert Map.get(addr, "description") == address.description end)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "geocode/3 reverse geocodes coordinates", %{conn: conn} do
|
||||||
|
address =
|
||||||
|
insert(:address,
|
||||||
|
description: "10 rue Jangot, Lyon"
|
||||||
|
)
|
||||||
|
|
||||||
|
query = """
|
||||||
|
{
|
||||||
|
reverseGeocode(longitude: -23.01, latitude: 30.01) {
|
||||||
|
description,
|
||||||
|
geom
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
res =
|
||||||
|
conn
|
||||||
|
|> get("/api", AbsintheHelpers.query_skeleton(query, "address"))
|
||||||
|
|
||||||
|
assert json_response(res, 200)["data"]["reverseGeocode"] == []
|
||||||
|
|
||||||
|
query = """
|
||||||
|
{
|
||||||
|
reverseGeocode(longitude: 45.75, latitude: 4.85) {
|
||||||
|
description,
|
||||||
|
geom
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
res =
|
||||||
|
conn
|
||||||
|
|> get("/api", AbsintheHelpers.query_skeleton(query, "address"))
|
||||||
|
|
||||||
|
assert json_response(res, 200)["data"]["reverseGeocode"] |> hd |> Map.get("description") ==
|
||||||
|
address.description
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -70,7 +70,7 @@ defmodule Mobilizon.Factory do
|
||||||
def address_factory do
|
def address_factory do
|
||||||
%Mobilizon.Addresses.Address{
|
%Mobilizon.Addresses.Address{
|
||||||
description: sequence("MyAddress"),
|
description: sequence("MyAddress"),
|
||||||
geom: %Geo.Point{coordinates: {30, -90}, srid: 4326},
|
geom: %Geo.Point{coordinates: {45.75, 4.85}, srid: 4326},
|
||||||
floor: "Myfloor",
|
floor: "Myfloor",
|
||||||
addressCountry: "My Country",
|
addressCountry: "My Country",
|
||||||
addressLocality: "My Locality",
|
addressLocality: "My Locality",
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
defmodule Mobilizon.Mobilizon.Service.Geospatial.Mock do
|
defmodule Mobilizon.Service.Geospatial.Mock do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
Mock for Geospatial Provider implementations
|
Mock for Geospatial Provider implementations
|
||||||
"""
|
"""
|
||||||
|
@ -8,8 +8,8 @@ defmodule Mobilizon.Mobilizon.Service.Geospatial.Mock do
|
||||||
@behaviour Provider
|
@behaviour Provider
|
||||||
|
|
||||||
@impl Provider
|
@impl Provider
|
||||||
def geocode(_lon, _lat, _options \\ []), do: [%Address{}]
|
def geocode(_lon, _lat, _options \\ []), do: []
|
||||||
|
|
||||||
@impl Provider
|
@impl Provider
|
||||||
def search(_q, _options \\ []), do: [%Address{}]
|
def search(_q, _options \\ []), do: [%Address{description: "10 rue Jangot, Lyon"}]
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue