From d0c99745585514ee5b8ec4d49b46f27699e7004a Mon Sep 17 00:00:00 2001 From: miffy Date: Sat, 7 Sep 2019 02:38:13 +0200 Subject: [PATCH] Refactoring of Reports context --- lib/mobilizon/reports.ex | 236 ------------------ lib/mobilizon/reports/note.ex | 22 +- lib/mobilizon/reports/report.ex | 48 ++-- lib/mobilizon/reports/reports.ex | 162 ++++++++++++ lib/mobilizon_web/api/reports.ex | 6 +- .../20190712125833_create_reports.exs | 8 +- 6 files changed, 214 insertions(+), 268 deletions(-) delete mode 100644 lib/mobilizon/reports.ex create mode 100644 lib/mobilizon/reports/reports.ex diff --git a/lib/mobilizon/reports.ex b/lib/mobilizon/reports.ex deleted file mode 100644 index 1bfaac7ac..000000000 --- a/lib/mobilizon/reports.ex +++ /dev/null @@ -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 diff --git a/lib/mobilizon/reports/note.ex b/lib/mobilizon/reports/note.ex index a7d8ad30e..c28d18df4 100644 --- a/lib/mobilizon/reports/note.ex +++ b/lib/mobilizon/reports/note.ex @@ -1,27 +1,39 @@ defmodule Mobilizon.Reports.Note do @moduledoc """ - Report Note entity + Represents a note entity. """ + use Ecto.Schema - import Ecto.Changeset + + import Ecto.Changeset, only: [cast: 3, validate_required: 2] + alias Mobilizon.Actors.Actor 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]} schema "report_notes" do field(:content, :string) - belongs_to(:moderator, Actor) + belongs_to(:report, Report) + belongs_to(:moderator, Actor) timestamps() end @doc false + @spec changeset(t | Ecto.Changeset.t(), map) :: Ecto.Changeset.t() def changeset(note, attrs) do note |> cast(attrs, @attrs) - |> validate_required(@attrs) + |> validate_required(@required_attrs) end end diff --git a/lib/mobilizon/reports/report.ex b/lib/mobilizon/reports/report.ex index 11d55e824..768a5ed44 100644 --- a/lib/mobilizon/reports/report.ex +++ b/lib/mobilizon/reports/report.ex @@ -1,43 +1,48 @@ -import EctoEnum - -defenum(Mobilizon.Reports.ReportStateEnum, :report_state, [ - :open, - :closed, - :resolved -]) - defmodule Mobilizon.Reports.Report do @moduledoc """ - Report entity + Represents a report entity. """ + use Ecto.Schema + import Ecto.Changeset - alias Mobilizon.Events.Comment - alias Mobilizon.Events.Event + 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]} schema "reports" do field(:content, :string) - field(:status, Mobilizon.Reports.ReportStateEnum, default: :open) + field(:status, ReportStatus, 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) @@ -45,12 +50,15 @@ defmodule Mobilizon.Reports.Report do end @doc false + @spec changeset(t | Ecto.Changeset.t(), map) :: Ecto.Changeset.t() 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]) + |> cast(attrs, @attrs) + |> validate_required(@required_attrs) end + @doc false + @spec creation_changeset(Report.t(), map) :: Ecto.Changeset.t() def creation_changeset(report, attrs) do report |> changeset(attrs) diff --git a/lib/mobilizon/reports/reports.ex b/lib/mobilizon/reports/reports.ex new file mode 100644 index 000000000..173dc5fa7 --- /dev/null +++ b/lib/mobilizon/reports/reports.ex @@ -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 diff --git a/lib/mobilizon_web/api/reports.ex b/lib/mobilizon_web/api/reports.ex index 3010c6ecb..815471acd 100644 --- a/lib/mobilizon_web/api/reports.ex +++ b/lib/mobilizon_web/api/reports.ex @@ -61,7 +61,7 @@ defmodule MobilizonWeb.API.Reports do """ def update_report_status(%Actor{} = actor, %Report{} = report, state) do 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, _} <- log_action(actor, "update", report) do {:ok, report} @@ -89,7 +89,7 @@ defmodule MobilizonWeb.API.Reports 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(%{ + Mobilizon.Reports.create_note(%{ "report_id" => report_id, "moderator_id" => moderator_id, "content" => content @@ -114,7 +114,7 @@ defmodule MobilizonWeb.API.Reports do %User{role: role} <- Users.get_user!(user_id), {:role, true} <- {:role, role in [:administrator, :moderator]}, {:ok, %Note{} = note} <- - Mobilizon.Reports.delete_report_note(note), + Mobilizon.Reports.delete_note(note), {:ok, _} <- log_action(moderator, "delete", note) do {:ok, note} else diff --git a/priv/repo/migrations/20190712125833_create_reports.exs b/priv/repo/migrations/20190712125833_create_reports.exs index ce466b3e9..334b7ac08 100644 --- a/priv/repo/migrations/20190712125833_create_reports.exs +++ b/priv/repo/migrations/20190712125833_create_reports.exs @@ -1,13 +1,13 @@ defmodule Mobilizon.Repo.Migrations.CreateReports do use Ecto.Migration - alias Mobilizon.Reports.ReportStateEnum + alias Mobilizon.Reports.ReportStatus def up do - ReportStateEnum.create_type() + ReportStatus.create_type() create table(:reports) do 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(: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)) - ReportStateEnum.drop_type() + ReportStatus.drop_type() end end