Add timezone handling

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel
2021-10-10 16:25:50 +02:00
parent eba3c70c9b
commit d58ca5743d
49 changed files with 1218 additions and 429 deletions

View File

@@ -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

View File

@@ -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}

View File

@@ -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,

View File

@@ -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

View File

@@ -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 """

View File

@@ -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,