From f3a443138a0e1e6cf34fc593f5c174d56c21e904 Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Thu, 20 Apr 2023 11:30:10 +0200 Subject: [PATCH] fix(feeds): Only provide future events in ICS/Atom feeds Closes #1246 Signed-off-by: Thomas Citharel --- lib/mobilizon/events/events.ex | 68 ++++++++++++++++--- lib/service/export/common.ex | 4 +- test/web/controllers/feed_controller_test.exs | 30 +++++++- 3 files changed, 88 insertions(+), 14 deletions(-) diff --git a/lib/mobilizon/events/events.ex b/lib/mobilizon/events/events.ex index 6f109d8d..900b5620 100644 --- a/lib/mobilizon/events/events.ex +++ b/lib/mobilizon/events/events.ex @@ -418,12 +418,35 @@ defmodule Mobilizon.Events do do: list_organized_events_for_group(group, :public, nil, nil, page, limit) def list_public_events_for_actor(%Actor{id: actor_id}, page, limit) do + actor_id + |> do_list_public_events_for_actor() + |> Page.build_page(page, limit) + end + + @doc """ + Lists public upcoming events for the actor, with all associations loaded. + """ + @spec list_public_upcoming_events_for_actor(Actor.t(), integer | nil, integer | nil) :: + Page.t(Event.t()) + def list_public_upcoming_events_for_actor(actor, page \\ nil, limit \\ nil) + + def list_public_upcoming_events_for_actor(%Actor{type: :Group} = group, page, limit), + do: list_organized_events_for_group(group, :public, DateTime.utc_now(), nil, page, limit) + + def list_public_upcoming_events_for_actor(%Actor{id: actor_id}, page, limit) do + actor_id + |> do_list_public_events_for_actor() + |> event_filter_begins_on(DateTime.utc_now(), nil) + |> Page.build_page(page, limit) + end + + @spec do_list_public_events_for_actor(integer()) :: Ecto.Query.t() + defp do_list_public_events_for_actor(actor_id) do actor_id |> event_for_actor_query() |> filter_public_visibility() |> filter_draft() |> preload_for_event() - |> Page.build_page(page, limit) end @spec list_organized_events_for_actor(Actor.t(), integer | nil, integer | nil) :: @@ -905,6 +928,21 @@ defmodule Mobilizon.Events do |> Page.build_page(page, limit) end + @doc """ + Returns the list of upcoming participations for an actor. + """ + @spec list_upcoming_event_participations_for_actor(Actor.t(), integer | nil, integer | nil) :: + Page.t(Participant.t()) + def list_upcoming_event_participations_for_actor( + %Actor{id: actor_id}, + page \\ nil, + limit \\ nil + ) do + actor_id + |> event_participations_for_actor_query(DateTime.utc_now()) + |> Page.build_page(page, limit) + end + @doc """ Counts participant participants (participants with no extra role) """ @@ -1572,16 +1610,24 @@ defmodule Mobilizon.Events do from(p in Participant, where: p.event_id == ^event_id) end - @spec event_participations_for_actor_query(integer) :: Ecto.Query.t() - def event_participations_for_actor_query(actor_id) do - from( - p in Participant, - join: e in Event, - on: p.event_id == e.id, - where: p.actor_id == ^actor_id and p.role != ^:not_approved, - preload: [:event], - order_by: [desc: e.begins_on] - ) + @spec event_participations_for_actor_query(integer, DateTime.t() | nil) :: Ecto.Query.t() + defp event_participations_for_actor_query(actor_id, after_date \\ nil) + + defp event_participations_for_actor_query(actor_id, nil) do + do_event_participations_for_actor_query(actor_id) + end + + defp event_participations_for_actor_query(actor_id, %DateTime{} = after_date) do + actor_id + |> do_event_participations_for_actor_query() + |> where([_p, e], e.begins_on > ^after_date) + end + + defp do_event_participations_for_actor_query(actor_id) do + Participant + |> join(:inner, [p], e in Event, on: p.event_id == e.id) + |> where([p], p.actor_id == ^actor_id and p.role != ^:not_approved) + |> order_by([_p, e], desc: e.begins_on) end @spec sessions_for_event_query(integer) :: Ecto.Query.t() diff --git a/lib/service/export/common.ex b/lib/service/export/common.ex index f0dba59b..14da864d 100644 --- a/lib/service/export/common.ex +++ b/lib/service/export/common.ex @@ -17,7 +17,7 @@ defmodule Mobilizon.Service.Export.Common do case Actors.get_actor_by_name(name) do %Actor{} = actor -> if Actor.is_public_visibility?(actor) do - %Page{elements: events} = Events.list_public_events_for_actor(actor, 1, limit) + %Page{elements: events} = Events.list_public_upcoming_events_for_actor(actor, 1, limit) %Page{elements: posts} = Posts.get_public_posts_for_group(actor, 1, limit) {:ok, actor, events, posts} else @@ -108,7 +108,7 @@ defmodule Mobilizon.Service.Export.Common do @spec fetch_identity_participations(Actor.t(), integer()) :: Page.t(Participant.t()) defp fetch_identity_participations(%Actor{} = actor, limit) do - with %Page{} = page <- Events.list_event_participations_for_actor(actor, 1, limit) do + with %Page{} = page <- Events.list_upcoming_event_participations_for_actor(actor, 1, limit) do page end end diff --git a/test/web/controllers/feed_controller_test.exs b/test/web/controllers/feed_controller_test.exs index 8b8c549e..d8163cfc 100644 --- a/test/web/controllers/feed_controller_test.exs +++ b/test/web/controllers/feed_controller_test.exs @@ -16,6 +16,12 @@ defmodule Mobilizon.Web.FeedControllerTest do event1 = insert(:event, organizer_actor: actor, tags: [tag1]) event2 = insert(:event, organizer_actor: actor, tags: [tag1, tag2]) + event3 = + insert(:event, + organizer_actor: actor, + begins_on: DateTime.add(DateTime.utc_now(), -2, :day) + ) + conn = conn |> get( @@ -36,6 +42,7 @@ defmodule Mobilizon.Web.FeedControllerTest do Enum.each(entries, fn entry -> assert entry.title in [event1.title, event2.title] + refute entry.title == event3.title end) # It seems categories takes term instead of Label @@ -112,7 +119,15 @@ defmodule Mobilizon.Web.FeedControllerTest do attributed_to: group, tags: [tag1, tag2], title: "Event Two", - begins_on: DateTime.add(DateTime.utc_now(), 3_600 * 12 * 4) + begins_on: DateTime.add(DateTime.utc_now(), 4, :day) + ) + + event3 = + insert(:event, + organizer_actor: actor, + attributed_to: group, + title: "Event Three", + begins_on: DateTime.add(DateTime.utc_now(), -2, :day) ) conn = @@ -131,6 +146,7 @@ defmodule Mobilizon.Web.FeedControllerTest do Enum.each(entries, fn entry -> assert entry.summary in [event1.title, event2.title] + refute entry.summary == event3.title end) assert entry1.categories == [tag1.title] @@ -217,8 +233,10 @@ defmodule Mobilizon.Web.FeedControllerTest do actor2 = insert(:actor, user: user) event1 = insert(:event) event2 = insert(:event) + event3 = insert(:event, begins_on: DateTime.add(DateTime.utc_now(), -5, :day)) insert(:participant, event: event1, actor: actor1) insert(:participant, event: event2, actor: actor2) + insert(:participant, event: event3, actor: actor2) feed_token = insert(:feed_token, user: user, actor: nil) conn = @@ -240,6 +258,7 @@ defmodule Mobilizon.Web.FeedControllerTest do Enum.each(entries, fn entry -> assert entry.title in [event1.title, event2.title] + refute entry.title == event3.title end) end @@ -251,8 +270,10 @@ defmodule Mobilizon.Web.FeedControllerTest do actor2 = insert(:actor, user: user) event1 = insert(:event) event2 = insert(:event) + event3 = insert(:event, begins_on: DateTime.add(DateTime.utc_now(), -5, :day)) insert(:participant, event: event1, actor: actor1) insert(:participant, event: event2, actor: actor2) + insert(:participant, event: event3, actor: actor1) feed_token = insert(:feed_token, user: user, actor: actor1) conn = @@ -274,6 +295,7 @@ defmodule Mobilizon.Web.FeedControllerTest do [entry] = feed.entries assert entry.title == event1.title + refute entry.title == event3.title end test "it returns 404 for an not existing feed", %{conn: conn} do @@ -298,8 +320,10 @@ defmodule Mobilizon.Web.FeedControllerTest do actor2 = insert(:actor, user: user) event1 = insert(:event) event2 = insert(:event) + event3 = insert(:event, begins_on: DateTime.add(DateTime.utc_now(), -5, :day)) insert(:participant, event: event1, actor: actor1) insert(:participant, event: event2, actor: actor2) + insert(:participant, event: event3, actor: actor1) feed_token = insert(:feed_token, user: user, actor: nil) conn = @@ -318,6 +342,7 @@ defmodule Mobilizon.Web.FeedControllerTest do Enum.each(entries, fn entry -> assert entry.summary in [event1.title, event2.title] + refute entry.summary == event3.title end) end @@ -329,8 +354,10 @@ defmodule Mobilizon.Web.FeedControllerTest do actor2 = insert(:actor, user: user) event1 = insert(:event) event2 = insert(:event) + event3 = insert(:event, begins_on: DateTime.add(DateTime.utc_now(), -5, :day)) insert(:participant, event: event1, actor: actor1) insert(:participant, event: event2, actor: actor2) + insert(:participant, event: event3, actor: actor1) feed_token = insert(:feed_token, user: user, actor: actor1) conn = @@ -348,6 +375,7 @@ defmodule Mobilizon.Web.FeedControllerTest do [entry1] = ExIcal.parse(conn.resp_body) assert entry1.summary == event1.title assert entry1.categories == event1.tags |> Enum.map(& &1.title) + refute entry1.summary == event3.title end test "it returns 404 for an not existing feed", %{conn: conn} do