Introduce backend for reports

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel
2019-07-23 13:49:22 +02:00
parent 33a8da4570
commit aef841e192
36 changed files with 2028 additions and 36 deletions

View File

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

View 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

View File

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

View 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

View File

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

View File

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

View 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

View 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

View File

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