Refactoring of Reports context

This commit is contained in:
miffy 2019-09-07 02:38:13 +02:00
parent c2b4fb6cff
commit d0c9974558
6 changed files with 214 additions and 268 deletions

View file

@ -1,236 +0,0 @@
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

@ -1,27 +1,39 @@
defmodule Mobilizon.Reports.Note do defmodule Mobilizon.Reports.Note do
@moduledoc """ @moduledoc """
Report Note entity Represents a note entity.
""" """
use Ecto.Schema use Ecto.Schema
import Ecto.Changeset
import Ecto.Changeset, only: [cast: 3, validate_required: 2]
alias Mobilizon.Actors.Actor alias Mobilizon.Actors.Actor
alias Mobilizon.Reports.Report alias Mobilizon.Reports.Report
@attrs [:content, :moderator_id, :report_id] @required_attrs [:content, :moderator_id, :report_id]
@attrs @required_attrs
@type t :: %__MODULE__{
content: String.t(),
report: Report.t(),
moderator: Actor.t()
}
@derive {Jason.Encoder, only: [:content]} @derive {Jason.Encoder, only: [:content]}
schema "report_notes" do schema "report_notes" do
field(:content, :string) field(:content, :string)
belongs_to(:moderator, Actor)
belongs_to(:report, Report) belongs_to(:report, Report)
belongs_to(:moderator, Actor)
timestamps() timestamps()
end end
@doc false @doc false
@spec changeset(t | Ecto.Changeset.t(), map) :: Ecto.Changeset.t()
def changeset(note, attrs) do def changeset(note, attrs) do
note note
|> cast(attrs, @attrs) |> cast(attrs, @attrs)
|> validate_required(@attrs) |> validate_required(@required_attrs)
end end
end end

View file

@ -1,43 +1,48 @@
import EctoEnum
defenum(Mobilizon.Reports.ReportStateEnum, :report_state, [
:open,
:closed,
:resolved
])
defmodule Mobilizon.Reports.Report do defmodule Mobilizon.Reports.Report do
@moduledoc """ @moduledoc """
Report entity Represents a report entity.
""" """
use Ecto.Schema use Ecto.Schema
import Ecto.Changeset import Ecto.Changeset
alias Mobilizon.Events.Comment
alias Mobilizon.Events.Event
alias Mobilizon.Actors.Actor alias Mobilizon.Actors.Actor
alias Mobilizon.Reports.Note alias Mobilizon.Events.{Comment, Event}
alias Mobilizon.Reports.{Note, ReportStatus}
@type t :: %__MODULE__{
content: String.t(),
status: ReportStatus.t(),
uri: String.t(),
reported: Actor.t(),
reporter: Actor.t(),
manager: Actor.t(),
event: Event.t(),
comments: [Comment.t()],
notes: [Note.t()]
}
@required_attrs [:content, :uri, :reported_id, :reporter_id]
@optional_attrs [:status, :manager_id, :event_id]
@attrs @required_attrs ++ @optional_attrs
@derive {Jason.Encoder, only: [:status, :uri]} @derive {Jason.Encoder, only: [:status, :uri]}
schema "reports" do schema "reports" do
field(:content, :string) field(:content, :string)
field(:status, Mobilizon.Reports.ReportStateEnum, default: :open) field(:status, ReportStatus, default: :open)
field(:uri, :string) field(:uri, :string)
# The reported actor # The reported actor
belongs_to(:reported, Actor) belongs_to(:reported, Actor)
# The actor who reported # The actor who reported
belongs_to(:reporter, Actor) belongs_to(:reporter, Actor)
# The actor who last acted on this report # The actor who last acted on this report
belongs_to(:manager, Actor) belongs_to(:manager, Actor)
# The eventual Event inside the report # The eventual Event inside the report
belongs_to(:event, Event) belongs_to(:event, Event)
# The eventual Comments inside the report # The eventual Comments inside the report
many_to_many(:comments, Comment, join_through: "reports_comments", on_replace: :delete) many_to_many(:comments, Comment, join_through: "reports_comments", on_replace: :delete)
# The notes associated to the report # The notes associated to the report
has_many(:notes, Note, foreign_key: :report_id) has_many(:notes, Note, foreign_key: :report_id)
@ -45,12 +50,15 @@ defmodule Mobilizon.Reports.Report do
end end
@doc false @doc false
@spec changeset(t | Ecto.Changeset.t(), map) :: Ecto.Changeset.t()
def changeset(report, attrs) do def changeset(report, attrs) do
report report
|> cast(attrs, [:content, :status, :uri, :reported_id, :reporter_id, :manager_id, :event_id]) |> cast(attrs, @attrs)
|> validate_required([:content, :uri, :reported_id, :reporter_id]) |> validate_required(@required_attrs)
end end
@doc false
@spec creation_changeset(Report.t(), map) :: Ecto.Changeset.t()
def creation_changeset(report, attrs) do def creation_changeset(report, attrs) do
report report
|> changeset(attrs) |> changeset(attrs)

