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(
|
||||
|
||||
Reference in New Issue
Block a user