Export participants to different formats
* CSV * PDF (requires Python dependency `weasyprint`) * ODS (requires Python dependency `pyexcel_ods3`) Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
@@ -31,7 +31,8 @@ defmodule Mobilizon.GraphQL.API.Participations do
|
||||
@doc """
|
||||
Update participation status
|
||||
"""
|
||||
@spec update(Participant.t(), Actor.t(), atom()) :: {:ok, Activity.t(), Participant.t()}
|
||||
@spec update(Participant.t(), Actor.t(), atom()) ::
|
||||
{:ok, Activity.t(), Participant.t()} | {:error, Ecto.Changeset.t()}
|
||||
def update(%Participant{} = participation, %Actor{} = moderator, :participant),
|
||||
do: accept(participation, moderator)
|
||||
|
||||
@@ -46,7 +47,8 @@ defmodule Mobilizon.GraphQL.API.Participations do
|
||||
def update(%Participant{} = participation, %Actor{} = moderator, :rejected),
|
||||
do: reject(participation, moderator)
|
||||
|
||||
@spec accept(Participant.t(), Actor.t()) :: {:ok, Activity.t(), Participant.t()}
|
||||
@spec accept(Participant.t(), Actor.t()) ::
|
||||
{:ok, Activity.t(), Participant.t()} | {:error, Ecto.Changeset.t()}
|
||||
defp accept(
|
||||
%Participant{} = participation,
|
||||
%Actor{} = moderator
|
||||
|
||||
@@ -153,7 +153,8 @@ defmodule Mobilizon.GraphQL.Resolvers.Config do
|
||||
enabled: !is_nil(Application.get_env(:web_push_encryption, :vapid_details)),
|
||||
public_key:
|
||||
get_in(Application.get_env(:web_push_encryption, :vapid_details), [:public_key])
|
||||
}
|
||||
},
|
||||
export_formats: Config.instance_export_formats()
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,7 +8,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Event.Utils do
|
||||
alias Mobilizon.Federation.ActivityPub.Permission
|
||||
import Mobilizon.Service.Guards, only: [is_valid_string: 1]
|
||||
|
||||
@spec can_event_be_updated_by?(%Event{id: String.t()}, Actor.t()) ::
|
||||
@spec can_event_be_updated_by?(Event.t(), Actor.t()) ::
|
||||
boolean
|
||||
def can_event_be_updated_by?(
|
||||
%Event{attributed_to: %Actor{type: :Group}} = event,
|
||||
@@ -24,7 +24,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Event.Utils do
|
||||
Event.can_be_managed_by?(event, actor_member_id)
|
||||
end
|
||||
|
||||
@spec can_event_be_deleted_by?(%Event{id: String.t(), url: String.t()}, Actor.t()) ::
|
||||
@spec can_event_be_deleted_by?(Event.t(), Actor.t()) ::
|
||||
boolean
|
||||
def can_event_be_deleted_by?(
|
||||
%Event{attributed_to: %Actor{type: :Group}, id: event_id, url: event_url} = event,
|
||||
|
||||
@@ -6,6 +6,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Participant do
|
||||
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
|
||||
@@ -225,7 +226,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Participant do
|
||||
end
|
||||
|
||||
@spec update_participation(any(), map(), Absinthe.Resolution.t()) ::
|
||||
{:ok, Participation.t()} | {:error, String.t()}
|
||||
{:ok, Participation.t()} | {:error, String.t() | Ecto.Changeset.t()}
|
||||
def update_participation(
|
||||
_parent,
|
||||
%{id: participation_id, role: new_role},
|
||||
@@ -236,28 +237,29 @@ defmodule Mobilizon.GraphQL.Resolvers.Participant do
|
||||
}
|
||||
) do
|
||||
# Check that participation already exists
|
||||
with {:has_participation, %Participant{role: old_role, event_id: event_id} = participation} <-
|
||||
{:has_participation, Events.get_participant(participation_id)},
|
||||
{:same_role, false} <- {:same_role, new_role == old_role},
|
||||
# Check that moderator has right
|
||||
{:event, %Event{} = event} <- {:event, Events.get_event_with_preload!(event_id)},
|
||||
{:event_can_be_managed, true} <-
|
||||
{:event_can_be_managed, can_event_be_updated_by?(event, moderator_actor)},
|
||||
{:ok, _activity, participation} <-
|
||||
Participations.update(participation, moderator_actor, new_role) do
|
||||
{:ok, participation}
|
||||
else
|
||||
{:has_participation, nil} ->
|
||||
{:error, dgettext("errors", "Participant not found")}
|
||||
|
||||
{:event_can_be_managed, _} ->
|
||||
{:error,
|
||||
dgettext("errors", "Provided profile doesn't have moderator permissions on this event")}
|
||||
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)
|
||||
|
||||
{:same_role, true} ->
|
||||
{:error, dgettext("errors", "Participant already has role %{role}", role: new_role)}
|
||||
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
|
||||
|
||||
{:error, :participant_not_found} ->
|
||||
nil ->
|
||||
{:error, dgettext("errors", "Participant not found")}
|
||||
end
|
||||
end
|
||||
@@ -272,16 +274,71 @@ defmodule Mobilizon.GraphQL.Resolvers.Participant do
|
||||
with {:has_participant,
|
||||
%Participant{actor: actor, role: :not_confirmed, event: event} = participant} <-
|
||||
{:has_participant, Events.get_participant_by_confirmation_token(confirmation_token)},
|
||||
default_role <- Events.get_default_participant_role(event),
|
||||
{:ok, _activity, %Participant{} = participant} <-
|
||||
Participations.update(participant, actor, default_role) do
|
||||
Participations.update(participant, actor, Events.get_default_participant_role(event)) do
|
||||
{:ok, participant}
|
||||
else
|
||||
{:has_participant, _} ->
|
||||
{: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
|
||||
|
||||
@@ -290,4 +347,24 @@ defmodule Mobilizon.GraphQL.Resolvers.Participant do
|
||||
|> 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
|
||||
|
||||
@@ -65,6 +65,8 @@ defmodule Mobilizon.GraphQL.Schema.ConfigType do
|
||||
field(:auth, :auth, description: "The instance auth methods")
|
||||
field(:instance_feeds, :instance_feeds, description: "The instance's feed settings")
|
||||
field(:web_push, :web_push, description: "Web Push settings for the instance")
|
||||
|
||||
field(:export_formats, :export_formats, description: "The instance list of export formats")
|
||||
end
|
||||
|
||||
@desc """
|
||||
@@ -307,6 +309,15 @@ defmodule Mobilizon.GraphQL.Schema.ConfigType do
|
||||
field(:public_key, :string, description: "The server's public WebPush VAPID key")
|
||||
end
|
||||
|
||||
@desc """
|
||||
Export formats configuration
|
||||
"""
|
||||
object :export_formats do
|
||||
field(:event_participants, list_of(:string),
|
||||
description: "The list of formats the event participants can be exported to"
|
||||
)
|
||||
end
|
||||
|
||||
object :config_queries do
|
||||
@desc "Get the instance config"
|
||||
field :config, :config do
|
||||
|
||||
@@ -70,6 +70,12 @@ defmodule Mobilizon.GraphQL.Schema.Events.ParticipantType do
|
||||
value(:rejected, description: "The participant has been rejected from this event")
|
||||
end
|
||||
|
||||
enum :export_format_enum do
|
||||
value(:csv, description: "CSV format")
|
||||
value(:pdf, description: "PDF format")
|
||||
value(:ods, description: "ODS format")
|
||||
end
|
||||
|
||||
@desc "Represents a deleted participant"
|
||||
object :deleted_participant do
|
||||
field(:id, :id, description: "The participant ID")
|
||||
@@ -111,5 +117,20 @@ defmodule Mobilizon.GraphQL.Schema.Events.ParticipantType do
|
||||
arg(:confirmation_token, non_null(:string), description: "The participation token")
|
||||
resolve(&Participant.confirm_participation_from_token/3)
|
||||
end
|
||||
|
||||
@desc "Export the event participants as a file"
|
||||
field :export_event_participants, :string do
|
||||
arg(:event_id, non_null(:id),
|
||||
description: "The ID from the event for which to export participants"
|
||||
)
|
||||
|
||||
arg(:roles, list_of(:participant_role_enum),
|
||||
default_value: [],
|
||||
description: "The participant roles to include"
|
||||
)
|
||||
|
||||
arg(:format, :export_format_enum, description: "The format in which to return the file")
|
||||
resolve(&Participant.export_event_participants/3)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user