View file

@ -0,0 +1,162 @@
defmodule Mobilizon.Reports do
@moduledoc """
The Reports context.
"""
import Ecto.Query
import EctoEnum
import Mobilizon.Ecto
alias Mobilizon.{Page, Repo}
alias Mobilizon.Reports.{Note, Report}
defenum(ReportStatus, :report_status, [:open, :closed, :resolved])
@doc false
@spec data :: Dataloader.Ecto.t()
def data, do: Dataloader.Ecto.new(Mobilizon.Repo, query: &query/2)
@doc false
@spec query(Ecto.Query.t(), map) :: Ecto.Query.t()
def query(queryable, _params), do: queryable
@doc """
Returns the list of reports.
"""
@spec list_reports(integer | nil, integer | nil, atom, atom) :: [Report.t()]
def list_reports(page \\ nil, limit \\ nil, sort \\ :updated_at, direction \\ :asc) do
list_reports_query()
|> Page.paginate(page, limit)
|> sort(sort, direction)
|> Repo.all()
end
@doc """
Gets a single report.
"""
@spec get_report(integer | String.t()) :: Report.t() | nil
def get_report(id) do
Report
|> Repo.get(id)
|> Repo.preload([:reported, :reporter, :manager, :event, :comments, :notes])
end
@doc """
Gets a single report.
Raises `Ecto.NoResultsError` if the report does not exist.
"""
@spec get_report!(integer | String.t()) :: Report.t()
def get_report!(id) do
Report
|> Repo.get!(id)
|> Repo.preload([:reported, :reporter, :manager, :event, :comments, :notes])
end
@doc """
Get a report by its URL
"""
@spec get_report_by_url(String.t()) :: Report.t() | nil
def get_report_by_url(url) do
url
|> report_by_url_query()
|> Repo.one()
end
@doc """
Creates a report.
"""
@spec create_report(map) :: {:ok, Report.t()} | {:error, Ecto.Changeset.t()}
def create_report(attrs \\ %{}) do
with {:ok, %Report{} = report} <-
%Report{}
|> Report.changeset(attrs)
|> Repo.insert() do
{:ok, Repo.preload(report, [:event, :reported, :reporter, :comments])}
end
end
@doc """
Updates a report.
"""
@spec update_report(Report.t(), map) :: {:ok, Report.t()} | {:error, Ecto.Changeset.t()}
def update_report(%Report{} = report, attrs) do
report
|> Report.changeset(attrs)
|> Repo.update()
end
@doc """
Deletes a report.
"""
@spec delete_report(Report.t()) :: {:ok, Report.t()} | {:error, Ecto.Changeset.t()}
def delete_report(%Report{} = report) do
Repo.delete(report)
end
@doc """
Returns the list of notes for a report.
"""
@spec list_notes_for_report(Report.t()) :: [Note.t()]
def list_notes_for_report(%Report{id: report_id}) do
report_id
|> list_notes_for_report_query()
|> Repo.all()
end
@doc """
Gets a single note.
"""
@spec get_note(integer | String.t()) :: Note.t() | nil
def get_note(id), do: Repo.get(Note, id)
@doc """
Gets a single note.
Raises `Ecto.NoResultsError` if the Note does not exist.
"""
@spec get_note!(integer | String.t()) :: Note.t()
def get_note!(id), do: Repo.get!(Note, id)
@doc """
Creates a note.
"""
@spec create_note(map) :: {:ok, Note.t()} | {:error, Ecto.Changeset.t()}
def create_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.
"""
@spec delete_note(Note.t()) :: {:ok, Note.t()} | {:error, Ecto.Changeset.t()}
def delete_note(%Note{} = note) do
Repo.delete(note)
end
@spec list_reports_query :: Ecto.Query.t()
defp list_reports_query do
from(
r in Report,
preload: [:reported, :reporter, :manager, :event, :comments, :notes]
)
end
@spec report_by_url_query(String.t()) :: Ecto.Query.t()
defp report_by_url_query(url) do
from(r in Report, where: r.uri == ^url)
end
@spec list_notes_for_report_query(integer | String.t()) :: Ecto.Query.t()
defp list_notes_for_report_query(report_id) do
from(
n in Note,
where: n.report_id == ^report_id,
preload: [:report, :moderator]
)
end
end

View file

@ -61,7 +61,7 @@ defmodule MobilizonWeb.API.Reports do
""" """
def update_report_status(%Actor{} = actor, %Report{} = report, state) do def update_report_status(%Actor{} = actor, %Report{} = report, state) do
with {:valid_state, true} <- with {:valid_state, true} <-
{:valid_state, Mobilizon.Reports.ReportStateEnum.valid_value?(state)}, {:valid_state, Mobilizon.Reports.ReportStatus.valid_value?(state)},
{:ok, report} <- ReportsAction.update_report(report, %{"status" => state}), {:ok, report} <- ReportsAction.update_report(report, %{"status" => state}),
{:ok, _} <- log_action(actor, "update", report) do {:ok, _} <- log_action(actor, "update", report) do
{:ok, report} {:ok, report}
@ -89,7 +89,7 @@ defmodule MobilizonWeb.API.Reports do
with %User{role: role} <- Users.get_user!(user_id), with %User{role: role} <- Users.get_user!(user_id),
{:role, true} <- {:role, role in [:administrator, :moderator]}, {:role, true} <- {:role, role in [:administrator, :moderator]},
{:ok, %Note{} = note} <- {:ok, %Note{} = note} <-
Mobilizon.Reports.create_report_note(%{ Mobilizon.Reports.create_note(%{
"report_id" => report_id, "report_id" => report_id,
"moderator_id" => moderator_id, "moderator_id" => moderator_id,
"content" => content "content" => content
@ -114,7 +114,7 @@ defmodule MobilizonWeb.API.Reports do
%User{role: role} <- Users.get_user!(user_id), %User{role: role} <- Users.get_user!(user_id),
{:role, true} <- {:role, role in [:administrator, :moderator]}, {:role, true} <- {:role, role in [:administrator, :moderator]},
{:ok, %Note{} = note} <- {:ok, %Note{} = note} <-
Mobilizon.Reports.delete_report_note(note), Mobilizon.Reports.delete_note(note),
{:ok, _} <- log_action(moderator, "delete", note) do {:ok, _} <- log_action(moderator, "delete", note) do
{:ok, note} {:ok, note}
else else

View file

@ -1,13 +1,13 @@
defmodule Mobilizon.Repo.Migrations.CreateReports do defmodule Mobilizon.Repo.Migrations.CreateReports do
use Ecto.Migration use Ecto.Migration
alias Mobilizon.Reports.ReportStateEnum alias Mobilizon.Reports.ReportStatus
def up do def up do
ReportStateEnum.create_type() ReportStatus.create_type()
create table(:reports) do create table(:reports) do
add(:content, :string) add(:content, :string)
add(:status, ReportStateEnum.type(), default: "open", null: false) add(:status, ReportStatus.type(), default: "open", null: false)
add(:uri, :string, null: false) add(:uri, :string, null: false)
add(:reported_id, references(:actors, on_delete: :delete_all), null: false) add(:reported_id, references(:actors, on_delete: :delete_all), null: false)
@ -28,6 +28,6 @@ defmodule Mobilizon.Repo.Migrations.CreateReports do
drop(table(:reports_comments)) drop(table(:reports_comments))
drop(table(:reports)) drop(table(:reports))
ReportStateEnum.drop_type() ReportStatus.drop_type()
end end
end end