Browse Source

feat: send websocket event to user when a message is sent (#802)

Karol Sójko 1 year ago
parent
commit
9a568b0f73

+ 2 - 2
packages/analytics/src/Domain/Handler/AccountDeletionRequestedEventHandler.ts

@@ -41,13 +41,13 @@ export class AccountDeletionRequestedEventHandler implements DomainEventHandlerI
       Period.ThisMonth,
     ])
 
-    await this.analyticsEntityRepository.remove(analyticsEntity)
-
     if (this.mixpanelClient !== null) {
       this.mixpanelClient.track(event.type, {
         distinct_id: analyticsEntity.id.toString(),
         user_created_at: this.timer.convertMicrosecondsToDate(event.payload.userCreatedAtTimestamp),
       })
     }
+
+    await this.analyticsEntityRepository.remove(analyticsEntity)
   }
 }

+ 8 - 0
packages/domain-events/src/Domain/Event/MessageSentToUserEvent.ts

@@ -0,0 +1,8 @@
+import { DomainEventInterface } from './DomainEventInterface'
+
+import { MessageSentToUserEventPayload } from './MessageSentToUserEventPayload'
+
+export interface MessageSentToUserEvent extends DomainEventInterface {
+  type: 'MESSAGE_SENT_TO_USER'
+  payload: MessageSentToUserEventPayload
+}

+ 11 - 0
packages/domain-events/src/Domain/Event/MessageSentToUserEventPayload.ts

@@ -0,0 +1,11 @@
+export interface MessageSentToUserEventPayload {
+  message: {
+    uuid: string
+    recipient_uuid: string
+    sender_uuid: string
+    encrypted_message: string
+    replaceability_identifier: string | null
+    created_at_timestamp: number
+    updated_at_timestamp: number
+  }
+}

+ 2 - 0
packages/domain-events/src/Domain/index.ts

@@ -40,6 +40,8 @@ export * from './Event/ListedAccountDeletedEvent'
 export * from './Event/ListedAccountDeletedEventPayload'
 export * from './Event/ListedAccountRequestedEvent'
 export * from './Event/ListedAccountRequestedEventPayload'
+export * from './Event/MessageSentToUserEvent'
+export * from './Event/MessageSentToUserEventPayload'
 export * from './Event/MuteEmailsSettingChangedEvent'
 export * from './Event/MuteEmailsSettingChangedEventPayload'
 export * from './Event/NotificationAddedForUserEvent'

+ 7 - 1
packages/syncing-server/src/Bootstrap/Container.ts

@@ -783,7 +783,13 @@ export class ContainerConfigLoader {
     container
       .bind<SendMessageToUser>(TYPES.Sync_SendMessageToUser)
       .toConstantValue(
-        new SendMessageToUser(container.get(TYPES.Sync_MessageRepository), container.get(TYPES.Sync_Timer)),
+        new SendMessageToUser(
+          container.get<MessageRepositoryInterface>(TYPES.Sync_MessageRepository),
+          container.get<TimerInterface>(TYPES.Sync_Timer),
+          container.get<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory),
+          container.get<SendEventToClient>(TYPES.Sync_SendEventToClient),
+          container.get<Logger>(TYPES.Sync_Logger),
+        ),
       )
     container
       .bind<DeleteMessage>(TYPES.Sync_DeleteMessage)

+ 26 - 0
packages/syncing-server/src/Domain/Event/DomainEventFactory.ts

