Skip to content

Commit

Permalink
feat: Filter out notifications from blocked users (#2598)
Browse files Browse the repository at this point in the history
This should cover the following workers:

- articleNewCommentCommentedComment (what the hell is this name 😂)
- articleNewCommentPostCommented
- commentMention
- commentReply
- postAdded
- postAddedUserNotification
- postMention

I think these are all the workers that are triggered by another user
which are not specifically meant for admins/moderators. Let me know if I
missed any!

---------

Co-authored-by: Lee Hansel Solevilla <13744167+sshanzel@users.noreply.github.com>
  • Loading branch information
AmarTrebinjac and sshanzel authored Jan 16, 2025
1 parent b3f540b commit 7f9c7d1
Show file tree
Hide file tree
Showing 9 changed files with 122 additions and 4 deletions.
60 changes: 59 additions & 1 deletion __tests__/workers/notifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
User,
UserAction,
UserActionType,
UserNotification,
UserPost,
UserStreak,
} from '../../src/entity';
Expand All @@ -30,6 +31,7 @@ import createOrGetConnection from '../../src/db';
import { usersFixture, sourcesFixture, badUsersFixture } from '../fixture';
import { postsFixture } from '../fixture/post';
import {
generateAndStoreNotificationsV2,
NotificationBookmarkContext,
NotificationCommentContext,
NotificationCommenterContext,
Expand All @@ -54,7 +56,11 @@ import { generateStorageKey, StorageKey, StorageTopic } from '../../src/config';
import { ioRedisPool, setRedisObject } from '../../src/redis';
import { ReportReason } from '../../src/entity/common';
import { ContentPreferenceUser } from '../../src/entity/contentPreference/ContentPreferenceUser';
import { ContentPreferenceStatus } from '../../src/entity/contentPreference/types';
import {
ContentPreferenceStatus,
ContentPreferenceType,
} from '../../src/entity/contentPreference/types';
import { ContentPreference } from '../../src/entity/contentPreference/ContentPreference';

let con: DataSource;

Expand Down Expand Up @@ -1053,6 +1059,58 @@ describe('article new comment', () => {
expect(actual[0].ctx.userIds).toIncludeSameMembers(['1', '3']);
});

it('should filter out blocked users when generating notifications', async () => {
await con.getRepository(Feed).save({
id: '1',
userId: '1',
});

await con.getRepository(ContentPreference).save({
userId: '1',
feedId: '1',
status: ContentPreferenceStatus.Blocked,
type: ContentPreferenceType.User,
referenceId: '2',
});

await generateAndStoreNotificationsV2(con.manager, [
{
type: NotificationType.ArticleNewComment,
ctx: {
userIds: ['1', '3'],
initiatorId: '2',
commenter: {
id: '2',
name: 'Commenter',
},
comment: {
id: 'c1',
content: 'Comment',
},
post: {
id: 'p1',
authorId: '1',
scoutId: '3',
},
source: {
id: 'a',
type: SourceType.Squad,
},
},
},
]);

const notifications = await con
.getRepository(UserNotification)
.createQueryBuilder('un')
.innerJoinAndSelect('un.notification', 'n')
.where('n.type = :type', { type: NotificationType.ArticleNewComment })
.getMany();

expect(notifications).toHaveLength(1);
expect(notifications[0].userId).toEqual('3');
});

it('should add notification but ignore users with muted settings', async () => {
const worker = await import(
'../../src/workers/notifications/articleNewCommentCommentCommented'
Expand Down
55 changes: 53 additions & 2 deletions src/notifications/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,19 @@ import {
NotificationV2,
UserNotification,
} from '../entity';
import { DeepPartial, EntityManager, ObjectLiteral } from 'typeorm';
import { DeepPartial, EntityManager, In, ObjectLiteral } from 'typeorm';
import { NotificationBuilder } from './builder';
import { NotificationBaseContext, NotificationBundleV2 } from './types';
import { generateNotificationMap, notificationTitleMap } from './generate';
import { generateUserNotificationUniqueKey, NotificationType } from './common';
import { NotificationHandlerReturn } from '../workers/notifications/worker';
import { EntityTarget } from 'typeorm/common/EntityTarget';
import { logger } from '../logger';
import { ContentPreference } from '../entity/contentPreference/ContentPreference';
import {
ContentPreferenceStatus,
ContentPreferenceType,
} from '../entity/contentPreference/types';

export * from './types';

Expand Down Expand Up @@ -212,9 +217,55 @@ export async function generateAndStoreNotificationsV2(
if (!args) {
return;
}
const filteredArgs = [];

for (const arg of args) {
const { type, ctx } = arg;
if (!ctx.initiatorId) {
filteredArgs.push(arg);
continue;
}

const userIdChunks: string[][] = [];
for (let i = 0; i < ctx.userIds.length; i += 500) {
userIdChunks.push(ctx.userIds.slice(i, i + 500));
}

const contentPreferences: ContentPreference[] = [];

for (const chunk of userIdChunks) {
const preferences = await entityManager
.getRepository(ContentPreference)
.find({
where: {
feedId: In(chunk),
referenceId: ctx.initiatorId!,
status: ContentPreferenceStatus.Blocked,
type: ContentPreferenceType.User,
},
});
contentPreferences.push(...preferences);
}

const receivingUserIds = ctx.userIds.filter(
(id) => !contentPreferences.some((pref) => pref.userId === id),
);

if (receivingUserIds.length === 0) {
continue;
}

filteredArgs.push({
type,
ctx: {
...ctx,
userIds: receivingUserIds,
},
});
}

await Promise.all(
args.map(({ type, ctx }) => {
filteredArgs.map(({ type, ctx }) => {
const bundle = generateNotificationV2(type, ctx);
if (!bundle.userIds.length) {
return;
Expand Down
5 changes: 4 additions & 1 deletion src/notifications/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ export type NotificationBundleV2 = {
attachments?: DeepPartial<NotificationAttachmentV2>[];
};

export type NotificationBaseContext = { userIds: string[] };
export type NotificationBaseContext = {
userIds: string[];
initiatorId?: string | null;
};
export type NotificationSubmissionContext = NotificationBaseContext & {
submission: Pick<Submission, 'id'>;
};
Expand Down
1 change: 1 addition & 0 deletions src/workers/notifications/commentMention.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ const worker: NotificationWorker = {
userIds: [data.commentMention.mentionedUserId],
commenter,
comment,
initiatorId: commenter.id,
},
},
];
Expand Down
1 change: 1 addition & 0 deletions src/workers/notifications/commentReply.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ const worker: NotificationWorker = {
type,
ctx: {
...ctx,
initiatorId: commenter.id,
userIds: userIds.filter((id) =>
mutes.every(({ userId }) => userId !== id),
),
Expand Down
1 change: 1 addition & 0 deletions src/workers/notifications/postAdded.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ const worker: NotificationWorker = {
ctx: {
...baseCtx,
doneBy,
initiatorId: post.authorId,
userIds: members.map(({ userId }) => userId),
} as NotificationPostContext & Partial<NotificationDoneByContext>,
});
Expand Down
1 change: 1 addition & 0 deletions src/workers/notifications/postAddedUserNotification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export const postAddedUserNotification =
...baseCtx,
userIds: [],
user: item,
initiatorId: post.authorId,
},
});

Expand Down
1 change: 1 addition & 0 deletions src/workers/notifications/postMention.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const worker: NotificationWorker = {

const ctx: NotificationPostContext & NotificationDoneByContext = {
...postCtx,
initiatorId: mentionedByUserId,
userIds: [mentionedUserId],
doneBy,
doneTo,
Expand Down
1 change: 1 addition & 0 deletions src/workers/notifications/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ export async function articleNewCommentHandler(
userIds: users.filter((id) =>
muted.every(({ userId }) => userId !== id),
),
initiatorId: post.authorId,
},
},
];
Expand Down

0 comments on commit 7f9c7d1

Please sign in to comment.