Skip to content

Commit faf1fca

Browse files
authored
Merge pull request #9164 from Couchers-org/na/backend/disc-events-exclude-attending
Add exclude_attending to event search query
2 parents 3e7baf1 + 588527d commit faf1fca

3 files changed

Lines changed: 92 additions & 6 deletions

File tree

app/backend/src/couchers/servicers/search.py

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -787,6 +787,10 @@ def _user_to_search_user(user_id: int) -> search_pb2.SearchUser:
787787
def EventSearch(
788788
self, request: search_pb2.EventSearchReq, context: CouchersContext, session: Session
789789
) -> search_pb2.EventSearchRes:
790+
if request.attending and request.exclude_attending:
791+
context.abort_with_error_code(
792+
grpc.StatusCode.INVALID_ARGUMENT, "cannot_combine_attending_and_exclude_attending"
793+
)
790794
statement = (
791795
select(EventOccurrence).join(Event, Event.id == EventOccurrence.event_id).where(~EventOccurrence.is_deleted)
792796
)
@@ -809,7 +813,13 @@ def EventSearch(
809813
elif request.only_offline:
810814
statement = statement.where(EventOccurrence.geom != None)
811815

812-
if request.subscribed or request.attending or request.organizing or request.my_communities:
816+
if (
817+
request.subscribed
818+
or request.attending
819+
or request.organizing
820+
or request.my_communities
821+
or request.exclude_attending
822+
):
813823
where_ = []
814824

815825
if request.subscribed:
@@ -818,21 +828,34 @@ def EventSearch(
818828
and_(EventSubscription.event_id == Event.id, EventSubscription.user_id == context.user_id),
819829
)
820830
where_.append(EventSubscription.user_id != None)
821-
if request.organizing:
831+
if request.organizing or request.attending:
832+
if request.organizing:
833+
statement = statement.outerjoin(
834+
EventOrganizer,
835+
and_(EventOrganizer.event_id == Event.id, EventOrganizer.user_id == context.user_id),
836+
)
837+
where_.append(EventOrganizer.user_id != None)
838+
if request.attending:
839+
statement = statement.outerjoin(
840+
EventOccurrenceAttendee,
841+
and_(
842+
EventOccurrenceAttendee.occurrence_id == EventOccurrence.id,
843+
EventOccurrenceAttendee.user_id == context.user_id,
844+
),
845+
)
846+
where_.append(EventOccurrenceAttendee.user_id != None)
847+
elif request.exclude_attending:
822848
statement = statement.outerjoin(
823849
EventOrganizer,
824850
and_(EventOrganizer.event_id == Event.id, EventOrganizer.user_id == context.user_id),
825851
)
826-
where_.append(EventOrganizer.user_id != None)
827-
if request.attending:
828852
statement = statement.outerjoin(
829853
EventOccurrenceAttendee,
830854
and_(
831855
EventOccurrenceAttendee.occurrence_id == EventOccurrence.id,
832856
EventOccurrenceAttendee.user_id == context.user_id,
833857
),
834858
)
835-
where_.append(EventOccurrenceAttendee.user_id != None)
836859
if request.my_communities:
837860
my_communities = (
838861
session.execute(
@@ -849,7 +872,11 @@ def EventSearch(
849872
)
850873
where_.append(Event.parent_node_id.in_(my_communities))
851874

852-
statement = statement.where(or_(*where_))
875+
if where_:
876+
statement = statement.where(or_(*where_))
877+
878+
if request.exclude_attending:
879+
statement = statement.where(EventOccurrenceAttendee.user_id == None, EventOrganizer.user_id == None)
853880

854881
if not request.include_cancelled:
855882
statement = statement.where(~EventOccurrence.is_cancelled)

app/backend/src/tests/test_search.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from datetime import timedelta
22
from typing import Any
33

4+
import grpc
45
import pytest
56
from google.protobuf import empty_pb2, wrappers_pb2
67
from sqlalchemy import select
@@ -751,6 +752,62 @@ def test_event_search_filter_subscription_attendance_organizing_my_communities(
751752
assert {event.title for event in res.events} == {"Subscribed event", "Attending event", "Organized event"}
752753

753754

755+
def test_event_search_exclude_attending(sample_community, create_event, moderator: Moderator):
756+
"""Test that exclude_attending removes events the user is attending or organizing."""
757+
user, token = generate_user()
758+
other_user, other_token = generate_user()
759+
760+
with communities_session(token) as api:
761+
api.JoinCommunity(communities_pb2.JoinCommunityReq(community_id=sample_community))
762+
763+
with session_scope() as session:
764+
create_community(session, 55, 60, "Other community", [other_user], [], None)
765+
766+
with events_session(other_token) as api:
767+
e_attending = create_event(api, title="Attending event")
768+
e_community_only = create_event(api, title="Community only event")
769+
create_event(
770+
api,
771+
title="Other community event",
772+
offline_information=events_pb2.OfflineEventInformation(lat=58, lng=1, address="Somewhere"),
773+
)
774+
775+
with session_scope() as session:
776+
occurrence_ids = session.execute(select(EventOccurrence.id)).scalars().all()
777+
for oid in occurrence_ids:
778+
moderator.approve_event_occurrence(oid)
779+
780+
with events_session(token) as api:
781+
e_organized = create_event(api, title="Organized event")
782+
api.SetEventAttendance(
783+
events_pb2.SetEventAttendanceReq(
784+
event_id=e_attending.event_id, attendance_state=events_pb2.ATTENDANCE_STATE_GOING
785+
)
786+
)
787+
788+
with search_session(token) as api:
789+
# baseline: my_communities returns all community events including attended/organized
790+
res = api.EventSearch(search_pb2.EventSearchReq(my_communities=True))
791+
assert {event.title for event in res.events} == {
792+
"Attending event",
793+
"Community only event",
794+
"Organized event",
795+
}
796+
797+
# my_communities + exclude_attending: drops attended and organized events
798+
res = api.EventSearch(search_pb2.EventSearchReq(my_communities=True, exclude_attending=True))
799+
assert {event.title for event in res.events} == {"Community only event"}
800+
801+
# exclude_attending alone (no other filter = all events): drops attended and organized
802+
res = api.EventSearch(search_pb2.EventSearchReq(exclude_attending=True))
803+
assert {event.title for event in res.events} == {"Community only event", "Other community event"}
804+
805+
# attending + exclude_attending is invalid
806+
with pytest.raises(grpc.RpcError) as e:
807+
api.EventSearch(search_pb2.EventSearchReq(attending=True, exclude_attending=True))
808+
assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
809+
810+
754811
def test_regression_search_multiple_pages(db):
755812
"""
756813
There was a bug when there are multiple pages of results

app/proto/search.proto

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,8 @@ message EventSearchReq {
263263
string page_token = 11;
264264
uint32 page_number = 18;
265265
}
266+
// exclude events the user is already attending or organizing (useful with my_communities)
267+
bool exclude_attending = 19;
266268
}
267269

268270
message EventSearchRes {

0 commit comments

Comments
 (0)