diff --git a/__tests__/__snapshots__/feeds.ts.snap b/__tests__/__snapshots__/feeds.ts.snap index a30611054..b469dd621 100644 --- a/__tests__/__snapshots__/feeds.ts.snap +++ b/__tests__/__snapshots__/feeds.ts.snap @@ -10,6 +10,7 @@ Object { "excludeSourceTypes": Array [], "excludeSources": Array [], "excludeTypes": Array [], + "excludeUsers": Array [], "followingSources": Array [], "followingUsers": Array [], "includeTags": Array [], @@ -27,6 +28,7 @@ Object { ], "excludeSources": Array [], "excludeTypes": Array [], + "excludeUsers": Array [], "followingSources": Array [], "followingUsers": Array [], "includeTags": Array [], @@ -42,6 +44,7 @@ Object { "excludeSourceTypes": Array [], "excludeSources": Array [], "excludeTypes": Array [], + "excludeUsers": Array [], "followingSources": Array [], "followingUsers": Array [], "includeTags": Array [], diff --git a/__tests__/feeds.ts b/__tests__/feeds.ts index 1827e7351..15d4e244a 100644 --- a/__tests__/feeds.ts +++ b/__tests__/feeds.ts @@ -394,6 +394,7 @@ describe('query anonymousFeed', () => { const filters = await feedToFilters(con, '1', '1'); delete filters.sourceIds; delete filters.excludeTypes; + delete filters.excludeUsers; const res = await client.query(QUERY, { variables: { ...variables, filters }, }); @@ -2960,6 +2961,35 @@ describe('function feedToFilters', () => { expect(filters.followingUsers).toEqual(['2', '3']); }); + it('should return filters having excluded users based on content preference', async () => { + loggedUser = '1'; + await saveAdvancedSettingsFiltersFixtures(); + await con.getRepository(ContentPreferenceUser).save([ + { + feedId: '1', + userId: '1', + status: ContentPreferenceStatus.Follow, + referenceId: '2', + }, + { + feedId: '1', + userId: '1', + status: ContentPreferenceStatus.Subscribed, + referenceId: '3', + }, + { + feedId: '1', + userId: '1', + status: ContentPreferenceStatus.Blocked, + referenceId: '4', + type: ContentPreferenceType.User, + }, + ]); + const filters = await feedToFilters(con, '1', '1'); + expect(filters.followingUsers).toEqual(['2', '3']); + expect(filters.excludeUsers).toEqual(['4']); + }); + it('should return filters having excluded content types based on advanced settings', async () => { loggedUser = '1'; await saveAdvancedSettingsFiltersFixtures(); diff --git a/__tests__/integrations/feed.ts b/__tests__/integrations/feed.ts index 71b811342..355974432 100644 --- a/__tests__/integrations/feed.ts +++ b/__tests__/integrations/feed.ts @@ -313,6 +313,7 @@ describe('FeedPreferencesConfigGenerator', () => { includeBlockedWords: true, includeFollowedUsers: true, includeFollowedSources: true, + includeBlockedUsers: true, }, ); @@ -327,6 +328,7 @@ describe('FeedPreferencesConfigGenerator', () => { blocked_sources: expect.arrayContaining(['a', 'b']), blocked_tags: expect.arrayContaining(['python', 'java']), blocked_title_words: expect.arrayContaining(['word-abc', 'word-def']), + blocked_author_ids: expect.arrayContaining(['4']), followed_sources: expect.arrayContaining(['c', 'p']), followed_user_ids: expect.arrayContaining(['2', '3']), allowed_post_types: postTypes.filter( diff --git a/__tests__/workers/__snapshots__/personalizedDigestEmail.ts.snap b/__tests__/workers/__snapshots__/personalizedDigestEmail.ts.snap index 3690d2897..e1e6ce3b7 100644 --- a/__tests__/workers/__snapshots__/personalizedDigestEmail.ts.snap +++ b/__tests__/workers/__snapshots__/personalizedDigestEmail.ts.snap @@ -207,6 +207,7 @@ Object { exports[`personalizedDigestEmail worker should generate personalized digest for user in timezone ahead UTC 2`] = ` Object { "allowed_tags": Array [], + "blocked_author_ids": Array [], "blocked_sources": Array [], "blocked_tags": Array [], "date_from": Any, @@ -328,6 +329,7 @@ Object { exports[`personalizedDigestEmail worker should generate personalized digest for user in timezone behind UTC 2`] = ` Object { "allowed_tags": Array [], + "blocked_author_ids": Array [], "blocked_sources": Array [], "blocked_tags": Array [], "date_from": Any, @@ -449,6 +451,7 @@ Object { exports[`personalizedDigestEmail worker should generate personalized digest for user with provided config 2`] = ` Object { "allowed_tags": Array [], + "blocked_author_ids": Array [], "blocked_sources": Array [], "blocked_tags": Array [], "date_from": Any, @@ -570,6 +573,7 @@ Object { exports[`personalizedDigestEmail worker should generate personalized digest for user with subscription 2`] = ` Object { "allowed_tags": Array [], + "blocked_author_ids": Array [], "blocked_sources": Array [], "blocked_tags": Array [], "date_from": Any, diff --git a/src/common/feedGenerator.ts b/src/common/feedGenerator.ts index 621e11a62..3a4871cd4 100644 --- a/src/common/feedGenerator.ts +++ b/src/common/feedGenerator.ts @@ -101,7 +101,7 @@ type RawFiltersData = { | null; tags: Pick[] | null; words: Pick[] | null; - users: Pick[] | null; + users: Pick[] | null; sources: Pick[] | null; memberships: { sourceId: SourceMember['sourceId']; hide: boolean }[] | null; feeds: Pick[] | null; @@ -136,12 +136,11 @@ const getRawFiltersData = async ( ), rawFilterSelect(con, 'users', (qb) => qb - .select('"referenceId"') + .select(['"referenceId"', 'status']) .from(ContentPreference, 'u') .where('"feedId" = $1') .andWhere('"userId" = $2') - .andWhere(`type = '${ContentPreferenceType.User}'`) - .andWhere(`status != '${ContentPreferenceStatus.Blocked}'`), + .andWhere(`type = '${ContentPreferenceType.User}'`), ), rawFilterSelect(con, 'sources', (qb) => qb @@ -253,15 +252,17 @@ const wordsToFilters = ({ const usersToFilters = ({ users, -}: RawFiltersData): { - followingUsers: string[]; -} => { +}: RawFiltersData): Record<'followingUsers' | 'excludeUsers', string[]> => { return (users || []).reduce>( - (acc, value) => { - acc.followingUsers.push(value.referenceId); + (acc, { referenceId, status }) => { + if (status === ContentPreferenceStatus.Blocked) { + acc.excludeUsers.push(referenceId); + } else { + acc.followingUsers.push(referenceId); + } return acc; }, - { followingUsers: [] }, + { followingUsers: [], excludeUsers: [] }, ); }; @@ -663,6 +664,7 @@ export interface AnonymousFeedFilters { blockedWords?: string[]; excludeSourceTypes?: string[]; followingUsers?: string[]; + excludeUsers?: string[]; followingSources?: string[]; flags?: FeedFlagsFilters; } diff --git a/src/common/personalizedDigest.ts b/src/common/personalizedDigest.ts index 44a6171ef..514214d1e 100644 --- a/src/common/personalizedDigest.ts +++ b/src/common/personalizedDigest.ts @@ -255,6 +255,7 @@ export const getPersonalizedDigestEmailPayload = async ({ ) || [], page_size: feature.maxPosts, total_pages: 1, + blocked_author_ids: feedConfig.excludeUsers, }; const feedResponse = await personalizedDigestFeedClient.fetchFeed( { log: logger }, diff --git a/src/integrations/feed/configs.ts b/src/integrations/feed/configs.ts index e4050e0c2..ff00ebce9 100644 --- a/src/integrations/feed/configs.ts +++ b/src/integrations/feed/configs.ts @@ -17,6 +17,7 @@ export type Options = { includeBlockedTags?: boolean; includeAllowedSources?: boolean; includeBlockedSources?: boolean; + includeBlockedUsers?: boolean; includeAllowedUsers?: boolean; includeSourceMemberships?: boolean; includePostTypes?: boolean; @@ -167,6 +168,13 @@ const addFiltersToConfig = ({ ); } + if (filters.excludeUsers?.length && opts.includeBlockedUsers) { + baseConfig.blocked_author_ids = mergeSingleFilter( + baseConfig.blocked_author_ids, + filters.excludeUsers, + ); + } + return baseConfig; }; diff --git a/src/integrations/feed/generators.ts b/src/integrations/feed/generators.ts index 2f9945e97..a264c818f 100644 --- a/src/integrations/feed/generators.ts +++ b/src/integrations/feed/generators.ts @@ -88,6 +88,7 @@ const opts: Options = { includeBlockedWords: true, includeFollowedSources: true, includeFollowedUsers: true, + includeBlockedUsers: true, }; export const feedGenerators: Partial> = @@ -113,6 +114,7 @@ export const feedGenerators: Partial> = includeBlockedTags: true, includeContentCuration: true, includeBlockedWords: true, + includeBlockedUsers: true, }, ), 'popular', diff --git a/src/integrations/feed/types.ts b/src/integrations/feed/types.ts index af2d63610..3e196ab2c 100644 --- a/src/integrations/feed/types.ts +++ b/src/integrations/feed/types.ts @@ -55,6 +55,7 @@ export type FeedConfig = { allowed_content_curations?: string[]; blocked_title_words?: string[]; allowed_author_ids?: string[]; + blocked_author_ids?: string[]; followed_user_ids?: string[]; followed_sources?: string[]; squad_ids?: string[]; diff --git a/src/schema/feeds.ts b/src/schema/feeds.ts index 040856931..7935ba5b7 100644 --- a/src/schema/feeds.ts +++ b/src/schema/feeds.ts @@ -1513,6 +1513,7 @@ export const resolvers: IResolvers = traceResolvers< includeContentCuration: true, includeBlockedWords: true, includeAllowedUsers: true, + includeBlockedUsers: true, includeAllowedSources: true, feedId: feedId, },