From 50be8816019d34725b4ecd0e432d37c37516d383 Mon Sep 17 00:00:00 2001 From: SimpleTest Date: Fri, 24 Jul 2026 21:02:39 +0300 Subject: [PATCH] fix(moderation): expose reported parent actions --- lib/who_need_help/activities/activity.ex | 9 +++ lib/who_need_help/help/help_request.ex | 9 +++ lib/who_need_help/trust.ex | 6 +- lib/who_need_help_web/live/moderation_live.ex | 78 +++++++++++++++++-- test/who_need_help/trust_safety_test.exs | 15 ++++ .../live/mutual_aid_live_test.exs | 63 ++++++++++++++- 6 files changed, 169 insertions(+), 11 deletions(-) diff --git a/lib/who_need_help/activities/activity.ex b/lib/who_need_help/activities/activity.ex index 4eb5693..2adf712 100644 --- a/lib/who_need_help/activities/activity.ex +++ b/lib/who_need_help/activities/activity.ex @@ -78,6 +78,15 @@ defmodule WhoNeedHelp.Activities.Activity do activity |> cast(attrs, [:hidden_at, :hidden_reason]) |> validate_length(:hidden_reason, max: 1_000) + |> require_hidden_reason() + end + + defp require_hidden_reason(changeset) do + if get_field(changeset, :hidden_at) do + validate_required(changeset, :hidden_reason) + else + changeset + end end def public_coordinates(%__MODULE__{ diff --git a/lib/who_need_help/help/help_request.ex b/lib/who_need_help/help/help_request.ex index a8508b5..c233ae3 100644 --- a/lib/who_need_help/help/help_request.ex +++ b/lib/who_need_help/help/help_request.ex @@ -87,6 +87,15 @@ defmodule WhoNeedHelp.Help.HelpRequest do request |> cast(attrs, [:hidden_at, :hidden_reason]) |> validate_length(:hidden_reason, max: 1_000) + |> require_hidden_reason() + end + + defp require_hidden_reason(changeset) do + if get_field(changeset, :hidden_at) do + validate_required(changeset, :hidden_reason) + else + changeset + end end def cancellation_changeset(request, attrs) do diff --git a/lib/who_need_help/trust.ex b/lib/who_need_help/trust.ex index 6a1ec00..065ea02 100644 --- a/lib/who_need_help/trust.ex +++ b/lib/who_need_help/trust.ex @@ -345,10 +345,10 @@ defmodule WhoNeedHelp.Trust do :reporter, :reviewed_by, request: :requester, - assignment: :helper, - message: :sender, + assignment: [:helper, :request], + message: [:sender, assignment: :request], activity: :creator, - activity_message: :sender + activity_message: [:sender, :activity] ]) |> Repo.all() |> Pagination.page(limit, &{&1.inserted_at, &1.id}) diff --git a/lib/who_need_help_web/live/moderation_live.ex b/lib/who_need_help_web/live/moderation_live.ex index a8fe7ba..ef73cc6 100644 --- a/lib/who_need_help_web/live/moderation_live.ex +++ b/lib/who_need_help_web/live/moderation_live.ex @@ -284,6 +284,38 @@ defmodule WhoNeedHelpWeb.ModerationLive do defp signal_label(:manual), do: gettext("Manual review") defp signal_label(value), do: to_string(value) + defp report_request(%{request: %WhoNeedHelp.Help.HelpRequest{} = request}), do: request + + defp report_request(%{ + assignment: %WhoNeedHelp.Help.Assignment{ + request: %WhoNeedHelp.Help.HelpRequest{} = request + } + }), + do: request + + defp report_request(%{ + message: %WhoNeedHelp.Messaging.Message{ + assignment: %WhoNeedHelp.Help.Assignment{ + request: %WhoNeedHelp.Help.HelpRequest{} = request + } + } + }), + do: request + + defp report_request(_report), do: nil + + defp report_activity(%{activity: %WhoNeedHelp.Activities.Activity{} = activity}), + do: activity + + defp report_activity(%{ + activity_message: %WhoNeedHelp.Activities.Message{ + activity: %WhoNeedHelp.Activities.Activity{} = activity + } + }), + do: activity + + defp report_activity(_report), do: nil + @impl true def render(assigns) do ~H""" @@ -309,6 +341,8 @@ defmodule WhoNeedHelpWeb.ModerationLive do
<% hide_form = scoped_form(%{"note" => ""}, :moderation, "report-hide-#{report.id}") %> + <% reported_request = report_request(report) %> + <% reported_activity = report_activity(report) %> <% report_form = scoped_form( %{"status" => to_string(report.status), "resolution_note" => ""}, @@ -324,6 +358,12 @@ defmodule WhoNeedHelpWeb.ModerationLive do {gettext("activity message")} + + {gettext("matched chat message")} + + + {gettext("matched conversation")} + {gettext("by %{name}", name: report.reporter.display_name)} @@ -338,29 +378,55 @@ defmodule WhoNeedHelpWeb.ModerationLive do {gettext("View scoped evidence")} <.form - :if={report.request_id} + :if={reported_request && is_nil(reported_request.hidden_at)} for={hide_form} phx-submit="hide-request" - phx-value-id={report.request_id} + phx-value-id={reported_request.id} class="flex gap-2" > - <.input field={hide_form[:note]} placeholder={gettext("Hide reason")} /> + <.input + field={hide_form[:note]} + placeholder={gettext("Required reason for hiding")} + required + /> <.button class="btn btn-sm btn-error self-end"> {gettext("Hide request")} + <.form - :if={report.activity_id} + :if={reported_activity && is_nil(reported_activity.hidden_at)} for={hide_form} phx-submit="hide-activity" - phx-value-id={report.activity_id} + phx-value-id={reported_activity.id} class="flex gap-2" > - <.input field={hide_form[:note]} placeholder={gettext("Hide reason")} /> + <.input + field={hide_form[:note]} + placeholder={gettext("Required reason for hiding")} + required + /> <.button class="btn btn-sm btn-error self-end"> {gettext("Hide activity")} +
<.form for={report_form} diff --git a/test/who_need_help/trust_safety_test.exs b/test/who_need_help/trust_safety_test.exs index 60aaae1..c1da46b 100644 --- a/test/who_need_help/trust_safety_test.exs +++ b/test/who_need_help/trust_safety_test.exs @@ -153,6 +153,21 @@ defmodule WhoNeedHelp.TrustSafetyTest do ) end + test "hiding a request requires a moderation reason", context do + moderator = + user_fixture(display_name: "Reason moderator") + |> Ecto.Changeset.change(role: :moderator) + |> Repo.update!() + + {:ok, request} = Help.create_request(context.requester_scope, context.attrs) + + assert {:error, %Ecto.Changeset{} = changeset} = + Trust.hide_request(user_scope_fixture(moderator), request.id, "") + + assert "can't be blank" in errors_on(changeset).hidden_reason + refute Repo.get!(WhoNeedHelp.Help.HelpRequest, request.id).hidden_at + end + test "category moderation cannot cross help and activity taxonomies", context do moderator = user_fixture(display_name: "Category moderator") diff --git a/test/who_need_help_web/live/mutual_aid_live_test.exs b/test/who_need_help_web/live/mutual_aid_live_test.exs index e1cf205..19c5480 100644 --- a/test/who_need_help_web/live/mutual_aid_live_test.exs +++ b/test/who_need_help_web/live/mutual_aid_live_test.exs @@ -1113,7 +1113,7 @@ defmodule WhoNeedHelpWeb.MutualAidLiveTest do {:ok, request} = Help.create_request(user_scope_fixture(requester), attrs) {:ok, assignment} = Help.accept_request(user_scope_fixture(helper), request.id) - {:ok, _message} = + {:ok, message} = Messaging.send_message(user_scope_fixture(helper), assignment, %{ "body" => "Evidence message" }) @@ -1122,7 +1122,7 @@ defmodule WhoNeedHelpWeb.MutualAidLiveTest do Trust.report(user_scope_fixture(requester), %{ "reason" => "harassment", "details" => "Please review the matched conversation.", - "assignment_id" => assignment.id + "message_id" => message.id }) {:ok, _proposal} = @@ -1140,6 +1140,11 @@ defmodule WhoNeedHelpWeb.MutualAidLiveTest do assert html =~ "Please review the matched conversation." assert html =~ "Bicycle repair" + assert has_element?( + view, + "form[phx-submit='hide-request'][phx-value-id='#{request.id}']" + ) + evidence_html = view |> element("button[phx-click='view-evidence']") @@ -1148,6 +1153,60 @@ defmodule WhoNeedHelpWeb.MutualAidLiveTest do assert evidence_html =~ "Evidence message" end + test "message reports expose parent activity moderation and switch to restore", %{ + conn: conn, + user: user, + scope: participant_scope + } do + user |> Ecto.Changeset.change(role: :moderator) |> Repo.update!() + organizer = user_fixture(display_name: "Activity organizer") + Catalog.seed_defaults() + activity_category = Catalog.list_categories(:activity) |> List.first() + + {:ok, activity} = + Activities.create_activity( + user_scope_fixture(organizer), + activity_attrs(activity_category) + ) + + {:ok, join_request} = Activities.request_to_join(participant_scope, activity.id) + + {:ok, _approved} = + Activities.approve_participant(user_scope_fixture(organizer), join_request.id) + + {:ok, message} = + Activities.send_message(user_scope_fixture(organizer), activity.id, %{ + "body" => "Reported activity message" + }) + + {:ok, _report} = + Trust.report(participant_scope, %{ + "reason" => "harassment", + "details" => "Please review this activity message.", + "activity_message_id" => message.id + }) + + {:ok, view, _html} = live(conn, ~p"/moderation") + + assert has_element?( + view, + "form[phx-submit='hide-activity'][phx-value-id='#{activity.id}']" + ) + + view + |> form("form[phx-submit='hide-activity'][phx-value-id='#{activity.id}']", %{ + "moderation" => %{"note" => "Unsafe public activity details"} + }) + |> render_submit() + + assert has_element?( + view, + "button[phx-click='restore-activity'][phx-value-id='#{activity.id}']" + ) + + assert Repo.get!(WhoNeedHelp.Activities.Activity, activity.id).hidden_at + end + test "abuse-signal pagination stays available without opening report evidence", %{ conn: conn, user: user