Browse Source

feat(syncing-server): distinct notifications upon user removal from shared vault (#840)

Karol Sójko 1 year ago
parent
commit
41e2136bc0

+ 2 - 1
packages/domain-core/src/Domain/Notification/NotificationType.ts

@@ -5,7 +5,8 @@ import { NotificationTypeProps } from './NotificationTypeProps'
 export class NotificationType extends ValueObject<NotificationTypeProps> {
   static readonly TYPES = {
     SharedVaultItemRemoved: 'shared_vault_item_removed',
-    RemovedFromSharedVault: 'removed_from_shared_vault',
+    SelfRemovedFromSharedVault: 'self_removed_from_shared_vault',
+    UserRemovedFromSharedVault: 'user_removed_from_shared_vault',
     UserAddedToSharedVault: 'user_added_to_shared_vault',
     SharedVaultInviteDeclined: 'shared_vault_invite_declined',
     SharedVaultFileUploaded: 'shared_vault_file_uploaded',

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

@@ -766,6 +766,7 @@ export class ContainerConfigLoader {
           container.get<SharedVaultUserRepositoryInterface>(TYPES.Sync_SharedVaultUserRepository),
           container.get<SharedVaultRepositoryInterface>(TYPES.Sync_SharedVaultRepository),
           container.get<AddNotificationsForUsers>(TYPES.Sync_AddNotificationsForUsers),
+          container.get<AddNotificationForUser>(TYPES.Sync_AddNotificationForUser),
           container.get<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory),
           container.get<DomainEventPublisherInterface>(TYPES.Sync_DomainEventPublisher),
         ),

+ 1 - 1
packages/syncing-server/src/Domain/Notifications/Notification.spec.ts

@@ -6,7 +6,7 @@ describe('Notification', () => {
   it('should create an entity', () => {
     const payload = NotificationPayload.create({
       sharedVaultUuid: Uuid.create('0e8c3c7e-3f1a-4f7a-9b5a-5b2b0a7d4b1e').getValue(),
-      type: NotificationType.create(NotificationType.TYPES.RemovedFromSharedVault).getValue(),
+      type: NotificationType.create(NotificationType.TYPES.SelfRemovedFromSharedVault).getValue(),
       version: '1.0',
     }).getValue()
 

+ 5 - 5
packages/syncing-server/src/Domain/UseCase/Messaging/AddNotificationForUser/AddNotificationForUser.spec.ts

@@ -29,7 +29,7 @@ describe('AddNotificationForUser', () => {
 
     payload = NotificationPayload.create({
       sharedVaultUuid: Uuid.create('0e8c3c7e-3f1a-4f7a-9b5a-5b2b0a7d4b1e').getValue(),
-      type: NotificationType.create(NotificationType.TYPES.RemovedFromSharedVault).getValue(),
+      type: NotificationType.create(NotificationType.TYPES.SelfRemovedFromSharedVault).getValue(),
       version: '1.0',
     }).getValue()
 
@@ -50,7 +50,7 @@ describe('AddNotificationForUser', () => {
 
     const result = await useCase.execute({
       userUuid: '0e8c3c7e-3f1a-4f7a-9b5a-5b2b0a7d4b1e',
-      type: NotificationType.TYPES.RemovedFromSharedVault,
+      type: NotificationType.TYPES.SelfRemovedFromSharedVault,
       payload,
       version: '1.0',
     })
@@ -63,7 +63,7 @@ describe('AddNotificationForUser', () => {
 
     const result = await useCase.execute({
       userUuid: 'invalid',
-      type: NotificationType.TYPES.RemovedFromSharedVault,
+      type: NotificationType.TYPES.SelfRemovedFromSharedVault,
       payload,
       version: '1.0',
     })
@@ -94,7 +94,7 @@ describe('AddNotificationForUser', () => {
 
     const result = await useCase.execute({
       userUuid: '0e8c3c7e-3f1a-4f7a-9b5a-5b2b0a7d4b1e',
-      type: NotificationType.TYPES.RemovedFromSharedVault,
+      type: NotificationType.TYPES.SelfRemovedFromSharedVault,
       payload,
       version: '1.0',
     })
@@ -111,7 +111,7 @@ describe('AddNotificationForUser', () => {
 
     const result = await useCase.execute({
       userUuid: '0e8c3c7e-3f1a-4f7a-9b5a-5b2b0a7d4b1e',
-      type: NotificationType.TYPES.RemovedFromSharedVault,
+      type: NotificationType.TYPES.SelfRemovedFromSharedVault,
       payload,
       version: '1.0',
     })

+ 29 - 0
packages/syncing-server/src/Domain/UseCase/Messaging/AddNotificationsForUsers/AddNotificationsForUsers.spec.ts

@@ -54,6 +54,35 @@ describe('AddNotificationsForUsers', () => {
     expect(addNotificationForUser.execute).toHaveBeenCalledTimes(1)
   })
 
+  it('should not add notification for exceptUserUuid', async () => {
+    const useCase = createUseCase()
+
+    const result = await useCase.execute({
+      exceptUserUuid: '00000000-0000-0000-0000-000000000000',
+      sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
+      type: 'test',
+      payload,
+      version: '1.0',
+    })
+
+    expect(result.isFailed()).toBeFalsy()
+    expect(addNotificationForUser.execute).toHaveBeenCalledTimes(0)
+  })
+
+  it('should return error if exceptUserUuid is invalid', async () => {
+    const useCase = createUseCase()
+
+    const result = await useCase.execute({
+      exceptUserUuid: 'invalid',
+      sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
+      type: 'test',
+      payload,
+      version: '1.0',
+    })
+
+    expect(result.isFailed()).toBeTruthy()
+  })
+
   it('should return error if shared vault uuid is invalid', async () => {
     const useCase = createUseCase()
 

+ 13 - 0
packages/syncing-server/src/Domain/UseCase/Messaging/AddNotificationsForUsers/AddNotificationsForUsers.ts

@@ -17,8 +17,21 @@ export class AddNotificationsForUsers implements UseCaseInterface<void> {
     }
     const sharedVaultUuid = sharedVaultUuidOrError.getValue()
 
+    let exceptUserUuid: Uuid | undefined
+    if (dto.exceptUserUuid) {
+      const exceptUserUuidOrError = Uuid.create(dto.exceptUserUuid)
+      if (exceptUserUuidOrError.isFailed()) {
+        return Result.fail(exceptUserUuidOrError.getError())
+      }
+      exceptUserUuid = exceptUserUuidOrError.getValue()
+    }
+
     const sharedVaultUsers = await this.sharedVaultUserRepository.findBySharedVaultUuid(sharedVaultUuid)
     for (const sharedVaultUser of sharedVaultUsers) {
+      if (exceptUserUuid && sharedVaultUser.props.userUuid.equals(exceptUserUuid)) {
+        continue
+      }
+
       const result = await this.addNotificationForUser.execute({
         userUuid: sharedVaultUser.props.userUuid.value,
         type: dto.type,

+ 1 - 0
packages/syncing-server/src/Domain/UseCase/Messaging/AddNotificationsForUsers/AddNotificationsForUsersDTO.ts

@@ -1,6 +1,7 @@
 import { NotificationPayload } from '@standardnotes/domain-core'
 
 export interface AddNotificationsForUsersDTO {
+  exceptUserUuid?: string
   sharedVaultUuid: string
   version: string
   type: string

+ 36 - 0
packages/syncing-server/src/Domain/UseCase/SharedVaults/RemoveUserFromSharedVault/RemoveUserFromSharedVault.spec.ts

@@ -14,11 +14,13 @@ import { RemoveUserFromSharedVault } from './RemoveUserFromSharedVault'
 import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
 import { DomainEventInterface, DomainEventPublisherInterface } from '@standardnotes/domain-events'
 import { AddNotificationsForUsers } from '../../Messaging/AddNotificationsForUsers/AddNotificationsForUsers'
+import { AddNotificationForUser } from '../../Messaging/AddNotificationForUser/AddNotificationForUser'
 
 describe('RemoveUserFromSharedVault', () => {
   let sharedVaultRepository: SharedVaultRepositoryInterface
   let sharedVaultUserRepository: SharedVaultUserRepositoryInterface
   let addNotificationsForUsers: AddNotificationsForUsers
+  let addNotificationForUser: AddNotificationForUser
   let sharedVault: SharedVault
   let sharedVaultUser: SharedVaultUser
   let domainEventFactory: DomainEventFactoryInterface
@@ -29,6 +31,7 @@ describe('RemoveUserFromSharedVault', () => {
       sharedVaultUserRepository,
       sharedVaultRepository,
       addNotificationsForUsers,
+      addNotificationForUser,
       domainEventFactory,
       domainEventPublisher,
     )
@@ -56,6 +59,9 @@ describe('RemoveUserFromSharedVault', () => {
     addNotificationsForUsers = {} as jest.Mocked<AddNotificationsForUsers>
     addNotificationsForUsers.execute = jest.fn().mockReturnValue(Result.ok())
 
+    addNotificationForUser = {} as jest.Mocked<AddNotificationForUser>
+    addNotificationForUser.execute = jest.fn().mockReturnValue(Result.ok())
+
     domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
     domainEventFactory.createUserRemovedFromSharedVaultEvent = jest
       .fn()
@@ -215,6 +221,19 @@ describe('RemoveUserFromSharedVault', () => {
     expect(result.isFailed()).toBe(true)
   })
 
+  it('should return error if notification could not be added for the user removed', async () => {
+    addNotificationForUser.execute = jest.fn().mockResolvedValue(Result.fail('Could not add notification'))
+
+    const useCase = createUseCase()
+    const result = await useCase.execute({
+      originatorUuid: '00000000-0000-0000-0000-000000000000',
+      sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
+      userUuid: '00000000-0000-0000-0000-000000000001',
+    })
+
+    expect(result.isFailed()).toBe(true)
+  })
+
   it('should return error if notification payload could not be created', async () => {
     const mock = jest.spyOn(NotificationPayload, 'create')
     mock.mockReturnValue(Result.fail('Oops'))
@@ -231,4 +250,21 @@ describe('RemoveUserFromSharedVault', () => {
 
     mock.mockRestore()
   })
+
+  it('should return error if self notification payload could not be created', async () => {
+    const mock = jest.spyOn(NotificationPayload, 'create')
+    mock.mockReturnValueOnce(Result.ok()).mockReturnValueOnce(Result.fail('Oops'))
+
+    const useCase = createUseCase()
+    const result = await useCase.execute({
+      originatorUuid: '00000000-0000-0000-0000-000000000000',
+      sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
+      userUuid: '00000000-0000-0000-0000-000000000001',
+    })
+
+    expect(result.isFailed()).toBe(true)
+    expect(result.getError()).toBe('Oops')
+
+    mock.mockRestore()
+  })
 })

+ 27 - 4
packages/syncing-server/src/Domain/UseCase/SharedVaults/RemoveUserFromSharedVault/RemoveUserFromSharedVault.ts

@@ -6,12 +6,14 @@ import { SharedVaultRepositoryInterface } from '../../../SharedVault/SharedVault
 import { SharedVaultUserRepositoryInterface } from '../../../SharedVault/User/SharedVaultUserRepositoryInterface'
 import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
 import { AddNotificationsForUsers } from '../../Messaging/AddNotificationsForUsers/AddNotificationsForUsers'
+import { AddNotificationForUser } from '../../Messaging/AddNotificationForUser/AddNotificationForUser'
 
 export class RemoveUserFromSharedVault implements UseCaseInterface<void> {
   constructor(
     private sharedVaultUsersRepository: SharedVaultUserRepositoryInterface,
     private sharedVaultRepository: SharedVaultRepositoryInterface,
-    private addNotificationForUsers: AddNotificationsForUsers,
+    private addNotificationsForUsers: AddNotificationsForUsers,
+    private addNotificationForUser: AddNotificationForUser,
     private domainEventFactory: DomainEventFactoryInterface,
     private domainEventPublisher: DomainEventPublisherInterface,
   ) {}
@@ -63,7 +65,7 @@ export class RemoveUserFromSharedVault implements UseCaseInterface<void> {
 
     const notificationPayloadOrError = NotificationPayload.create({
       sharedVaultUuid: sharedVault.uuid,
-      type: NotificationType.create(NotificationType.TYPES.RemovedFromSharedVault).getValue(),
+      type: NotificationType.create(NotificationType.TYPES.UserRemovedFromSharedVault).getValue(),
       version: '1.0',
     })
     if (notificationPayloadOrError.isFailed()) {
@@ -71,9 +73,10 @@ export class RemoveUserFromSharedVault implements UseCaseInterface<void> {
     }
     const notificationPayload = notificationPayloadOrError.getValue()
 
-    const result = await this.addNotificationForUsers.execute({
+    const result = await this.addNotificationsForUsers.execute({
       sharedVaultUuid: sharedVault.id.toString(),
-      type: NotificationType.TYPES.RemovedFromSharedVault,
+      exceptUserUuid: userUuid.value,
+      type: NotificationType.TYPES.UserRemovedFromSharedVault,
       payload: notificationPayload,
       version: '1.0',
     })
@@ -81,6 +84,26 @@ export class RemoveUserFromSharedVault implements UseCaseInterface<void> {
       return Result.fail(result.getError())
     }
 
+    const selfNotificationPayloadOrError = NotificationPayload.create({
+      sharedVaultUuid: sharedVault.uuid,
+      type: NotificationType.create(NotificationType.TYPES.SelfRemovedFromSharedVault).getValue(),
+      version: '1.0',
+    })
+    if (selfNotificationPayloadOrError.isFailed()) {
+      return Result.fail(selfNotificationPayloadOrError.getError())
+    }
+    const selfNotificationPayload = selfNotificationPayloadOrError.getValue()
+
+    const selfResult = await this.addNotificationForUser.execute({
+      userUuid: userUuid.value,
+      type: NotificationType.TYPES.SelfRemovedFromSharedVault,
+      payload: selfNotificationPayload,
+      version: '1.0',
+    })
+    if (selfResult.isFailed()) {
+      return Result.fail(selfResult.getError())
+    }
+
     await this.domainEventPublisher.publish(
       this.domainEventFactory.createUserRemovedFromSharedVaultEvent({
         sharedVaultUuid: dto.sharedVaultUuid,