@@ -5,6 +5,7 @@ import {
   EmailRequestedEvent,
   ItemDumpedEvent,
   ItemRevisionCreationRequestedEvent,
+  MessageSentToUserEvent,
   NotificationAddedForUserEvent,
   RevisionsCopyRequestedEvent,
   TransitionStatusUpdatedEvent,
@@ -16,6 +17,31 @@ import { DomainEventFactoryInterface } from './DomainEventFactoryInterface'
 export class DomainEventFactory implements DomainEventFactoryInterface {
   constructor(private timer: TimerInterface) {}
 
+  createMessageSentToUserEvent(dto: {
+    message: {
+      uuid: string
+      recipient_uuid: string
+      sender_uuid: string
+      encrypted_message: string
+      replaceability_identifier: string | null
+      created_at_timestamp: number
+      updated_at_timestamp: number
+    }
+  }): MessageSentToUserEvent {
+    return {
+      type: 'MESSAGE_SENT_TO_USER',
+      createdAt: this.timer.getUTCDate(),
+      meta: {
+        correlation: {
+          userIdentifier: dto.message.recipient_uuid,
+          userIdentifierType: 'uuid',
+        },
+        origin: DomainEventService.SyncingServer,
+      },
+      payload: dto,
+    }
+  }
+
   createNotificationAddedForUserEvent(dto: {
     notification: {
       uuid: string

+ 12 - 0
packages/syncing-server/src/Domain/Event/DomainEventFactoryInterface.ts

@@ -3,6 +3,7 @@ import {
   EmailRequestedEvent,
   ItemDumpedEvent,
   ItemRevisionCreationRequestedEvent,
+  MessageSentToUserEvent,
   NotificationAddedForUserEvent,
   RevisionsCopyRequestedEvent,
   TransitionStatusUpdatedEvent,
@@ -11,6 +12,17 @@ import {
 
 export interface DomainEventFactoryInterface {
   createWebSocketMessageRequestedEvent(dto: { userUuid: string; message: string }): WebSocketMessageRequestedEvent
+  createMessageSentToUserEvent(dto: {
+    message: {
+      uuid: string
+      recipient_uuid: string
+      sender_uuid: string
+      encrypted_message: string
+      replaceability_identifier: string | null
+      created_at_timestamp: number
+      updated_at_timestamp: number
+    }
+  }): MessageSentToUserEvent
   createNotificationAddedForUserEvent(dto: {
     notification: {
       uuid: string

+ 35 - 1
packages/syncing-server/src/Domain/UseCase/Messaging/SendMessageToUser/SendMessageToUser.spec.ts

@@ -3,13 +3,21 @@ import { MessageRepositoryInterface } from '../../../Message/MessageRepositoryIn
 import { SendMessageToUser } from './SendMessageToUser'
 import { Message } from '../../../Message/Message'
 import { Result } from '@standardnotes/domain-core'
+import { Logger } from 'winston'
+import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
+import { SendEventToClient } from '../../Syncing/SendEventToClient/SendEventToClient'
+import { MessageSentToUserEvent } from '@standardnotes/domain-events'
 
 describe('SendMessageToUser', () => {
   let messageRepository: MessageRepositoryInterface
   let timer: TimerInterface
   let existingMessage: Message
+  let domainEventFactory: DomainEventFactoryInterface
+  let sendEventToClientUseCase: SendEventToClient
+  let logger: Logger
 
-  const createUseCase = () => new SendMessageToUser(messageRepository, timer)
+  const createUseCase = () =>
+    new SendMessageToUser(messageRepository, timer, domainEventFactory, sendEventToClientUseCase, logger)
 
   beforeEach(() => {
     existingMessage = {} as jest.Mocked<Message>
@@ -21,6 +29,17 @@ describe('SendMessageToUser', () => {
 
     timer = {} as jest.Mocked<TimerInterface>
     timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(123456789)
+
+    domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
+    domainEventFactory.createMessageSentToUserEvent = jest.fn().mockReturnValue({
+      type: 'MESSAGE_SENT_TO_USER',
+    } as jest.Mocked<MessageSentToUserEvent>)
+
+    sendEventToClientUseCase = {} as jest.Mocked<SendEventToClient>
+    sendEventToClientUseCase.execute = jest.fn().mockReturnValue(Result.ok())
+
+    logger = {} as jest.Mocked<Logger>
+    logger.error = jest.fn()
   })
 
   it('saves a new message', async () => {
@@ -104,4 +123,19 @@ describe('SendMessageToUser', () => {
 
     mock.mockRestore()
   })
+
+  it('should log error if event could not be sent to user', async () => {
+    sendEventToClientUseCase.execute = jest.fn().mockReturnValue(Result.fail('Oops'))
+
+    const useCase = createUseCase()
+
+    const result = await useCase.execute({
+      recipientUuid: '00000000-0000-0000-0000-000000000000',
+      senderUuid: '00000000-0000-0000-0000-000000000000',
+      encryptedMessage: 'encrypted-message',
+    })
+
+    expect(result.isFailed()).toBeFalsy()
+    expect(logger.error).toHaveBeenCalled()
+  })
 })

+ 34 - 1
packages/syncing-server/src/Domain/UseCase/Messaging/SendMessageToUser/SendMessageToUser.ts

@@ -4,9 +4,18 @@ import { TimerInterface } from '@standardnotes/time'
 import { SendMessageToUserDTO } from './SendMessageToUserDTO'
 import { MessageRepositoryInterface } from '../../../Message/MessageRepositoryInterface'
 import { Message } from '../../../Message/Message'
+import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
+import { SendEventToClient } from '../../Syncing/SendEventToClient/SendEventToClient'
+import { Logger } from 'winston'
 
 export class SendMessageToUser implements UseCaseInterface<Message> {
-  constructor(private messageRepository: MessageRepositoryInterface, private timer: TimerInterface) {}
+  constructor(
+    private messageRepository: MessageRepositoryInterface,
+    private timer: TimerInterface,
+    private domainEventFactory: DomainEventFactoryInterface,
+    private sendEventToClientUseCase: SendEventToClient,
+    private logger: Logger,
+  ) {}
 
   async execute(dto: SendMessageToUserDTO): Promise<Result<Message>> {
     const recipientUuidOrError = Uuid.create(dto.recipientUuid)
@@ -54,6 +63,30 @@ export class SendMessageToUser implements UseCaseInterface<Message> {
 
     await this.messageRepository.save(message)
 
+    const event = this.domainEventFactory.createMessageSentToUserEvent({
+      message: {
+        uuid: message.id.toString(),
+        recipient_uuid: message.props.recipientUuid.value,
+        sender_uuid: message.props.senderUuid.value,
+        encrypted_message: message.props.encryptedMessage,
+        replaceability_identifier: message.props.replaceabilityIdentifier,
+        created_at_timestamp: message.props.timestamps.createdAt,
+        updated_at_timestamp: message.props.timestamps.updatedAt,
+      },
+    })
+
+    const result = await this.sendEventToClientUseCase.execute({
+      userUuid: message.props.recipientUuid.value,
+      event,
+    })
+    if (result.isFailed()) {
+      this.logger.error(
+        `Failed to send message sent event to client for user ${
+          message.props.recipientUuid.value
+        }: ${result.getError()}`,
+      )
+    }
+
     return Result.ok(message)
   }
 }