Add admin dashboard, event reporting, moderation report screens, moderation log

Close #156 and #158

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel
2019-09-09 09:31:08 +02:00
parent 164429964a
commit 27f2597b07
77 changed files with 1682 additions and 201 deletions

View File

@@ -22,7 +22,8 @@ defmodule Mobilizon.Admin do
def list_action_logs(page \\ nil, limit \\ nil) do
from(
r in ActionLog,
preload: [:actor]
preload: [:actor],
order_by: [desc: :id]
)
|> paginate(page, limit)
|> Repo.all()

View File

@@ -1,3 +1,11 @@
import EctoEnum
defenum(Mobilizon.Admin.ActionLogAction, [
"update",
"create",
"delete"
])
defmodule Mobilizon.Admin.ActionLog do
@moduledoc """
ActionLog entity schema
@@ -5,11 +13,13 @@ defmodule Mobilizon.Admin.ActionLog do
use Ecto.Schema
import Ecto.Changeset
alias Mobilizon.Actors.Actor
alias Mobilizon.Admin.ActionLogAction
@timestamps_opts [type: :utc_datetime]
@required_attrs [:action, :target_type, :target_id, :changes, :actor_id]
schema "admin_action_logs" do
field(:action, :string)
field(:action, ActionLogAction)
field(:target_type, :string)
field(:target_id, :integer)
field(:changes, :map)

View File

@@ -54,6 +54,21 @@ defmodule Mobilizon.Application do
],
id: :cache_ics
),
worker(
Cachex,
[
:statistics,
[
limit: 10,
expiration:
expiration(
default: :timer.minutes(60),
interval: :timer.seconds(60)
)
]
],
id: :cache_statistics
),
worker(
Cachex,
[

View File

@@ -42,6 +42,7 @@ defmodule Mobilizon.Events.EventOptions do
}
@primary_key false
@derive Jason.Encoder
embedded_schema do
field(:maximum_attendee_capacity, :integer)
field(:remaining_attendee_capacity, :integer)

View File

@@ -30,16 +30,28 @@ defmodule Mobilizon.Reports do
"""
@spec list_reports(integer(), integer(), atom(), atom()) :: list(Report.t())
def list_reports(page \\ nil, limit \\ nil, sort \\ :updated_at, direction \\ :asc) do
def list_reports(
page \\ nil,
limit \\ nil,
sort \\ :updated_at,
direction \\ :desc,
status \\ :open
) do
from(
r in Report,
preload: [:reported, :reporter, :manager, :event, :comments, :notes]
preload: [:reported, :reporter, :manager, :event, :comments, :notes],
where: r.status == ^status
)
|> paginate(page, limit)
|> sort(sort, direction)
|> Repo.all()
end
def count_opened_reports() do
query = from(r in Report, where: r.status == ^:open)
Repo.aggregate(query, :count, :id)
end
@doc """
Gets a single report.

View File

@@ -7,6 +7,7 @@ defmodule Mobilizon.Reports.Note do
alias Mobilizon.Actors.Actor
alias Mobilizon.Reports.Report
@timestamps_opts [type: :utc_datetime]
@attrs [:content, :moderator_id, :report_id]
@derive {Jason.Encoder, only: [:content]}

View File

@@ -17,6 +17,8 @@ defmodule Mobilizon.Reports.Report do
alias Mobilizon.Actors.Actor
alias Mobilizon.Reports.Note
@timestamps_opts [type: :utc_datetime]
@derive {Jason.Encoder, only: [:status, :uri]}
schema "reports" do
field(:content, :string)
@@ -48,7 +50,7 @@ defmodule Mobilizon.Reports.Report do
def changeset(report, attrs) do
report
|> cast(attrs, [:content, :status, :uri, :reported_id, :reporter_id, :manager_id, :event_id])
|> validate_required([:content, :uri, :reported_id, :reporter_id])
|> validate_required([:uri, :reported_id, :reporter_id])
end
def creation_changeset(report, attrs) do

View File

@@ -135,4 +135,13 @@ defmodule MobilizonWeb.API.Events do
}
end
end
@doc """
Trigger the deletion of an event
If the event is deleted by
"""
def delete_event(%Event{} = event, federate \\ true) do
ActivityPub.delete(event, federate)
end
end

View File

@@ -21,21 +21,17 @@ defmodule MobilizonWeb.API.Reports do
def report(
%{
reporter_actor_id: reporter_actor_id,
reported_actor_id: reported_actor_id,
event_id: event_id,
comments_ids: comments_ids,
report_content: report_content
reported_actor_id: reported_actor_id
} = args
) do
with {:reporter, %Actor{url: reporter_url} = _reporter_actor} <-
{:reporter, Actors.get_actor!(reporter_actor_id)},
{:reported, %Actor{url: reported_actor_url} = reported_actor} <-
{:reported, Actors.get_actor!(reported_actor_id)},
{:ok, content} <- make_report_content_html(report_content),
{:ok, event} <-
if(event_id, do: Events.get_event(event_id), else: {:ok, nil}),
{:ok, content} <- args |> Map.get(:content, nil) |> make_report_content_text(),
{:ok, event} <- args |> Map.get(:event_id, nil) |> get_event(),
{:get_report_comments, comments_urls} <-
get_report_comments(reported_actor, comments_ids),
get_report_comments(reported_actor, Map.get(args, :comments_ids, [])),
{:make_activity, {:ok, %Activity{} = activity, %Report{} = report}} <-
{:make_activity,
ActivityPub.flag(%{
@@ -49,6 +45,7 @@ defmodule MobilizonWeb.API.Reports do
})} do
{:ok, activity, report}
else
{:make_activity, err} -> {:error, err}
{:error, err} -> {:error, err}
{:actor_id, %{}} -> {:error, "Valid `actor_id` required"}
{:reporter, nil} -> {:error, "Reporter Actor not found"}
@@ -56,6 +53,9 @@ defmodule MobilizonWeb.API.Reports do
end
end
defp get_event(nil), do: {:ok, nil}
defp get_event(event_id), do: Events.get_event(event_id)
@doc """
Update the state of a report
"""

View File

@@ -122,9 +122,9 @@ defmodule MobilizonWeb.API.Utils do
# |> Formatter.html_escape("text/html")
# end
def make_report_content_html(nil), do: {:ok, {nil, [], []}}
def make_report_content_text(nil), do: {:ok, nil}
def make_report_content_html(comment) do
def make_report_content_text(comment) do
max_size = Mobilizon.CommonConfig.get([:instance, :max_report_comment_size], 1000)
if String.length(comment) <= max_size do

View File

@@ -6,8 +6,8 @@
defmodule MobilizonWeb.NodeInfoController do
use MobilizonWeb, :controller
alias Mobilizon.{Events, Users}
alias Mobilizon.CommonConfig
alias Mobilizon.Service.Statistics
@instance Application.get_env(:mobilizon, :instance)
@node_info_supported_versions ["2.0", "2.1"]
@@ -34,7 +34,7 @@ defmodule MobilizonWeb.NodeInfoController do
response = %{
version: version,
software: %{
name: "mobilizon",
name: "Mobilizon",
version: Keyword.get(@instance, :version)
},
protocols: ["activitypub"],
@@ -45,10 +45,10 @@ defmodule MobilizonWeb.NodeInfoController do
openRegistrations: CommonConfig.registrations_open?(),
usage: %{
users: %{
total: Users.count_users()
total: Statistics.get_cached_value(:local_users)
},
localPosts: Events.count_local_events(),
localComments: Events.count_local_comments()
localPosts: Statistics.get_cached_value(:local_events),
localComments: Statistics.get_cached_value(:local_comments)
},
metadata: %{
nodeName: CommonConfig.instance_name(),

View File

@@ -2,10 +2,13 @@ defmodule MobilizonWeb.Resolvers.Admin do
@moduledoc """
Handles the report-related GraphQL calls
"""
alias Mobilizon.Events
alias Mobilizon.Users.User
import Mobilizon.Users.Guards
alias Mobilizon.Admin.ActionLog
alias Mobilizon.Reports.{Report, Note}
alias Mobilizon.Events.Event
alias Mobilizon.Service.Statistics
def list_action_logs(_parent, %{page: page, limit: limit}, %{
context: %{current_user: %User{role: role}}
@@ -17,14 +20,19 @@ defmodule MobilizonWeb.Resolvers.Admin do
target_type: target_type,
action: action,
actor: actor,
id: id
id: id,
inserted_at: inserted_at
} = action_log ->
transform_action_log(target_type, action, action_log)
|> Map.merge(%{
actor: actor,
id: id
})
with data when is_map(data) <-
transform_action_log(String.to_existing_atom(target_type), action, action_log) do
Map.merge(data, %{
actor: actor,
id: id,
inserted_at: inserted_at
})
end
end)
|> Enum.filter(& &1)
{:ok, action_logs}
end
@@ -35,38 +43,87 @@ defmodule MobilizonWeb.Resolvers.Admin do
end
defp transform_action_log(
"Elixir.Mobilizon.Reports.Report",
"update",
Report,
:update,
%ActionLog{} = action_log
) do
with %Report{status: status} = report <- Mobilizon.Reports.get_report(action_log.target_id) do
with %Report{} = report <- Mobilizon.Reports.get_report(action_log.target_id) do
action =
case action_log do
%ActionLog{changes: %{"status" => "closed"}} -> :report_update_closed
%ActionLog{changes: %{"status" => "open"}} -> :report_update_opened
%ActionLog{changes: %{"status" => "resolved"}} -> :report_update_resolved
end
%{
action: "report_update_" <> to_string(status),
action: action,
object: report
}
end
end
defp transform_action_log("Elixir.Mobilizon.Reports.Note", "create", %ActionLog{
defp transform_action_log(Note, :create, %ActionLog{
changes: changes
}) do
%{
action: "note_creation",
action: :note_creation,
object: convert_changes_to_struct(Note, changes)
}
end
defp transform_action_log("Elixir.Mobilizon.Reports.Note", "delete", %ActionLog{
defp transform_action_log(Note, :delete, %ActionLog{
changes: changes
}) do
%{
action: "note_deletion",
action: :note_deletion,
object: convert_changes_to_struct(Note, changes)
}
end
defp transform_action_log(Event, :delete, %ActionLog{
changes: changes
}) do
%{
action: :event_deletion,
object: convert_changes_to_struct(Event, changes)
}
end
# Changes are stored as %{"key" => "value"} so we need to convert them back as struct
defp convert_changes_to_struct(struct, %{"report_id" => _report_id} = changes) do
with data <- for({key, val} <- changes, into: %{}, do: {String.to_atom(key), val}),
data <- Map.put(data, :report, Mobilizon.Reports.get_report(data.report_id)) do
struct(struct, data)
end
end
defp convert_changes_to_struct(struct, changes) do
struct(struct, for({key, val} <- changes, into: %{}, do: {String.to_atom(key), val}))
with data <- for({key, val} <- changes, into: %{}, do: {String.to_atom(key), val}) do
struct(struct, data)
end
end
def get_dashboard(_parent, _args, %{
context: %{current_user: %User{role: role}}
})
when is_admin(role) do
last_public_event_published =
case Events.list_events(1, 1, :inserted_at, :desc) do
[event | _] -> event
_ -> nil
end
{:ok,
%{
number_of_users: Statistics.get_cached_value(:local_users),
number_of_events: Statistics.get_cached_value(:local_events),
number_of_comments: Statistics.get_cached_value(:local_comments),
number_of_reports: Mobilizon.Reports.count_opened_reports(),
last_public_event_published: last_public_event_published
}}
end
def get_dashboard(_parent, _args, _resolution) do
{:error, "You need to be logged-in and an administrator to access dashboard statistics"}
end
end

View File

@@ -9,7 +9,10 @@ defmodule MobilizonWeb.Resolvers.Event do
alias Mobilizon.Events.{Event, Participant}
alias Mobilizon.Media.Picture
alias Mobilizon.Users.User
alias Mobilizon.Actors
alias Mobilizon.Actors.Actor
alias MobilizonWeb.Resolvers.Person
import Mobilizon.Service.Admin.ActionLogService
# We limit the max number of events that can be retrieved
@event_max_limit 100
@@ -328,28 +331,43 @@ defmodule MobilizonWeb.Resolvers.Event do
%{event_id: event_id, actor_id: actor_id},
%{
context: %{
current_user: user
current_user: %User{role: role} = user
}
}
) do
with {:ok, %Event{} = event} <- Mobilizon.Events.get_event(event_id),
{:is_owned, true, _} <- User.owns_actor(user, actor_id),
{:event_can_be_managed, true} <- Event.can_event_be_managed_by(event, actor_id),
event <- Mobilizon.Events.delete_event!(event) do
{:ok, %{id: event.id}}
with {:ok, %Event{local: is_local} = event} <- Mobilizon.Events.get_event_full(event_id),
{actor_id, ""} <- Integer.parse(actor_id),
{:is_owned, true, _} <- User.owns_actor(user, actor_id) do
cond do
Event.can_event_be_managed_by(event, actor_id) == {:event_can_be_managed, true} ->
do_delete_event(event)
role in [:moderator, :administrator] ->
with {:ok, res} <- do_delete_event(event, !is_local),
%Actor{} = actor <- Actors.get_actor(actor_id) do
log_action(actor, "delete", event)
{:ok, res}
end
true ->
{:error, "You cannot delete this event"}
end
else
{:error, :event_not_found} ->
{:error, "Event not found"}
{:is_owned, false} ->
{:error, "Actor id is not owned by authenticated user"}
{:event_can_be_managed, false} ->
{:error, "You cannot delete this event"}
end
end
def delete_event(_parent, _args, _resolution) do
{:error, "You need to be logged-in to delete an event"}
end
defp do_delete_event(event, federate \\ true) when is_boolean(federate) do
with {:ok, _activity, event} <- MobilizonWeb.API.Events.delete_event(event) do
{:ok, %{id: event.id}}
end
end
end

View File

@@ -89,7 +89,9 @@ defmodule MobilizonWeb.Resolvers.Group do
}
}
) do
with {:ok, %Actor{} = group} <- Actors.get_group_by_actor_id(group_id),
with {actor_id, ""} <- Integer.parse(actor_id),
{group_id, ""} <- Integer.parse(group_id),
{:ok, %Actor{} = group} <- Actors.get_group_by_actor_id(group_id),
{:is_owned, true, _} <- User.owns_actor(user, actor_id),
{:ok, %Member{} = member} <- Member.get_member(actor_id, group.id),
{:is_admin, true} <- Member.is_administrator(member),
@@ -126,7 +128,9 @@ defmodule MobilizonWeb.Resolvers.Group do
}
}
) do
with {:is_owned, true, actor} <- User.owns_actor(user, actor_id),
with {actor_id, ""} <- Integer.parse(actor_id),
{group_id, ""} <- Integer.parse(group_id),
{:is_owned, true, actor} <- User.owns_actor(user, actor_id),
{:ok, %Actor{} = group} <- Actors.get_group_by_actor_id(group_id),
{:error, :member_not_found} <- Member.get_member(actor.id, group.id),
{:is_able_to_join, true} <- {:is_able_to_join, Member.can_be_joined(group)},
@@ -180,7 +184,9 @@ defmodule MobilizonWeb.Resolvers.Group do
}
}
) do
with {:is_owned, true, actor} <- User.owns_actor(user, actor_id),
with {actor_id, ""} <- Integer.parse(actor_id),
{group_id, ""} <- Integer.parse(group_id),
{:is_owned, true, actor} <- User.owns_actor(user, actor_id),
{:ok, %Member{} = member} <- Member.get_member(actor.id, group_id),
{:only_administrator, false} <-
{:only_administrator, check_that_member_is_not_last_administrator(group_id, actor_id)},

View File

@@ -10,11 +10,11 @@ defmodule MobilizonWeb.Resolvers.Report do
alias MobilizonWeb.API.Reports, as: ReportsAPI
import Mobilizon.Users.Guards
def list_reports(_parent, %{page: page, limit: limit}, %{
def list_reports(_parent, %{page: page, limit: limit, status: status}, %{
context: %{current_user: %User{role: role}}
})
when is_moderator(role) do
{:ok, Mobilizon.Reports.list_reports(page, limit)}
{:ok, Mobilizon.Reports.list_reports(page, limit, :updated_at, :desc, status)}
end
def list_reports(_parent, _args, _resolution) do
@@ -25,7 +25,13 @@ defmodule MobilizonWeb.Resolvers.Report do
context: %{current_user: %User{role: role}}
})
when is_moderator(role) do
{:ok, Mobilizon.Reports.get_report(id)}
case Mobilizon.Reports.get_report(id) do
%Report{} = report ->
{:ok, report}
nil ->
{:error, "Report not found"}
end
end
def get_report(_parent, _args, _resolution) do

View File

@@ -78,6 +78,9 @@ defmodule MobilizonWeb.Router do
get("/events/create", PageController, :index)
get("/events/list", PageController, :index)
get("/events/:uuid/edit", PageController, :index)
# This is a hack to ease link generation into emails
get("/moderation/reports/:id", PageController, :index, as: "moderation_report")
end
scope "/", MobilizonWeb do

View File

@@ -4,7 +4,7 @@ defmodule MobilizonWeb.Schema do
"""
use Absinthe.Schema
alias Mobilizon.{Actors, Events, Users, Addresses, Media}
alias Mobilizon.{Actors, Events, Users, Addresses, Media, Reports}
alias Mobilizon.Actors.{Actor, Follower, Member}
alias Mobilizon.Events.{Event, Comment, Participant}
@@ -26,7 +26,7 @@ defmodule MobilizonWeb.Schema do
@desc "A struct containing the id of the deleted object"
object :deleted_object do
field(:id, :integer)
field(:id, :id)
end
@desc "A JWT and the associated user ID"
@@ -44,7 +44,7 @@ defmodule MobilizonWeb.Schema do
Represents a notification for an user
"""
object :notification do
field(:id, :integer, description: "The notification ID")
field(:id, :id, description: "The notification ID")
field(:user, :user, description: "The user to transmit the notification to")
field(:actor, :actor, description: "The notification target profile")
@@ -94,6 +94,7 @@ defmodule MobilizonWeb.Schema do
|> Dataloader.add_source(Events, Events.data())
|> Dataloader.add_source(Addresses, Addresses.data())
|> Dataloader.add_source(Media, Media.data())
|> Dataloader.add_source(Reports, Reports.data())
Map.put(ctx, :loader, loader)
end

View File

@@ -13,7 +13,7 @@ defmodule MobilizonWeb.Schema.ActorInterface do
@desc "An ActivityPub actor"
interface :actor do
field(:id, :integer, description: "Internal ID for this actor")
field(:id, :id, description: "Internal ID for this actor")
field(:url, :string, description: "The ActivityPub actor's URL")
field(:type, :actor_type, description: "The type of Actor (Person, Group,…)")
field(:name, :string, description: "The actor's displayed name")

View File

@@ -14,7 +14,7 @@ defmodule MobilizonWeb.Schema.Actors.GroupType do
object :group do
interfaces([:actor])
field(:id, :integer, description: "Internal ID for this group")
field(:id, :id, description: "Internal ID for this group")
field(:url, :string, description: "The ActivityPub actor's URL")
field(:type, :actor_type, description: "The type of Actor (Person, Group,…)")
field(:name, :string, description: "The actor's displayed name")
@@ -96,9 +96,7 @@ defmodule MobilizonWeb.Schema.Actors.GroupType do
field :create_group, :group do
arg(:preferred_username, non_null(:string), description: "The name for the group")
arg(:creator_actor_id, non_null(:integer),
description: "The identity that creates the group"
)
arg(:creator_actor_id, non_null(:id), description: "The identity that creates the group")
arg(:name, :string, description: "The displayed name for the group")
arg(:summary, :string, description: "The summary for the group", default_value: "")
@@ -118,8 +116,8 @@ defmodule MobilizonWeb.Schema.Actors.GroupType do
@desc "Delete a group"
field :delete_group, :deleted_object do
arg(:group_id, non_null(:integer))
arg(:actor_id, non_null(:integer))
arg(:group_id, non_null(:id))
arg(:actor_id, non_null(:id))
resolve(&Group.delete_group/3)
end

View File

@@ -24,16 +24,16 @@ defmodule MobilizonWeb.Schema.Actors.MemberType do
object :member_mutations do
@desc "Join a group"
field :join_group, :member do
arg(:group_id, non_null(:integer))
arg(:actor_id, non_null(:integer))
arg(:group_id, non_null(:id))
arg(:actor_id, non_null(:id))
resolve(&Resolvers.Group.join_group/3)
end
@desc "Leave an event"
field :leave_group, :deleted_member do
arg(:group_id, non_null(:integer))
arg(:actor_id, non_null(:integer))
arg(:group_id, non_null(:id))
arg(:actor_id, non_null(:id))
resolve(&Resolvers.Group.leave_group/3)
end

View File

@@ -15,7 +15,7 @@ defmodule MobilizonWeb.Schema.Actors.PersonType do
"""
object :person do
interfaces([:actor])
field(:id, :integer, description: "Internal ID for this person")
field(:id, :id, description: "Internal ID for this person")
field(:user, :user, description: "The user this actor is associated to")
field(:member_of, list_of(:member), description: "The list of groups this person is member of")

