@@ -49,7 +49,7 @@ defmodule Mobilizon.Federation.ActivityPub.Actor do
|
||||
Create an actor locally by its URL (AP ID)
|
||||
"""
|
||||
@spec make_actor_from_url(url :: String.t(), preload :: boolean()) ::
|
||||
{:ok, Actor.t()} | {:error, make_actor_errors}
|
||||
{:ok, Actor.t()} | {:error, make_actor_errors | Ecto.Changeset.t()}
|
||||
def make_actor_from_url(url, preload \\ false) do
|
||||
if are_same_origin?(url, Endpoint.url()) do
|
||||
{:error, :actor_is_local}
|
||||
@@ -63,7 +63,7 @@ defmodule Mobilizon.Federation.ActivityPub.Actor do
|
||||
Logger.info("Actor #{url} was deleted")
|
||||
{:error, :actor_deleted}
|
||||
|
||||
{:error, err} when err in [:http_error, :json_decode_error] ->
|
||||
{:error, err} ->
|
||||
{:error, err}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -35,7 +35,7 @@ defmodule Mobilizon.Federation.ActivityPub.Permission do
|
||||
@doc """
|
||||
Check that actor can create such an object
|
||||
"""
|
||||
@spec can_create_group_object?(String.t() | integer(), String.t() | integer(), Entity.t()) ::
|
||||
@spec can_create_group_object?(String.t() | integer(), String.t() | integer(), struct()) ::
|
||||
boolean()
|
||||
def can_create_group_object?(
|
||||
actor_id,
|
||||
|
||||
@@ -156,7 +156,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Events do
|
||||
role
|
||||
)
|
||||
|
||||
{:error, %Ecto.Changeset{} = err} ->
|
||||
{:error, _, %Ecto.Changeset{} = err, _} ->
|
||||
{:error, err}
|
||||
end
|
||||
else
|
||||
|
||||
@@ -12,7 +12,8 @@ defmodule Mobilizon.GraphQL.API.Events do
|
||||
@doc """
|
||||
Create an event
|
||||
"""
|
||||
@spec create_event(map) :: {:ok, Activity.t(), Event.t()} | any
|
||||
@spec create_event(map) ::
|
||||
{:ok, Activity.t(), Event.t()} | {:error, atom() | Ecto.Changeset.t()}
|
||||
def create_event(args) do
|
||||
# For now we don't federate drafts but it will be needed if we want to edit them as groups
|
||||
Actions.Create.create(:event, prepare_args(args), should_federate(args))
|
||||
@@ -21,7 +22,8 @@ defmodule Mobilizon.GraphQL.API.Events do
|
||||
@doc """
|
||||
Update an event
|
||||
"""
|
||||
@spec update_event(map, Event.t()) :: {:ok, Activity.t(), Event.t()} | any
|
||||
@spec update_event(map, Event.t()) ::
|
||||
{:ok, Activity.t(), Event.t()} | {:error, atom | Ecto.Changeset.t()}
|
||||
def update_event(args, %Event{} = event) do
|
||||
Actions.Update.update(event, prepare_args(args), should_federate(args))
|
||||
end
|
||||
|
||||
@@ -16,7 +16,9 @@ defmodule Mobilizon.GraphQL.Middleware.CurrentActorProvider do
|
||||
_config
|
||||
) do
|
||||
case Cachex.fetch(:default_actors, to_string(user_id), fn -> default(user) end) do
|
||||
{status, %Actor{} = current_actor} when status in [:ok, :commit] ->
|
||||
{status, %Actor{preferred_username: preferred_username} = current_actor}
|
||||
when status in [:ok, :commit] ->
|
||||
Sentry.Context.set_user_context(%{name: preferred_username})
|
||||
context = Map.put(context, :current_actor, current_actor)
|
||||
%Absinthe.Resolution{resolution | context: context}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Address do
|
||||
@doc """
|
||||
Search an address
|
||||
"""
|
||||
@spec search(map, map, map) :: {:ok, [Address.t()]}
|
||||
@spec search(map, map, map) :: {:ok, [map()]}
|
||||
def search(
|
||||
_parent,
|
||||
%{query: query, locale: locale, page: _page, limit: _limit} = args,
|
||||
|
||||
@@ -13,9 +13,11 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
|
||||
|
||||
alias Mobilizon.Federation.ActivityPub.Activity
|
||||
alias Mobilizon.Federation.ActivityPub.Permission
|
||||
alias Mobilizon.Service.TimezoneDetector
|
||||
import Mobilizon.Users.Guards, only: [is_moderator: 1]
|
||||
import Mobilizon.Web.Gettext
|
||||
import Mobilizon.GraphQL.Resolvers.Event.Utils
|
||||
require Logger
|
||||
|
||||
# We limit the max number of events that can be retrieved
|
||||
@event_max_limit 100
|
||||
@@ -262,35 +264,47 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
|
||||
def create_event(
|
||||
_parent,
|
||||
%{organizer_actor_id: organizer_actor_id} = args,
|
||||
%{context: %{current_user: user}} = _resolution
|
||||
%{context: %{current_user: %User{} = user}} = _resolution
|
||||
) do
|
||||
# See https://github.com/absinthe-graphql/absinthe/issues/490
|
||||
if Config.only_groups_can_create_events?() and Map.get(args, :attributed_to_id) == nil do
|
||||
{:error, "only groups can create events"}
|
||||
{:error,
|
||||
dgettext(
|
||||
"errors",
|
||||
"Only groups can create events"
|
||||
)}
|
||||
else
|
||||
with {:is_owned, %Actor{} = organizer_actor} <- User.owns_actor(user, organizer_actor_id),
|
||||
args <- Map.put(args, :options, args[:options] || %{}),
|
||||
{:group_check, true} <- {:group_check, is_organizer_group_member?(args)},
|
||||
args_with_organizer <- Map.put(args, :organizer_actor, organizer_actor),
|
||||
{:ok, %Activity{data: %{"object" => %{"type" => "Event"}}}, %Event{} = event} <-
|
||||
API.Events.create_event(args_with_organizer) do
|
||||
{:ok, event}
|
||||
else
|
||||
{:group_check, false} ->
|
||||
{:error,
|
||||
dgettext(
|
||||
"errors",
|
||||
"Organizer profile doesn't have permission to create an event on behalf of this group"
|
||||
)}
|
||||
case User.owns_actor(user, organizer_actor_id) do
|
||||
{:is_owned, %Actor{} = organizer_actor} ->
|
||||
if is_organizer_group_member?(args) do
|
||||
args_with_organizer =
|
||||
args |> Map.put(:organizer_actor, organizer_actor) |> extract_timezone(user.id)
|
||||
|
||||
case API.Events.create_event(args_with_organizer) do
|
||||
{:ok, %Activity{data: %{"object" => %{"type" => "Event"}}}, %Event{} = event} ->
|
||||
{:ok, event}
|
||||
|
||||
{:error, %Ecto.Changeset{} = error} ->
|
||||
{:error, error}
|
||||
|
||||
{:error, err} ->
|
||||
Logger.warning("Unknown error while creating event: #{inspect(err)}")
|
||||
|
||||
{:error,
|
||||
dgettext(
|
||||
"errors",
|
||||
"Unknown error while creating event"
|
||||
)}
|
||||
end
|
||||
else
|
||||
{:error,
|
||||
dgettext(
|
||||
"errors",
|
||||
"Organizer profile doesn't have permission to create an event on behalf of this group"
|
||||
)}
|
||||
end
|
||||
|
||||
{:is_owned, nil} ->
|
||||
{:error, dgettext("errors", "Organizer profile is not owned by the user")}
|
||||
|
||||
{:error, _, %Ecto.Changeset{} = error, _} ->
|
||||
{:error, error}
|
||||
|
||||
{:error, %Ecto.Changeset{} = error} ->
|
||||
{:error, error}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -314,6 +328,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
|
||||
|
||||
with {:ok, %Event{} = event} <- Events.get_event_with_preload(event_id),
|
||||
{:ok, args} <- verify_profile_change(args, event, user, actor),
|
||||
args <- extract_timezone(args, user.id),
|
||||
{:event_can_be_managed, true} <-
|
||||
{:event_can_be_managed, can_event_be_updated_by?(event, actor)},
|
||||
{:ok, %Activity{data: %{"object" => %{"type" => "Event"}}}, %Event{} = event} <-
|
||||
@@ -442,4 +457,42 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
|
||||
{:ok, args}
|
||||
end
|
||||
end
|
||||
|
||||
@spec extract_timezone(map(), String.t() | integer()) :: map()
|
||||
defp extract_timezone(args, user_id) do
|
||||
event_options = Map.get(args, :options, %{})
|
||||
timezone = Map.get(event_options, :timezone)
|
||||
physical_address = Map.get(args, :physical_address)
|
||||
|
||||
fallback_tz =
|
||||
case Mobilizon.Users.get_setting(user_id) do
|
||||
nil -> nil
|
||||
setting -> setting |> Map.from_struct() |> get_in([:timezone])
|
||||
end
|
||||
|
||||
timezone = determine_timezone(timezone, physical_address, fallback_tz)
|
||||
|
||||
event_options = Map.put(event_options, :timezone, timezone)
|
||||
|
||||
Map.put(args, :options, event_options)
|
||||
end
|
||||
|
||||
@spec determine_timezone(
|
||||
String.t() | nil,
|
||||
any(),
|
||||
String.t() | nil
|
||||
) :: String.t() | nil
|
||||
defp determine_timezone(timezone, physical_address, fallback_tz) do
|
||||
case physical_address do
|
||||
physical_address when is_map(physical_address) ->
|
||||
TimezoneDetector.detect(
|
||||
timezone,
|
||||
physical_address.geom,
|
||||
fallback_tz
|
||||
)
|
||||
|
||||
_ ->
|
||||
timezone || fallback_tz
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -21,6 +21,7 @@ defmodule Mobilizon.GraphQL.Schema.AddressType do
|
||||
field(:url, :string, description: "The address's URL")
|
||||
field(:id, :id, description: "The address's ID")
|
||||
field(:origin_id, :string, description: "The address's original ID from the provider")
|
||||
field(:timezone, :string, description: "The (estimated) timezone of the location")
|
||||
end
|
||||
|
||||
@desc """
|
||||
@@ -54,6 +55,7 @@ defmodule Mobilizon.GraphQL.Schema.AddressType do
|
||||
field(:url, :string, description: "The address's URL")
|
||||
field(:id, :id, description: "The address's ID")
|
||||
field(:origin_id, :string, description: "The address's original ID from the provider")
|
||||
field(:timezone, :string, description: "The (estimated) timezone of the location")
|
||||
end
|
||||
|
||||
@desc """
|
||||
|
||||
@@ -237,6 +237,8 @@ defmodule Mobilizon.GraphQL.Schema.EventType do
|
||||
field(:show_start_time, :boolean, description: "Show event start time")
|
||||
field(:show_end_time, :boolean, description: "Show event end time")
|
||||
|
||||
field(:timezone, :string, description: "The event's timezone")
|
||||
|
||||
field(:hide_organizer_when_group_event, :boolean,
|
||||
description:
|
||||
"Whether to show or hide the person organizer when event is organized by a group"
|
||||
@@ -286,6 +288,8 @@ defmodule Mobilizon.GraphQL.Schema.EventType do
|
||||
field(:show_start_time, :boolean, description: "Show event start time")
|
||||
field(:show_end_time, :boolean, description: "Show event end time")
|
||||
|
||||
field(:timezone, :string, description: "The event's timezone")
|
||||
|
||||
field(:hide_organizer_when_group_event, :boolean,
|
||||
description:
|
||||
"Whether to show or hide the person organizer when event is organized by a group"
|
||||
@@ -393,7 +397,7 @@ defmodule Mobilizon.GraphQL.Schema.EventType do
|
||||
|
||||
arg(:category, :string, default_value: "meeting", description: "The event's category")
|
||||
arg(:physical_address, :address_input, description: "The event's physical address")
|
||||
arg(:options, :event_options_input, description: "The event options")
|
||||
arg(:options, :event_options_input, default_value: %{}, description: "The event options")
|
||||
arg(:metadata, list_of(:event_metadata_input), description: "The event metadata")
|
||||
|
||||
arg(:draft, :boolean,
|
||||
|
||||
@@ -48,6 +48,7 @@ defmodule Mobilizon do
|
||||
Guardian.DB.Token.SweeperServer,
|
||||
ActivityPub.Federator,
|
||||
Mobilizon.PythonWorker,
|
||||
TzWorld.Backend.DetsWithIndexCache,
|
||||
cachex_spec(:feed, 2500, 60, 60, &Feed.create_cache/1),
|
||||
cachex_spec(:ics, 2500, 60, 60, &ICalendar.create_cache/1),
|
||||
cachex_spec(
|
||||
|
||||
@@ -12,17 +12,18 @@ defmodule Mobilizon.Addresses.Address do
|
||||
alias Mobilizon.Web.Endpoint
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
country: String.t(),
|
||||
locality: String.t(),
|
||||
region: String.t(),
|
||||
description: String.t(),
|
||||
geom: Geo.PostGIS.Geometry.t(),
|
||||
postal_code: String.t(),
|
||||
street: String.t(),
|
||||
type: String.t(),
|
||||
country: String.t() | nil,
|
||||
locality: String.t() | nil,
|
||||
region: String.t() | nil,
|
||||
description: String.t() | nil,
|
||||
geom: Geo.PostGIS.Geometry.t() | nil,
|
||||
postal_code: String.t() | nil,
|
||||
street: String.t() | nil,
|
||||
type: String.t() | nil,
|
||||
url: String.t(),
|
||||
origin_id: String.t(),
|
||||
events: [Event.t()]
|
||||
origin_id: String.t() | nil,
|
||||
events: [Event.t()],
|
||||
timezone: String.t() | nil
|
||||
}
|
||||
|
||||
@required_attrs [:url]
|
||||
@@ -35,7 +36,8 @@ defmodule Mobilizon.Addresses.Address do
|
||||
:postal_code,
|
||||
:street,
|
||||
:origin_id,
|
||||
:type
|
||||
:type,
|
||||
:timezone
|
||||
]
|
||||
@attrs @required_attrs ++ @optional_attrs
|
||||
|
||||
@@ -50,6 +52,7 @@ defmodule Mobilizon.Addresses.Address do
|
||||
field(:type, :string)
|
||||
field(:url, :string)
|
||||
field(:origin_id, :string)
|
||||
field(:timezone, :string)
|
||||
|
||||
has_many(:events, Event, foreign_key: :physical_address_id)
|
||||
|
||||
@@ -61,6 +64,7 @@ defmodule Mobilizon.Addresses.Address do
|
||||
def changeset(%__MODULE__{} = address, attrs) do
|
||||
address
|
||||
|> cast(attrs, @attrs)
|
||||
|> maybe_set_timezone()
|
||||
|> set_url()
|
||||
|> validate_required(@required_attrs)
|
||||
|> unique_constraint(:url, name: :addresses_url_index)
|
||||
@@ -90,4 +94,29 @@ defmodule Mobilizon.Addresses.Address do
|
||||
"#{address.street} #{address.postal_code} #{address.locality} #{address.region} #{address.country}"
|
||||
)
|
||||
end
|
||||
|
||||
@spec maybe_set_timezone(Ecto.Changeset.t()) :: Ecto.Changeset.t()
|
||||
defp maybe_set_timezone(%Ecto.Changeset{} = changeset) do
|
||||
case get_change(changeset, :geom) do
|
||||
nil ->
|
||||
changeset
|
||||
|
||||
geom ->
|
||||
case get_field(changeset, :timezone) do
|
||||
# Only update the timezone if the geom has change and we don't already have a set timezone
|
||||
nil -> put_change(changeset, :timezone, timezone(geom))
|
||||
_ -> changeset
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@spec timezone(Geo.PostGIS.Geometry.t() | nil) :: String.t() | nil
|
||||
defp timezone(nil), do: nil
|
||||
|
||||
defp timezone(geom) do
|
||||
case TzWorld.timezone_at(geom) do
|
||||
{:ok, tz} -> tz
|
||||
{:error, _err} -> nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -27,6 +27,7 @@ defmodule Mobilizon.Events.EventOptions do
|
||||
participation_condition: [EventParticipationCondition.t()],
|
||||
show_start_time: boolean,
|
||||
show_end_time: boolean,
|
||||
timezone: String.t() | nil,
|
||||
hide_organizer_when_group_event: boolean
|
||||
}
|
||||
|
||||
@@ -41,6 +42,7 @@ defmodule Mobilizon.Events.EventOptions do
|
||||
:show_participation_price,
|
||||
:show_start_time,
|
||||
:show_end_time,
|
||||
:timezone,
|
||||
:hide_organizer_when_group_event
|
||||
]
|
||||
|
||||
@@ -57,6 +59,7 @@ defmodule Mobilizon.Events.EventOptions do
|
||||
field(:show_participation_price, :boolean)
|
||||
field(:show_start_time, :boolean, default: true)
|
||||
field(:show_end_time, :boolean, default: true)
|
||||
field(:timezone, :string)
|
||||
field(:hide_organizer_when_group_event, :boolean, default: false)
|
||||
|
||||
embeds_many(:offers, EventOffer)
|
||||
|
||||
@@ -401,7 +401,8 @@ defmodule Mobilizon.Events do
|
||||
|> Page.build_page(page, limit)
|
||||
end
|
||||
|
||||
@spec list_organized_events_for_actor(Actor.t(), integer | nil, integer | nil) :: Page.t()
|
||||
@spec list_organized_events_for_actor(Actor.t(), integer | nil, integer | nil) ::
|
||||
Page.t(Event.t())
|
||||
def list_organized_events_for_actor(%Actor{id: actor_id}, page \\ nil, limit \\ nil) do
|
||||
actor_id
|
||||
|> event_for_actor_query(desc: :begins_on)
|
||||
@@ -409,13 +410,15 @@ defmodule Mobilizon.Events do
|
||||
|> Page.build_page(page, limit)
|
||||
end
|
||||
|
||||
@spec list_simple_organized_events_for_group(Actor.t(), integer | nil, integer | nil) ::
|
||||
Page.t(Event.t())
|
||||
def list_simple_organized_events_for_group(%Actor{} = actor, page \\ nil, limit \\ nil) do
|
||||
list_organized_events_for_group(actor, :all, nil, nil, page, limit)
|
||||
end
|
||||
|
||||
@spec list_organized_events_for_group(
|
||||
Actor.t(),
|
||||
EventVisibility.t(),
|
||||
EventVisibility.t() | :all,
|
||||
DateTime.t() | nil,
|
||||
DateTime.t() | nil,
|
||||
integer | nil,
|
||||
@@ -885,7 +888,9 @@ defmodule Mobilizon.Events do
|
||||
@doc """
|
||||
Creates a participant.
|
||||
"""
|
||||
@spec create_participant(map) :: {:ok, Participant.t()} | {:error, Changeset.t()}
|
||||
@spec create_participant(map) ::
|
||||
{:ok, Participant.t()}
|
||||
| {:error, :participant | :update_event_participation_stats, Changeset.t(), map()}
|
||||
def create_participant(attrs \\ %{}, update_event_participation_stats \\ true) do
|
||||
with {:ok, %{participant: %Participant{} = participant}} <-
|
||||
Multi.new()
|
||||
@@ -912,7 +917,8 @@ defmodule Mobilizon.Events do
|
||||
Updates a participant.
|
||||
"""
|
||||
@spec update_participant(Participant.t(), map) ::
|
||||
{:ok, Participant.t()} | {:error, Changeset.t()}
|
||||
{:ok, Participant.t()}
|
||||
| {:error, :participant | :update_event_participation_stats, Changeset.t(), map()}
|
||||
def update_participant(%Participant{role: old_role} = participant, attrs) do
|
||||
with {:ok, %{participant: %Participant{} = participant}} <-
|
||||
Multi.new()
|
||||
@@ -1625,11 +1631,12 @@ defmodule Mobilizon.Events do
|
||||
from(p in query, where: p.role == ^role)
|
||||
end
|
||||
|
||||
@spec event_filter_visibility(Ecto.Queryable.t(), :public | :all) ::
|
||||
Ecto.Queryable.t() | Ecto.Query.t()
|
||||
defp event_filter_visibility(query, :all), do: query
|
||||
|
||||
defp event_filter_visibility(query, :public) do
|
||||
query
|
||||
|> where(visibility: ^:public, draft: false)
|
||||
where(query, visibility: ^:public, draft: false)
|
||||
end
|
||||
|
||||
defp event_filter_begins_on(query, nil, nil),
|
||||
|
||||
@@ -66,12 +66,15 @@ defmodule Mobilizon.Service.Geospatial.Addok do
|
||||
defp process_data(features) do
|
||||
features
|
||||
|> Enum.map(fn %{"geometry" => geometry, "properties" => properties} ->
|
||||
coordinates = geometry |> Map.get("coordinates") |> Provider.coordinates()
|
||||
|
||||
%Address{
|
||||
country: Map.get(properties, "country", default_country()),
|
||||
locality: Map.get(properties, "city"),
|
||||
region: Map.get(properties, "context"),
|
||||
description: Map.get(properties, "name") || street_address(properties),
|
||||
geom: geometry |> Map.get("coordinates") |> Provider.coordinates(),
|
||||
geom: coordinates,
|
||||
timezone: Provider.timezone(coordinates),
|
||||
postal_code: Map.get(properties, "postcode"),
|
||||
street: properties |> street_address()
|
||||
}
|
||||
|
||||
@@ -124,12 +124,15 @@ defmodule Mobilizon.Service.Geospatial.GoogleMaps do
|
||||
description
|
||||
end
|
||||
|
||||
coordinates = Provider.coordinates([lon, lat])
|
||||
|
||||
%Address{
|
||||
country: Map.get(components, "country"),
|
||||
locality: Map.get(components, "locality"),
|
||||
region: Map.get(components, "administrative_area_level_1"),
|
||||
description: description,
|
||||
geom: [lon, lat] |> Provider.coordinates(),
|
||||
geom: coordinates,
|
||||
timezone: Provider.timezone(coordinates),
|
||||
postal_code: Map.get(components, "postal_code"),
|
||||
street: street_address(components),
|
||||
origin_id: "gm:" <> to_string(place_id)
|
||||
|
||||
@@ -98,12 +98,15 @@ defmodule Mobilizon.Service.Geospatial.MapQuest do
|
||||
end
|
||||
|
||||
defp produce_address(address, lat, lng) do
|
||||
coordinates = Provider.coordinates([lng, lat])
|
||||
|
||||
%Address{
|
||||
country: Map.get(address, "adminArea1"),
|
||||
locality: Map.get(address, "adminArea5"),
|
||||
region: Map.get(address, "adminArea3"),
|
||||
description: Map.get(address, "street"),
|
||||
geom: [lng, lat] |> Provider.coordinates(),
|
||||
geom: coordinates,
|
||||
timezone: Provider.timezone(coordinates),
|
||||
postal_code: Map.get(address, "postalCode"),
|
||||
street: Map.get(address, "street")
|
||||
}
|
||||
|
||||
@@ -75,7 +75,8 @@ defmodule Mobilizon.Service.Geospatial.Mimirsbrunn do
|
||||
"properties" => %{"geocoding" => geocoding}
|
||||
} ->
|
||||
address = process_address(geocoding)
|
||||
%Address{address | geom: Provider.coordinates(coordinates)}
|
||||
coordinates = Provider.coordinates(coordinates)
|
||||
%Address{address | geom: coordinates, timezone: Provider.timezone(coordinates)}
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
@@ -75,7 +75,8 @@ defmodule Mobilizon.Service.Geospatial.Nominatim do
|
||||
"properties" => %{"geocoding" => geocoding}
|
||||
} ->
|
||||
address = process_address(geocoding)
|
||||
%Address{address | geom: Provider.coordinates(coordinates)}
|
||||
coordinates = Provider.coordinates(coordinates)
|
||||
%Address{address | geom: coordinates, timezone: Provider.timezone(coordinates)}
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
@@ -76,7 +76,8 @@ defmodule Mobilizon.Service.Geospatial.Pelias do
|
||||
"properties" => properties
|
||||
} ->
|
||||
address = process_address(properties)
|
||||
%Address{address | geom: Provider.coordinates(coordinates)}
|
||||
coordinates = Provider.coordinates(coordinates)
|
||||
%Address{address | geom: coordinates, timezone: Provider.timezone(coordinates)}
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
@@ -69,12 +69,15 @@ defmodule Mobilizon.Service.Geospatial.Photon do
|
||||
defp process_data(features) do
|
||||
features
|
||||
|> Enum.map(fn %{"geometry" => geometry, "properties" => properties} ->
|
||||
coordinates = geometry |> Map.get("coordinates") |> Provider.coordinates()
|
||||
|
||||
%Address{
|
||||
country: Map.get(properties, "country"),
|
||||
locality: Map.get(properties, "city"),
|
||||
region: Map.get(properties, "state"),
|
||||
description: Map.get(properties, "name") || street_address(properties),
|
||||
geom: geometry |> Map.get("coordinates") |> Provider.coordinates(),
|
||||
geom: coordinates,
|
||||
timezone: Provider.timezone(coordinates),
|
||||
postal_code: Map.get(properties, "postcode"),
|
||||
street: properties |> street_address()
|
||||
}
|
||||
|
||||
@@ -79,6 +79,19 @@ defmodule Mobilizon.Service.Geospatial.Provider do
|
||||
|
||||
def coordinates(_), do: nil
|
||||
|
||||
@doc """
|
||||
Returns the timezone for a Geo.Point
|
||||
"""
|
||||
@spec timezone(nil | Geo.Point.t()) :: nil | String.t()
|
||||
def timezone(nil), do: nil
|
||||
|
||||
def timezone(%Geo.Point{} = point) do
|
||||
case TzWorld.timezone_at(point) do
|
||||
{:ok, tz} -> tz
|
||||
{:error, _err} -> nil
|
||||
end
|
||||
end
|
||||
|
||||
@spec endpoint(atom()) :: String.t()
|
||||
def endpoint(provider) do
|
||||
Application.get_env(:mobilizon, provider) |> get_in([:endpoint])
|
||||
|
||||
@@ -2,7 +2,7 @@ defimpl Mobilizon.Service.Metadata, for: Mobilizon.Events.Event do
|
||||
alias Phoenix.HTML
|
||||
alias Phoenix.HTML.Tag
|
||||
alias Mobilizon.Addresses.Address
|
||||
alias Mobilizon.Events.Event
|
||||
alias Mobilizon.Events.{Event, EventOptions}
|
||||
alias Mobilizon.Web.JsonLD.ObjectView
|
||||
|
||||
import Mobilizon.Service.Metadata.Utils,
|
||||
@@ -53,20 +53,52 @@ defimpl Mobilizon.Service.Metadata, for: Mobilizon.Events.Event do
|
||||
%Event{
|
||||
description: description,
|
||||
begins_on: begins_on,
|
||||
physical_address: %Address{} = address
|
||||
physical_address: address,
|
||||
options: %EventOptions{timezone: timezone},
|
||||
language: language
|
||||
},
|
||||
locale
|
||||
) do
|
||||
"#{datetime_to_string(begins_on, locale)} - #{render_address(address)} - #{process_description(description, locale)}"
|
||||
language = build_language(language, locale)
|
||||
begins_on = build_begins_on(begins_on, timezone, language)
|
||||
|
||||
begins_on
|
||||
|> datetime_to_string(language)
|
||||
|> (&[&1]).()
|
||||
|> add_timezone(begins_on)
|
||||
|> maybe_build_address(address)
|
||||
|> build_description(description, language)
|
||||
|> Enum.join(" - ")
|
||||
end
|
||||
|
||||
defp description(
|
||||
%Event{
|
||||
description: description,
|
||||
begins_on: begins_on
|
||||
},
|
||||
locale
|
||||
) do
|
||||
"#{datetime_to_string(begins_on, locale)} - #{process_description(description, locale)}"
|
||||
@spec build_language(String.t() | nil, String.t()) :: String.t()
|
||||
defp build_language(language, locale), do: language || locale
|
||||
|
||||
@spec build_begins_on(DateTime.t(), String.t() | nil, String.t()) :: DateTime.t()
|
||||
defp build_begins_on(begins_on, timezone, language) do
|
||||
if timezone do
|
||||
case DateTime.shift_zone(begins_on, timezone) do
|
||||
{:ok, begins_on} -> begins_on
|
||||
{:error, _err} -> begins_on
|
||||
end
|
||||
else
|
||||
begins_on
|
||||
end
|
||||
end
|
||||
|
||||
defp add_timezone(elements, %DateTime{} = begins_on) do
|
||||
elements ++ [Cldr.DateTime.Formatter.zone_gmt(begins_on)]
|
||||
end
|
||||
|
||||
@spec maybe_build_address(list(String.t()), Address.t() | nil) :: list(String.t())
|
||||
defp maybe_build_address(elements, %Address{} = address) do
|
||||
elements ++ [render_address(address)]
|
||||
end
|
||||
|
||||
defp maybe_build_address(elements, _address), do: elements
|
||||
|
||||
@spec build_description(list(String.t()), String.t(), String.t()) :: list(String.t())
|
||||
defp build_description(elements, description, language) do
|
||||
elements ++ [process_description(description, language)]
|
||||
end
|
||||
end
|
||||
|
||||
40
lib/service/timezone_detector.ex
Normal file
40
lib/service/timezone_detector.ex
Normal file
@@ -0,0 +1,40 @@
|
||||
defmodule Mobilizon.Service.TimezoneDetector do
|
||||
@moduledoc """
|
||||
Detect the timezone from a point
|
||||
"""
|
||||
|
||||
@type detectable :: Geo.Point.t() | Geo.PointZ.t() | {float() | float()}
|
||||
|
||||
@doc """
|
||||
Detect the most appropriate timezone from a value, a geographic set of coordinates and a fallback
|
||||
"""
|
||||
@spec detect(String.t() | nil, detectable(), String.t()) :: String.t()
|
||||
def detect(nil, geo, fallback) do
|
||||
case TzWorld.timezone_at(geo) do
|
||||
{:ok, timezone} ->
|
||||
timezone
|
||||
|
||||
{:error, :time_zone_not_found} ->
|
||||
fallback
|
||||
end
|
||||
end
|
||||
|
||||
def detect(timezone, geo, fallback) do
|
||||
if Tzdata.zone_exists?(timezone) do
|
||||
timezone
|
||||
else
|
||||
detect(nil, geo, fallback)
|
||||
end
|
||||
end
|
||||
|
||||
@spec detect(String.t() | nil, String.t()) :: String.t()
|
||||
def detect(nil, fallback), do: fallback
|
||||
|
||||
def detect(timezone, fallback) do
|
||||
if Tzdata.zone_exists?(timezone) do
|
||||
timezone
|
||||
else
|
||||
fallback
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -27,23 +27,30 @@ defmodule Mobilizon.Web.Auth.Context do
|
||||
|
||||
user_agent = conn |> Plug.Conn.get_req_header("user-agent") |> List.first()
|
||||
|
||||
if SentryAdapter.enabled?() do
|
||||
Sentry.Context.set_request_context(%{
|
||||
url: Plug.Conn.request_url(conn),
|
||||
method: conn.method,
|
||||
headers: %{
|
||||
"User-Agent": user_agent,
|
||||
Referer: conn |> Plug.Conn.get_req_header("referer") |> List.first()
|
||||
},
|
||||
query_string: conn.query_string,
|
||||
env: %{
|
||||
REQUEST_ID: conn |> Plug.Conn.get_resp_header("x-request-id") |> List.first(),
|
||||
SERVER_NAME: conn.host
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
{conn, context} =
|
||||
case Guardian.Plug.current_resource(conn) do
|
||||
%User{id: user_id, email: user_email} = user ->
|
||||
if SentryAdapter.enabled?() do
|
||||
Sentry.Context.set_user_context(%{id: user_id, name: user_email})
|
||||
|
||||
Sentry.Context.set_request_context(%{
|
||||
url: Plug.Conn.request_url(conn),
|
||||
method: conn.method,
|
||||
headers: %{
|
||||
"User-Agent": user_agent
|
||||
},
|
||||
query_string: conn.query_string,
|
||||
env: %{
|
||||
REQUEST_ID: conn |> Plug.Conn.get_resp_header("x-request-id") |> List.first(),
|
||||
SERVER_NAME: conn.host
|
||||
}
|
||||
Sentry.Context.set_user_context(%{
|
||||
id: user_id,
|
||||
email: user_email,
|
||||
ip_address: context.ip
|
||||
})
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user