From 6693cf03ff786615ec8779f4c688a4969f7d466e Mon Sep 17 00:00:00 2001 From: Alessandro Magionami Date: Fri, 27 Sep 2024 09:32:04 +0200 Subject: [PATCH 1/3] chore(activitystream): refactor createActivitySummary --- .../activitystream/domain/operations.ts | 13 ++- .../modules/activitystream/domain/types.ts | 17 +++- .../activitystream/services/summary.ts | 79 ++++++++++--------- .../tests/activitySummary.spec.ts | 51 ++++++------ .../services/handlers/activityDigest.ts | 26 +++--- 5 files changed, 114 insertions(+), 72 deletions(-) diff --git a/packages/server/modules/activitystream/domain/operations.ts b/packages/server/modules/activitystream/domain/operations.ts index f05f68cb2..a894eda7f 100644 --- a/packages/server/modules/activitystream/domain/operations.ts +++ b/packages/server/modules/activitystream/domain/operations.ts @@ -1,4 +1,8 @@ -import { ResourceType, StreamActionType } from '@/modules/activitystream/domain/types' +import { + ActivitySummary, + ResourceType, + StreamActionType +} from '@/modules/activitystream/domain/types' import { StreamActivityRecord, StreamScopeActivity @@ -135,3 +139,10 @@ export type GetUserActivity = ({ }> export type SaveActivity = (args: Omit) => Promise + +export type CreateActivitySummary = (args: { + userId: string + streamIds: string[] + start: Date + end: Date +}) => Promise diff --git a/packages/server/modules/activitystream/domain/types.ts b/packages/server/modules/activitystream/domain/types.ts index 38083272c..bba595d60 100644 --- a/packages/server/modules/activitystream/domain/types.ts +++ b/packages/server/modules/activitystream/domain/types.ts @@ -1,6 +1,21 @@ -import { ActionTypes, ResourceTypes } from '@/modules/activitystream/helpers/types' +import { + ActionTypes, + ResourceTypes, + StreamScopeActivity +} from '@/modules/activitystream/helpers/types' +import { StreamRecord, UserRecord } from '@/modules/core/helpers/types' export type StreamActionType = (typeof ActionTypes.Stream)[keyof (typeof ActionTypes)['Stream']] export type ResourceType = (typeof ResourceTypes)[keyof typeof ResourceTypes] + +export type StreamActivitySummary = { + stream: StreamRecord | null + activity: StreamScopeActivity[] +} + +export type ActivitySummary = { + user: UserRecord + streamActivities: StreamActivitySummary[] +} diff --git a/packages/server/modules/activitystream/services/summary.ts b/packages/server/modules/activitystream/services/summary.ts index f3b29081c..540099ee6 100644 --- a/packages/server/modules/activitystream/services/summary.ts +++ b/packages/server/modules/activitystream/services/summary.ts @@ -1,48 +1,51 @@ -import { getActivityFactory } from '@/modules/activitystream/repositories' -import { StreamScopeActivity } from '@/modules/activitystream/helpers/types' import { NotificationPublisher, NotificationType } from '@/modules/notifications/helpers/types' -import { StreamRecord, UserRecord } from '@/modules/core/helpers/types' import { getUser } from '@/modules/core/repositories/users' -import { getStream } from '@/modules/core/services/streams' -import { db } from '@/db/knex' -import { GetActiveUserStreams } from '@/modules/activitystream/domain/operations' +import { getStream as getStreamService } from '@/modules/core/services/streams' +import { + CreateActivitySummary, + GetActiveUserStreams, + GetActivity +} from '@/modules/activitystream/domain/operations' -export type StreamActivitySummary = { - stream: StreamRecord | null - activity: StreamScopeActivity[] -} - -export type ActivitySummary = { - user: UserRecord - streamActivities: StreamActivitySummary[] -} - -export const createActivitySummary = async ( - userId: string, - streamIds: string[], - start: Date, - end: Date -): Promise => { - const streamActivities = ( - await Promise.all( - streamIds.map(async (streamId) => { - return { - stream: (await getStream({ streamId, userId })) ?? null, - activity: await getActivityFactory({ db })(streamId, start, end, null) //userId is null for now, to not filter out any activity - } - }) - ) - ).filter((sa) => sa.activity.length) - const user = await getUser(userId) - if (!user) return null - return { - user, - streamActivities +export const createActivitySummaryFactory = + ({ + getStream, + getActivity + }: { + getStream: typeof getStreamService + getActivity: GetActivity + }): CreateActivitySummary => + async ({ + userId, + streamIds, + start, + end + }: { + userId: string + streamIds: string[] + start: Date + end: Date + }) => { + const streamActivities = ( + await Promise.all( + streamIds.map(async (streamId) => { + return { + stream: (await getStream({ streamId, userId })) ?? null, + activity: await getActivity(streamId, start, end, null) //userId is null for now, to not filter out any activity + } + }) + ) + ).filter((sa) => sa.activity.length) + const user = await getUser(userId) + if (!user) return null + return { + user, + streamActivities + } } -} export const sendActivityNotificationsFactory = ({ diff --git a/packages/server/modules/activitystream/tests/activitySummary.spec.ts b/packages/server/modules/activitystream/tests/activitySummary.spec.ts index 269f3bb1e..34a64f46d 100644 --- a/packages/server/modules/activitystream/tests/activitySummary.spec.ts +++ b/packages/server/modules/activitystream/tests/activitySummary.spec.ts @@ -2,11 +2,11 @@ import { truncateTables } from '@/test/hooks' import { BasicTestUser, createTestUsers } from '@/test/authHelper' import { StreamActivity, Users } from '@/modules/core/dbSchema' import { - createActivitySummary, + createActivitySummaryFactory, sendActivityNotificationsFactory } from '@/modules/activitystream/services/summary' import { expect } from 'chai' -import { createStream, deleteStream } from '@/modules/core/services/streams' +import { createStream, deleteStream, getStream } from '@/modules/core/services/streams' import { ActionTypes, ResourceTypes } from '@/modules/activitystream/helpers/types' import { ActivityDigestMessage, @@ -14,7 +14,10 @@ import { NotificationTypeMessageMap } from '@/modules/notifications/helpers/types' import { sleep } from '@/test/helpers' -import { saveActivityFactory } from '@/modules/activitystream/repositories' +import { + getActivityFactory, + saveActivityFactory +} from '@/modules/activitystream/repositories' import { db } from '@/db/knex' const cleanup = async () => { @@ -22,6 +25,10 @@ const cleanup = async () => { } const saveActivity = saveActivityFactory({ db }) +const createActivitySummary = createActivitySummaryFactory({ + getStream, + getActivity: getActivityFactory({ db }) +}) describe('Activity summary @activity', () => { const userA: BasicTestUser = { @@ -35,12 +42,12 @@ describe('Activity summary @activity', () => { }) describe('create activity summary', () => { it('returns null for non existing users', async () => { - const summary = await createActivitySummary( - 'notAUserId', - ['someStreamIds'], - new Date(), - new Date() - ) + const summary = await createActivitySummary({ + userId: 'notAUserId', + streamIds: ['someStreamIds'], + start: new Date(), + end: new Date() + }) expect(summary).to.be.null }) it('no activity returns empty summary', async () => { @@ -51,12 +58,12 @@ describe('Activity summary @activity', () => { ) ) - const summary = await createActivitySummary( - userA.id, + const summary = await createActivitySummary({ + userId: userA.id, streamIds, start, - new Date() - ) + end: new Date() + }) expect(summary?.streamActivities).to.have.length(0) }) @@ -77,12 +84,12 @@ describe('Activity summary @activity', () => { message: 'foo' }) await sleep(100) - const summary = await createActivitySummary( - userA.id, + const summary = await createActivitySummary({ + userId: userA.id, streamIds, start, - new Date() - ) + end: new Date() + }) expect(summary?.streamActivities).to.have.length(1) }) @@ -104,12 +111,12 @@ describe('Activity summary @activity', () => { message: 'foo' }) await deleteStream({ streamId }) - const summary = await createActivitySummary( - userA.id, - [streamId], + const summary = await createActivitySummary({ + userId: userA.id, + streamIds: [streamId], start, - new Date() - ) + end: new Date() + }) expect(summary?.streamActivities).to.have.length(1) expect(summary?.streamActivities[0].stream).to.be.null diff --git a/packages/server/modules/notifications/services/handlers/activityDigest.ts b/packages/server/modules/notifications/services/handlers/activityDigest.ts index 31f855be8..66c4c0128 100644 --- a/packages/server/modules/notifications/services/handlers/activityDigest.ts +++ b/packages/server/modules/notifications/services/handlers/activityDigest.ts @@ -15,11 +15,6 @@ import { groupBy } from 'lodash' import { packageRoot } from '@/bootstrap' import path from 'path' import * as ejs from 'ejs' -import { - ActivitySummary, - createActivitySummary, - StreamActivitySummary -} from '@/modules/activitystream/services/summary' import { EmailBody, EmailInput, @@ -29,12 +24,20 @@ import { getUserNotificationPreferencesFactory } from '@/modules/notifications/s import { getSavedUserNotificationPreferencesFactory } from '@/modules/notifications/repositories' import { db } from '@/db/knex' import { GetUserNotificationPreferences } from '@/modules/notifications/domain/operations' +import { CreateActivitySummary } from '@/modules/activitystream/domain/operations' +import { + ActivitySummary, + StreamActivitySummary +} from '@/modules/activitystream/domain/types' +import { createActivitySummaryFactory } from '@/modules/activitystream/services/summary' +import { getStream } from '@/modules/core/services/streams' +import { getActivityFactory } from '@/modules/activitystream/repositories' const digestNotificationEmailHandlerFactory = ( deps: { getUserNotificationPreferences: GetUserNotificationPreferences - createActivitySummary: typeof createActivitySummary + createActivitySummary: CreateActivitySummary getServerInfo: typeof getServerInfo } & PrepareSummaryEmailDeps ) => @@ -48,12 +51,12 @@ const digestNotificationEmailHandlerFactory = const wantDigests = (await deps.getUserNotificationPreferences(userId)).activityDigest?.email !== false - const activitySummary = await deps.createActivitySummary( + const activitySummary = await deps.createActivitySummary({ userId, streamIds, start, end - ) + }) // if there are no activities stop early if (!wantDigests || !activitySummary || !activitySummary.streamActivities.length) return null @@ -202,7 +205,7 @@ export const mostActiveComment: TopicDigesterFunction = ( const heading = 'Most active comment' - const fact = `The most active comment was on ${streamActivity.stream.name} stream. + const fact = `The most active comment was on ${streamActivity.stream.name} stream. It received ${replies.length} replies.` const text = `${heading}\n\n${fact}` @@ -432,7 +435,10 @@ const digestNotificationEmailHandler = digestNotificationEmailHandlerFactory({ db }) }), - createActivitySummary, + createActivitySummary: createActivitySummaryFactory({ + getStream, + getActivity: getActivityFactory({ db }) + }), getServerInfo, renderEmail }) From d3ce2009b756e697cd96700b78fa20d20746ddaf Mon Sep 17 00:00:00 2001 From: Alessandro Magionami Date: Fri, 27 Sep 2024 10:23:35 +0200 Subject: [PATCH 2/3] chore(activitystream): fix imports --- .../modules/notifications/tests/activityDigest.spec.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/server/modules/notifications/tests/activityDigest.spec.ts b/packages/server/modules/notifications/tests/activityDigest.spec.ts index b773c28f1..2fbea4d5d 100644 --- a/packages/server/modules/notifications/tests/activityDigest.spec.ts +++ b/packages/server/modules/notifications/tests/activityDigest.spec.ts @@ -1,13 +1,13 @@ +import { + ActivitySummary, + StreamActivitySummary +} from '@/modules/activitystream/domain/types' import { ActionTypes, ResourceTypes, StreamScopeActivity, AllActivityTypes } from '@/modules/activitystream/helpers/types' -import { - ActivitySummary, - StreamActivitySummary -} from '@/modules/activitystream/services/summary' import { ServerInfo, UserRecord } from '@/modules/core/helpers/types' import { renderEmail } from '@/modules/emails/services/emailRendering' import { From 2eff539ac1d94ca6551946b7765fd0fed76d0bbc Mon Sep 17 00:00:00 2001 From: Alessandro Magionami Date: Fri, 27 Sep 2024 10:35:42 +0200 Subject: [PATCH 3/3] chore(activitystream): refactor addStreamCommentMentionActivity --- .../activitystream/domain/operations.ts | 8 ++++ .../activitystream/services/streamActivity.ts | 45 +++++++++---------- packages/server/modules/comments/index.ts | 8 +++- .../comments/services/notifications.ts | 4 +- 4 files changed, 38 insertions(+), 27 deletions(-) diff --git a/packages/server/modules/activitystream/domain/operations.ts b/packages/server/modules/activitystream/domain/operations.ts index a894eda7f..3a8a60088 100644 --- a/packages/server/modules/activitystream/domain/operations.ts +++ b/packages/server/modules/activitystream/domain/operations.ts @@ -146,3 +146,11 @@ export type CreateActivitySummary = (args: { start: Date end: Date }) => Promise + +export type AddStreamCommentMentionActivity = (params: { + streamId: string + mentionAuthorId: string + mentionTargetId: string + commentId: string + threadId: string +}) => Promise diff --git a/packages/server/modules/activitystream/services/streamActivity.ts b/packages/server/modules/activitystream/services/streamActivity.ts index 6bec7b826..651eba313 100644 --- a/packages/server/modules/activitystream/services/streamActivity.ts +++ b/packages/server/modules/activitystream/services/streamActivity.ts @@ -23,6 +23,10 @@ import { } from '@/modules/shared/utils/subscriptions' import { saveActivityFactory } from '@/modules/activitystream/repositories' import { db } from '@/db/knex' +import { + AddStreamCommentMentionActivity, + SaveActivity +} from '@/modules/activitystream/domain/operations' /** * Save "stream updated" activity @@ -408,26 +412,21 @@ export async function addStreamInviteDeclinedActivity(params: { /** * Save "user mentioned in stream comment" activity item */ -export async function addStreamCommentMentionActivity(params: { - streamId: string - mentionAuthorId: string - mentionTargetId: string - commentId: string - threadId: string -}) { - const { streamId, mentionAuthorId, mentionTargetId, commentId, threadId } = params - await saveActivityFactory({ db })({ - streamId, - resourceType: ResourceTypes.Comment, - resourceId: commentId, - actionType: ActionTypes.Comment.Mention, - userId: mentionAuthorId, - message: `User ${mentionAuthorId} mentioned user ${mentionTargetId} in comment ${commentId}`, - info: { - mentionAuthorId, - mentionTargetId, - commentId, - threadId - } - }) -} +export const addStreamCommentMentionActivityFactory = + ({ saveActivity }: { saveActivity: SaveActivity }): AddStreamCommentMentionActivity => + async ({ streamId, mentionAuthorId, mentionTargetId, commentId, threadId }) => { + await saveActivity({ + streamId, + resourceType: ResourceTypes.Comment, + resourceId: commentId, + actionType: ActionTypes.Comment.Mention, + userId: mentionAuthorId, + message: `User ${mentionAuthorId} mentioned user ${mentionTargetId} in comment ${commentId}`, + info: { + mentionAuthorId, + mentionTargetId, + commentId, + threadId + } + }) + } diff --git a/packages/server/modules/comments/index.ts b/packages/server/modules/comments/index.ts index 08fa0bef9..934658cdd 100644 --- a/packages/server/modules/comments/index.ts +++ b/packages/server/modules/comments/index.ts @@ -1,5 +1,7 @@ +import { db } from '@/db/knex' import { moduleLogger } from '@/logging/logging' -import { addStreamCommentMentionActivity } from '@/modules/activitystream/services/streamActivity' +import { saveActivityFactory } from '@/modules/activitystream/repositories' +import { addStreamCommentMentionActivityFactory } from '@/modules/activitystream/services/streamActivity' import { CommentsEmitter } from '@/modules/comments/events/emitter' import { notifyUsersOnCommentEventsFactory } from '@/modules/comments/services/notifications' import { publishNotification } from '@/modules/notifications/services/publication' @@ -15,7 +17,9 @@ const commentsModule: SpeckleModule = { const notifyUsersOnCommentEvents = notifyUsersOnCommentEventsFactory({ commentsEventsListen: CommentsEmitter.listen, publish: publishNotification, - addStreamCommentMentionActivity + addStreamCommentMentionActivity: addStreamCommentMentionActivityFactory({ + saveActivity: saveActivityFactory({ db }) + }) }) unsubFromEvents = await notifyUsersOnCommentEvents() } diff --git a/packages/server/modules/comments/services/notifications.ts b/packages/server/modules/comments/services/notifications.ts index 35a4562b2..52b105ffa 100644 --- a/packages/server/modules/comments/services/notifications.ts +++ b/packages/server/modules/comments/services/notifications.ts @@ -8,7 +8,7 @@ import { NotificationPublisher, NotificationType } from '@/modules/notifications/helpers/types' -import { addStreamCommentMentionActivity } from '@/modules/activitystream/services/streamActivity' +import { AddStreamCommentMentionActivity } from '@/modules/activitystream/domain/operations' function findMentionedUserIds(doc: JSONContent) { const mentionedUserIds = new Set() @@ -36,7 +36,7 @@ function collectMentionedUserIds(comment: CommentRecord): string[] { type SendNotificationsForUsersDeps = { publish: NotificationPublisher - addStreamCommentMentionActivity: typeof addStreamCommentMentionActivity + addStreamCommentMentionActivity: AddStreamCommentMentionActivity } const sendNotificationsForUsersFactory =