View File

@@ -15,7 +15,7 @@ defmodule MobilizonWeb.Schema.AddressType do
field(:country, :string)
field(:description, :string)
field(:url, :string)
field(:id, :integer)
field(:id, :id)
field(:origin_id, :string)
end
@@ -40,7 +40,7 @@ defmodule MobilizonWeb.Schema.AddressType do
field(:country, :string)
field(:description, :string)
field(:url, :string)
field(:id, :integer)
field(:id, :id)
field(:origin_id, :string)
end

View File

@@ -5,13 +5,25 @@ defmodule MobilizonWeb.Schema.AdminType do
use Absinthe.Schema.Notation
alias MobilizonWeb.Resolvers.Admin
alias Mobilizon.Reports.{Report, Note}
alias Mobilizon.Events.Event
@desc "An action log"
object :action_log do
field(:id, :id, description: "Internal ID for this comment")
field(:actor, :actor, description: "The actor that acted")
field(:object, :action_log_object, description: "The object that was acted upon")
field(:action, :string, description: "The action that was done")
field(:action, :action_log_action, description: "The action that was done")
field(:inserted_at, :datetime, description: "The time when the action was performed")
end
enum :action_log_action do
value(:report_update_closed)
value(:report_update_opened)
value(:report_update_resolved)
value(:note_creation)
value(:note_deletion)
value(:event_deletion)
value(:event_update)
end
@desc "The objects that can be in an action log"
@@ -25,11 +37,22 @@ defmodule MobilizonWeb.Schema.AdminType do
%Note{}, _ ->
:report_note
%Event{}, _ ->
:event
_, _ ->
nil
end)
end
object :dashboard do
field(:last_public_event_published, :event, description: "Last public event publish")
field(:number_of_users, :integer, description: "The number of local users")
field(:number_of_events, :integer, description: "The number of local events")
field(:number_of_comments, :integer, description: "The number of local comments")
field(:number_of_reports, :integer, description: "The number of current opened reports")
end
object :admin_queries do
@desc "Get the list of action logs"
field :action_logs, type: list_of(:action_log) do
@@ -37,5 +60,9 @@ defmodule MobilizonWeb.Schema.AdminType do
arg(:limit, :integer, default_value: 10)
resolve(&Admin.list_action_logs/3)
end
field :dashboard, type: :dashboard do
resolve(&Admin.get_dashboard/3)
end
end
end

