version chapril de mobilizon
https://mobilizon.chapril.org/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
370 lines
13 KiB
370 lines
13 KiB
defmodule Mobilizon.GraphQL.Resolvers.Participant do |
|
@moduledoc """ |
|
Handles the participation-related GraphQL calls. |
|
""" |
|
alias Mobilizon.{Actors, Config, Crypto, Events} |
|
alias Mobilizon.Actors.Actor |
|
alias Mobilizon.Events.{Event, Participant} |
|
alias Mobilizon.GraphQL.API.Participations |
|
alias Mobilizon.Service.Export.Participants.{CSV, ODS, PDF} |
|
alias Mobilizon.Users.User |
|
alias Mobilizon.Web.Email |
|
alias Mobilizon.Web.Email.Checker |
|
require Logger |
|
import Mobilizon.Web.Gettext |
|
import Mobilizon.GraphQL.Resolvers.Event.Utils |
|
|
|
@doc """ |
|
Join an event for an regular or anonymous actor |
|
""" |
|
@spec actor_join_event(any(), map(), Absinthe.Resolution.t()) :: |
|
{:ok, Participant.t()} | {:error, String.t()} |
|
def actor_join_event( |
|
_parent, |
|
%{actor_id: actor_id, event_id: event_id} = args, |
|
%{context: %{current_user: %User{} = user}} |
|
) do |
|
case User.owns_actor(user, actor_id) do |
|
{:is_owned, %Actor{} = actor} -> |
|
do_actor_join_event(actor, event_id, args) |
|
|
|
_ -> |
|
{:error, dgettext("errors", "Profile is not owned by authenticated user")} |
|
end |
|
end |
|
|
|
def actor_join_event( |
|
_parent, |
|
%{actor_id: actor_id, event_id: event_id} = args, |
|
_resolution |
|
) do |
|
with {:has_event, {:ok, %Event{} = event}} <- |
|
{:has_event, Mobilizon.Events.get_event_with_preload(event_id)}, |
|
{:anonymous_participation_enabled, true} <- |
|
{:anonymous_participation_enabled, |
|
event.local == true && Config.anonymous_participation?() && |
|
event.options.anonymous_participation == true}, |
|
{:anonymous_actor_id, true} <- |
|
{:anonymous_actor_id, to_string(Config.anonymous_actor_id()) == actor_id}, |
|
{:email_required, true} <- |
|
{:email_required, |
|
Config.anonymous_participation_email_required?() && |
|
args |> Map.get(:email) |> valid_email?()}, |
|
{:confirmation_token, {confirmation_token, role}} <- |
|
{:confirmation_token, |
|
if(Config.anonymous_participation_email_confirmation_required?(), |
|
do: {Crypto.random_string(30), :not_confirmed}, |
|
else: {nil, :participant} |
|
)}, |
|
# We only federate if the participation is not to be confirmed later |
|
args <- |
|
args |
|
|> Map.put(:confirmation_token, confirmation_token) |
|
|> Map.put(:cancellation_token, Crypto.random_string(30)) |
|
|> Map.put(:role, role) |
|
|> Map.put(:local, role == :participant), |
|
{:actor_not_found, %Actor{} = actor} <- |
|
{:actor_not_found, Actors.get_actor_with_preload(actor_id)}, |
|
{:ok, %Participant{} = participant} <- do_actor_join_event(actor, event_id, args) do |
|
if Config.anonymous_participation_email_required?() && |
|
Config.anonymous_participation_email_confirmation_required?() do |
|
args |
|
|> Map.get(:email) |
|
|> Email.Participation.anonymous_participation_confirmation( |
|
participant, |
|
Map.get(args, :locale, "en") |
|
) |
|
|> Email.Mailer.send_email() |
|
end |
|
|
|
{:ok, participant} |
|
else |
|
{:error, err} -> |
|
{:error, err} |
|
|
|
{:has_event, _} -> |
|
{:error, |
|
dgettext("errors", "Event with this ID %{id} doesn't exist", id: inspect(event_id))} |
|
|
|
{:anonymous_participation_enabled, false} -> |
|
{:error, dgettext("errors", "Anonymous participation is not enabled")} |
|
|
|
{:anonymous_actor_id, false} -> |
|
{:error, dgettext("errors", "Profile ID provided is not the anonymous profile one")} |
|
|
|
{:email_required, _} -> |
|
{:error, dgettext("errors", "A valid email is required by your instance")} |
|
|
|
{:actor_not_found, _} -> |
|
Logger.error( |
|
"The actor ID \"#{actor_id}\" provided by configuration doesn't match any actor in database" |
|
) |
|
|
|
{:error, dgettext("errors", "Internal Error")} |
|
end |
|
end |
|
|
|
def actor_join_event(_parent, _args, _resolution) do |
|
{:error, dgettext("errors", "You need to be logged-in to join an event")} |
|
end |
|
|
|
@spec do_actor_join_event(Actor.t(), integer | String.t(), map()) :: |
|
{:ok, Participant.t()} | {:error, String.t()} |
|
defp do_actor_join_event(actor, event_id, args) do |
|
with {:has_event, {:ok, %Event{} = event}} <- |
|
{:has_event, Events.get_event_with_preload(event_id)}, |
|
{:ok, _activity, participant} <- Participations.join(event, actor, args), |
|
%Participant{} = participant <- |
|
participant |
|
|> Map.put(:event, event) |
|
|> Map.put(:actor, actor) do |
|
{:ok, participant} |
|
else |
|
{:error, :maximum_attendee_capacity_reached} -> |
|
{:error, dgettext("errors", "The event has already reached its maximum capacity")} |
|
|
|
{:has_event, _} -> |
|
{:error, |
|
dgettext("errors", "Event with this ID %{id} doesn't exist", id: inspect(event_id))} |
|
|
|
{:error, :event_not_found} -> |
|
{:error, dgettext("errors", "Event id not found")} |
|
|
|
{:error, :already_participant} -> |
|
{:error, dgettext("errors", "You are already a participant of this event")} |
|
end |
|
end |
|
|
|
@spec check_anonymous_participation(String.t(), String.t()) :: |
|
{:ok, Event.t()} | {:error, String.t()} |
|
defp check_anonymous_participation(actor_id, event_id) do |
|
cond do |
|
Config.anonymous_participation?() == false -> |
|
{:error, dgettext("errors", "Anonymous participation is not enabled")} |
|
|
|
to_string(Config.anonymous_actor_id()) != actor_id -> |
|
{:error, dgettext("errors", "The anonymous actor ID is invalid")} |
|
|
|
true -> |
|
case Mobilizon.Events.get_event_with_preload(event_id) do |
|
{:ok, %Event{} = event} -> |
|
{:ok, event} |
|
|
|
{:error, :event_not_found} -> |
|
{:error, |
|
dgettext("errors", "Event with this ID %{id} doesn't exist", id: inspect(event_id))} |
|
end |
|
end |
|
end |
|
|
|
@doc """ |
|
Leave an event for an anonymous actor |
|
""" |
|
@spec actor_leave_event(any(), map(), Absinthe.Resolution.t()) :: |
|
{:ok, map()} | {:error, String.t()} |
|
def actor_leave_event( |
|
_parent, |
|
%{actor_id: actor_id, event_id: event_id, token: token}, |
|
_resolution |
|
) |
|
when not is_nil(token) do |
|
case check_anonymous_participation(actor_id, event_id) do |
|
{:ok, %Event{} = event} -> |
|
%Actor{} = actor = Actors.get_actor_with_preload!(actor_id) |
|
|
|
case Participations.leave(event, actor, %{local: false, cancellation_token: token}) do |
|
{:ok, _activity, %Participant{id: participant_id} = _participant} -> |
|
{:ok, %{event: %{id: event_id}, actor: %{id: actor_id}, id: participant_id}} |
|
|
|
{:error, :is_only_organizer} -> |
|
{:error, |
|
dgettext( |
|
"errors", |
|
"You can't leave event because you're the only event creator participant" |
|
)} |
|
|
|
{:error, :participant_not_found} -> |
|
{:error, dgettext("errors", "Participant not found")} |
|
|
|
{:error, _err} -> |
|
{:error, dgettext("errors", "Failed to leave the event")} |
|
end |
|
end |
|
end |
|
|
|
def actor_leave_event( |
|
_parent, |
|
%{actor_id: actor_id, event_id: event_id}, |
|
%{context: %{current_user: user}} |
|
) do |
|
with {:is_owned, %Actor{} = actor} <- User.owns_actor(user, actor_id), |
|
{:has_event, {:ok, %Event{} = event}} <- |
|
{:has_event, Events.get_event_with_preload(event_id)}, |
|
{:ok, _activity, _participant} <- Participations.leave(event, actor) do |
|
{:ok, %{event: %{id: event_id}, actor: %{id: actor_id}}} |
|
else |
|
{:has_event, _} -> |
|
{:error, "Event with this ID #{inspect(event_id)} doesn't exist"} |
|
|
|
{:is_owned, nil} -> |
|
{:error, dgettext("errors", "Profile is not owned by authenticated user")} |
|
|
|
{:error, :is_only_organizer} -> |
|
{:error, |
|
dgettext( |
|
"errors", |
|
"You can't leave event because you're the only event creator participant" |
|
)} |
|
|
|
{:error, :participant_not_found} -> |
|
{:error, dgettext("errors", "Participant not found")} |
|
end |
|
end |
|
|
|
def actor_leave_event(_parent, _args, _resolution) do |
|
{:error, dgettext("errors", "You need to be logged-in to leave an event")} |
|
end |
|
|
|
@spec update_participation(any(), map(), Absinthe.Resolution.t()) :: |
|
{:ok, Participation.t()} | {:error, String.t() | Ecto.Changeset.t()} |
|
def update_participation( |
|
_parent, |
|
%{id: participation_id, role: new_role}, |
|
%{ |
|
context: %{ |
|
current_actor: %Actor{} = moderator_actor |
|
} |
|
} |
|
) do |
|
# Check that participation already exists |
|
|
|
case Events.get_participant(participation_id) do |
|
%Participant{role: old_role, event_id: event_id} = participation -> |
|
if new_role != old_role do |
|
%Event{} = event = Events.get_event_with_preload!(event_id) |
|
|
|
if can_event_be_updated_by?(event, moderator_actor) do |
|
with {:ok, _activity, participation} <- |
|
Participations.update(participation, moderator_actor, new_role) do |
|
{:ok, participation} |
|
end |
|
else |
|
{:error, |
|
dgettext( |
|
"errors", |
|
"Provided profile doesn't have moderator permissions on this event" |
|
)} |
|
end |
|
else |
|
{:error, dgettext("errors", "Participant already has role %{role}", role: new_role)} |
|
end |
|
|
|
nil -> |
|
{:error, dgettext("errors", "Participant not found")} |
|
end |
|
end |
|
|
|
@spec confirm_participation_from_token(map(), map(), map()) :: |
|
{:ok, Participant.t()} | {:error, String.t()} |
|
def confirm_participation_from_token( |
|
_parent, |
|
%{confirmation_token: confirmation_token}, |
|
_context |
|
) do |
|
with {:has_participant, |
|
%Participant{actor: actor, role: :not_confirmed, event: event} = participant} <- |
|
{:has_participant, Events.get_participant_by_confirmation_token(confirmation_token)}, |
|
{:ok, _activity, %Participant{} = participant} <- |
|
Participations.update(participant, actor, Events.get_default_participant_role(event)) do |
|
{:ok, participant} |
|
else |
|
{:has_participant, nil} -> |
|
{:error, dgettext("errors", "This token is invalid")} |
|
|
|
{:error, %Ecto.Changeset{} = err} -> |
|
{:error, err} |
|
end |
|
end |
|
|
|
@spec export_event_participants(any(), map(), Absinthe.Resolution.t()) :: {:ok, String.t()} |
|
def export_event_participants(_parent, %{event_id: event_id, roles: roles, format: format}, %{ |
|
context: %{ |
|
current_user: %User{locale: locale}, |
|
current_actor: %Actor{} = moderator_actor |
|
} |
|
}) do |
|
case Events.get_event_with_preload(event_id) do |
|
{:ok, %Event{} = event} -> |
|
if can_event_be_updated_by?(event, moderator_actor) do |
|
case export_format(format, event, roles, locale) do |
|
{:ok, path} -> |
|
{:ok, path} |
|
|
|
{:error, :export_dependency_not_installed} -> |
|
{:error, |
|
dgettext( |
|
"errors", |
|
"A dependency needed to export to %{format} is not installed", |
|
format: format |
|
)} |
|
|
|
{:error, :failed_to_save_upload} -> |
|
{:error, |
|
dgettext( |
|
"errors", |
|
"An error occured while saving export", |
|
format: format |
|
)} |
|
|
|
{:error, :format_not_supported} -> |
|
{:error, |
|
dgettext( |
|
"errors", |
|
"Format not supported" |
|
)} |
|
end |
|
else |
|
{:error, |
|
dgettext( |
|
"errors", |
|
"Provided profile doesn't have moderator permissions on this event" |
|
)} |
|
end |
|
|
|
{:error, :event_not_found} -> |
|
{:error, |
|
dgettext("errors", "Event with this ID %{id} doesn't exist", id: inspect(event_id))} |
|
end |
|
end |
|
|
|
def export_event_participants(_, _, _), do: {:error, :unauthorized} |
|
|
|
@spec valid_email?(String.t() | nil) :: boolean |
|
defp valid_email?(email) when is_nil(email), do: false |
|
|
|
defp valid_email?(email) when is_binary(email) do |
|
email |
|
|> String.trim() |
|
|> Checker.valid?() |
|
end |
|
|
|
@spec export_format(atom(), Event.t(), list(), String.t()) :: |
|
{:ok, String.t()} |
|
| {:error, |
|
:format_not_supported | :export_dependency_not_installed | :failed_to_save_upload} |
|
defp export_format(format, event, roles, locale) do |
|
case format do |
|
:csv -> |
|
CSV.export(event, roles: roles, locale: locale) |
|
|
|
:pdf -> |
|
PDF.export(event, roles: roles, locale: locale) |
|
|
|
:ods -> |
|
ODS.export(event, roles: roles, locale: locale) |
|
|
|
_ -> |
|
{:error, :format_not_supported} |
|
end |
|
end |
|
end
|
|
|