From 7b1eec21e54330bebbeebb80cec3ba4284112aab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20S=C3=B3jko?= Date: Mon, 25 Sep 2023 12:56:31 +0200 Subject: [PATCH] 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 --- docker/localstack_bootstrap.sh | 5 ++ jest.config.js | 2 - .../Event/SharedVaultRemovedEventPayload.ts | 1 + ...countDeletionRequestedEventHandler.spec.ts | 73 ------------------- .../AccountDeletionRequestedEventHandler.ts | 8 +- ...tionInvitationCanceledEventHandler.spec.ts | 73 ------------------- ...scriptionInvitationCanceledEventHandler.ts | 8 +- .../Handler/SharedVaultRemovedEventHandler.ts | 44 +++++++++++ .../MarkFilesToBeRemoved.spec.ts | 10 +-- .../MarkFilesToBeRemoved.ts | 18 ++--- .../src/Domain/Event/DomainEventFactory.ts | 2 +- .../Event/DomainEventFactoryInterface.ts | 2 +- .../DeleteSharedVault/DeleteSharedVault.ts | 1 + 13 files changed, 74 insertions(+), 173 deletions(-) delete mode 100644 packages/files/src/Domain/Handler/AccountDeletionRequestedEventHandler.spec.ts delete mode 100644 packages/files/src/Domain/Handler/SharedSubscriptionInvitationCanceledEventHandler.spec.ts create mode 100644 packages/files/src/Domain/Handler/SharedVaultRemovedEventHandler.ts diff --git a/docker/localstack_bootstrap.sh b/docker/localstack_bootstrap.sh index c64672f00..23561d6be 100755 --- a/docker/localstack_bootstrap.sh +++ b/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" diff --git a/jest.config.js b/jest.config.js index b0857c1ae..7478c95e3 100644 --- a/jest.config.js +++ b/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, diff --git a/packages/domain-events/src/Domain/Event/SharedVaultRemovedEventPayload.ts b/packages/domain-events/src/Domain/Event/SharedVaultRemovedEventPayload.ts index 2f3e4cd35..f8bee7f4b 100644 --- a/packages/domain-events/src/Domain/Event/SharedVaultRemovedEventPayload.ts +++ b/packages/domain-events/src/Domain/Event/SharedVaultRemovedEventPayload.ts @@ -1,3 +1,4 @@ export interface SharedVaultRemovedEventPayload { sharedVaultUuid: string + vaultOwnerUuid: string } diff --git a/packages/files/src/Domain/Handler/AccountDeletionRequestedEventHandler.spec.ts b/packages/files/src/Domain/Handler/AccountDeletionRequestedEventHandler.spec.ts deleted file mode 100644 index 1ca633278..000000000 --- a/packages/files/src/Domain/Handler/AccountDeletionRequestedEventHandler.spec.ts +++ /dev/null @@ -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.execute = jest.fn().mockReturnValue({ - success: true, - filesRemoved: [{} as jest.Mocked], - }) - - event = {} as jest.Mocked - event.payload = { - userUuid: '1-2-3', - regularSubscriptionUuid: '1-2-3', - } as jest.Mocked - - domainEventPublisher = {} as jest.Mocked - domainEventPublisher.publish = jest.fn() - - domainEventFactory = {} as jest.Mocked - domainEventFactory.createFileRemovedEvent = jest.fn().mockReturnValue({} as jest.Mocked) - }) - - 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() - }) -}) diff --git a/packages/files/src/Domain/Handler/AccountDeletionRequestedEventHandler.ts b/packages/files/src/Domain/Handler/AccountDeletionRequestedEventHandler.ts index 8790c7bba..1bd90a250 100644 --- a/packages/files/src/Domain/Handler/AccountDeletionRequestedEventHandler.ts +++ b/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, diff --git a/packages/files/src/Domain/Handler/SharedSubscriptionInvitationCanceledEventHandler.spec.ts b/packages/files/src/Domain/Handler/SharedSubscriptionInvitationCanceledEventHandler.spec.ts deleted file mode 100644 index be3ce18ef..000000000 --- a/packages/files/src/Domain/Handler/SharedSubscriptionInvitationCanceledEventHandler.spec.ts +++ /dev/null @@ -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.execute = jest.fn().mockReturnValue({ - success: true, - filesRemoved: [{} as jest.Mocked], - }) - - event = {} as jest.Mocked - event.payload = { - inviteeIdentifier: '1-2-3', - inviteeIdentifierType: 'uuid', - } as jest.Mocked - - domainEventPublisher = {} as jest.Mocked - domainEventPublisher.publish = jest.fn() - - domainEventFactory = {} as jest.Mocked - domainEventFactory.createFileRemovedEvent = jest.fn().mockReturnValue({} as jest.Mocked) - }) - - 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() - }) -}) diff --git a/packages/files/src/Domain/Handler/SharedSubscriptionInvitationCanceledEventHandler.ts b/packages/files/src/Domain/Handler/SharedSubscriptionInvitationCanceledEventHandler.ts index c122aa8b3..0b5cc4a8a 100644 --- a/packages/files/src/Domain/Handler/SharedSubscriptionInvitationCanceledEventHandler.ts +++ b/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, diff --git a/packages/files/src/Domain/Handler/SharedVaultRemovedEventHandler.ts b/packages/files/src/Domain/Handler/SharedVaultRemovedEventHandler.ts new file mode 100644 index 000000000..af04ce48e --- /dev/null +++ b/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 { + 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, + }), + ) + } + } +} diff --git a/packages/files/src/Domain/UseCase/MarkFilesToBeRemoved/MarkFilesToBeRemoved.spec.ts b/packages/files/src/Domain/UseCase/MarkFilesToBeRemoved/MarkFilesToBeRemoved.spec.ts index c0d4d0734..4718ed748 100644 --- a/packages/files/src/Domain/UseCase/MarkFilesToBeRemoved/MarkFilesToBeRemoved.spec.ts +++ b/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) }) }) diff --git a/packages/files/src/Domain/UseCase/MarkFilesToBeRemoved/MarkFilesToBeRemoved.ts b/packages/files/src/Domain/UseCase/MarkFilesToBeRemoved/MarkFilesToBeRemoved.ts index d55ceef51..7e1bcb6d5 100644 --- a/packages/files/src/Domain/UseCase/MarkFilesToBeRemoved/MarkFilesToBeRemoved.ts +++ b/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 { constructor( @inject(TYPES.Files_FileRemover) private fileRemover: FileRemoverInterface, @inject(TYPES.Files_Logger) private logger: Logger, ) {} - async execute(dto: MarkFilesToBeRemovedDTO): Promise { + async execute(dto: MarkFilesToBeRemovedDTO): Promise> { 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') } } } diff --git a/packages/syncing-server/src/Domain/Event/DomainEventFactory.ts b/packages/syncing-server/src/Domain/Event/DomainEventFactory.ts index 7fd338da9..f2ef8d075 100644 --- a/packages/syncing-server/src/Domain/Event/DomainEventFactory.ts +++ b/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(), diff --git a/packages/syncing-server/src/Domain/Event/DomainEventFactoryInterface.ts b/packages/syncing-server/src/Domain/Event/DomainEventFactoryInterface.ts index e33dd0fba..73deaa48b 100644 --- a/packages/syncing-server/src/Domain/Event/DomainEventFactoryInterface.ts +++ b/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 diff --git a/packages/syncing-server/src/Domain/UseCase/SharedVaults/DeleteSharedVault/DeleteSharedVault.ts b/packages/syncing-server/src/Domain/UseCase/SharedVaults/DeleteSharedVault/DeleteSharedVault.ts index cd4404aa5..0a99923e5 100644 --- a/packages/syncing-server/src/Domain/UseCase/SharedVaults/DeleteSharedVault/DeleteSharedVault.ts +++ b/packages/syncing-server/src/Domain/UseCase/SharedVaults/DeleteSharedVault/DeleteSharedVault.ts @@ -101,6 +101,7 @@ export class DeleteSharedVault implements UseCaseInterface { await this.domainEventPublisher.publish( this.domainEventFactory.createSharedVaultRemovedEvent({ sharedVaultUuid: sharedVaultUuid.value, + vaultOwnerUuid: sharedVault.props.userUuid.value, }), )