From a167d2ba972d76e805add199dc6ac06af4fbfffd Mon Sep 17 00:00:00 2001 From: SimpleTest Date: Thu, 23 Jul 2026 05:11:19 +0300 Subject: [PATCH] fix: preserve pair chat when helpers rejoin --- lib/who_need_help/messaging.ex | 16 +++++++- ...d_help_assignment_request_helper_index.exs | 7 ++++ test/who_need_help/mutual_aid_flow_test.exs | 37 +++++++++++++++++++ 3 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 priv/repo/migrations/20260723015032_add_help_assignment_request_helper_index.exs diff --git a/lib/who_need_help/messaging.ex b/lib/who_need_help/messaging.ex index 2e4796d..1da01d6 100644 --- a/lib/who_need_help/messaging.ex +++ b/lib/who_need_help/messaging.ex @@ -28,10 +28,11 @@ defmodule WhoNeedHelp.Messaging do limit = Pagination.limit(options, 50) cursor = Pagination.cursor(options) public_user = Accounts.public_user_query() + assignment_ids = conversation_assignment_ids(assignment) page = Message - |> where([message], message.assignment_id == ^assignment.id) + |> where([message], message.assignment_id in subquery(assignment_ids)) |> before_message(cursor) |> order_by([message], desc: message.inserted_at, desc: message.id) |> limit(^(limit + 1)) @@ -112,10 +113,11 @@ defmodule WhoNeedHelp.Messaging do if Trust.eligible?(scope) and Help.participant?(scope, assignment) and not blocked_assignment?(scope, assignment) do now = DateTime.utc_now(:second) + assignment_ids = conversation_assignment_ids(assignment) {count, _messages} = Message - |> where([message], message.assignment_id == ^assignment.id) + |> where([message], message.assignment_id in subquery(assignment_ids)) |> where([message], message.sender_id != ^user.id) |> where([message], is_nil(message.read_at)) |> Repo.update_all(set: [read_at: now, updated_at: now]) @@ -139,6 +141,16 @@ defmodule WhoNeedHelp.Messaging do Trust.blocked_between?(user.id, counterpart_id(user.id, assignment, request)) end + defp conversation_assignment_ids(%Assignment{ + request_id: request_id, + helper_id: helper_id + }) do + from(current in Assignment, + where: current.request_id == ^request_id and current.helper_id == ^helper_id, + select: current.id + ) + end + defp counterpart_id(user_id, assignment, request) do if user_id == assignment.helper_id, do: request.requester_id, else: assignment.helper_id end diff --git a/priv/repo/migrations/20260723015032_add_help_assignment_request_helper_index.exs b/priv/repo/migrations/20260723015032_add_help_assignment_request_helper_index.exs new file mode 100644 index 0000000..3417263 --- /dev/null +++ b/priv/repo/migrations/20260723015032_add_help_assignment_request_helper_index.exs @@ -0,0 +1,7 @@ +defmodule WhoNeedHelp.Repo.Migrations.AddHelpAssignmentRequestHelperIndex do + use Ecto.Migration + + def change do + create index(:help_assignments, [:request_id, :helper_id]) + end +end diff --git a/test/who_need_help/mutual_aid_flow_test.exs b/test/who_need_help/mutual_aid_flow_test.exs index bd6bdff..e8a51e7 100644 --- a/test/who_need_help/mutual_aid_flow_test.exs +++ b/test/who_need_help/mutual_aid_flow_test.exs @@ -269,6 +269,43 @@ defmodule WhoNeedHelp.MutualAidFlowTest do Messaging.send_message(outsider_scope, assignment, %{"body" => "not allowed"}) end + test "the same helper keeps pair chat after rejoining without exposing it to a replacement", + context do + replacement = user_fixture(display_name: "Replacement helper") + replacement_scope = user_scope_fixture(replacement) + + {:ok, request} = Help.create_request(context.requester_scope, context.request_attrs) + {:ok, first_assignment} = Help.accept_request(context.helper_scope, request.id) + + {:ok, first_message} = + Messaging.send_message(context.helper_scope, first_assignment, %{ + "body" => "I can collect this order." + }) + + assert {:ok, _withdrawn} = + Help.withdraw_assignment(context.helper_scope, first_assignment.id) + + assert {:ok, rejoined_assignment} = Help.accept_request(context.helper_scope, request.id) + assert rejoined_assignment.id != first_assignment.id + + assert [%{id: message_id, body: "I can collect this order."}] = + Messaging.list_messages(context.requester_scope, rejoined_assignment) + + assert message_id == first_message.id + + assert [%{id: ^message_id}] = + Messaging.list_messages(context.helper_scope, rejoined_assignment) + + assert {:ok, _withdrawn_again} = + Help.withdraw_assignment(context.helper_scope, rejoined_assignment.id) + + assert {:ok, replacement_assignment} = + Help.accept_request(replacement_scope, request.id) + + assert [] == Messaging.list_messages(context.requester_scope, replacement_assignment) + assert [] == Messaging.list_messages(replacement_scope, replacement_assignment) + end + test "cancelling in-progress help closes interaction and deletes live positions", context do {:ok, request} = Help.create_request(context.requester_scope, context.request_attrs) {:ok, assignment} = Help.accept_request(context.helper_scope, request.id)