View File

@@ -12,7 +12,8 @@ defmodule MobilizonWeb.Schema.EventType do
@desc "An event"
object :event do
field(:id, :integer, description: "Internal ID for this event")
interfaces([:action_log_object])
field(:id, :id, description: "Internal ID for this event")
field(:uuid, :uuid, description: "The Event UUID")
field(:url, :string, description: "The ActivityPub Event URL")
field(:local, :boolean, description: "Whether the event is local or not")
@@ -261,8 +262,8 @@ defmodule MobilizonWeb.Schema.EventType do
@desc "Delete an event"
field :delete_event, :deleted_object do
arg(:event_id, non_null(:integer))
arg(:actor_id, non_null(:integer))
arg(:event_id, non_null(:id))
arg(:actor_id, non_null(:id))
resolve(&Event.delete_event/3)
end

View File

@@ -36,7 +36,7 @@ defmodule MobilizonWeb.Schema.Events.FeedTokenType do
object :feed_token_mutations do
@desc "Create a Feed Token"
field :create_feed_token, :feed_token do
arg(:actor_id, :integer)
arg(:actor_id, :id)
resolve(&Resolvers.FeedToken.create_feed_token/3)
end

View File

@@ -46,16 +46,16 @@ defmodule MobilizonWeb.Schema.Events.ParticipantType do
object :participant_mutations do
@desc "Join an event"
field :join_event, :participant do
arg(:event_id, non_null(:integer))
arg(:actor_id, non_null(:integer))
arg(:event_id, non_null(:id))
arg(:actor_id, non_null(:id))
resolve(&Resolvers.Event.actor_join_event/3)
end
@desc "Leave an event"
field :leave_event, :deleted_participant do
arg(:event_id, non_null(:integer))
arg(:actor_id, non_null(:integer))
arg(:event_id, non_null(:id))
arg(:actor_id, non_null(:id))
resolve(&Resolvers.Event.actor_leave_event/3)
end

