Introduce backend for reports
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
@@ -35,6 +35,8 @@ defmodule Mobilizon.Actors.Actor do
|
||||
alias Mobilizon.Events.{Event, FeedToken}
|
||||
alias Mobilizon.Media.File
|
||||
|
||||
alias Mobilizon.Reports.{Report, Note}
|
||||
|
||||
alias MobilizonWeb.Router.Helpers, as: Routes
|
||||
alias MobilizonWeb.Endpoint
|
||||
|
||||
@@ -72,6 +74,9 @@ defmodule Mobilizon.Actors.Actor do
|
||||
has_many(:feed_tokens, FeedToken, foreign_key: :actor_id)
|
||||
embeds_one(:avatar, File, on_replace: :update)
|
||||
embeds_one(:banner, File, on_replace: :update)
|
||||
has_many(:created_reports, Report, foreign_key: :reporter_id)
|
||||
has_many(:subject_reports, Report, foreign_key: :reported_id)
|
||||
has_many(:report_notes, Note, foreign_key: :moderator_id)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
48
lib/mobilizon/admin.ex
Normal file
48
lib/mobilizon/admin.ex
Normal file
@@ -0,0 +1,48 @@
|
||||
defmodule Mobilizon.Admin do
|
||||
@moduledoc """
|
||||
The Admin context.
|
||||
"""
|
||||
|
||||
import Ecto.Query, warn: false
|
||||
alias Mobilizon.Repo
|
||||
import Mobilizon.Ecto
|
||||
|
||||
alias Mobilizon.Admin.ActionLog
|
||||
|
||||
@doc """
|
||||
Returns the list of action_logs.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> list_action_logs()
|
||||
[%ActionLog{}, ...]
|
||||
|
||||
"""
|
||||
@spec list_action_logs(integer(), integer()) :: list(ActionLog.t())
|
||||
def list_action_logs(page \\ nil, limit \\ nil) do
|
||||
from(
|
||||
r in ActionLog,
|
||||
preload: [:actor]
|
||||
)
|
||||
|> paginate(page, limit)
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a action_log.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> create_action_log(%{field: value})
|
||||
{:ok, %ActionLog{}}
|
||||
|
||||
iex> create_action_log(%{field: bad_value})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def create_action_log(attrs \\ %{}) do
|
||||
%ActionLog{}
|
||||
|> ActionLog.changeset(attrs)
|
||||
|> Repo.insert()
|
||||
end
|
||||
end
|
||||
27
lib/mobilizon/admin/action_log.ex
Normal file
27
lib/mobilizon/admin/action_log.ex
Normal file
@@ -0,0 +1,27 @@
|
||||
defmodule Mobilizon.Admin.ActionLog do
|
||||
@moduledoc """
|
||||
ActionLog entity schema
|
||||
"""
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
alias Mobilizon.Actors.Actor
|
||||
|
||||
@required_attrs [:action, :target_type, :target_id, :changes, :actor_id]
|
||||
|
||||
schema "admin_action_logs" do
|
||||
field(:action, :string)
|
||||
field(:target_type, :string)
|
||||
field(:target_id, :integer)
|
||||
field(:changes, :map)
|
||||
belongs_to(:actor, Actor)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
@doc false
|
||||
def changeset(action_log, attrs) do
|
||||
action_log
|
||||
|> cast(attrs, @required_attrs)
|
||||
|> validate_required(@required_attrs -- [:changes])
|
||||
end
|
||||
end
|
||||
@@ -19,7 +19,12 @@ defmodule Mobilizon.CommonConfig do
|
||||
|> get_in([:description])
|
||||
end
|
||||
|
||||
defp instance_config(), do: Application.get_env(:mobilizon, :instance)
|
||||
def instance_hostname() do
|
||||
instance_config()
|
||||
|> get_in([:hostname])
|
||||
end
|
||||
|
||||
def instance_config(), do: Application.get_env(:mobilizon, :instance)
|
||||
|
||||
defp to_bool(v), do: v == true or v == "true" or v == "True"
|
||||
|
||||
|
||||
38
lib/mobilizon/email/admin.ex
Normal file
38
lib/mobilizon/email/admin.ex
Normal file
@@ -0,0 +1,38 @@
|
||||
defmodule Mobilizon.Email.Admin do
|
||||
@moduledoc """
|
||||
Handles emails sent to admins
|
||||
"""
|
||||
alias Mobilizon.Users.User
|
||||
|
||||
import Bamboo.Email
|
||||
import Bamboo.Phoenix
|
||||
use Bamboo.Phoenix, view: Mobilizon.EmailView
|
||||
import MobilizonWeb.Gettext
|
||||
alias Mobilizon.Reports.Report
|
||||
|
||||
def report(%User{email: email} = _user, %Report{} = report, locale \\ "en") do
|
||||
Gettext.put_locale(locale)
|
||||
instance_url = get_config(:hostname)
|
||||
|
||||
base_email()
|
||||
|> to(email)
|
||||
|> subject(gettext("Mobilizon: New report on instance %{instance}", instance: instance_url))
|
||||
|> put_header("Reply-To", get_config(:email_reply_to))
|
||||
|> assign(:report, report)
|
||||
|> assign(:instance, instance_url)
|
||||
|> render(:report)
|
||||
end
|
||||
|
||||
defp base_email do
|
||||
# Here you can set a default from, default headers, etc.
|
||||
new_email()
|
||||
|> from(get_config(:email_from))
|
||||
|> put_html_layout({Mobilizon.EmailView, "email.html"})
|
||||
|> put_text_layout({Mobilizon.EmailView, "email.text"})
|
||||
end
|
||||
|
||||
@spec get_config(atom()) :: any()
|
||||
defp get_config(key) do
|
||||
Mobilizon.CommonConfig.instance_config() |> Keyword.get(key)
|
||||
end
|
||||
end
|
||||
@@ -18,7 +18,7 @@ defmodule Mobilizon.Email.User do
|
||||
|> subject(
|
||||
gettext("Mobilizon: Confirmation instructions for %{instance}", instance: instance_url)
|
||||
)
|
||||
|> put_header("Reply-To", get_config(:reply_to))
|
||||
|> put_header("Reply-To", get_config(:email_reply_to))
|
||||
|> assign(:token, user.confirmation_token)
|
||||
|> assign(:instance, instance_url)
|
||||
|> render(:registration_confirmation)
|
||||
@@ -26,7 +26,7 @@ defmodule Mobilizon.Email.User do
|
||||
|
||||
def reset_password_email(%User{} = user, locale \\ "en") do
|
||||
Gettext.put_locale(locale)
|
||||
instance_url = get_config(:instance)
|
||||
instance_url = get_config(:hostname)
|
||||
|
||||
base_email()
|
||||
|> to(user.email)
|
||||
@@ -36,7 +36,7 @@ defmodule Mobilizon.Email.User do
|
||||
instance: instance_url
|
||||
)
|
||||
)
|
||||
|> put_header("Reply-To", get_config(:reply_to))
|
||||
|> put_header("Reply-To", get_config(:email_reply_to))
|
||||
|> assign(:token, user.reset_password_token)
|
||||
|> assign(:instance, instance_url)
|
||||
|> render(:password_reset)
|
||||
@@ -45,13 +45,13 @@ defmodule Mobilizon.Email.User do
|
||||
defp base_email do
|
||||
# Here you can set a default from, default headers, etc.
|
||||
new_email()
|
||||
|> from(Application.get_env(:mobilizon, MobilizonWeb.Endpoint)[:email_from])
|
||||
|> from(get_config(:email_from))
|
||||
|> put_html_layout({Mobilizon.EmailView, "email.html"})
|
||||
|> put_text_layout({Mobilizon.EmailView, "email.text"})
|
||||
end
|
||||
|
||||
@spec get_config(atom()) :: any()
|
||||
defp get_config(key) do
|
||||
_config = Application.get_env(:mobilizon, MobilizonWeb.Endpoint) |> Keyword.get(key)
|
||||
Mobilizon.CommonConfig.instance_config() |> Keyword.get(key)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1160,6 +1160,19 @@ defmodule Mobilizon.Events do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Get all comments by an actor and a list of ids
|
||||
"""
|
||||
def get_all_comments_by_actor_and_ids(actor_id, comment_ids \\ [])
|
||||
def get_all_comments_by_actor_and_ids(_actor_id, []), do: []
|
||||
|
||||
def get_all_comments_by_actor_and_ids(actor_id, comment_ids) do
|
||||
Comment
|
||||
|> where([c], c.id in ^comment_ids)
|
||||
|> where([c], c.actor_id == ^actor_id)
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a comment.
|
||||
|
||||
|
||||
236
lib/mobilizon/reports.ex
Normal file
236
lib/mobilizon/reports.ex
Normal file
@@ -0,0 +1,236 @@
|
||||
defmodule Mobilizon.Reports do
|
||||
@moduledoc """
|
||||
The Reports context.
|
||||
"""
|
||||
|
||||
import Ecto.Query, warn: false
|
||||
alias Mobilizon.Repo
|
||||
import Mobilizon.Ecto
|
||||
|
||||
alias Mobilizon.Reports.Report
|
||||
alias Mobilizon.Reports.Note
|
||||
|
||||
@doc false
|
||||
def data() do
|
||||
Dataloader.Ecto.new(Mobilizon.Repo, query: &query/2)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def query(queryable, _params) do
|
||||
queryable
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the list of reports.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> list_reports()
|
||||
[%Report{}, ...]
|
||||
|
||||
"""
|
||||
@spec list_reports(integer(), integer(), atom(), atom()) :: list(Report.t())
|
||||
def list_reports(page \\ nil, limit \\ nil, sort \\ :updated_at, direction \\ :asc) do
|
||||
from(
|
||||
r in Report,
|
||||
preload: [:reported, :reporter, :manager, :event, :comments, :notes]
|
||||
)
|
||||
|> paginate(page, limit)
|
||||
|> sort(sort, direction)
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a single report.
|
||||
|
||||
Raises `Ecto.NoResultsError` if the Report does not exist.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> get_report!(123)
|
||||
%Report{}
|
||||
|
||||
iex> get_report!(456)
|
||||
** (Ecto.NoResultsError)
|
||||
|
||||
"""
|
||||
def get_report!(id) do
|
||||
with %Report{} = report <- Repo.get!(Report, id) do
|
||||
Repo.preload(report, [:reported, :reporter, :manager, :event, :comments, :notes])
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a single report.
|
||||
|
||||
Returns `nil` if the Report does not exist.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> get_report(123)
|
||||
%Report{}
|
||||
|
||||
iex> get_report(456)
|
||||
nil
|
||||
|
||||
"""
|
||||
def get_report(id) do
|
||||
with %Report{} = report <- Repo.get(Report, id) do
|
||||
Repo.preload(report, [:reported, :reporter, :manager, :event, :comments, :notes])
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Get a report by it's URL
|
||||
"""
|
||||
@spec get_report_by_url(String.t()) :: Report.t() | nil
|
||||
def get_report_by_url(url) do
|
||||
from(
|
||||
r in Report,
|
||||
where: r.uri == ^url
|
||||
)
|
||||
|> Repo.one()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a report.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> create_report(%{field: value})
|
||||
{:ok, %Report{}}
|
||||
|
||||
iex> create_report(%{field: bad_value})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def create_report(attrs \\ %{}) do
|
||||
with {:ok, %Report{} = report} <-
|
||||
%Report{}
|
||||
|> Report.creation_changeset(attrs)
|
||||
|> Repo.insert() do
|
||||
{:ok, Repo.preload(report, [:event, :reported, :reporter, :comments])}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates a report.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> update_report(report, %{field: new_value})
|
||||
{:ok, %Report{}}
|
||||
|
||||
iex> update_report(report, %{field: bad_value})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def update_report(%Report{} = report, attrs) do
|
||||
report
|
||||
|> Report.changeset(attrs)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes a Report.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> delete_report(report)
|
||||
{:ok, %Report{}}
|
||||
|
||||
iex> delete_report(report)
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def delete_report(%Report{} = report) do
|
||||
Repo.delete(report)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an `%Ecto.Changeset{}` for tracking report changes.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> change_report(report)
|
||||
%Ecto.Changeset{source: %Report{}}
|
||||
|
||||
"""
|
||||
def change_report(%Report{} = report) do
|
||||
Report.changeset(report, %{})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the list of notes for a report.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> list_notes_for_report(%Report{id: 1})
|
||||
[%Note{}, ...]
|
||||
|
||||
"""
|
||||
@spec list_notes_for_report(Report.t()) :: list(Report.t())
|
||||
def list_notes_for_report(%Report{id: report_id}) do
|
||||
from(
|
||||
n in Note,
|
||||
where: n.report_id == ^report_id,
|
||||
preload: [:report, :moderator]
|
||||
)
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a single note.
|
||||
|
||||
Raises `Ecto.NoResultsError` if the Note does not exist.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> get_note!(123)
|
||||
%Note{}
|
||||
|
||||
iex> get_note!(456)
|
||||
** (Ecto.NoResultsError)
|
||||
|
||||
"""
|
||||
def get_note!(id), do: Repo.get!(Note, id)
|
||||
|
||||
def get_note(id), do: Repo.get(Note, id)
|
||||
|
||||
@doc """
|
||||
Creates a note report.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> create_report_note(%{field: value})
|
||||
{:ok, %Note{}}
|
||||
|
||||
iex> create_report_note(%{field: bad_value})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def create_report_note(attrs \\ %{}) do
|
||||
with {:ok, %Note{} = note} <-
|
||||
%Note{}
|
||||
|> Note.changeset(attrs)
|
||||
|> Repo.insert() do
|
||||
{:ok, Repo.preload(note, [:report, :moderator])}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes a note report.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> delete_report_note(note)
|
||||
{:ok, %Note{}}
|
||||
|
||||
iex> delete_report_note(note)
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def delete_report_note(%Note{} = note) do
|
||||
Repo.delete(note)
|
||||
end
|
||||
end
|
||||
27
lib/mobilizon/reports/note.ex
Normal file
27
lib/mobilizon/reports/note.ex
Normal file
@@ -0,0 +1,27 @@
|
||||
defmodule Mobilizon.Reports.Note do
|
||||
@moduledoc """
|
||||
Report Note entity
|
||||
"""
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Reports.Report
|
||||
|
||||
@attrs [:content, :moderator_id, :report_id]
|
||||
|
||||
@derive {Jason.Encoder, only: [:content]}
|
||||
schema "report_notes" do
|
||||
field(:content, :string)
|
||||
belongs_to(:moderator, Actor)
|
||||
belongs_to(:report, Report)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
@doc false
|
||||
def changeset(note, attrs) do
|
||||
note
|
||||
|> cast(attrs, @attrs)
|
||||
|> validate_required(@attrs)
|
||||
end
|
||||
end
|
||||
59
lib/mobilizon/reports/report.ex
Normal file
59
lib/mobilizon/reports/report.ex
Normal file
@@ -0,0 +1,59 @@
|
||||
import EctoEnum
|
||||
|
||||
defenum(Mobilizon.Reports.ReportStateEnum, :report_state, [
|
||||
:open,
|
||||
:closed,
|
||||
:resolved
|
||||
])
|
||||
|
||||
defmodule Mobilizon.Reports.Report do
|
||||
@moduledoc """
|
||||
Report entity
|
||||
"""
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
alias Mobilizon.Events.Comment
|
||||
alias Mobilizon.Events.Event
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Reports.Note
|
||||
|
||||
@derive {Jason.Encoder, only: [:status, :uri]}
|
||||
schema "reports" do
|
||||
field(:content, :string)
|
||||
field(:status, Mobilizon.Reports.ReportStateEnum, default: :open)
|
||||
field(:uri, :string)
|
||||
|
||||
# The reported actor
|
||||
belongs_to(:reported, Actor)
|
||||
|
||||
# The actor who reported
|
||||
belongs_to(:reporter, Actor)
|
||||
|
||||
# The actor who last acted on this report
|
||||
belongs_to(:manager, Actor)
|
||||
|
||||
# The eventual Event inside the report
|
||||
belongs_to(:event, Event)
|
||||
|
||||
# The eventual Comments inside the report
|
||||
many_to_many(:comments, Comment, join_through: "reports_comments", on_replace: :delete)
|
||||
|
||||
# The notes associated to the report
|
||||
has_many(:notes, Note, foreign_key: :report_id)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
@doc false
|
||||
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])
|
||||
end
|
||||
|
||||
def creation_changeset(report, attrs) do
|
||||
report
|
||||
|> changeset(attrs)
|
||||
|> put_assoc(:comments, attrs["comments"])
|
||||
end
|
||||
end
|
||||
@@ -258,6 +258,36 @@ defmodule Mobilizon.Users do
|
||||
)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the list of administrators.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> list_admins()
|
||||
[%Mobilizon.Users.User{role: :administrator}]
|
||||
|
||||
"""
|
||||
def list_admins() do
|
||||
User
|
||||
|> where([u], u.role == ^:administrator)
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the list of moderators.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> list_moderators()
|
||||
[%Mobilizon.Users.User{role: :moderator}, %Mobilizon.Users.User{role: :administrator}]
|
||||
|
||||
"""
|
||||
def list_moderators() do
|
||||
User
|
||||
|> where([u], u.role in ^[:administrator, :moderator])
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
def count_users() do
|
||||
Repo.one(
|
||||
from(
|
||||
|
||||
@@ -26,7 +26,7 @@ defmodule MobilizonWeb.API.Events do
|
||||
Actors.get_local_actor_with_everything(organizer_actor_id),
|
||||
title <- String.trim(title),
|
||||
mentions <- Formatter.parse_mentions(description),
|
||||
visibility <- Map.get(args, :visibility, "public"),
|
||||
visibility <- Map.get(args, :visibility, :public),
|
||||
{to, cc} <- to_for_actor_and_mentions(actor, mentions, nil, Atom.to_string(visibility)),
|
||||
tags <- Formatter.parse_tags(description),
|
||||
picture <- Map.get(args, :picture, nil),
|
||||
|
||||
128
lib/mobilizon_web/api/reports.ex
Normal file
128
lib/mobilizon_web/api/reports.ex
Normal file
@@ -0,0 +1,128 @@
|
||||
defmodule MobilizonWeb.API.Reports do
|
||||
@moduledoc """
|
||||
API for Reports
|
||||
"""
|
||||
|
||||
alias Mobilizon.Actors
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Events
|
||||
alias Mobilizon.Activity
|
||||
alias Mobilizon.Reports, as: ReportsAction
|
||||
alias Mobilizon.Reports.{Report, Note}
|
||||
alias Mobilizon.Service.ActivityPub
|
||||
alias Mobilizon.Users
|
||||
alias Mobilizon.Users.User
|
||||
import MobilizonWeb.API.Utils
|
||||
import Mobilizon.Service.Admin.ActionLogService
|
||||
|
||||
@doc """
|
||||
Create a report/flag on an actor, and optionally on an event or on comments.
|
||||
"""
|
||||
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
|
||||
} = 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}),
|
||||
{:get_report_comments, comments_urls} <-
|
||||
get_report_comments(reported_actor, comments_ids),
|
||||
{:make_activity, {:ok, %Activity{} = activity, %Report{} = report}} <-
|
||||
{:make_activity,
|
||||
ActivityPub.flag(%{
|
||||
reporter_url: reporter_url,
|
||||
reported_actor_url: reported_actor_url,
|
||||
event_url: (!is_nil(event) && event.url) || nil,
|
||||
comments_url: comments_urls,
|
||||
content: content,
|
||||
forward: args[:forward] || false,
|
||||
local: args[:local] || args[:forward] || false
|
||||
})} do
|
||||
{:ok, activity, report}
|
||||
else
|
||||
{:error, err} -> {:error, err}
|
||||
{:actor_id, %{}} -> {:error, "Valid `actor_id` required"}
|
||||
{:reporter, nil} -> {:error, "Reporter Actor not found"}
|
||||
{:reported, nil} -> {:error, "Reported Actor not found"}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Update the state of a report
|
||||
"""
|
||||
def update_report_status(%Actor{} = actor, %Report{} = report, state) do
|
||||
with {:valid_state, true} <-
|
||||
{:valid_state, Mobilizon.Reports.ReportStateEnum.valid_value?(state)},
|
||||
{:ok, report} <- ReportsAction.update_report(report, %{"status" => state}),
|
||||
{:ok, _} <- log_action(actor, "update", report) do
|
||||
{:ok, report}
|
||||
else
|
||||
{:valid_state, false} -> {:error, "Unsupported state"}
|
||||
end
|
||||
end
|
||||
|
||||
defp get_report_comments(%Actor{id: actor_id}, comment_ids) do
|
||||
{:get_report_comments,
|
||||
Events.get_all_comments_by_actor_and_ids(actor_id, comment_ids) |> Enum.map(& &1.url)}
|
||||
end
|
||||
|
||||
defp get_report_comments(_, _), do: {:get_report_comments, nil}
|
||||
|
||||
@doc """
|
||||
Create a note on a report
|
||||
"""
|
||||
@spec create_report_note(Report.t(), Actor.t(), String.t()) :: {:ok, Note.t()}
|
||||
def create_report_note(
|
||||
%Report{id: report_id},
|
||||
%Actor{id: moderator_id, user_id: user_id} = moderator,
|
||||
content
|
||||
) do
|
||||
with %User{role: role} <- Users.get_user!(user_id),
|
||||
{:role, true} <- {:role, role in [:administrator, :moderator]},
|
||||
{:ok, %Note{} = note} <-
|
||||
Mobilizon.Reports.create_report_note(%{
|
||||
"report_id" => report_id,
|
||||
"moderator_id" => moderator_id,
|
||||
"content" => content
|
||||
}),
|
||||
{:ok, _} <- log_action(moderator, "create", note) do
|
||||
{:ok, note}
|
||||
else
|
||||
{:role, false} ->
|
||||
{:error, "You need to be a moderator or an administrator to create a note on a report"}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Delete a report note
|
||||
"""
|
||||
@spec delete_report_note(Note.t(), Actor.t()) :: {:ok, Note.t()}
|
||||
def delete_report_note(
|
||||
%Note{moderator_id: note_moderator_id} = note,
|
||||
%Actor{id: moderator_id, user_id: user_id} = moderator
|
||||
) do
|
||||
with {:same_actor, true} <- {:same_actor, note_moderator_id == moderator_id},
|
||||
%User{role: role} <- Users.get_user!(user_id),
|
||||
{:role, true} <- {:role, role in [:administrator, :moderator]},
|
||||
{:ok, %Note{} = note} <-
|
||||
Mobilizon.Reports.delete_report_note(note),
|
||||
{:ok, _} <- log_action(moderator, "delete", note) do
|
||||
{:ok, note}
|
||||
else
|
||||
{:role, false} ->
|
||||
{:error, "You need to be a moderator or an administrator to create a note on a report"}
|
||||
|
||||
{:same_actor, false} ->
|
||||
{:error, "You can only remove your own notes"}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -120,4 +120,16 @@ defmodule MobilizonWeb.API.Utils do
|
||||
# |> Formatter.add_hashtag_links(tags)
|
||||
# |> Formatter.finalize()
|
||||
# end
|
||||
|
||||
def make_report_content_html(nil), do: {:ok, {nil, [], []}}
|
||||
|
||||
def make_report_content_html(comment) do
|
||||
max_size = Mobilizon.CommonConfig.get([:instance, :max_report_comment_size], 1000)
|
||||
|
||||
if String.length(comment) <= max_size do
|
||||
{:ok, Formatter.html_escape(comment, "text/plain")}
|
||||
else
|
||||
{:error, "Comment must be up to #{max_size} characters"}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
72
lib/mobilizon_web/resolvers/admin.ex
Normal file
72
lib/mobilizon_web/resolvers/admin.ex
Normal file
@@ -0,0 +1,72 @@
|
||||
defmodule MobilizonWeb.Resolvers.Admin do
|
||||
@moduledoc """
|
||||
Handles the report-related GraphQL calls
|
||||
"""
|
||||
alias Mobilizon.Users.User
|
||||
import Mobilizon.Users.Guards
|
||||
alias Mobilizon.Admin.ActionLog
|
||||
alias Mobilizon.Reports.{Report, Note}
|
||||
|
||||
def list_action_logs(_parent, %{page: page, limit: limit}, %{
|
||||
context: %{current_user: %User{role: role}}
|
||||
})
|
||||
when is_moderator(role) do
|
||||
with action_logs <- Mobilizon.Admin.list_action_logs(page, limit) do
|
||||
action_logs =
|
||||
Enum.map(action_logs, fn %ActionLog{
|
||||
target_type: target_type,
|
||||
action: action,
|
||||
actor: actor,
|
||||
id: id
|
||||
} = action_log ->
|
||||
transform_action_log(target_type, action, action_log)
|
||||
|> Map.merge(%{
|
||||
actor: actor,
|
||||
id: id
|
||||
})
|
||||
end)
|
||||
|
||||
{:ok, action_logs}
|
||||
end
|
||||
end
|
||||
|
||||
def list_action_logs(_parent, _args, _resolution) do
|
||||
{:error, "You need to be logged-in and a moderator to list action logs"}
|
||||
end
|
||||
|
||||
defp transform_action_log(
|
||||
"Elixir.Mobilizon.Reports.Report",
|
||||
"update",
|
||||
%ActionLog{} = action_log
|
||||
) do
|
||||
with %Report{status: status} = report <- Mobilizon.Reports.get_report(action_log.target_id) do
|
||||
%{
|
||||
action: "report_update_" <> to_string(status),
|
||||
object: report
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
defp transform_action_log("Elixir.Mobilizon.Reports.Note", "create", %ActionLog{
|
||||
changes: changes
|
||||
}) do
|
||||
%{
|
||||
action: "note_creation",
|
||||
object: convert_changes_to_struct(Note, changes)
|
||||
}
|
||||
end
|
||||
|
||||
defp transform_action_log("Elixir.Mobilizon.Reports.Note", "delete", %ActionLog{
|
||||
changes: changes
|
||||
}) do
|
||||
%{
|
||||
action: "note_deletion",
|
||||
object: convert_changes_to_struct(Note, changes)
|
||||
}
|
||||
end
|
||||
|
||||
# Changes are stored as %{"key" => "value"} so we need to convert them back as struct
|
||||
defp convert_changes_to_struct(struct, changes) do
|
||||
struct(struct, for({key, val} <- changes, into: %{}, do: {String.to_atom(key), val}))
|
||||
end
|
||||
end
|
||||
118
lib/mobilizon_web/resolvers/report.ex
Normal file
118
lib/mobilizon_web/resolvers/report.ex
Normal file
@@ -0,0 +1,118 @@
|
||||
defmodule MobilizonWeb.Resolvers.Report do
|
||||
@moduledoc """
|
||||
Handles the report-related GraphQL calls
|
||||
"""
|
||||
alias Mobilizon.Reports
|
||||
alias Mobilizon.Reports.{Report, Note}
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Actors
|
||||
alias Mobilizon.Users.User
|
||||
alias MobilizonWeb.API.Reports, as: ReportsAPI
|
||||
import Mobilizon.Users.Guards
|
||||
|
||||
def list_reports(_parent, %{page: page, limit: limit}, %{
|
||||
context: %{current_user: %User{role: role}}
|
||||
})
|
||||
when is_moderator(role) do
|
||||
{:ok, Mobilizon.Reports.list_reports(page, limit)}
|
||||
end
|
||||
|
||||
def list_reports(_parent, _args, _resolution) do
|
||||
{:error, "You need to be logged-in and a moderator to list reports"}
|
||||
end
|
||||
|
||||
def get_report(_parent, %{id: id}, %{
|
||||
context: %{current_user: %User{role: role}}
|
||||
})
|
||||
when is_moderator(role) do
|
||||
{:ok, Mobilizon.Reports.get_report(id)}
|
||||
end
|
||||
|
||||
def get_report(_parent, _args, _resolution) do
|
||||
{:error, "You need to be logged-in and a moderator to view a report"}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Create a report
|
||||
"""
|
||||
def create_report(
|
||||
_parent,
|
||||
%{reporter_actor_id: reporter_actor_id} = args,
|
||||
%{context: %{current_user: user}} = _resolution
|
||||
) do
|
||||
with {:is_owned, true, _} <- User.owns_actor(user, reporter_actor_id),
|
||||
{:ok, _, %Report{} = report} <- ReportsAPI.report(args) do
|
||||
{:ok, report}
|
||||
else
|
||||
{:is_owned, false} ->
|
||||
{:error, "Reporter actor id is not owned by authenticated user"}
|
||||
|
||||
_err ->
|
||||
{:error, "Error while saving report"}
|
||||
end
|
||||
end
|
||||
|
||||
def create_report(_parent, _args, _resolution) do
|
||||
{:error, "You need to be logged-in to create reports"}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Update a report's status
|
||||
"""
|
||||
def update_report(
|
||||
_parent,
|
||||
%{report_id: report_id, moderator_id: moderator_id, status: status},
|
||||
%{
|
||||
context: %{current_user: %User{role: role} = user}
|
||||
}
|
||||
)
|
||||
when is_moderator(role) do
|
||||
with {:is_owned, true, _} <- User.owns_actor(user, moderator_id),
|
||||
%Actor{} = actor <- Actors.get_actor!(moderator_id),
|
||||
%Report{} = report <- Mobilizon.Reports.get_report(report_id),
|
||||
{:ok, %Report{} = report} <-
|
||||
MobilizonWeb.API.Reports.update_report_status(actor, report, status) do
|
||||
{:ok, report}
|
||||
else
|
||||
{:is_owned, false} ->
|
||||
{:error, "Actor id is not owned by authenticated user"}
|
||||
|
||||
_err ->
|
||||
{:error, "Error while updating report"}
|
||||
end
|
||||
end
|
||||
|
||||
def update_report(_parent, _args, _resolution) do
|
||||
{:error, "You need to be logged-in and a moderator to update a report"}
|
||||
end
|
||||
|
||||
def create_report_note(
|
||||
_parent,
|
||||
%{report_id: report_id, moderator_id: moderator_id, content: content},
|
||||
%{
|
||||
context: %{current_user: %User{role: role} = user}
|
||||
}
|
||||
)
|
||||
when is_moderator(role) do
|
||||
with {:is_owned, true, _} <- User.owns_actor(user, moderator_id),
|
||||
%Report{} = report <- Reports.get_report(report_id),
|
||||
%Actor{} = moderator <- Actors.get_local_actor_with_everything(moderator_id),
|
||||
{:ok, %Note{} = note} <-
|
||||
MobilizonWeb.API.Reports.create_report_note(report, moderator, content) do
|
||||
{:ok, note}
|
||||
end
|
||||
end
|
||||
|
||||
def delete_report_note(_parent, %{note_id: note_id, moderator_id: moderator_id}, %{
|
||||
context: %{current_user: %User{role: role} = user}
|
||||
})
|
||||
when is_moderator(role) do
|
||||
with {:is_owned, true, _} <- User.owns_actor(user, moderator_id),
|
||||
%Note{} = note <- Reports.get_note(note_id),
|
||||
%Actor{} = moderator <- Actors.get_local_actor_with_everything(moderator_id),
|
||||
{:ok, %Note{} = note} <-
|
||||
MobilizonWeb.API.Reports.delete_report_note(note, moderator) do
|
||||
{:ok, %{id: note.id}}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -21,6 +21,8 @@ defmodule MobilizonWeb.Schema do
|
||||
import_types(MobilizonWeb.Schema.CommentType)
|
||||
import_types(MobilizonWeb.Schema.SearchType)
|
||||
import_types(MobilizonWeb.Schema.ConfigType)
|
||||
import_types(MobilizonWeb.Schema.ReportType)
|
||||
import_types(MobilizonWeb.Schema.AdminType)
|
||||
|
||||
@desc "A struct containing the id of the deleted object"
|
||||
object :deleted_object do
|
||||
@@ -109,6 +111,8 @@ defmodule MobilizonWeb.Schema do
|
||||
import_fields(:address_queries)
|
||||
import_fields(:config_queries)
|
||||
import_fields(:picture_queries)
|
||||
import_fields(:report_queries)
|
||||
import_fields(:admin_queries)
|
||||
end
|
||||
|
||||
@desc """
|
||||
@@ -124,5 +128,6 @@ defmodule MobilizonWeb.Schema do
|
||||
import_fields(:member_mutations)
|
||||
import_fields(:feed_token_mutations)
|
||||
import_fields(:picture_mutations)
|
||||
import_fields(:report_mutations)
|
||||
end
|
||||
end
|
||||
|
||||
41
lib/mobilizon_web/schema/admin.ex
Normal file
41
lib/mobilizon_web/schema/admin.ex
Normal file
@@ -0,0 +1,41 @@
|
||||
defmodule MobilizonWeb.Schema.AdminType do
|
||||
@moduledoc """
|
||||
Schema representation for ActionLog
|
||||
"""
|
||||
use Absinthe.Schema.Notation
|
||||
alias MobilizonWeb.Resolvers.Admin
|
||||
alias Mobilizon.Reports.{Report, Note}
|
||||
|
||||
@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")
|
||||
end
|
||||
|
||||
@desc "The objects that can be in an action log"
|
||||
interface :action_log_object do
|
||||
field(:id, :id, description: "Internal ID for this object")
|
||||
|
||||
resolve_type(fn
|
||||
%Report{}, _ ->
|
||||
:report
|
||||
|
||||
%Note{}, _ ->
|
||||
:report_note
|
||||
|
||||
_, _ ->
|
||||
nil
|
||||
end)
|
||||
end
|
||||
|
||||
object :admin_queries do
|
||||
@desc "Get the list of action logs"
|
||||
field :action_logs, type: list_of(:action_log) do
|
||||
arg(:page, :integer, default_value: 1)
|
||||
arg(:limit, :integer, default_value: 10)
|
||||
resolve(&Admin.list_action_logs/3)
|
||||
end
|
||||
end
|
||||
end
|
||||
86
lib/mobilizon_web/schema/report.ex
Normal file
86
lib/mobilizon_web/schema/report.ex
Normal file
@@ -0,0 +1,86 @@
|
||||
defmodule MobilizonWeb.Schema.ReportType do
|
||||
@moduledoc """
|
||||
Schema representation for User
|
||||
"""
|
||||
use Absinthe.Schema.Notation
|
||||
|
||||
alias MobilizonWeb.Resolvers.Report
|
||||
|
||||
@desc "A report object"
|
||||
object :report do
|
||||
interfaces([:action_log_object])
|
||||
field(:id, :id, description: "The internal ID of the report")
|
||||
field(:content, :string, description: "The comment the reporter added about this report")
|
||||
field(:status, :report_status, description: "Whether the report is still active")
|
||||
field(:uri, :string, description: "The URI of the report")
|
||||
field(:reported, :actor, description: "The actor that is being reported")
|
||||
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")
|
||||
end
|
||||
|
||||
@desc "A report note object"
|
||||
object :report_note 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(:report, :report, description: "The report on which this note is added")
|
||||
end
|
||||
|
||||
@desc "The list of possible statuses for a report object"
|
||||
enum :report_status do
|
||||
value(:open, description: "The report has been opened")
|
||||
value(:closed, description: "The report has been closed")
|
||||
value(:resolved, description: "The report has been marked as resolved")
|
||||
end
|
||||
|
||||
object :report_queries do
|
||||
@desc "Get all reports"
|
||||
field :reports, list_of(:report) do
|
||||
arg(:page, :integer, default_value: 1)
|
||||
arg(:limit, :integer, default_value: 10)
|
||||
resolve(&Report.list_reports/3)
|
||||
end
|
||||
|
||||
@desc "Get a report by id"
|
||||
field :report, :report do
|
||||
arg(:id, non_null(:id))
|
||||
resolve(&Report.get_report/3)
|
||||
end
|
||||
end
|
||||
|
||||
object :report_mutations do
|
||||
@desc "Create a report"
|
||||
field :create_report, type: :report do
|
||||
arg(:report_content, :string)
|
||||
arg(:reporter_actor_id, non_null(:id))
|
||||
arg(:reported_actor_id, non_null(:id))
|
||||
arg(:event_id, :id, default_value: nil)
|
||||
arg(:comments_ids, list_of(:id), default_value: [])
|
||||
resolve(&Report.create_report/3)
|
||||
end
|
||||
|
||||
@desc "Update a report"
|
||||
field :update_report_status, type: :report do
|
||||
arg(:report_id, non_null(:id))
|
||||
arg(:moderator_id, non_null(:id))
|
||||
arg(:status, non_null(:report_status))
|
||||
resolve(&Report.update_report/3)
|
||||
end
|
||||
|
||||
@desc "Create a note on a report"
|
||||
field :create_report_note, type: :report_note do
|
||||
arg(:content, :string)
|
||||
arg(:moderator_id, non_null(:id))
|
||||
arg(:report_id, non_null(:id))
|
||||
resolve(&Report.create_report_note/3)
|
||||
end
|
||||
|
||||
field :delete_report_note, type: :deleted_object do
|
||||
arg(:note_id, non_null(:id))
|
||||
arg(:moderator_id, non_null(:id))
|
||||
resolve(&Report.delete_report_note/3)
|
||||
end
|
||||
end
|
||||
end
|
||||
15
lib/mobilizon_web/templates/email/report.html.eex
Normal file
15
lib/mobilizon_web/templates/email/report.html.eex
Normal file
@@ -0,0 +1,15 @@
|
||||
<h1><%= gettext "New report from %{reporter} on %{instance}", reporter: @report.reporter, instance: @instance %></h1>
|
||||
|
||||
<% if @report.event do %>
|
||||
<p><%= gettext "Event: %{event}", event: @report.event %></p>
|
||||
<% end %>
|
||||
|
||||
<%= for comment <- @report.comments do %>
|
||||
<p><%= gettext "Comment: %{comment}", comment: comment %></p>
|
||||
<% end %>
|
||||
|
||||
<% if @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>
|
||||
19
lib/mobilizon_web/templates/email/report.text.eex
Normal file
19
lib/mobilizon_web/templates/email/report.text.eex
Normal file
@@ -0,0 +1,19 @@
|
||||
<%= gettext "New report from %{reporter} on %{instance}", reporter: @report.reporter, instance: @instance %>
|
||||
|
||||
--
|
||||
|
||||
<% if @report.event do %>
|
||||
<%= gettext "Event: %{event}", event: @report.event %>
|
||||
<% end %>
|
||||
|
||||
<%= for comment <- @report.comments do %>
|
||||
<%= gettext "Comment: %{comment}", comment: comment %>
|
||||
<% end %>
|
||||
|
||||
<% if @content do %>
|
||||
<%= gettext "Reason: %{content}", event: @report.content %>
|
||||
<% end %>
|
||||
|
||||
<%= link "View the report", to: MobilizonWeb.Endpoint.url() <> "/reports/#{@report.id}", target: "_blank" %>
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||
@spec insert(map(), boolean()) :: {:ok, %Activity{}} | {:error, any()}
|
||||
def insert(map, local \\ true) when is_map(map) do
|
||||
with map <- lazy_put_activity_defaults(map),
|
||||
:ok <- insert_full_object(map) do
|
||||
{:ok, object} <- insert_full_object(map) do
|
||||
object_id = if is_map(map["object"]), do: map["object"]["id"], else: map["id"]
|
||||
|
||||
map = if local, do: Map.put(map, "id", "#{object_id}/activity"), else: map
|
||||
@@ -55,7 +55,7 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||
|
||||
# Notification.create_notifications(activity)
|
||||
# stream_out(activity)
|
||||
{:ok, activity}
|
||||
{:ok, activity, object}
|
||||
else
|
||||
%Activity{} = activity -> {:ok, activity}
|
||||
error -> {:error, error}
|
||||
@@ -130,7 +130,7 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||
additional
|
||||
),
|
||||
:ok <- Logger.debug(inspect(create_data)),
|
||||
{:ok, activity} <- insert(create_data, local),
|
||||
{:ok, activity, _object} <- insert(create_data, local),
|
||||
:ok <- maybe_federate(activity) do
|
||||
# {:ok, actor} <- Actors.increase_event_count(actor) do
|
||||
{:ok, activity}
|
||||
@@ -147,7 +147,7 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||
local = !(params[:local] == false)
|
||||
|
||||
with data <- %{"to" => to, "type" => "Accept", "actor" => actor, "object" => object},
|
||||
{:ok, activity} <- insert(data, local),
|
||||
{:ok, activity, _object} <- insert(data, local),
|
||||
:ok <- maybe_federate(activity) do
|
||||
{:ok, activity}
|
||||
end
|
||||
@@ -164,7 +164,7 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||
"actor" => actor,
|
||||
"object" => object
|
||||
},
|
||||
{:ok, activity} <- insert(data, local),
|
||||
{:ok, activity, _object} <- insert(data, local),
|
||||
:ok <- maybe_federate(activity) do
|
||||
{:ok, activity}
|
||||
end
|
||||
@@ -179,7 +179,7 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||
# ) do
|
||||
# with nil <- get_existing_like(url, object),
|
||||
# like_data <- make_like_data(user, object, activity_id),
|
||||
# {:ok, activity} <- insert(like_data, local),
|
||||
# {:ok, activity, _object} <- insert(like_data, local),
|
||||
# {:ok, object} <- add_like_to_object(activity, object),
|
||||
# :ok <- maybe_federate(activity) do
|
||||
# {:ok, activity, object}
|
||||
@@ -197,7 +197,7 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||
# ) do
|
||||
# with %Activity{} = like_activity <- get_existing_like(actor.ap_id, object),
|
||||
# unlike_data <- make_unlike_data(actor, like_activity, activity_id),
|
||||
# {:ok, unlike_activity} <- insert(unlike_data, local),
|
||||
# {:ok, unlike_activity, _object} <- insert(unlike_data, local),
|
||||
# {:ok, _activity} <- Repo.delete(like_activity),
|
||||
# {:ok, object} <- remove_like_from_object(like_activity, object),
|
||||
# :ok <- maybe_federate(unlike_activity) do
|
||||
@@ -215,7 +215,7 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||
# ) do
|
||||
# #with true <- is_public?(object),
|
||||
# with announce_data <- make_announce_data(actor, object, activity_id),
|
||||
# {:ok, activity} <- insert(announce_data, local),
|
||||
# {:ok, activity, _object} <- insert(announce_data, local),
|
||||
# # {:ok, object} <- add_announce_to_object(activity, object),
|
||||
# :ok <- maybe_federate(activity) do
|
||||
# {:ok, activity, object}
|
||||
@@ -232,7 +232,7 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||
# ) do
|
||||
# with %Activity{} = announce_activity <- get_existing_announce(actor.ap_id, object),
|
||||
# unannounce_data <- make_unannounce_data(actor, announce_activity, activity_id),
|
||||
# {:ok, unannounce_activity} <- insert(unannounce_data, local),
|
||||
# {:ok, unannounce_activity, _object} <- insert(unannounce_data, local),
|
||||
# :ok <- maybe_federate(unannounce_activity),
|
||||
# {:ok, _activity} <- Repo.delete(announce_activity),
|
||||
# {:ok, object} <- remove_announce_from_object(announce_activity, object) do
|
||||
@@ -250,7 +250,7 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||
activity_follow_id <-
|
||||
activity_id || "#{MobilizonWeb.Endpoint.url()}/follow/#{follow_id}/activity",
|
||||
data <- make_follow_data(followed, follower, activity_follow_id),
|
||||
{:ok, activity} <- insert(data, local),
|
||||
{:ok, activity, _object} <- insert(data, local),
|
||||
:ok <- maybe_federate(activity) do
|
||||
{:ok, activity}
|
||||
else
|
||||
@@ -267,9 +267,9 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||
with {:ok, %Follower{id: follow_id}} <- Actor.unfollow(followed, follower),
|
||||
# We recreate the follow activity
|
||||
data <- make_follow_data(followed, follower, follow_id),
|
||||
{:ok, follow_activity} <- insert(data, local),
|
||||
{:ok, follow_activity, _object} <- insert(data, local),
|
||||
unfollow_data <- make_unfollow_data(follower, followed, follow_activity, activity_id),
|
||||
{:ok, activity} <- insert(unfollow_data, local),
|
||||
{:ok, activity, _object} <- insert(unfollow_data, local),
|
||||
:ok <- maybe_federate(activity) do
|
||||
{:ok, activity}
|
||||
else
|
||||
@@ -290,7 +290,7 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||
}
|
||||
|
||||
with Events.delete_event(event),
|
||||
{:ok, activity} <- insert(data, local),
|
||||
{:ok, activity, _object} <- insert(data, local),
|
||||
:ok <- maybe_federate(activity) do
|
||||
{:ok, activity}
|
||||
end
|
||||
@@ -305,7 +305,7 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||
}
|
||||
|
||||
with Events.delete_comment(comment),
|
||||
{:ok, activity} <- insert(data, local),
|
||||
{:ok, activity, _object} <- insert(data, local),
|
||||
:ok <- maybe_federate(activity) do
|
||||
{:ok, activity}
|
||||
end
|
||||
@@ -320,12 +320,33 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||
}
|
||||
|
||||
with Actors.delete_actor(actor),
|
||||
{:ok, activity} <- insert(data, local),
|
||||
{:ok, activity, _object} <- insert(data, local),
|
||||
:ok <- maybe_federate(activity) do
|
||||
{:ok, activity}
|
||||
end
|
||||
end
|
||||
|
||||
def flag(params) do
|
||||
# only accept false as false value
|
||||
local = !(params[:local] == false)
|
||||
forward = !(params[:forward] == false)
|
||||
|
||||
additional = params[:additional] || %{}
|
||||
|
||||
additional =
|
||||
if forward do
|
||||
Map.merge(additional, %{"to" => [], "cc" => [params.reported_actor_url]})
|
||||
else
|
||||
Map.merge(additional, %{"to" => [], "cc" => []})
|
||||
end
|
||||
|
||||
with flag_data <- make_flag_data(params, additional),
|
||||
{:ok, activity, report} <- insert(flag_data, local),
|
||||
:ok <- maybe_federate(activity) do
|
||||
{:ok, activity, report}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Create an actor locally by it's URL (AP ID)
|
||||
"""
|
||||
|
||||
85
lib/service/activity_pub/converters/flag.ex
Normal file
85
lib/service/activity_pub/converters/flag.ex
Normal file
@@ -0,0 +1,85 @@
|
||||
defmodule Mobilizon.Service.ActivityPub.Converters.Flag do
|
||||
@moduledoc """
|
||||
Flag converter
|
||||
|
||||
This module allows to convert reports from ActivityStream format to our own internal one, and back.
|
||||
|
||||
Note: Reports are named Flag in AS.
|
||||
"""
|
||||
alias Mobilizon.Actors
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Events
|
||||
alias Mobilizon.Events.Event
|
||||
alias Mobilizon.Reports.Report
|
||||
alias Mobilizon.Service.ActivityPub.Converter
|
||||
|
||||
@behaviour Converter
|
||||
|
||||
@doc """
|
||||
Converts an AP object data to our internal data structure
|
||||
"""
|
||||
@impl Converter
|
||||
@spec as_to_model_data(map()) :: map()
|
||||
def as_to_model_data(object) do
|
||||
with params <- as_to_model(object) do
|
||||
%{
|
||||
"reporter_id" => params["reporter"].id,
|
||||
"uri" => params["uri"],
|
||||
"content" => params["content"],
|
||||
"reported_id" => params["reported"].id,
|
||||
"event_id" => (!is_nil(params["event"]) && params["event"].id) || nil,
|
||||
"comments" => params["comments"]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def as_to_model(%{"object" => objects} = object) do
|
||||
with {:ok, %Actor{} = reporter} <- Actors.get_actor_by_url(object["actor"]),
|
||||
%Actor{} = reported <-
|
||||
Enum.reduce_while(objects, nil, fn url, _ ->
|
||||
with {:ok, %Actor{} = actor} <- Actors.get_actor_by_url(url) do
|
||||
{:halt, actor}
|
||||
else
|
||||
_ -> {:cont, nil}
|
||||
end
|
||||
end),
|
||||
event <-
|
||||
Enum.reduce_while(objects, nil, fn url, _ ->
|
||||
with %Event{} = event <- Events.get_event_by_url(url) do
|
||||
{:halt, event}
|
||||
else
|
||||
_ -> {:cont, nil}
|
||||
end
|
||||
end),
|
||||
|
||||
# Remove the reported user from the object list.
|
||||
comments <-
|
||||
Enum.filter(objects, fn url ->
|
||||
!(url == reported.url || (!is_nil(event) && event.url == url))
|
||||
end),
|
||||
comments <- Enum.map(comments, &Events.get_comment_from_url/1) do
|
||||
%{
|
||||
"reporter" => reporter,
|
||||
"uri" => object["id"],
|
||||
"content" => object["content"],
|
||||
"reported" => reported,
|
||||
"event" => event,
|
||||
"comments" => comments
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Convert an event struct to an ActivityStream representation
|
||||
"""
|
||||
@impl Converter
|
||||
@spec model_to_as(EventModel.t()) :: map()
|
||||
def model_to_as(%Report{} = report) do
|
||||
%{
|
||||
"type" => "Flag",
|
||||
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||
"actor" => report.reporter.url,
|
||||
"id" => report.url
|
||||
}
|
||||
end
|
||||
end
|
||||
@@ -116,6 +116,22 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
|
||||
|> Map.put("tag", combined)
|
||||
end
|
||||
|
||||
def handle_incoming(%{"type" => "Flag"} = data) do
|
||||
with params <- Mobilizon.Service.ActivityPub.Converters.Flag.as_to_model(data) do
|
||||
params = %{
|
||||
reporter_url: params["reporter"].url,
|
||||
reported_actor_url: params["reported"].url,
|
||||
comments_url: params["comments"] |> Enum.map(& &1.url),
|
||||
content: params["content"] || "",
|
||||
additional: %{
|
||||
"cc" => [params["reported"].url]
|
||||
}
|
||||
}
|
||||
|
||||
ActivityPub.flag(params)
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: validate those with a Ecto scheme
|
||||
# - tags
|
||||
# - emoji
|
||||
|
||||
@@ -18,6 +18,10 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
|
||||
alias Mobilizon.Media.Picture
|
||||
alias Mobilizon.Events
|
||||
alias Mobilizon.Activity
|
||||
alias Mobilizon.Reports
|
||||
alias Mobilizon.Reports.Report
|
||||
alias Mobilizon.Users
|
||||
alias Mobilizon.Service.ActivityPub.Converters
|
||||
alias Ecto.Changeset
|
||||
require Logger
|
||||
alias MobilizonWeb.Router.Helpers, as: Routes
|
||||
@@ -119,9 +123,9 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
|
||||
def insert_full_object(%{"object" => %{"type" => "Event"} = object_data})
|
||||
when is_map(object_data) do
|
||||
with object_data <-
|
||||
Mobilizon.Service.ActivityPub.Converters.Event.as_to_model_data(object_data),
|
||||
{:ok, _} <- Events.create_event(object_data) do
|
||||
:ok
|
||||
Converters.Event.as_to_model_data(object_data),
|
||||
{:ok, %Event{} = event} <- Events.create_event(object_data) do
|
||||
{:ok, event}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -129,8 +133,8 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
|
||||
when is_map(object_data) do
|
||||
with object_data <-
|
||||
Map.put(object_data, "preferred_username", object_data["preferredUsername"]),
|
||||
{:ok, _} <- Actors.create_group(object_data) do
|
||||
:ok
|
||||
{:ok, %Actor{} = group} <- Actors.create_group(object_data) do
|
||||
{:ok, group}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -139,9 +143,9 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
|
||||
"""
|
||||
def insert_full_object(%{"object" => %{"type" => "Note"} = object_data})
|
||||
when is_map(object_data) do
|
||||
with data <- Mobilizon.Service.ActivityPub.Converters.Comment.as_to_model_data(object_data),
|
||||
{:ok, _comment} <- Events.create_comment(data) do
|
||||
:ok
|
||||
with data <- Converters.Comment.as_to_model_data(object_data),
|
||||
{:ok, %Comment{} = comment} <- Events.create_comment(data) do
|
||||
{:ok, comment}
|
||||
else
|
||||
err ->
|
||||
Logger.error("Error while inserting a remote comment inside database")
|
||||
@@ -150,7 +154,29 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
|
||||
end
|
||||
end
|
||||
|
||||
def insert_full_object(_), do: :ok
|
||||
@doc """
|
||||
Inserts a full object if it is contained in an activity.
|
||||
"""
|
||||
def insert_full_object(%{"type" => "Flag"} = object_data)
|
||||
when is_map(object_data) do
|
||||
with data <- Converters.Flag.as_to_model_data(object_data),
|
||||
{:ok, %Report{} = report} <- Reports.create_report(data) do
|
||||
Enum.each(Users.list_moderators(), fn moderator ->
|
||||
moderator
|
||||
|> Mobilizon.Email.Admin.report(moderator, report)
|
||||
|> Mobilizon.Mailer.deliver_later()
|
||||
end)
|
||||
|
||||
{:ok, report}
|
||||
else
|
||||
err ->
|
||||
Logger.error("Error while inserting a remote comment inside database")
|
||||
Logger.error(inspect(err))
|
||||
{:error, err}
|
||||
end
|
||||
end
|
||||
|
||||
def insert_full_object(_), do: {:ok, nil}
|
||||
|
||||
#### Like-related helpers
|
||||
|
||||
@@ -497,6 +523,24 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
|
||||
|> Map.merge(additional)
|
||||
end
|
||||
|
||||
#### Flag-related helpers
|
||||
@spec make_flag_data(map(), map()) :: map()
|
||||
def make_flag_data(params, additional) do
|
||||
object = [params.reported_actor_url] ++ params.comments_url
|
||||
|
||||
object = if params[:event_url], do: object ++ [params.event_url], else: object
|
||||
|
||||
%{
|
||||
"type" => "Flag",
|
||||
"id" => "#{MobilizonWeb.Endpoint.url()}/report/#{Ecto.UUID.generate()}",
|
||||
"actor" => params.reporter_url,
|
||||
"content" => params.content,
|
||||
"object" => object,
|
||||
"state" => "open"
|
||||
}
|
||||
|> Map.merge(additional)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts PEM encoded keys to a public key representation
|
||||
"""
|
||||
|
||||
30
lib/service/admin/action_log_service.ex
Normal file
30
lib/service/admin/action_log_service.ex
Normal file
@@ -0,0 +1,30 @@
|
||||
defmodule Mobilizon.Service.Admin.ActionLogService do
|
||||
@moduledoc """
|
||||
Module to handle action log creations
|
||||
"""
|
||||
|
||||
alias Mobilizon.Users
|
||||
alias Mobilizon.Users.User
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Admin
|
||||
alias Mobilizon.Admin.ActionLog
|
||||
|
||||
@doc """
|
||||
Log an admin action
|
||||
"""
|
||||
@spec log_action(Actor.t(), String.t(), String.t()) :: {:ok, ActionLog.t()}
|
||||
def log_action(%Actor{user_id: user_id, id: actor_id}, action, target) do
|
||||
with %User{role: role} <- Users.get_user!(user_id),
|
||||
{:role, true} <- {:role, role in [:administrator, :moderator]},
|
||||
{:ok, %ActionLog{} = create_action_log} <-
|
||||
Admin.create_action_log(%{
|
||||
"actor_id" => actor_id,
|
||||
"target_type" => to_string(target.__struct__),
|
||||
"target_id" => target.id,
|
||||
"action" => action,
|
||||
"changes" => Map.from_struct(target) |> Map.take([:status, :uri, :content])
|
||||
}) do
|
||||
{:ok, create_action_log}
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user