فهرست منبع

feat(syncing-server): add sending invites via websockets (#804)

* feat(domain-events): add user invited to shared vault event

* feat(syncing-server): add sending invites via websockets
Karol Sójko 1 سال پیش
والد
کامیت
dc3a41e4bb

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

@@ -0,0 +1,8 @@
+import { DomainEventInterface } from './DomainEventInterface'
+
+import { UserInvitedToSharedVaultEventPayload } from './UserInvitedToSharedVaultEventPayload'
+
+export interface UserInvitedToSharedVaultEvent extends DomainEventInterface {
+  type: 'USER_INVITED_TO_SHARED_VAULT'
+  payload: UserInvitedToSharedVaultEventPayload
+}

+ 12 - 0
packages/domain-events/src/Domain/Event/UserInvitedToSharedVaultEventPayload.ts

@@ -0,0 +1,12 @@
+export interface UserInvitedToSharedVaultEventPayload {
+  invite: {
+    uuid: string
+    shared_vault_uuid: string
+    user_uuid: string
+    sender_uuid: string
+    encrypted_message: string
+    permission: string
+    created_at_timestamp: number
+    updated_at_timestamp: number
+  }
+}

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

@@ -100,6 +100,8 @@ export * from './Event/UserDisabledSessionUserAgentLoggingEvent'
 export * from './Event/UserDisabledSessionUserAgentLoggingEventPayload'
 export * from './Event/UserEmailChangedEvent'
 export * from './Event/UserEmailChangedEventPayload'
+export * from './Event/UserInvitedToSharedVaultEvent'
+export * from './Event/UserInvitedToSharedVaultEventPayload'
 export * from './Event/UserRegisteredEvent'
 export * from './Event/UserRegisteredEventPayload'
 export * from './Event/UserRolesChangedEvent'

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

@@ -678,10 +678,13 @@ export class ContainerConfigLoader {
       .bind<InviteUserToSharedVault>(TYPES.Sync_InviteUserToSharedVault)
       .toConstantValue(
         new InviteUserToSharedVault(
-          container.get(TYPES.Sync_SharedVaultRepository),
-          container.get(TYPES.Sync_SharedVaultInviteRepository),
-          container.get(TYPES.Sync_SharedVaultUserRepository),
-          container.get(TYPES.Sync_Timer),
+          container.get<SharedVaultRepositoryInterface>(TYPES.Sync_SharedVaultRepository),
+          container.get<SharedVaultInviteRepositoryInterface>(TYPES.Sync_SharedVaultInviteRepository),
+          container.get<SharedVaultUserRepositoryInterface>(TYPES.Sync_SharedVaultUserRepository),
+          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

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

@@ -9,6 +9,7 @@ import {
   NotificationAddedForUserEvent,
   RevisionsCopyRequestedEvent,
   TransitionStatusUpdatedEvent,
+  UserInvitedToSharedVaultEvent,
   WebSocketMessageRequestedEvent,
 } from '@standardnotes/domain-events'
 import { TimerInterface } from '@standardnotes/time'
@@ -17,6 +18,32 @@ import { DomainEventFactoryInterface } from './DomainEventFactoryInterface'
 export class DomainEventFactory implements DomainEventFactoryInterface {
   constructor(private timer: TimerInterface) {}
 
+  createUserInvitedToSharedVaultEvent(dto: {
+    invite: {
+      uuid: string
+      shared_vault_uuid: string
+      user_uuid: string
+      sender_uuid: string
+      encrypted_message: string
+      permission: string
+      created_at_timestamp: number
+      updated_at_timestamp: number
+    }
+  }): UserInvitedToSharedVaultEvent {
+    return {
+      type: 'USER_INVITED_TO_SHARED_VAULT',
+      createdAt: this.timer.getUTCDate(),
+      meta: {
+        correlation: {
+          userIdentifier: dto.invite.user_uuid,
+          userIdentifierType: 'uuid',
+        },
+        origin: DomainEventService.SyncingServer,
+      },
+      payload: dto,
+    }
+  }
+
   createMessageSentToUserEvent(dto: {
     message: {
       uuid: string

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

@@ -7,11 +7,24 @@ import {
   NotificationAddedForUserEvent,
   RevisionsCopyRequestedEvent,
   TransitionStatusUpdatedEvent,
+  UserInvitedToSharedVaultEvent,
   WebSocketMessageRequestedEvent,
 } from '@standardnotes/domain-events'
 
 export interface DomainEventFactoryInterface {
   createWebSocketMessageRequestedEvent(dto: { userUuid: string; message: string }): WebSocketMessageRequestedEvent
+  createUserInvitedToSharedVaultEvent(dto: {
+    invite: {
+      uuid: string
+      shared_vault_uuid: string
+      user_uuid: string
+      sender_uuid: string
+      encrypted_message: string
+      permission: string
+      created_at_timestamp: number
+      updated_at_timestamp: number
+    }
+  }): UserInvitedToSharedVaultEvent
   createMessageSentToUserEvent(dto: {
     message: {
       uuid: string

+ 44 - 1
packages/syncing-server/src/Domain/UseCase/SharedVaults/InviteUserToSharedVault/InviteUserToSharedVault.spec.ts

@@ -1,5 +1,6 @@
 import { TimerInterface } from '@standardnotes/time'
 import { Uuid, Timestamps, Result, SharedVaultUserPermission } from '@standardnotes/domain-core'
+import { Logger } from 'winston'
 
 import { SharedVaultRepositoryInterface } from '../../../SharedVault/SharedVaultRepositoryInterface'
 import { SharedVaultInviteRepositoryInterface } from '../../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
@@ -8,6 +9,9 @@ import { SharedVault } from '../../../SharedVault/SharedVault'
 import { SharedVaultInvite } from '../../../SharedVault/User/Invite/SharedVaultInvite'
 import { SharedVaultUserRepositoryInterface } from '../../../SharedVault/User/SharedVaultUserRepositoryInterface'
 import { SharedVaultUser } from '../../../SharedVault/User/SharedVaultUser'
+import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
+import { SendEventToClient } from '../../Syncing/SendEventToClient/SendEventToClient'
+import { UserInvitedToSharedVaultEvent } from '@standardnotes/domain-events'
 
 describe('InviteUserToSharedVault', () => {
   let sharedVaultRepository: SharedVaultRepositoryInterface
@@ -16,9 +20,20 @@ describe('InviteUserToSharedVault', () => {
   let timer: TimerInterface
   let sharedVault: SharedVault
   let sharedVaultUser: SharedVaultUser
+  let domainEventFactory: DomainEventFactoryInterface
+  let sendEventToClientUseCase: SendEventToClient
+  let logger: Logger
 
   const createUseCase = () =>
-    new InviteUserToSharedVault(sharedVaultRepository, sharedVaultInviteRepository, sharedVaultUserRepository, timer)
+    new InviteUserToSharedVault(
+      sharedVaultRepository,
+      sharedVaultInviteRepository,
+      sharedVaultUserRepository,
+      timer,
+      domainEventFactory,
+      sendEventToClientUseCase,
+      logger,
+    )
 
   beforeEach(() => {
     sharedVault = SharedVault.create({
@@ -46,6 +61,17 @@ describe('InviteUserToSharedVault', () => {
 
     timer = {} as jest.Mocked<TimerInterface>
     timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(123)
+
+    domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
+    domainEventFactory.createUserInvitedToSharedVaultEvent = jest.fn().mockReturnValue({
+      type: 'USER_INVITED_TO_SHARED_VAULT',
+    } as jest.Mocked<UserInvitedToSharedVaultEvent>)
+
+    sendEventToClientUseCase = {} as jest.Mocked<SendEventToClient>
+    sendEventToClientUseCase.execute = jest.fn().mockReturnValue(Result.ok())
+
+    logger = {} as jest.Mocked<Logger>
+    logger.error = jest.fn()
   })
 
   it('should return a failure result if the shared vault uuid is invalid', async () => {
@@ -217,4 +243,21 @@ describe('InviteUserToSharedVault', () => {
 
     mockSharedVaultInvite.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({
+      sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
+      senderUuid: '00000000-0000-0000-0000-000000000000',
+      recipientUuid: '00000000-0000-0000-0000-000000000000',
+      permission: SharedVaultUserPermission.PERMISSIONS.Read,
+      encryptedMessage: 'encryptedMessage',
+    })
+
+    expect(result.isFailed()).toBe(false)
+    expect(logger.error).toHaveBeenCalled()
+  })
 })

+ 31 - 0
packages/syncing-server/src/Domain/UseCase/SharedVaults/InviteUserToSharedVault/InviteUserToSharedVault.ts

@@ -6,6 +6,9 @@ import { SharedVaultRepositoryInterface } from '../../../SharedVault/SharedVault
 import { InviteUserToSharedVaultDTO } from './InviteUserToSharedVaultDTO'
 import { SharedVaultInviteRepositoryInterface } from '../../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
 import { SharedVaultUserRepositoryInterface } from '../../../SharedVault/User/SharedVaultUserRepositoryInterface'
+import { Logger } from 'winston'
+import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
+import { SendEventToClient } from '../../Syncing/SendEventToClient/SendEventToClient'
 
 export class InviteUserToSharedVault implements UseCaseInterface<SharedVaultInvite> {
   constructor(
@@ -13,6 +16,9 @@ export class InviteUserToSharedVault implements UseCaseInterface<SharedVaultInvi
     private sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface,
     private sharedVaultUserRepository: SharedVaultUserRepositoryInterface,
     private timer: TimerInterface,
+    private domainEventFactory: DomainEventFactoryInterface,
+    private sendEventToClientUseCase: SendEventToClient,
+    private logger: Logger,
   ) {}
   async execute(dto: InviteUserToSharedVaultDTO): Promise<Result<SharedVaultInvite>> {
     const sharedVaultUuidOrError = Uuid.create(dto.sharedVaultUuid)
@@ -82,6 +88,31 @@ export class InviteUserToSharedVault implements UseCaseInterface<SharedVaultInvi
 
     await this.sharedVaultInviteRepository.save(sharedVaultInvite)
 
+    const event = this.domainEventFactory.createUserInvitedToSharedVaultEvent({
+      invite: {
+        uuid: sharedVaultInvite.id.toString(),
+        shared_vault_uuid: sharedVaultInvite.props.sharedVaultUuid.value,
+        user_uuid: sharedVaultInvite.props.userUuid.value,
+        sender_uuid: sharedVaultInvite.props.senderUuid.value,
+        encrypted_message: sharedVaultInvite.props.encryptedMessage,
+        permission: sharedVaultInvite.props.permission.value,
+        created_at_timestamp: sharedVaultInvite.props.timestamps.createdAt,
+        updated_at_timestamp: sharedVaultInvite.props.timestamps.updatedAt,
+      },
+    })
+
+    const result = await this.sendEventToClientUseCase.execute({
+      userUuid: sharedVaultInvite.props.userUuid.value,
+      event,
+    })
+    if (result.isFailed()) {
+      this.logger.error(
+        `Failed to send user invited to shared vault event to client for user ${
+          sharedVaultInvite.props.userUuid.value
+        }: ${result.getError()}`,
+      )
+    }
+
     return Result.ok(sharedVaultInvite)
   }
 }