From bf25d227861c2f6e5914379f981f32436372807d Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Fri, 11 Oct 2019 11:50:06 +0200 Subject: [PATCH] Make sure people can't join an event with limited participants Signed-off-by: Thomas Citharel --- lib/mobilizon/events/events.ex | 16 +++++ lib/mobilizon_web/resolvers/event.ex | 3 + lib/service/activity_pub/activity_pub.ex | 12 +++- .../resolvers/participant_resolver_test.exs | 70 +++++++++++++++++++ test/support/factory.ex | 3 +- 5 files changed, 101 insertions(+), 3 deletions(-) diff --git a/lib/mobilizon/events/events.ex b/lib/mobilizon/events/events.ex index 3bae79e1a..739c65a41 100644 --- a/lib/mobilizon/events/events.ex +++ b/lib/mobilizon/events/events.ex @@ -762,6 +762,17 @@ defmodule Mobilizon.Events do |> Repo.aggregate(:count, :id) end + @doc """ + Counts participant participants. + """ + @spec count_participant_participants(integer | String.t()) :: integer + def count_participant_participants(event_id) do + event_id + |> count_participants_query() + |> filter_participant_role() + |> Repo.aggregate(:count, :id) + end + @doc """ Counts unapproved participants. """ @@ -1457,6 +1468,11 @@ defmodule Mobilizon.Events do from(p in query, where: p.role not in ^[:not_approved, :rejected]) end + @spec filter_participant_role(Ecto.Query.t()) :: Ecto.Query.t() + defp filter_participant_role(query) do + from(p in query, where: p.role == ^:participant) + end + @spec filter_unapproved_role(Ecto.Query.t()) :: Ecto.Query.t() defp filter_unapproved_role(query) do from(p in query, where: p.role == ^:not_approved) diff --git a/lib/mobilizon_web/resolvers/event.ex b/lib/mobilizon_web/resolvers/event.ex index 9fb155bf3..7105e628f 100644 --- a/lib/mobilizon_web/resolvers/event.ex +++ b/lib/mobilizon_web/resolvers/event.ex @@ -170,6 +170,9 @@ defmodule MobilizonWeb.Resolvers.Event do |> Map.put(:actor, Person.proxify_pictures(actor)) do {:ok, participant} else + {:maximum_attendee_capacity, _} -> + {:error, "The event has already reached it's maximum capacity"} + {:has_event, _} -> {:error, "Event with this ID #{inspect(event_id)} doesn't exist"} diff --git a/lib/service/activity_pub/activity_pub.ex b/lib/service/activity_pub/activity_pub.ex index b98753c06..f3de44b05 100644 --- a/lib/service/activity_pub/activity_pub.ex +++ b/lib/service/activity_pub/activity_pub.ex @@ -411,8 +411,16 @@ defmodule Mobilizon.Service.ActivityPub do def join(object, actor, local \\ true) - def join(%Event{} = event, %Actor{} = actor, local) do - with role <- Mobilizon.Events.get_default_participant_role(event), + def join(%Event{options: options} = event, %Actor{} = actor, local) do + # TODO Refactor me for federation + with maximum_attendee_capacity <- + Map.get(options, :maximum_attendee_capacity, 2_000_000) || false, + {:maximum_attendee_capacity, true} <- + {:maximum_attendee_capacity, + !maximum_attendee_capacity || + Mobilizon.Events.count_participant_participants(event.id) < + maximum_attendee_capacity}, + role <- Mobilizon.Events.get_default_participant_role(event), {:ok, %Participant{} = participant} <- Mobilizon.Events.create_participant(%{ role: role, diff --git a/test/mobilizon_web/resolvers/participant_resolver_test.exs b/test/mobilizon_web/resolvers/participant_resolver_test.exs index c0e69ea2f..bd195d47c 100644 --- a/test/mobilizon_web/resolvers/participant_resolver_test.exs +++ b/test/mobilizon_web/resolvers/participant_resolver_test.exs @@ -74,6 +74,76 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do assert hd(json_response(res, 200)["errors"])["message"] =~ "already a participant" end + test "actor_join_event/3 doesn't work if the event already has too much participants", %{ + conn: conn, + actor: actor + } do + event = insert(:event, options: %{maximum_attendee_capacity: 2}) + insert(:participant, event: event, actor: actor, role: :creator) + insert(:participant, event: event, role: :participant) + insert(:participant, event: event, role: :not_approved) + insert(:participant, event: event, role: :rejected) + user_participant = insert(:user) + actor_participant = insert(:actor, user: user_participant) + + mutation = """ + mutation { + joinEvent( + actor_id: #{actor_participant.id}, + event_id: #{event.id} + ) { + role, + actor { + id + }, + event { + id + } + } + } + """ + + res = + conn + |> auth_conn(user_participant) + |> post("/api", AbsintheHelpers.mutation_skeleton(mutation)) + + assert json_response(res, 200)["errors"] == nil + assert json_response(res, 200)["data"]["joinEvent"]["role"] == "PARTICIPANT" + assert json_response(res, 200)["data"]["joinEvent"]["event"]["id"] == to_string(event.id) + + assert json_response(res, 200)["data"]["joinEvent"]["actor"]["id"] == + to_string(actor_participant.id) + + user_participant_2 = insert(:user) + actor_participant_2 = insert(:actor, user: user_participant_2) + + mutation = """ + mutation { + joinEvent( + actor_id: #{actor_participant_2.id}, + event_id: #{event.id} + ) { + role, + actor { + id + }, + event { + id + } + } + } + """ + + res = + conn + |> auth_conn(user_participant_2) + |> post("/api", AbsintheHelpers.mutation_skeleton(mutation)) + + assert hd(json_response(res, 200)["errors"])["message"] == + "The event has already reached it's maximum capacity" + end + test "actor_join_event/3 should check the actor is owned by the user", %{ conn: conn, user: user diff --git a/test/support/factory.ex b/test/support/factory.ex index c866ca6c4..7e7c8597f 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -124,7 +124,8 @@ defmodule Mobilizon.Factory do url: Routes.page_url(Endpoint, :event, uuid), picture: insert(:picture), uuid: uuid, - join_options: :free + join_options: :free, + options: %{} } end