Explorar o código

feat: remove shared vault files upon shared vault removal (#852)

* feat: remove shared vault files upon shared vault removal

* fix: link files queue with syncing-server-js topic
Karol Sójko hai 1 ano
pai
achega
7b1eec21e5

+ 5 - 0
docker/localstack_bootstrap.sh

@@ -139,6 +139,11 @@ LINKING_RESULT=$(link_queue_and_topic $AUTH_TOPIC_ARN $FILES_QUEUE_ARN)
 echo "linking done:"
 echo "$LINKING_RESULT"
 
+echo "linking topic $SYNCING_SERVER_TOPIC_ARN to queue $FILES_QUEUE_ARN"
+LINKING_RESULT=$(link_queue_and_topic $SYNCING_SERVER_TOPIC_ARN $FILES_QUEUE_ARN)
+echo "linking done:"
+echo "$LINKING_RESULT"
+
 QUEUE_NAME="syncing-server-local-queue"
 
 echo "creating queue $QUEUE_NAME"

+ 0 - 2
jest.config.js

@@ -3,8 +3,6 @@ module.exports = {
   testEnvironment: 'node',
   testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.ts$',
   testTimeout: 20000,
-  coverageReporters: ['text'],
-  reporters: ['summary'],
   coverageThreshold: {
     global: {
       branches: 100,

+ 1 - 0
packages/domain-events/src/Domain/Event/SharedVaultRemovedEventPayload.ts

@@ -1,3 +1,4 @@
 export interface SharedVaultRemovedEventPayload {
   sharedVaultUuid: string
+  vaultOwnerUuid: string
 }

+ 0 - 73
packages/files/src/Domain/Handler/AccountDeletionRequestedEventHandler.spec.ts

@@ -1,73 +0,0 @@
-import 'reflect-metadata'
-
-import {
-  AccountDeletionRequestedEvent,
-  AccountDeletionRequestedEventPayload,
-  DomainEventPublisherInterface,
-  FileRemovedEvent,
-} from '@standardnotes/domain-events'
-import { MarkFilesToBeRemoved } from '../UseCase/MarkFilesToBeRemoved/MarkFilesToBeRemoved'
-
-import { AccountDeletionRequestedEventHandler } from './AccountDeletionRequestedEventHandler'
-import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
-import { RemovedFileDescription } from '../File/RemovedFileDescription'
-
-describe('AccountDeletionRequestedEventHandler', () => {
-  let markFilesToBeRemoved: MarkFilesToBeRemoved
-  let event: AccountDeletionRequestedEvent
-  let domainEventPublisher: DomainEventPublisherInterface
-  let domainEventFactory: DomainEventFactoryInterface
-
-  const createHandler = () =>
-    new AccountDeletionRequestedEventHandler(markFilesToBeRemoved, domainEventPublisher, domainEventFactory)
-
-  beforeEach(() => {
-    markFilesToBeRemoved = {} as jest.Mocked<MarkFilesToBeRemoved>
-    markFilesToBeRemoved.execute = jest.fn().mockReturnValue({
-      success: true,
-      filesRemoved: [{} as jest.Mocked<RemovedFileDescription>],
-    })
-
-    event = {} as jest.Mocked<AccountDeletionRequestedEvent>
-    event.payload = {
-      userUuid: '1-2-3',
-      regularSubscriptionUuid: '1-2-3',
-    } as jest.Mocked<AccountDeletionRequestedEventPayload>
-
-    domainEventPublisher = {} as jest.Mocked<DomainEventPublisherInterface>
-    domainEventPublisher.publish = jest.fn()
-
-    domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
-    domainEventFactory.createFileRemovedEvent = jest.fn().mockReturnValue({} as jest.Mocked<FileRemovedEvent>)
-  })
-
-  it('should mark files to be remove for user', async () => {
-    await createHandler().handle(event)
-
-    expect(markFilesToBeRemoved.execute).toHaveBeenCalledWith({ ownerUuid: '1-2-3' })
-
-    expect(domainEventPublisher.publish).toHaveBeenCalled()
-  })
-
-  it('should not mark files to be remove for user if user has no regular subscription', async () => {
-    event.payload.regularSubscriptionUuid = undefined
-
-    await createHandler().handle(event)
-
-    expect(markFilesToBeRemoved.execute).not.toHaveBeenCalled()
-
-    expect(domainEventPublisher.publish).not.toHaveBeenCalled()
-  })
-
-  it('should not publish events if failed to mark files to be removed', async () => {
-    markFilesToBeRemoved.execute = jest.fn().mockReturnValue({
-      success: false,
-    })
-
-    await createHandler().handle(event)
-
-    expect(markFilesToBeRemoved.execute).toHaveBeenCalledWith({ ownerUuid: '1-2-3' })
-
-    expect(domainEventPublisher.publish).not.toHaveBeenCalled()
-  })
-})

+ 5 - 3
packages/files/src/Domain/Handler/AccountDeletionRequestedEventHandler.ts

@@ -22,15 +22,17 @@ export class AccountDeletionRequestedEventHandler implements DomainEventHandlerI
       return
     }
 
-    const response = await this.markFilesToBeRemoved.execute({
+    const result = await this.markFilesToBeRemoved.execute({
       ownerUuid: event.payload.userUuid,
     })
 
-    if (!response.success) {
+    if (result.isFailed()) {
       return
     }
 
-    for (const fileRemoved of response.filesRemoved) {
+    const filesRemoved = result.getValue()
+
+    for (const fileRemoved of filesRemoved) {
       await this.domainEventPublisher.publish(
         this.domainEventFactory.createFileRemovedEvent({
           regularSubscriptionUuid: event.payload.regularSubscriptionUuid,

+ 0 - 73
packages/files/src/Domain/Handler/SharedSubscriptionInvitationCanceledEventHandler.spec.ts

@@ -1,73 +0,0 @@
-import 'reflect-metadata'
-
-import {
-  SharedSubscriptionInvitationCanceledEvent,
-  SharedSubscriptionInvitationCanceledEventPayload,
-  DomainEventPublisherInterface,
-  FileRemovedEvent,
-} from '@standardnotes/domain-events'
-import { MarkFilesToBeRemoved } from '../UseCase/MarkFilesToBeRemoved/MarkFilesToBeRemoved'
-
-import { SharedSubscriptionInvitationCanceledEventHandler } from './SharedSubscriptionInvitationCanceledEventHandler'
-import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
-import { RemovedFileDescription } from '../File/RemovedFileDescription'
-
-describe('SharedSubscriptionInvitationCanceledEventHandler', () => {
-  let markFilesToBeRemoved: MarkFilesToBeRemoved
-  let event: SharedSubscriptionInvitationCanceledEvent
-  let domainEventPublisher: DomainEventPublisherInterface
-  let domainEventFactory: DomainEventFactoryInterface
-
-  const createHandler = () =>
-    new SharedSubscriptionInvitationCanceledEventHandler(markFilesToBeRemoved, domainEventPublisher, domainEventFactory)
-
-  beforeEach(() => {
-    markFilesToBeRemoved = {} as jest.Mocked<MarkFilesToBeRemoved>
-    markFilesToBeRemoved.execute = jest.fn().mockReturnValue({
-      success: true,
-      filesRemoved: [{} as jest.Mocked<RemovedFileDescription>],
-    })
-
-    event = {} as jest.Mocked<SharedSubscriptionInvitationCanceledEvent>
-    event.payload = {
-      inviteeIdentifier: '1-2-3',
-      inviteeIdentifierType: 'uuid',
-    } as jest.Mocked<SharedSubscriptionInvitationCanceledEventPayload>
-
-    domainEventPublisher = {} as jest.Mocked<DomainEventPublisherInterface>
-    domainEventPublisher.publish = jest.fn()
-
-    domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
-    domainEventFactory.createFileRemovedEvent = jest.fn().mockReturnValue({} as jest.Mocked<FileRemovedEvent>)
-  })
-
-  it('should mark files to be remove for user', async () => {
-    await createHandler().handle(event)
-
-    expect(markFilesToBeRemoved.execute).toHaveBeenCalledWith({ ownerUuid: '1-2-3' })
-
-    expect(domainEventPublisher.publish).toHaveBeenCalled()
-  })
-
-  it('should not mark files to be remove for user if identifier is not of uuid type', async () => {
-    event.payload.inviteeIdentifierType = 'email'
-
-    await createHandler().handle(event)
-
-    expect(markFilesToBeRemoved.execute).not.toHaveBeenCalled()
-
-    expect(domainEventPublisher.publish).not.toHaveBeenCalled()
-  })
-
-  it('should not publish events if failed to mark files to be removed', async () => {
-    markFilesToBeRemoved.execute = jest.fn().mockReturnValue({
-      success: false,
-    })
-
-    await createHandler().handle(event)
-
-    expect(markFilesToBeRemoved.execute).toHaveBeenCalledWith({ ownerUuid: '1-2-3' })
-
-    expect(domainEventPublisher.publish).not.toHaveBeenCalled()
-  })
-})

+ 5 - 3
packages/files/src/Domain/Handler/SharedSubscriptionInvitationCanceledEventHandler.ts

@@ -22,15 +22,17 @@ export class SharedSubscriptionInvitationCanceledEventHandler implements DomainE
       return
     }
 
-    const response = await this.markFilesToBeRemoved.execute({
+    const result = await this.markFilesToBeRemoved.execute({
       ownerUuid: event.payload.inviteeIdentifier,
     })
 
-    if (!response.success) {
+    if (result.isFailed()) {
       return
     }
 
-    for (const fileRemoved of response.filesRemoved) {
+    const filesRemoved = result.getValue()
+
+    for (const fileRemoved of filesRemoved) {
       await this.domainEventPublisher.publish(
         this.domainEventFactory.createFileRemovedEvent({
           regularSubscriptionUuid: event.payload.inviterSubscriptionUuid,

+ 44 - 0
packages/files/src/Domain/Handler/SharedVaultRemovedEventHandler.ts

@@ -0,0 +1,44 @@
+import {
+  DomainEventHandlerInterface,
+  DomainEventPublisherInterface,
+  SharedVaultRemovedEvent,
+} from '@standardnotes/domain-events'
+import { Logger } from 'winston'
+
+import { MarkFilesToBeRemoved } from '../UseCase/MarkFilesToBeRemoved/MarkFilesToBeRemoved'
+import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
+
+export class SharedVaultRemovedEventHandler implements DomainEventHandlerInterface {
+  constructor(
+    private markFilesToBeRemoved: MarkFilesToBeRemoved,
+    private domainEventPublisher: DomainEventPublisherInterface,
+    private domainEventFactory: DomainEventFactoryInterface,
+    private logger: Logger,
+  ) {}
+
+  async handle(event: SharedVaultRemovedEvent): Promise<void> {
+    const result = await this.markFilesToBeRemoved.execute({
+      ownerUuid: event.payload.sharedVaultUuid,
+    })
+
+    if (result.isFailed()) {
+      this.logger.error(
+        `Could not mark files to be removed for shared vault: ${event.payload.sharedVaultUuid}: ${result.getError()}`,
+      )
+    }
+
+    const filesRemoved = result.getValue()
+
+    for (const fileRemoved of filesRemoved) {
+      await this.domainEventPublisher.publish(
+        this.domainEventFactory.createSharedVaultFileRemovedEvent({
+          fileByteSize: fileRemoved.fileByteSize,
+          fileName: fileRemoved.fileName,
+          filePath: fileRemoved.filePath,
+          sharedVaultUuid: event.payload.sharedVaultUuid,
+          vaultOwnerUuid: event.payload.vaultOwnerUuid,
+        }),
+      )
+    }
+  }
+}

+ 5 - 5
packages/files/src/Domain/UseCase/MarkFilesToBeRemoved/MarkFilesToBeRemoved.spec.ts

@@ -21,7 +21,9 @@ describe('MarkFilesToBeRemoved', () => {
   })
 
   it('should mark files for being removed', async () => {
-    expect(await createUseCase().execute({ ownerUuid: '1-2-3' })).toEqual({ success: true })
+    const result = await createUseCase().execute({ ownerUuid: '1-2-3' })
+
+    expect(result.isFailed()).toEqual(false)
 
     expect(fileRemover.markFilesToBeRemoved).toHaveBeenCalledWith('1-2-3')
   })
@@ -31,9 +33,7 @@ describe('MarkFilesToBeRemoved', () => {
       throw new Error('Oops')
     })
 
-    expect(await createUseCase().execute({ ownerUuid: '1-2-3' })).toEqual({
-      success: false,
-      message: 'Could not mark resources for removal',
-    })
+    const result = await createUseCase().execute({ ownerUuid: '1-2-3' })
+    expect(result.isFailed()).toEqual(true)
   })
 })

+ 6 - 12
packages/files/src/Domain/UseCase/MarkFilesToBeRemoved/MarkFilesToBeRemoved.ts

@@ -1,36 +1,30 @@
 import { inject, injectable } from 'inversify'
 import { Logger } from 'winston'
+import { Result, UseCaseInterface } from '@standardnotes/domain-core'
 
 import TYPES from '../../../Bootstrap/Types'
 import { FileRemoverInterface } from '../../Services/FileRemoverInterface'
-import { UseCaseInterface } from '../UseCaseInterface'
 import { MarkFilesToBeRemovedDTO } from './MarkFilesToBeRemovedDTO'
-import { MarkFilesToBeRemovedResponse } from './MarkFilesToBeRemovedResponse'
+import { RemovedFileDescription } from '../../File/RemovedFileDescription'
 
 @injectable()
-export class MarkFilesToBeRemoved implements UseCaseInterface {
+export class MarkFilesToBeRemoved implements UseCaseInterface<RemovedFileDescription[]> {
   constructor(
     @inject(TYPES.Files_FileRemover) private fileRemover: FileRemoverInterface,
     @inject(TYPES.Files_Logger) private logger: Logger,
   ) {}
 
-  async execute(dto: MarkFilesToBeRemovedDTO): Promise<MarkFilesToBeRemovedResponse> {
+  async execute(dto: MarkFilesToBeRemovedDTO): Promise<Result<RemovedFileDescription[]>> {
     try {
       this.logger.debug(`Marking files for later removal for user: ${dto.ownerUuid}`)
 
       const filesRemoved = await this.fileRemover.markFilesToBeRemoved(dto.ownerUuid)
 
-      return {
-        success: true,
-        filesRemoved,
-      }
+      return Result.ok(filesRemoved)
     } catch (error) {
       this.logger.error(`Could not mark resources for removal: ${dto.ownerUuid} - ${(error as Error).message}`)
 
-      return {
-        success: false,
-        message: 'Could not mark resources for removal',
-      }
+      return Result.fail('Could not mark resources for removal')
     }
   }
 }

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

@@ -42,7 +42,7 @@ export class DomainEventFactory implements DomainEventFactoryInterface {
     }
   }
 
-  createSharedVaultRemovedEvent(dto: { sharedVaultUuid: string }): SharedVaultRemovedEvent {
+  createSharedVaultRemovedEvent(dto: { sharedVaultUuid: string; vaultOwnerUuid: string }): SharedVaultRemovedEvent {
     return {
       type: 'SHARED_VAULT_REMOVED',
       createdAt: this.timer.getUTCDate(),

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

@@ -102,7 +102,7 @@ export interface DomainEventFactoryInterface {
     itemUuid: string
     userUuid: string
   }): ItemRemovedFromSharedVaultEvent
-  createSharedVaultRemovedEvent(dto: { sharedVaultUuid: string }): SharedVaultRemovedEvent
+  createSharedVaultRemovedEvent(dto: { sharedVaultUuid: string; vaultOwnerUuid: string }): SharedVaultRemovedEvent
   createUserDesignatedAsSurvivorInSharedVaultEvent(dto: {
     sharedVaultUuid: string
     userUuid: string

+ 1 - 0
packages/syncing-server/src/Domain/UseCase/SharedVaults/DeleteSharedVault/DeleteSharedVault.ts

@@ -101,6 +101,7 @@ export class DeleteSharedVault implements UseCaseInterface<void> {
     await this.domainEventPublisher.publish(
       this.domainEventFactory.createSharedVaultRemovedEvent({
         sharedVaultUuid: sharedVaultUuid.value,
+        vaultOwnerUuid: sharedVault.props.userUuid.value,
       }),
     )