The chat report queue.
Players report a message through report_message/3; the word filter files its
own reports (with reporter_id nil) when a flag word matches. Moderators
work the queue from the admin console via list_reports/2 and
resolve_report/3.
Reports keep a denormalized reported_user_id and content_snapshot, so the
queue still makes sense after the message itself is deleted.
Summary
Functions
Count reports grouped by status, as %{status => count}.
Count reports still awaiting a moderator.
Count reports filed by reporter_id in the last 24 hours.
Count reports matching filters.
Delete a report outright (admin).
Fetch one report with its associations loaded.
List reports, newest first. Filters: :status, :reported_user_id,
:reporter_id.
File a report on the word filter's behalf (reporter_id stays nil).
File a report about message_id on behalf of reporter_id.
Resolve a report: set its status, note who resolved it and when.
Claim a report for review ("open" → "reviewing").
Functions
@spec count_by_status() :: map()
Count reports grouped by status, as %{status => count}.
@spec count_open_reports() :: non_neg_integer()
Count reports still awaiting a moderator.
@spec count_recent_by_reporter(Ecto.UUID.t()) :: non_neg_integer()
Count reports filed by reporter_id in the last 24 hours.
Backs the per-player daily cap; unlike the rate limiter this is durable, so it holds across restarts and every instance.
@spec count_reports(map()) :: non_neg_integer()
Count reports matching filters.
@spec delete_report(Gamend.Chat.Report.t()) :: {:ok, Gamend.Chat.Report.t()} | {:error, Ecto.Changeset.t()}
Delete a report outright (admin).
@spec get_report(Ecto.UUID.t()) :: Gamend.Chat.Report.t() | nil
Fetch one report with its associations loaded.
@spec list_reports( map(), keyword() ) :: [Gamend.Chat.Report.t()]
List reports, newest first. Filters: :status, :reported_user_id,
:reporter_id.
@spec report_flagged_message(Gamend.Chat.Message.t(), [String.t()]) :: {:ok, Gamend.Chat.Report.t()} | {:error, term()}
File a report on the word filter's behalf (reporter_id stays nil).
Called after the flagged message is committed — at filter time it has no id yet.
@spec report_message(Ecto.UUID.t(), Ecto.UUID.t(), String.t() | nil) :: {:ok, Gamend.Chat.Report.t()} | {:error, term()}
File a report about message_id on behalf of reporter_id.
Returns {:error, :not_found} for an unknown message, {:error, :own_message}
when a player reports themselves, and {:error, :already_reported} when they
have already reported that message.
@spec resolve_report(Gamend.Chat.Report.t() | Ecto.UUID.t(), String.t(), map()) :: {:ok, Gamend.Chat.Report.t()} | {:error, term()}
Resolve a report: set its status, note who resolved it and when.
status is one of Gamend.Chat.Report.statuses/0 other than "open".
@spec review_report(Gamend.Chat.Report.t() | Ecto.UUID.t()) :: {:ok, Gamend.Chat.Report.t()} | {:error, term()}
Claim a report for review ("open" → "reviewing").
Purely a signal to other moderators that someone has picked this one up; it sets no resolution, so the report stays in the queue until it is dismissed or actioned.