diff --git a/server/channels/store/searchtest/post_layer.go b/server/channels/store/searchtest/post_layer.go index 37475097a1..f28196ad10 100644 --- a/server/channels/store/searchtest/post_layer.go +++ b/server/channels/store/searchtest/post_layer.go @@ -280,6 +280,11 @@ var searchPostStoreTests = []searchTest{ Fn: testSearchAcrossTeams, Tags: []string{EngineAll}, }, + { + Name: "Should search across teams with from filter", + Fn: testSearchAcrossTeamsWithFromFilter, + Tags: []string{EngineAll}, + }, { Name: "Should be removed from search index when deleted", Fn: testSearchPostDeleted, @@ -2025,6 +2030,60 @@ func testSearchAcrossTeams(t *testing.T, th *SearchTestHelper) { require.Len(t, results.Posts, 2) } +func testSearchAcrossTeamsWithFromFilter(t *testing.T, th *SearchTestHelper) { + err := th.addUserToChannels(th.User, []string{th.ChannelAnotherTeam.Id}) + require.NoError(t, err) + defer th.Store.Channel().RemoveMember(th.Context, th.ChannelAnotherTeam.Id, th.User.Id) + + err = th.addUserToChannels(th.User2, []string{th.ChannelAnotherTeam.Id}) + require.NoError(t, err) + defer th.Store.Channel().RemoveMember(th.Context, th.ChannelAnotherTeam.Id, th.User2.Id) + + // Create posts from different users in different teams + p1, err := th.createPost(th.User.Id, th.ChannelAnotherTeam.Id, "cross team search test", "", model.PostTypeDefault, 0, false) + require.NoError(t, err) + defer th.deleteUserPosts(th.User.Id) + + _, err = th.createPost(th.User2.Id, th.ChannelAnotherTeam.Id, "cross team search test", "", model.PostTypeDefault, 0, false) + require.NoError(t, err) + defer th.deleteUserPosts(th.User2.Id) + + p3, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "cross team search test", "", model.PostTypeDefault, 0, false) + require.NoError(t, err) + + _, err = th.createPost(th.User2.Id, th.ChannelBasic.Id, "cross team search test", "", model.PostTypeDefault, 0, false) + require.NoError(t, err) + + t.Run("Cross-team search with from filter should work", func(t *testing.T) { + // Cross-team search with from filter (empty teamId) + params := &model.SearchParams{ + Terms: "search test", + FromUsers: []string{th.User.Id}, + } + results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, "", 0, 20) + require.NoError(t, err) + + // Should find both posts from th.User across teams + require.Len(t, results.Posts, 2) + th.checkPostInSearchResults(t, p1.Id, results.Posts) + th.checkPostInSearchResults(t, p3.Id, results.Posts) + }) + + t.Run("Team-scoped search with from filter should still work", func(t *testing.T) { + // Team-scoped search with from filter (specific teamId) + params := &model.SearchParams{ + Terms: "search test", + FromUsers: []string{th.User.Id}, + } + results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20) + require.NoError(t, err) + + // Should find only the post from th.User in th.Team + require.Len(t, results.Posts, 1) + th.checkPostInSearchResults(t, p3.Id, results.Posts) + }) +} + func testSearchPostDeleted(t *testing.T, th *SearchTestHelper) { t.Run("Search for soft deleted post", func(t *testing.T) { p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "message to delete", "", model.PostTypeDefault, 0, false) diff --git a/server/channels/store/sqlstore/post_store.go b/server/channels/store/sqlstore/post_store.go index 51c84636ab..c4bf9d34e3 100644 --- a/server/channels/store/sqlstore/post_store.go +++ b/server/channels/store/sqlstore/post_store.go @@ -2079,11 +2079,18 @@ func (s *SqlPostStore) buildSearchPostFilterClause(teamID string, fromUsers []st } // Sub-query builder. - sb := s.getSubQueryBuilder().Select("Id").From("Users, TeamMembers").Where( - sq.And{ - sq.Eq{"TeamMembers.TeamId": teamID}, - sq.Expr("Users.Id = TeamMembers.UserId"), - }) + sb := s.getSubQueryBuilder().Select("Id") + if teamID == "" { + // Cross-team search: don't filter by team membership + sb = sb.From("Users") + } else { + // Team-scoped search: filter by team membership + sb = sb.From("Users, TeamMembers").Where( + sq.And{ + sq.Eq{"TeamMembers.TeamId": teamID}, + sq.Expr("Users.Id = TeamMembers.UserId"), + }) + } sb = s.buildSearchUserFilterClause(fromUsers, false, userByUsername, sb) sb = s.buildSearchUserFilterClause(excludedUsers, true, userByUsername, sb) subQuery, subQueryArgs, err := sb.ToSql()