View File

@@ -3,6 +3,8 @@ defmodule MobilizonWeb.Schema.ReportType do
Schema representation for User
"""
use Absinthe.Schema.Notation
import Absinthe.Resolution.Helpers, only: [dataloader: 1]
alias Mobilizon.Reports
alias MobilizonWeb.Resolvers.Report
@@ -17,6 +19,14 @@ defmodule MobilizonWeb.Schema.ReportType do
field(:reporter, :actor, description: "The actor that created the report")
field(:event, :event, description: "The event that is being reported")
field(:comments, list_of(:comment), description: "The comments that are reported")
field(:notes, list_of(:report_note),
description: "The notes made on the event",
resolve: dataloader(Reports)
)
field(:inserted_at, :datetime, description: "When the report was created")
field(:updated_at, :datetime, description: "When the report was updated")
end
@desc "A report note object"
@@ -24,8 +34,14 @@ defmodule MobilizonWeb.Schema.ReportType do
interfaces([:action_log_object])
field(:id, :id, description: "The internal ID of the report note")
field(:content, :string, description: "The content of the note")
field(:moderator, :actor, description: "The moderator who added the note")
field(:moderator, :actor,
description: "The moderator who added the note",
resolve: dataloader(Reports)
)
field(:report, :report, description: "The report on which this note is added")
field(:inserted_at, :datetime, description: "When the report note was created")
end
@desc "The list of possible statuses for a report object"
@@ -40,6 +56,7 @@ defmodule MobilizonWeb.Schema.ReportType do
field :reports, list_of(:report) do
arg(:page, :integer, default_value: 1)
arg(:limit, :integer, default_value: 10)
arg(:status, :report_status, default_value: :open)
resolve(&Report.list_reports/3)
end
@@ -53,7 +70,7 @@ defmodule MobilizonWeb.Schema.ReportType do
object :report_mutations do
@desc "Create a report"
field :create_report, type: :report do
arg(:report_content, :string)
arg(:content, :string)
arg(:reporter_actor_id, non_null(:id))
arg(:reported_actor_id, non_null(:id))
arg(:event_id, :id, default_value: nil)

View File

@@ -43,6 +43,14 @@ defmodule MobilizonWeb.Schema.UserType do
resolve: dataloader(Events),
description: "A list of the feed tokens for this user"
)
field(:role, :user_role, description: "The role for the user")
end
enum :user_role do
value(:administrator)
value(:moderator)
value(:user)
end
@desc "Token"

View File

@@ -1,15 +1,15 @@
<h1><%= gettext "New report from %{reporter} on %{instance}", reporter: @report.reporter, instance: @instance %></h1>
<h1><%= gettext "New report from %{reporter} on %{instance}", reporter: @report.reporter.preferred_username, instance: @instance %></h1>
<% if @report.event do %>
<p><%= gettext "Event: %{event}", event: @report.event %></p>
<p><%= gettext "Event: %{event}", event: @report.event.title %></p>
<% end %>
<%= for comment <- @report.comments do %>
<p><%= gettext "Comment: %{comment}", comment: comment %></p>
<% end %>
<% if @content do %>
<% if @report.content do %>
<p><%= gettext "Reason: %{content}", event: @report.content %></p>
<% end %>
<p><%= link "View the report", to: MobilizonWeb.Endpoint.url() <> "/reports/#{@report.id}", target: "_blank" %></p>
<p><%= link "View the report", to: moderation_report_url(MobilizonWeb.Endpoint, :index, @report.id), target: "_blank" %></p>

View File

@@ -1,19 +1,19 @@
<%= gettext "New report from %{reporter} on %{instance}", reporter: @report.reporter, instance: @instance %>
<%= gettext "New report from %{reporter} on %{instance}", reporter: @report.reporter.preferred_username, instance: @instance %>
--
<% if @report.event do %>
<%= gettext "Event: %{event}", event: @report.event %>
<%= gettext "Event: %{event}", event: @report.event.title %>
<% end %>
<%= for comment <- @report.comments do %>
<%= gettext "Comment: %{comment}", comment: comment %>
<%= gettext "Comment: %{comment}", comment: comment.text %>
<% end %>
<% if @content do %>
<% if @report.content do %>
<%= gettext "Reason: %{content}", event: @report.content %>
<% end %>
<%= link "View the report", to: MobilizonWeb.Endpoint.url() <> "/reports/#{@report.id}", target: "_blank" %>
View the report: <%= moderation_report_url(MobilizonWeb.Endpoint, :index, @report.id) %>

View File

@@ -335,9 +335,8 @@ defmodule Mobilizon.Service.ActivityPub do
with {:ok, _} <- Events.delete_event(event),
{:ok, activity} <- create_activity(data, local),
{:ok, object} <- insert_full_object(data),
:ok <- maybe_federate(activity) do
{:ok, activity, object}
{:ok, activity, event}
end
end
@@ -521,7 +520,8 @@ defmodule Mobilizon.Service.ActivityPub do
public = is_public?(activity)
if public && Mobilizon.CommonConfig.get([:instance, :allow_relay]) do
if public && is_delete_activity?(activity) == false &&
Mobilizon.CommonConfig.get([:instance, :allow_relay]) do
Logger.info(fn -> "Relaying #{activity.data["id"]} out" end)
Mobilizon.Service.ActivityPub.Relay.publish(activity)
end
@@ -552,6 +552,9 @@ defmodule Mobilizon.Service.ActivityPub do
end)
end
defp is_delete_activity?(%Activity{data: %{"type" => "Delete"}}), do: true
defp is_delete_activity?(_), do: false
@doc """
Publish an activity to a specific inbox
"""

View File

@@ -164,14 +164,14 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
{:ok, %Report{} = report} <- Reports.create_report(data) do
Enum.each(Users.list_moderators(), fn moderator ->
moderator
|> Mobilizon.Email.Admin.report(moderator, report)
|> Mobilizon.Email.Admin.report(report)
|> Mobilizon.Mailer.deliver_later()
end)
{:ok, report}
else
err ->
Logger.error("Error while inserting a remote comment inside database")
Logger.error("Error while inserting report inside database")
Logger.debug(inspect(err))
{:error, err}
end

View File

@@ -22,9 +22,19 @@ defmodule Mobilizon.Service.Admin.ActionLogService do
"target_type" => to_string(target.__struct__),
"target_id" => target.id,
"action" => action,
"changes" => Map.from_struct(target) |> Map.take([:status, :uri, :content])
"changes" => stringify_struct(target)
}) do
{:ok, create_action_log}
end
end
defp stringify_struct(%_{} = struct) do
association_fields = struct.__struct__.__schema__(:associations)
struct
|> Map.from_struct()
|> Map.drop(association_fields ++ [:__meta__])
end
defp stringify_struct(struct), do: struct
end

31
lib/service/statistics.ex Normal file
View File

@@ -0,0 +1,31 @@
defmodule Mobilizon.Service.Statistics do
@moduledoc """
A module that provides cached statistics
"""
alias Mobilizon.Events
alias Mobilizon.Users
def get_cached_value(key) do
case Cachex.fetch(:statistics, key, fn key ->
case create_cache(key) do
value when not is_nil(value) -> {:commit, value}
err -> {:ignore, err}
end
end) do
{status, value} when status in [:ok, :commit] -> value
_err -> nil
end
end
defp create_cache(:local_users) do
Users.count_users()
end
defp create_cache(:local_events) do
Events.count_local_events()
end
defp create_cache(:local_comments) do
Events.count_local_comments()
end
end