diff --git a/packages/common/src/store/pages/settings/reducer.ts b/packages/common/src/store/pages/settings/reducer.ts index a35869a7a77..fa9d6e9be17 100644 --- a/packages/common/src/store/pages/settings/reducer.ts +++ b/packages/common/src/store/pages/settings/reducer.ts @@ -31,7 +31,8 @@ export const initialState = { [BrowserNotificationSetting.Favorites]: true, [BrowserNotificationSetting.Remixes]: true, [BrowserNotificationSetting.Messages]: true, - [BrowserNotificationSetting.Comments]: true + [BrowserNotificationSetting.Comments]: true, + [BrowserNotificationSetting.Mentions]: true }, pushNotifications: { [PushNotificationSetting.MobilePush]: true, @@ -41,7 +42,8 @@ export const initialState = { [PushNotificationSetting.Remixes]: true, [PushNotificationSetting.Favorites]: true, [PushNotificationSetting.Messages]: true, - [BrowserNotificationSetting.Comments]: true + [PushNotificationSetting.Comments]: true, + [PushNotificationSetting.Mentions]: true }, [emailFrequency]: EmailFrequency.Daily } @@ -60,7 +62,8 @@ const actionsMap: ActionsMap = { [BrowserNotificationSetting.Favorites]: action.settings.favorites, [BrowserNotificationSetting.Remixes]: action.settings.remixes, [BrowserNotificationSetting.Messages]: action.settings.messages, - [BrowserNotificationSetting.Comments]: action.settings.comments + [BrowserNotificationSetting.Comments]: action.settings.comments, + [BrowserNotificationSetting.Mentions]: action.settings.mentions } } }, @@ -78,7 +81,8 @@ const actionsMap: ActionsMap = { [PushNotificationSetting.Remixes]: action.settings.remixes, [PushNotificationSetting.Favorites]: action.settings.favorites, [PushNotificationSetting.Messages]: action.settings.messages, - [PushNotificationSetting.Comments]: action.settings.comments + [PushNotificationSetting.Comments]: action.settings.comments, + [PushNotificationSetting.Mentions]: action.settings.mentions } } }, diff --git a/packages/common/src/store/pages/settings/types.ts b/packages/common/src/store/pages/settings/types.ts index 03375a66d06..3f046b36f97 100644 --- a/packages/common/src/store/pages/settings/types.ts +++ b/packages/common/src/store/pages/settings/types.ts @@ -9,7 +9,8 @@ export enum BrowserNotificationSetting { Permission = 'permission', Remixes = 'remixes', Messages = 'messages', - Comments = 'comments' + Comments = 'comments', + Mentions = 'mentions' } export enum PushNotificationSetting { @@ -20,7 +21,8 @@ export enum PushNotificationSetting { Favorites = 'favorites', Remixes = 'remixes', Messages = 'messages', - Comments = 'comments' + Comments = 'comments', + Mentions = 'mentions' } export enum EmailFrequency { @@ -42,6 +44,7 @@ export type Notifications = { [BrowserNotificationSetting.Remixes]: boolean [BrowserNotificationSetting.Messages]: boolean [BrowserNotificationSetting.Comments]: boolean + [BrowserNotificationSetting.Mentions]: boolean } export type PushNotifications = { @@ -53,6 +56,7 @@ export type PushNotifications = { [PushNotificationSetting.Remixes]: boolean [PushNotificationSetting.Messages]: boolean [PushNotificationSetting.Comments]: boolean + [PushNotificationSetting.Mentions]: boolean } export enum Cast { diff --git a/packages/discovery-provider/plugins/notifications/scripts/test-push-notification.ts b/packages/discovery-provider/plugins/notifications/scripts/test-push-notification.ts index 7c9e756fd01..f3fdb7012b0 100644 --- a/packages/discovery-provider/plugins/notifications/scripts/test-push-notification.ts +++ b/packages/discovery-provider/plugins/notifications/scripts/test-push-notification.ts @@ -34,7 +34,10 @@ export const main = async () => { message: 'type', name: 'type', type: 'list', - choices: [{ title: 'ios', value: 'ios' }, { title: 'android', value: 'android' }], + choices: [ + { title: 'ios', value: 'ios' }, + { title: 'android', value: 'android' } + ], initial: 'ios' }, { message: 'title', name: 'title', type: 'text', initial: 'test message' }, diff --git a/packages/discovery-provider/plugins/notifications/src/__tests__/mappings/__snapshots__/commentMention.test.ts.snap b/packages/discovery-provider/plugins/notifications/src/__tests__/mappings/__snapshots__/commentMention.test.ts.snap new file mode 100644 index 00000000000..9c039fa7d5f --- /dev/null +++ b/packages/discovery-provider/plugins/notifications/src/__tests__/mappings/__snapshots__/commentMention.test.ts.snap @@ -0,0 +1,1185 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Comment Mention Notification Render a multi comment email 1`] = ` +"
+ +
Daily Email - joey@audius.co

user_3 tagged you in a comment on user_1's track track_title_1 + ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ + + ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ + + ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ + + ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ + + ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ + + ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ +

Audius Logo
What You Missed

3 unread notifications from May 13th 2020

user_3user_4user_5
user_3 and 2 others tagged you in a comment on user_1's track track_title_1
Open AudiusArrow Right
See more on Audius
instagramtwitterdiscord
Made with ♥︎ in SF & LA
© 2024 Audius, Inc. All Rights Reserved.
Tired of seeing these emails? Update your notification preferences
" +`; + +exports[`Comment Mention Notification Render a single email 1`] = ` +"
+ +
Daily Email - joey@audius.co

user_1 tagged you in a comment on user_1's track track_title_1 + ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ + + ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ + + ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ + + ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ + + ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ + + ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ +

Audius Logo
What You Missed

1 unread notification from May 13th 2020

user_1 tagged you in a comment on user_1's track track_title_1
Open AudiusArrow Right
See more on Audius
instagramtwitterdiscord
Made with ♥︎ in SF & LA
© 2024 Audius, Inc. All Rights Reserved.
Tired of seeing these emails? Update your notification preferences
" +`; diff --git a/packages/discovery-provider/plugins/notifications/src/__tests__/mappings/__snapshots__/commentThread.test.ts.snap b/packages/discovery-provider/plugins/notifications/src/__tests__/mappings/__snapshots__/commentThread.test.ts.snap new file mode 100644 index 00000000000..5b774ac0781 --- /dev/null +++ b/packages/discovery-provider/plugins/notifications/src/__tests__/mappings/__snapshots__/commentThread.test.ts.snap @@ -0,0 +1,1185 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Comment Thread Notification Render a multi comment email 1`] = ` +"
+ +
Daily Email - joey@audius.co

user_3 replied to your comment on your track track_title_1 + ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ + + ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ + + ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ + + ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ + + ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ + + ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ +

Audius Logo
What You Missed

3 unread notifications from May 13th 2020

user_3user_4user_5
user_3 and 2 others replied to your comment on your track track_title_1
Open AudiusArrow Right
See more on Audius
instagramtwitterdiscord
Made with ♥︎ in SF & LA
© 2024 Audius, Inc. All Rights Reserved.
Tired of seeing these emails? Update your notification preferences
" +`; + +exports[`Comment Thread Notification Render a single email 1`] = ` +"
+ +
Daily Email - joey@audius.co

user_2 replied to your comment on your track track_title_1 + ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ + + ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ + + ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ + + ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ + + ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ + + ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ +

Audius Logo
What You Missed

1 unread notification from May 13th 2020

user_2 replied to your comment on your track track_title_1
Open AudiusArrow Right
See more on Audius
instagramtwitterdiscord
Made with ♥︎ in SF & LA
© 2024 Audius, Inc. All Rights Reserved.
Tired of seeing these emails? Update your notification preferences
" +`; diff --git a/packages/discovery-provider/plugins/notifications/src/__tests__/mappings/commentMention.test.ts b/packages/discovery-provider/plugins/notifications/src/__tests__/mappings/commentMention.test.ts new file mode 100644 index 00000000000..77f61554d7d --- /dev/null +++ b/packages/discovery-provider/plugins/notifications/src/__tests__/mappings/commentMention.test.ts @@ -0,0 +1,187 @@ +import { expect, jest, test } from '@jest/globals' +import { Processor } from '../../main' +import * as sns from '../../sns' + +import { + createUsers, + insertMobileDevices, + insertMobileSettings, + createTracks, + setupTest, + resetTests, + createComments, + createCommentMentions +} from '../../utils/populateDB' + +import { AppEmailNotification } from '../../types/notifications' +import { renderEmail } from '../../email/notifications/renderEmail' +import { commenttype } from '../../types/dn' +import { EntityType } from '../../email/notifications/types' + +describe('Comment Mention Notification', () => { + let processor: Processor + + const sendPushNotificationSpy = jest + .spyOn(sns, 'sendPushNotification') + .mockImplementation(() => Promise.resolve({ endpointDisabled: false })) + + beforeEach(async () => { + const setup = await setupTest() + processor = setup.processor + }) + + afterEach(async () => { + await resetTests(processor) + }) + + test('Process push notification for comment mention on others track', async () => { + await createUsers(processor.discoveryDB, [{ user_id: 1 }, { user_id: 2 }]) + await createTracks(processor.discoveryDB, [{ track_id: 1, owner_id: 1 }]) + await createComments(processor.discoveryDB, [ + { + comment_id: 1, + user_id: 1, + entity_id: 1, + entity_type: commenttype.track + } + ]) + await createCommentMentions(processor.discoveryDB, [ + { comment_id: 1, user_id: 2 } + ]) + await insertMobileSettings(processor.identityDB, [{ userId: 2 }]) + await insertMobileDevices(processor.identityDB, [{ userId: 2 }]) + await new Promise((resolve) => setTimeout(resolve, 10)) + const pending = processor.listener.takePending() + expect(pending?.appNotifications).toHaveLength(1) + // Assert single pending + await processor.appNotificationsProcessor.process(pending.appNotifications) + + expect(sendPushNotificationSpy).toHaveBeenCalledWith( + { + type: 'ios', + targetARN: 'arn:2', + badgeCount: 1 + }, + { + title: 'New Mention', + body: "user_1 tagged you in a comment on user_1's track track_title_1", + data: { + id: 'timestamp:1589373217:group_id:comment_mention:1:type:Track', + type: 'CommentMention', + userIds: [1] + } + } + ) + }) + + test('Render a single email', async () => { + await createUsers(processor.discoveryDB, [{ user_id: 1 }, { user_id: 2 }]) + await createTracks(processor.discoveryDB, [{ track_id: 1, owner_id: 1 }]) + await createComments(processor.discoveryDB, [ + { + comment_id: 1, + user_id: 1, + entity_id: 1, + entity_type: commenttype.track + } + ]) + + await createCommentMentions(processor.discoveryDB, [ + { comment_id: 1, user_id: 2 } + ]) + + await new Promise((resolve) => setTimeout(resolve, 10)) + + const notifications: AppEmailNotification[] = [ + { + type: 'comment_mention', + timestamp: new Date(), + specifier: '1', + group_id: 'comment_mention:1:type:Track', + data: { + type: EntityType.Track, + entity_id: 1, + entity_user_id: 1, + comment_user_id: 1 + }, + user_ids: [2], + receiver_user_id: 2 + } + ] + const notifHtml = await renderEmail({ + userId: 1, + email: 'joey@audius.co', + frequency: 'daily', + notifications, + dnDb: processor.discoveryDB, + identityDb: processor.identityDB + }) + expect(notifHtml).toMatchSnapshot() + }) + + test('Render a multi comment email', async () => { + await createUsers(processor.discoveryDB, [ + { user_id: 1 }, + { user_id: 2 }, + { user_id: 3 }, + { user_id: 4 }, + { user_id: 5 } + ]) + await createTracks(processor.discoveryDB, [{ track_id: 1, owner_id: 1 }]) + + await createComments(processor.discoveryDB, [ + { + comment_id: 1, + user_id: 3, + entity_id: 1, + entity_type: commenttype.track + }, + { + comment_id: 2, + user_id: 4, + entity_id: 1, + entity_type: commenttype.track + }, + { + comment_id: 3, + user_id: 5, + entity_id: 1, + entity_type: commenttype.track + } + ]) + + await createCommentMentions(processor.discoveryDB, [ + { comment_id: 1, user_id: 2 }, + { comment_id: 2, user_id: 2 }, + { comment_id: 3, user_id: 2 } + ]) + + const notifications: AppEmailNotification[] = Array.from( + new Array(3), + (_, num) => ({ + type: 'comment_mention', + timestamp: new Date(), + specifier: (num + 3).toString(), + group_id: 'comment_mention:1:type:Track', + data: { + type: EntityType.Track, + comment_user_id: num + 3, + entity_id: 1, + entity_user_id: 1 + }, + user_ids: [2], + receiver_user_id: 2 + }) + ) + + const notifHtml = await renderEmail({ + userId: 1, + email: 'joey@audius.co', + frequency: 'daily', + notifications, + dnDb: processor.discoveryDB, + identityDb: processor.identityDB + }) + expect(notifHtml).toMatchSnapshot() + }) +}) diff --git a/packages/discovery-provider/plugins/notifications/src/__tests__/mappings/commentThread.test.ts b/packages/discovery-provider/plugins/notifications/src/__tests__/mappings/commentThread.test.ts new file mode 100644 index 00000000000..4b8f3c792c6 --- /dev/null +++ b/packages/discovery-provider/plugins/notifications/src/__tests__/mappings/commentThread.test.ts @@ -0,0 +1,262 @@ +import { expect, jest, test } from '@jest/globals' +import { Processor } from '../../main' +import * as sns from '../../sns' + +import { + createUsers, + insertMobileDevices, + insertMobileSettings, + createTracks, + setupTest, + resetTests, + createComments, + createCommentThreads +} from '../../utils/populateDB' + +import { AppEmailNotification } from '../../types/notifications' +import { renderEmail } from '../../email/notifications/renderEmail' +import { commenttype } from '../../types/dn' +import { EntityType } from '../../email/notifications/types' + +describe('Comment Thread Notification', () => { + let processor: Processor + + const sendPushNotificationSpy = jest + .spyOn(sns, 'sendPushNotification') + .mockImplementation(() => Promise.resolve({ endpointDisabled: false })) + + beforeEach(async () => { + const setup = await setupTest() + processor = setup.processor + }) + + afterEach(async () => { + await resetTests(processor) + }) + + test('Process push notification for comment thread on your track', async () => { + await createUsers(processor.discoveryDB, [{ user_id: 1 }, { user_id: 2 }]) + await createTracks(processor.discoveryDB, [{ track_id: 1, owner_id: 1 }]) + await createComments(processor.discoveryDB, [ + { + comment_id: 1, + user_id: 1, + entity_id: 1, + entity_type: commenttype.track + }, + { + comment_id: 2, + user_id: 2, + entity_id: 1, + entity_type: commenttype.track + } + ]) + await createCommentThreads(processor.discoveryDB, [ + { + comment_id: 2, + parent_comment_id: 1 + } + ]) + await insertMobileSettings(processor.identityDB, [{ userId: 1 }]) + await insertMobileDevices(processor.identityDB, [{ userId: 1 }]) + await new Promise((resolve) => setTimeout(resolve, 10)) + const pending = processor.listener.takePending() + expect(pending?.appNotifications).toHaveLength(2) + // Assert single pending + await processor.appNotificationsProcessor.process(pending.appNotifications) + + expect(sendPushNotificationSpy).toHaveBeenNthCalledWith( + 2, + { + type: 'ios', + targetARN: 'arn:1', + badgeCount: 2 + }, + { + title: 'New Reply', + body: 'user_2 replied to your comment on your track track_title_1', + data: { + id: 'timestamp:1589373217:group_id:comment_thread:1', + type: 'CommentThread', + userIds: [2] + } + } + ) + }) + + test('Process push notification for comment thread on others track', async () => { + await createUsers(processor.discoveryDB, [ + { user_id: 1 }, + { user_id: 2 }, + { user_id: 3 } + ]) + await createTracks(processor.discoveryDB, [{ track_id: 1, owner_id: 3 }]) + await createComments(processor.discoveryDB, [ + { + comment_id: 1, + user_id: 1, + entity_id: 1, + entity_type: commenttype.track + }, + { + comment_id: 2, + user_id: 2, + entity_id: 1, + entity_type: commenttype.track + } + ]) + await createCommentThreads(processor.discoveryDB, [ + { + comment_id: 2, + parent_comment_id: 1 + } + ]) + await insertMobileSettings(processor.identityDB, [{ userId: 1 }]) + await insertMobileDevices(processor.identityDB, [{ userId: 1 }]) + await new Promise((resolve) => setTimeout(resolve, 10)) + const pending = processor.listener.takePending() + expect(pending?.appNotifications).toHaveLength(3) + // Assert single pending + await processor.appNotificationsProcessor.process(pending.appNotifications) + + expect(sendPushNotificationSpy).toHaveBeenCalledWith( + { + type: 'ios', + targetARN: 'arn:1', + badgeCount: 1 + }, + { + title: 'New Reply', + body: "user_2 replied to your comment on user_3's track track_title_1", + data: { + id: 'timestamp:1589373217:group_id:comment_thread:1', + type: 'CommentThread', + userIds: [2] + } + } + ) + }) + + test('Render a single email', async () => { + await createUsers(processor.discoveryDB, [{ user_id: 1 }, { user_id: 2 }]) + await createTracks(processor.discoveryDB, [{ track_id: 1, owner_id: 1 }]) + await createComments(processor.discoveryDB, [ + { + comment_id: 1, + user_id: 1, + entity_id: 1, + entity_type: commenttype.track + }, + { + comment_id: 2, + user_id: 2, + entity_id: 1, + entity_type: commenttype.track + } + ]) + + await createCommentThreads(processor.discoveryDB, [ + { comment_id: 2, parent_comment_id: 1 } + ]) + + await new Promise((resolve) => setTimeout(resolve, 10)) + + const notifications: AppEmailNotification[] = [ + { + type: 'comment_thread', + timestamp: new Date(), + specifier: '2', + group_id: 'comment_thread:1', + data: { + type: EntityType.Track, + entity_id: 1, + entity_user_id: 1, + comment_user_id: 2 + }, + user_ids: [1], + receiver_user_id: 1 + } + ] + const notifHtml = await renderEmail({ + userId: 1, + email: 'joey@audius.co', + frequency: 'daily', + notifications, + dnDb: processor.discoveryDB, + identityDb: processor.identityDB + }) + expect(notifHtml).toMatchSnapshot() + }) + + test('Render a multi comment email', async () => { + await createUsers(processor.discoveryDB, [ + { user_id: 1 }, + { user_id: 2 }, + { user_id: 3 }, + { user_id: 4 }, + { user_id: 5 } + ]) + await createTracks(processor.discoveryDB, [{ track_id: 1, owner_id: 1 }]) + + await createComments(processor.discoveryDB, [ + { + comment_id: 1, + user_id: 2, + entity_id: 1, + entity_type: commenttype.track + }, + { + comment_id: 2, + user_id: 3, + entity_id: 1, + entity_type: commenttype.track + }, + { + comment_id: 3, + user_id: 4, + entity_id: 1, + entity_type: commenttype.track + }, + { + comment_id: 4, + user_id: 5, + entity_id: 1, + entity_type: commenttype.track + } + ]) + + await createCommentThreads(processor.discoveryDB, [ + { comment_id: 2, parent_comment_id: 1 }, + { comment_id: 3, parent_comment_id: 1 }, + { comment_id: 4, parent_comment_id: 1 } + ]) + + const notifications: AppEmailNotification[] = Array.from( + new Array(3), + (_, num) => ({ + type: 'comment_thread', + timestamp: new Date(), + specifier: (num + 3).toString(), + group_id: 'comment_thread:1', + data: { + type: EntityType.Track, + comment_user_id: num + 3, + entity_id: 1, + entity_user_id: 1 + }, + user_ids: [1], + receiver_user_id: 1 + }) + ) + + const notifHtml = await renderEmail({ + userId: 1, + email: 'joey@audius.co', + frequency: 'daily', + notifications, + dnDb: processor.discoveryDB, + identityDb: processor.identityDB + }) + expect(notifHtml).toMatchSnapshot() + }) +}) diff --git a/packages/discovery-provider/plugins/notifications/src/email/notifications/components/Body.tsx b/packages/discovery-provider/plugins/notifications/src/email/notifications/components/Body.tsx index f83073e3d7a..57d929face3 100644 --- a/packages/discovery-provider/plugins/notifications/src/email/notifications/components/Body.tsx +++ b/packages/discovery-provider/plugins/notifications/src/email/notifications/components/Body.tsx @@ -186,6 +186,22 @@ const snippetMap = { } commented on your ${notification.entity.type.toLowerCase()} ${ notification.entity.name }` + }, + ['comment_thread'](notification) { + const [user] = notification.users + return `${user.name} replied to your comment on ${ + notification.entityUser.user_id === notification.receiverUserId + ? 'your' + : `${notification.entityUser.name}'s` + } ${notification.entity.type.toLowerCase()} ${notification.entity.name}` + }, + ['comment_mention'](notification) { + const [user] = notification.users + return `${user.name} tagged you in a comment on ${ + notification.entityUser.user_id === notification.receiverUserId + ? 'your' + : `${notification.entityUser.name}'s` + } ${notification.entity.type.toLowerCase()} ${notification.entity.name}` } } diff --git a/packages/discovery-provider/plugins/notifications/src/email/notifications/components/notifications/Notification.tsx b/packages/discovery-provider/plugins/notifications/src/email/notifications/components/notifications/Notification.tsx index b1b435cff2c..d2f91715f6f 100644 --- a/packages/discovery-provider/plugins/notifications/src/email/notifications/components/notifications/Notification.tsx +++ b/packages/discovery-provider/plugins/notifications/src/email/notifications/components/notifications/Notification.tsx @@ -479,6 +479,42 @@ const notificationMap = { {entity} ) + }, + ['comment_thread'](notification) { + const user = getUsers(notification.users) + const entity = getEntity(notification.entity) + return ( + + {user} + + + {entity} + + ) + }, + ['comment_mention'](notification) { + const user = getUsers(notification.users) + const entity = getEntity(notification.entity) + return ( + + {user} + + + {entity} + + ) } } diff --git a/packages/discovery-provider/plugins/notifications/src/email/notifications/renderEmail.ts b/packages/discovery-provider/plugins/notifications/src/email/notifications/renderEmail.ts index 78bb59794bd..bce1173b8d8 100644 --- a/packages/discovery-provider/plugins/notifications/src/email/notifications/renderEmail.ts +++ b/packages/discovery-provider/plugins/notifications/src/email/notifications/renderEmail.ts @@ -355,6 +355,7 @@ export const renderEmail = async ({ } } } + const notificationProps = await getNotificationProps( dnDb, identityDb, diff --git a/packages/discovery-provider/plugins/notifications/src/email/notifications/types.ts b/packages/discovery-provider/plugins/notifications/src/email/notifications/types.ts index 583e3d22d90..1674703b879 100644 --- a/packages/discovery-provider/plugins/notifications/src/email/notifications/types.ts +++ b/packages/discovery-provider/plugins/notifications/src/email/notifications/types.ts @@ -77,23 +77,3 @@ export type ChallengeId = | 'track-upload' | 'send-first-tip' | 'first-playlist' - -export type NotificationType = - | 'favorite' - | 'repost' - | 'save' - | 'follow' - | 'announcement' - | 'milestone' - | 'trendingTrack' - | 'createTrack' - | 'create' - | 'chllenge_reward' - | 'remix' - | 'cosign' - | 'tip_receive' - | 'tip_send' - | 'reaction' - | 'supporter_rank_up' - | 'supporting_rank_up' - | 'track_added_to_playlist' diff --git a/packages/discovery-provider/plugins/notifications/src/processNotifications/indexAppNotifications.ts b/packages/discovery-provider/plugins/notifications/src/processNotifications/indexAppNotifications.ts index e1a72a20fef..b6ab0ec6867 100644 --- a/packages/discovery-provider/plugins/notifications/src/processNotifications/indexAppNotifications.ts +++ b/packages/discovery-provider/plugins/notifications/src/processNotifications/indexAppNotifications.ts @@ -30,6 +30,8 @@ import { import { Timer } from '../utils/timer' import { getRedisConnection } from '../utils/redisConnection' import { RequiresRetry } from '../types/notifications' +import { CommentThread } from './mappers/commentThread' +import { CommentMention } from './mappers/commentMention' const NOTIFICATION_RETRY_QUEUE_REDIS_KEY = 'notification_retry' @@ -49,6 +51,8 @@ export type NotificationProcessor = | TipReceive | TipSend | Comment + | CommentThread + | CommentMention export const notificationTypeMapping = { follow: MappingVariable.PushFollow, @@ -82,7 +86,9 @@ export const notificationTypeMapping = { announcement: MappingVariable.PushAnnouncement, reaction: MappingVariable.PushReaction, reward_in_cooldown: MappingVariable.PushRewardInCooldown, - comment: MappingVariable.PushComment + comment: MappingVariable.PushComment, + comment_thread: MappingVariable.PushCommentThread, + comment_mention: MappingVariable.PushCommentMention } export class AppNotificationsProcessor { diff --git a/packages/discovery-provider/plugins/notifications/src/processNotifications/mappers/commentMention.ts b/packages/discovery-provider/plugins/notifications/src/processNotifications/mappers/commentMention.ts new file mode 100644 index 00000000000..014a619b6b3 --- /dev/null +++ b/packages/discovery-provider/plugins/notifications/src/processNotifications/mappers/commentMention.ts @@ -0,0 +1,221 @@ +import { Knex } from 'knex' +import { NotificationRow, TrackRow, UserRow } from '../../types/dn' +import { + AppEmailNotification, + CommentMentionNotification +} from '../../types/notifications' +import { BaseNotification } from './base' +import { sendPushNotification } from '../../sns' +import { ResourceIds, Resources } from '../../email/notifications/renderEmail' +import { EntityType } from '../../email/notifications/types' +import { sendNotificationEmail } from '../../email/notifications/sendEmail' +import { + buildUserNotificationSettings, + Device +} from './userNotificationSettings' +import { sendBrowserNotification } from '../../web' +import { disableDeviceArns } from '../../utils/disableArnEndpoint' + +type CommentMentionNotificationRow = Omit & { + data: CommentMentionNotification +} +export class CommentMention extends BaseNotification { + receiverUserId: number + entityId: number + entityType: EntityType + entityUserId: number + commenterUserId: number + + constructor( + dnDB: Knex, + identityDB: Knex, + notification: CommentMentionNotificationRow + ) { + super(dnDB, identityDB, notification) + const userIds: number[] = this.notification.user_ids! + this.receiverUserId = userIds[0] + this.entityId = this.notification.data.entity_id + this.entityType = this.notification.data.type.toLowerCase() as EntityType + this.entityUserId = this.notification.data.entity_user_id + this.commenterUserId = this.notification.data.comment_user_id + } + + async processNotification({ + isLiveEmailEnabled, + isBrowserPushEnabled + }: { + isLiveEmailEnabled: boolean + isBrowserPushEnabled: boolean + }) { + let entityType: string + let entityName: string + + if (this.entityType === EntityType.Track) { + const [track] = await this.dnDB + .select('track_id', 'title') + .from('tracks') + .where('is_current', true) + .where('track_id', this.entityId) + + if (track) { + const { title } = track + entityType = 'track' + entityName = title + } + } + + const users = await this.dnDB + .select('user_id', 'name', 'is_deactivated') + .from('users') + .where('is_current', true) + .whereIn('user_id', [ + this.receiverUserId, + this.commenterUserId, + this.entityUserId + ]) + .then((rows) => + rows.reduce((acc, row) => { + acc[row.user_id] = row + return acc + }, {} as Record) + ) + + if (users[this.receiverUserId]?.is_deactivated) { + return + } + + // Get the user's notification setting from identity service + const userNotificationSettings = await buildUserNotificationSettings( + this.identityDB, + [this.receiverUserId, this.commenterUserId] + ) + + const title = 'New Mention' + const body = `${ + users[this.commenterUserId]?.name + } tagged you in a comment on ${ + this.entityUserId === this.receiverUserId + ? 'your' + : `${users[this.entityUserId]?.name}'s` + } ${entityType?.toLowerCase()} ${entityName}` + if ( + userNotificationSettings.isNotificationTypeBrowserEnabled( + this.receiverUserId, + 'mentions' + ) + ) { + await sendBrowserNotification( + isBrowserPushEnabled, + userNotificationSettings, + this.receiverUserId, + title, + body + ) + } + + if ( + userNotificationSettings.shouldSendPushNotification({ + initiatorUserId: this.commenterUserId, + receiverUserId: this.receiverUserId + }) && + userNotificationSettings.isNotificationTypeEnabled( + this.receiverUserId, + 'mentions' + ) + ) { + const devices: Device[] = userNotificationSettings.getDevices( + this.receiverUserId + ) + // If the user's settings for the follow notification is set to true, proceed + const timestamp = Math.floor( + Date.parse(this.notification.timestamp as unknown as string) / 1000 + ) + const pushes = await Promise.all( + devices.map((device) => { + return sendPushNotification( + { + type: device.type, + badgeCount: + userNotificationSettings.getBadgeCount(this.receiverUserId) + 1, + targetARN: device.awsARN + }, + { + title, + body, + data: { + id: `timestamp:${timestamp}:group_id:${this.notification.group_id}`, + userIds: [this.commenterUserId], + type: 'CommentMention' + } + } + ) + }) + ) + await disableDeviceArns(this.identityDB, pushes) + await this.incrementBadgeCount(this.receiverUserId) + } + if ( + isLiveEmailEnabled && + userNotificationSettings.shouldSendEmailAtFrequency({ + initiatorUserId: this.commenterUserId, + receiverUserId: this.receiverUserId, + frequency: 'live' + }) + ) { + const notification: AppEmailNotification = { + receiver_user_id: this.receiverUserId, + ...this.notification + } + await sendNotificationEmail({ + userId: this.receiverUserId, + email: userNotificationSettings.getUserEmail(this.receiverUserId), + frequency: 'live', + notifications: [notification], + dnDb: this.dnDB, + identityDb: this.identityDB + }) + } + } + + getResourcesForEmail(): ResourceIds { + const tracks = new Set() + if (this.entityType === EntityType.Track) { + tracks.add(this.entityId) + } + + return { + users: new Set([ + this.receiverUserId, + this.commenterUserId, + this.entityUserId + ]), + tracks + } + } + + formatEmailProps( + resources: Resources, + additionalGroupNotifications: CommentMention[] = [] + ) { + const user = resources.users[this.commenterUserId] + const additionalUsers = additionalGroupNotifications.map( + (comment) => resources.users[comment.commenterUserId] + ) + let entity + if (this.entityType === EntityType.Track) { + const track = resources.tracks[this.entityId] + entity = { + type: EntityType.Track, + name: track.title, + imageUrl: track.imageUrl + } + } + return { + type: this.notification.type, + users: [user, ...additionalUsers], + receiverUserId: this.receiverUserId, + entityUser: resources.users[this.entityUserId], + entity + } + } +} diff --git a/packages/discovery-provider/plugins/notifications/src/processNotifications/mappers/commentThread.ts b/packages/discovery-provider/plugins/notifications/src/processNotifications/mappers/commentThread.ts new file mode 100644 index 00000000000..7990275984a --- /dev/null +++ b/packages/discovery-provider/plugins/notifications/src/processNotifications/mappers/commentThread.ts @@ -0,0 +1,221 @@ +import { Knex } from 'knex' +import { NotificationRow, TrackRow, UserRow } from '../../types/dn' +import { + AppEmailNotification, + CommentThreadNotification +} from '../../types/notifications' +import { BaseNotification } from './base' +import { sendPushNotification } from '../../sns' +import { ResourceIds, Resources } from '../../email/notifications/renderEmail' +import { EntityType } from '../../email/notifications/types' +import { sendNotificationEmail } from '../../email/notifications/sendEmail' +import { + buildUserNotificationSettings, + Device +} from './userNotificationSettings' +import { sendBrowserNotification } from '../../web' +import { disableDeviceArns } from '../../utils/disableArnEndpoint' + +type CommentThreadNotificationRow = Omit & { + data: CommentThreadNotification +} +export class CommentThread extends BaseNotification { + receiverUserId: number + entityId: number + entityType: EntityType + entityUserId: number + commenterUserId: number + + constructor( + dnDB: Knex, + identityDB: Knex, + notification: CommentThreadNotificationRow + ) { + super(dnDB, identityDB, notification) + const userIds: number[] = this.notification.user_ids! + this.receiverUserId = userIds[0] + this.entityId = this.notification.data.entity_id + this.entityType = this.notification.data.type.toLowerCase() as EntityType + this.entityUserId = this.notification.data.entity_user_id + this.commenterUserId = this.notification.data.comment_user_id + } + + async processNotification({ + isLiveEmailEnabled, + isBrowserPushEnabled + }: { + isLiveEmailEnabled: boolean + isBrowserPushEnabled: boolean + }) { + let entityType: string + let entityName: string + + if (this.entityType === EntityType.Track) { + const [track] = await this.dnDB + .select('track_id', 'title') + .from('tracks') + .where('is_current', true) + .where('track_id', this.entityId) + + if (track) { + const { title } = track + entityType = 'track' + entityName = title + } + } + + const users = await this.dnDB + .select('user_id', 'name', 'is_deactivated') + .from('users') + .where('is_current', true) + .whereIn('user_id', [ + this.receiverUserId, + this.commenterUserId, + this.entityUserId + ]) + .then((rows) => + rows.reduce((acc, row) => { + acc[row.user_id] = row + return acc + }, {} as Record) + ) + + if (users[this.receiverUserId]?.is_deactivated) { + return + } + + // Get the user's notification setting from identity service + const userNotificationSettings = await buildUserNotificationSettings( + this.identityDB, + [this.receiverUserId, this.commenterUserId] + ) + + const title = 'New Reply' + const body = `${ + users[this.commenterUserId]?.name + } replied to your comment on ${ + this.entityUserId === this.receiverUserId + ? 'your' + : `${users[this.entityUserId]?.name}'s` + } ${entityType?.toLowerCase()} ${entityName}` + if ( + userNotificationSettings.isNotificationTypeBrowserEnabled( + this.receiverUserId, + 'comments' + ) + ) { + await sendBrowserNotification( + isBrowserPushEnabled, + userNotificationSettings, + this.receiverUserId, + title, + body + ) + } + + if ( + userNotificationSettings.shouldSendPushNotification({ + initiatorUserId: this.commenterUserId, + receiverUserId: this.receiverUserId + }) && + userNotificationSettings.isNotificationTypeEnabled( + this.receiverUserId, + 'comments' + ) + ) { + const devices: Device[] = userNotificationSettings.getDevices( + this.receiverUserId + ) + // If the user's settings for the follow notification is set to true, proceed + const timestamp = Math.floor( + Date.parse(this.notification.timestamp as unknown as string) / 1000 + ) + const pushes = await Promise.all( + devices.map((device) => { + return sendPushNotification( + { + type: device.type, + badgeCount: + userNotificationSettings.getBadgeCount(this.receiverUserId) + 1, + targetARN: device.awsARN + }, + { + title, + body, + data: { + id: `timestamp:${timestamp}:group_id:${this.notification.group_id}`, + userIds: [this.commenterUserId], + type: 'CommentThread' + } + } + ) + }) + ) + await disableDeviceArns(this.identityDB, pushes) + await this.incrementBadgeCount(this.receiverUserId) + } + if ( + isLiveEmailEnabled && + userNotificationSettings.shouldSendEmailAtFrequency({ + initiatorUserId: this.commenterUserId, + receiverUserId: this.receiverUserId, + frequency: 'live' + }) + ) { + const notification: AppEmailNotification = { + receiver_user_id: this.receiverUserId, + ...this.notification + } + await sendNotificationEmail({ + userId: this.receiverUserId, + email: userNotificationSettings.getUserEmail(this.receiverUserId), + frequency: 'live', + notifications: [notification], + dnDb: this.dnDB, + identityDb: this.identityDB + }) + } + } + + getResourcesForEmail(): ResourceIds { + const tracks = new Set() + if (this.entityType === EntityType.Track) { + tracks.add(this.entityId) + } + + return { + users: new Set([ + this.receiverUserId, + this.commenterUserId, + this.entityUserId + ]), + tracks + } + } + + formatEmailProps( + resources: Resources, + additionalGroupNotifications: CommentThread[] = [] + ) { + const user = resources.users[this.commenterUserId] + const additionalUsers = additionalGroupNotifications.map( + (comment) => resources.users[comment.commenterUserId] + ) + let entity + if (this.entityType === EntityType.Track) { + const track = resources.tracks[this.entityId] + entity = { + type: EntityType.Track, + name: track.title, + imageUrl: track.imageUrl + } + } + return { + type: this.notification.type, + users: [user, ...additionalUsers], + receiverUserId: this.receiverUserId, + entityUser: resources.users[this.entityUserId], + entity + } + } +} diff --git a/packages/discovery-provider/plugins/notifications/src/processNotifications/mappers/mapNotifications.ts b/packages/discovery-provider/plugins/notifications/src/processNotifications/mappers/mapNotifications.ts index f46d5d34232..99a6da74d06 100644 --- a/packages/discovery-provider/plugins/notifications/src/processNotifications/mappers/mapNotifications.ts +++ b/packages/discovery-provider/plugins/notifications/src/processNotifications/mappers/mapNotifications.ts @@ -38,7 +38,9 @@ import { ApproveManagerNotification, ClaimableRewardNotification, RewardInCooldownNotification, - CommentNotification + CommentNotification, + CommentThreadNotification, + CommentMentionNotification } from '../../types/notifications' import { ApproveManagerRequest } from './approveManagerRequest' import { Follow } from './follow' @@ -73,6 +75,8 @@ import { TrackAddedToPurchasedAlbum } from './trackAddedToPurchasedAlbum' import { RewardInCooldown } from './rewardInCooldown' import { ClaimableReward } from './claimableReward' import { Comment } from './comment' +import { CommentThread } from './commentThread' +import { CommentMention } from './commentMention' export const mapNotifications = ( notifications: (NotificationRow | EmailNotification)[], @@ -288,6 +292,16 @@ const mapNotification = ( data: CommentNotification } return new Comment(dnDb, identityDb, saveNotification) + } else if (notification.type == 'comment_thread') { + const saveNotification = notification as NotificationRow & { + data: CommentThreadNotification + } + return new CommentThread(dnDb, identityDb, saveNotification) + } else if (notification.type == 'comment_mention') { + const saveNotification = notification as NotificationRow & { + data: CommentMentionNotification + } + return new CommentMention(dnDb, identityDb, saveNotification) } logger.info(`Notification type: ${notification.type} has no handler`) diff --git a/packages/discovery-provider/plugins/notifications/src/processNotifications/mappers/userNotificationSettings.ts b/packages/discovery-provider/plugins/notifications/src/processNotifications/mappers/userNotificationSettings.ts index 5bf230e876f..71d69f93b28 100644 --- a/packages/discovery-provider/plugins/notifications/src/processNotifications/mappers/userNotificationSettings.ts +++ b/packages/discovery-provider/plugins/notifications/src/processNotifications/mappers/userNotificationSettings.ts @@ -33,6 +33,7 @@ export type NotificationSettings = { remixes: boolean messages: boolean comments: boolean + mentions: boolean } type UserBrowserSettings = { @@ -253,6 +254,7 @@ export class UserNotificationSettings { remixes: boolean messages: boolean comments: boolean + mentions: boolean deviceType: string awsARN: string deviceToken: string @@ -268,6 +270,7 @@ export class UserNotificationSettings { 'UserNotificationMobileSettings.remixes', 'UserNotificationMobileSettings.messages', 'UserNotificationMobileSettings.comments', + 'UserNotificationMobileSettings.mentions', 'NotificationDeviceTokens.deviceType', 'NotificationDeviceTokens.awsARN', 'NotificationDeviceTokens.deviceToken', @@ -301,7 +304,8 @@ export class UserNotificationSettings { followers: setting.followers, remixes: setting.remixes, messages: setting.messages, - comments: setting.comments + comments: setting.comments, + mentions: setting.mentions }, devices: [ ...(acc?.[setting.userId]?.devices ?? []), @@ -381,6 +385,7 @@ export class UserNotificationSettings { remixes: boolean messages: boolean comments: boolean + mentions: boolean deviceType?: string awsARN?: string deviceToken?: string @@ -397,6 +402,8 @@ export class UserNotificationSettings { 'UserNotificationBrowserSettings.followers', 'UserNotificationBrowserSettings.remixes', 'UserNotificationBrowserSettings.messages', + 'UserNotificationBrowserSettings.comments', + 'UserNotificationBrowserSettings.mentions', 'NotificationBrowserSubscriptions.endpoint', 'NotificationBrowserSubscriptions.p256dhKey', 'NotificationBrowserSubscriptions.authKey' @@ -442,7 +449,8 @@ export class UserNotificationSettings { followers: setting.followers, remixes: setting.remixes, messages: setting.messages, - comments: setting.comments + comments: setting.comments, + mentions: setting.mentions }, browser: acc?.[setting.userId]?.browser ?? [] } diff --git a/packages/discovery-provider/plugins/notifications/src/remoteConfig.ts b/packages/discovery-provider/plugins/notifications/src/remoteConfig.ts index 730b223596a..77b7d2230f0 100644 --- a/packages/discovery-provider/plugins/notifications/src/remoteConfig.ts +++ b/packages/discovery-provider/plugins/notifications/src/remoteConfig.ts @@ -37,7 +37,9 @@ export enum MappingVariable { PushRequestManager = 'push_request_manager', PushApproveManagerRequest = 'push_approve_manager_request', PushRewardInCooldown = 'push_reward_in_cooldown', - PushComment = 'push_comment' + PushComment = 'push_comment', + PushCommentThread = 'push_comment_thread', + PushCommentMention = 'push_comment_mention' } export const NotificationsEmailPlugin = 'notification_email_plugin' @@ -82,7 +84,9 @@ const defaultMappingVariable = { [MappingVariable.PushUSDCWithdrawal]: false, [MappingVariable.PushRequestManager]: false, [MappingVariable.PushApproveManagerRequest]: false, - [MappingVariable.PushComment]: false + [MappingVariable.PushComment]: false, + [MappingVariable.PushCommentThread]: false, + [MappingVariable.PushCommentMention]: false } export const BrowserPushPlugin = 'browser_push_plugin' diff --git a/packages/discovery-provider/plugins/notifications/src/types/dn.ts b/packages/discovery-provider/plugins/notifications/src/types/dn.ts index 1faa9bfbc70..80dc46c4d09 100644 --- a/packages/discovery-provider/plugins/notifications/src/types/dn.ts +++ b/packages/discovery-provider/plugins/notifications/src/types/dn.ts @@ -841,6 +841,23 @@ export interface CommentRow { is_edited: boolean txhash?: string } + +export interface CommentThreadRow { + comment_id: number + parent_comment_id: number +} + +export interface CommentMentionRow { + comment_id: number + user_id: number + created_at?: Date + updated_at?: Date + is_delete: boolean + txhash: string + blockhash: string + blocknumber: number +} + export enum wallet_chain { 'eth' = 'eth', 'sol' = 'sol' diff --git a/packages/discovery-provider/plugins/notifications/src/types/identity.ts b/packages/discovery-provider/plugins/notifications/src/types/identity.ts index 2e3fc0a9647..1fca528f843 100644 --- a/packages/discovery-provider/plugins/notifications/src/types/identity.ts +++ b/packages/discovery-provider/plugins/notifications/src/types/identity.ts @@ -237,6 +237,8 @@ export interface UserNotificationBrowserSettingRow { milestonesAndAchievements?: boolean remixes?: boolean reposts?: boolean + comments?: boolean + mentions?: boolean updatedAt: Date userId: number } @@ -249,24 +251,8 @@ export interface UserNotificationMobileSettingRow { milestonesAndAchievements?: boolean remixes?: boolean reposts?: boolean - updatedAt: Date - userId: number -} -export interface UserNotificationSettingRow { - announcements?: boolean - browserPushNotifications?: boolean - createdAt: Date - emailFrequency?: enum_UserNotificationSettings_emailFrequency - favorites?: boolean - followers?: boolean - milestonesAndAchievements?: boolean - reposts?: boolean - updatedAt: Date - userId: number -} -export interface UserPlaylistFavoriteRow { - createdAt: Date - favorites: string[] + comments?: boolean + mentions?: boolean updatedAt: Date userId: number } @@ -288,20 +274,6 @@ export interface UserRow { updatedAt: Date walletAddress?: string | null } -export interface UserTrackListenRow { - count?: number - createdAt: Date - id?: number - trackId: number - updatedAt: Date - userId: number -} -export enum enum_UserNotificationSettings_emailFrequency { - 'daily' = 'daily', - 'weekly' = 'weekly', - 'off' = 'off', - 'live' = 'live' -} export enum enum_SolanaNotifications_type { 'ChallengeReward' = 'ChallengeReward', 'MilestoneListen' = 'MilestoneListen', diff --git a/packages/discovery-provider/plugins/notifications/src/types/notifications.ts b/packages/discovery-provider/plugins/notifications/src/types/notifications.ts index 5e174776035..e362e9a90bc 100644 --- a/packages/discovery-provider/plugins/notifications/src/types/notifications.ts +++ b/packages/discovery-provider/plugins/notifications/src/types/notifications.ts @@ -300,6 +300,20 @@ export type CommentNotification = { entity_id: number } +export type CommentThreadNotification = { + type: EntityType + entity_id: number + entity_user_id: number + comment_user_id: number +} + +export type CommentMentionNotification = { + type: EntityType + entity_id: number + entity_user_id: number + comment_user_id: number +} + export type NotificationData = | DMNotification | DMReactionNotification @@ -332,7 +346,8 @@ export type NotificationData = | TrendingUndergroundNotification | TrendingPlaylistNotification | CommentNotification - + | CommentThreadNotification + | CommentMentionNotification export class RequiresRetry extends Error { constructor(message: string) { super(message) diff --git a/packages/discovery-provider/plugins/notifications/src/utils/populateDB.ts b/packages/discovery-provider/plugins/notifications/src/utils/populateDB.ts index a76b1a98ef1..95ab07304b6 100644 --- a/packages/discovery-provider/plugins/notifications/src/utils/populateDB.ts +++ b/packages/discovery-provider/plugins/notifications/src/utils/populateDB.ts @@ -20,7 +20,9 @@ import { UsdcTransactionsHistoryRow, UsdcUserBankAccountRow, GrantRow, - CommentRow + CommentRow, + CommentThreadRow, + CommentMentionRow } from '../types/dn' import { UserRow as IdentityUserRow } from '../types/identity' import { @@ -723,11 +725,11 @@ export async function insertChatPermission( .into('chat_permissions') } -type MoblieDevice = Pick & +type MobileDevice = Pick & Partial export async function insertMobileDevices( db: Knex, - mobileDevices: MoblieDevice[] + mobileDevices: MobileDevice[] ) { const currentTimestamp = new Date(Date.now()).toISOString() await db @@ -823,6 +825,39 @@ export const createComments = async (db: Knex, comments: CreateComment[]) => { .into('comments') } +type CreateCommentThread = CommentThreadRow + +export const createCommentThreads = async ( + db: Knex, + commentThreads: CreateCommentThread[] +) => { + await db.insert(commentThreads).into('comment_threads') +} + +type CreateCommentMention = Pick & + Partial + +export const createCommentMentions = async ( + db: Knex, + commentMentions: CreateCommentMention[] +) => { + await db + .insert( + commentMentions.map((mention) => ({ + comment_id: mention.comment_id, + user_id: mention.user_id, + created_at: new Date(Date.now()), + updated_at: new Date(Date.now()), + is_delete: false, + txhash: `0x${mention.comment_id}`, + blockhash: `0x${mention.comment_id}`, + blocknumber: 0, + ...mention + })) + ) + .into('comment_mentions') +} + export type UserWithDevice = { userId: number name: string diff --git a/packages/identity-service/scripts/run-tests.sh b/packages/identity-service/scripts/run-tests.sh index a81dfad5bc6..2bd07b4265d 100755 --- a/packages/identity-service/scripts/run-tests.sh +++ b/packages/identity-service/scripts/run-tests.sh @@ -74,7 +74,7 @@ else TIMEOUT=12000 fi -./node_modules/ts-mocha/bin/ts-mocha test/index.ts --timeout "${TIMEOUT}" --exit +../../node_modules/ts-mocha/bin/ts-mocha test/index.ts --timeout "${TIMEOUT}" --exit # linter diff --git a/packages/identity-service/sequelize/migrations/20241009143933-add-mentions-to-browser-notification-settings.js b/packages/identity-service/sequelize/migrations/20241009143933-add-mentions-to-browser-notification-settings.js new file mode 100644 index 00000000000..8036b79c8d2 --- /dev/null +++ b/packages/identity-service/sequelize/migrations/20241009143933-add-mentions-to-browser-notification-settings.js @@ -0,0 +1,22 @@ +'use strict' + +module.exports = { + up: (queryInterface, Sequelize) => { + return queryInterface.addColumn( + 'UserNotificationBrowserSettings', + 'mentions', + { + type: Sequelize.BOOLEAN, + allowNull: false, + defaultValue: true + } + ) + }, + + down: (queryInterface, Sequelize) => { + return queryInterface.removeColumn( + 'UserNotificationBrowserSettings', + 'mentions' + ) + } +} diff --git a/packages/identity-service/sequelize/migrations/20241009144010-add-mentions-to-mobile-notification-settings.js b/packages/identity-service/sequelize/migrations/20241009144010-add-mentions-to-mobile-notification-settings.js new file mode 100644 index 00000000000..b1adc551a11 --- /dev/null +++ b/packages/identity-service/sequelize/migrations/20241009144010-add-mentions-to-mobile-notification-settings.js @@ -0,0 +1,22 @@ +'use strict' + +module.exports = { + up: (queryInterface, Sequelize) => { + return queryInterface.addColumn( + 'UserNotificationMobileSettings', + 'mentions', + { + type: Sequelize.BOOLEAN, + allowNull: false, + defaultValue: true + } + ) + }, + + down: (queryInterface, Sequelize) => { + return queryInterface.removeColumn( + 'UserNotificationMobileSettings', + 'mentions' + ) + } +} diff --git a/packages/identity-service/src/models/userNotificationBrowserSettings.js b/packages/identity-service/src/models/userNotificationBrowserSettings.js index 630be429e11..b70e33727f0 100644 --- a/packages/identity-service/src/models/userNotificationBrowserSettings.js +++ b/packages/identity-service/src/models/userNotificationBrowserSettings.js @@ -42,6 +42,11 @@ module.exports = (sequelize, DataTypes) => { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: true + }, + mentions: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: true } }, {} diff --git a/packages/identity-service/src/models/userNotificationMobileSettings.js b/packages/identity-service/src/models/userNotificationMobileSettings.js index 9fc547282d4..50c0c21df42 100644 --- a/packages/identity-service/src/models/userNotificationMobileSettings.js +++ b/packages/identity-service/src/models/userNotificationMobileSettings.js @@ -47,6 +47,11 @@ module.exports = (sequelize, DataTypes) => { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: true + }, + mentions: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: true } }, {} diff --git a/packages/mobile/src/screens/settings-screen/NotificationSettingsScreen.tsx b/packages/mobile/src/screens/settings-screen/NotificationSettingsScreen.tsx index d3be638e6cd..31d3b419cb7 100644 --- a/packages/mobile/src/screens/settings-screen/NotificationSettingsScreen.tsx +++ b/packages/mobile/src/screens/settings-screen/NotificationSettingsScreen.tsx @@ -25,7 +25,8 @@ const messages = { favorites: 'Favorites', remixes: 'Remixes of My Tracks', messages: 'Messages', - comments: 'Comments' + comments: 'Comments', + mentions: 'Mentions' } export const NotificationSettingsScreen = () => { @@ -73,10 +74,16 @@ export const NotificationSettingsScreen = () => { type={PushNotificationSetting.Messages} /> {isCommentsEnabled ? ( - + <> + + + ) : null} diff --git a/packages/web/src/pages/settings-page/components/desktop/NotificationSettings.tsx b/packages/web/src/pages/settings-page/components/desktop/NotificationSettings.tsx index dd28e5ed982..ad52327de1a 100644 --- a/packages/web/src/pages/settings-page/components/desktop/NotificationSettings.tsx +++ b/packages/web/src/pages/settings-page/components/desktop/NotificationSettings.tsx @@ -30,7 +30,8 @@ const messages = { remixes: 'Remixes of My Tracks', messages: 'Messages', comments: 'Comments', - emailFrequency: '‘What You Missed’ Email Frequency', + mentions: 'Mentions', + emailFrequency: "'What You Missed' Email Frequency", enablePermissions: 'Notifications for Audius are blocked. Please enable in your browser settings and reload the page.' } @@ -126,6 +127,13 @@ const NotificationSettings = (props: NotificationSettingsProps) => { browserPushEnabled && props.settings[BrowserNotificationSetting.Messages], type: BrowserNotificationSetting.Messages + }, + { + text: messages.mentions, + isOn: + browserPushEnabled && + props.settings[BrowserNotificationSetting.Mentions], + type: BrowserNotificationSetting.Mentions } ] @@ -137,6 +145,13 @@ const NotificationSettings = (props: NotificationSettingsProps) => { props.settings[BrowserNotificationSetting.Comments], type: BrowserNotificationSetting.Comments }) + notificationToggles.push({ + text: messages.mentions, + isOn: + browserPushEnabled && + props.settings[BrowserNotificationSetting.Mentions], + type: BrowserNotificationSetting.Mentions + }) } const emailOptions = [ diff --git a/packages/web/src/pages/settings-page/store/sagas.ts b/packages/web/src/pages/settings-page/store/sagas.ts index bb7b20c9bc2..3bf423e1201 100644 --- a/packages/web/src/pages/settings-page/store/sagas.ts +++ b/packages/web/src/pages/settings-page/store/sagas.ts @@ -149,7 +149,8 @@ function* watchSetBrowserNotificationSettingsOn() { [BrowserNotificationSetting.Favorites]: true, [BrowserNotificationSetting.Remixes]: true, [BrowserNotificationSetting.Messages]: true, - [BrowserNotificationSetting.Comments]: true + [BrowserNotificationSetting.Comments]: true, + [BrowserNotificationSetting.Mentions]: true } yield* put(actions.setNotificationSettings(updatedSettings)) yield* call( @@ -178,7 +179,8 @@ function* watchSetBrowserNotificationSettingsOff() { [BrowserNotificationSetting.Favorites]: false, [BrowserNotificationSetting.Remixes]: false, [BrowserNotificationSetting.Messages]: false, - [BrowserNotificationSetting.Comments]: false + [BrowserNotificationSetting.Comments]: false, + [BrowserNotificationSetting.Mentions]: false } yield* put(actions.setNotificationSettings(updatedSettings)) yield* call(