From 3415cae093ecd3631b924e722d4bd1d5015dd37a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20S=C3=B3jko?= Date: Wed, 9 Aug 2023 10:05:48 +0200 Subject: [PATCH] fix(syncing-server): update storage quota used in a shared vault (#691) --- .../syncing-server/src/Bootstrap/Container.ts | 24 ++++++ .../syncing-server/src/Bootstrap/Types.ts | 3 + .../SharedVaultFileRemovedEventHandler.ts | 22 ++++++ .../SharedVaultFileUploadedEventHandler.ts | 22 ++++++ ...pdateStorageQuotaUsedInSharedVault.spec.ts | 74 +++++++++++++++++++ .../UpdateStorageQuotaUsedInSharedVault.ts | 27 +++++++ .../UpdateStorageQuotaUsedInSharedVaultDTO.ts | 4 + 7 files changed, 176 insertions(+) create mode 100644 packages/syncing-server/src/Domain/Handler/SharedVaultFileRemovedEventHandler.ts create mode 100644 packages/syncing-server/src/Domain/Handler/SharedVaultFileUploadedEventHandler.ts create mode 100644 packages/syncing-server/src/Domain/UseCase/SharedVaults/UpdateStorageQuotaUsedInSharedVault/UpdateStorageQuotaUsedInSharedVault.spec.ts create mode 100644 packages/syncing-server/src/Domain/UseCase/SharedVaults/UpdateStorageQuotaUsedInSharedVault/UpdateStorageQuotaUsedInSharedVault.ts create mode 100644 packages/syncing-server/src/Domain/UseCase/SharedVaults/UpdateStorageQuotaUsedInSharedVault/UpdateStorageQuotaUsedInSharedVaultDTO.ts diff --git a/packages/syncing-server/src/Bootstrap/Container.ts b/packages/syncing-server/src/Bootstrap/Container.ts index b9b02fffe..062918d42 100644 --- a/packages/syncing-server/src/Bootstrap/Container.ts +++ b/packages/syncing-server/src/Bootstrap/Container.ts @@ -154,6 +154,9 @@ import { DetermineSharedVaultOperationOnItem } from '../Domain/UseCase/SharedVau import { SharedVaultFilter } from '../Domain/Item/SaveRule/SharedVaultFilter' import { RemoveNotificationsForUser } from '../Domain/UseCase/Messaging/RemoveNotificationsForUser/RemoveNotificationsForUser' import { SharedVaultSnjsFilter } from '../Domain/Item/SaveRule/SharedVaultSnjsFilter' +import { UpdateStorageQuotaUsedInSharedVault } from '../Domain/UseCase/SharedVaults/UpdateStorageQuotaUsedInSharedVault/UpdateStorageQuotaUsedInSharedVault' +import { SharedVaultFileUploadedEventHandler } from '../Domain/Handler/SharedVaultFileUploadedEventHandler' +import { SharedVaultFileRemovedEventHandler } from '../Domain/Handler/SharedVaultFileRemovedEventHandler' export class ContainerConfigLoader { private readonly DEFAULT_CONTENT_SIZE_TRANSFER_LIMIT = 10_000_000 @@ -747,6 +750,9 @@ export class ContainerConfigLoader { container.get(TYPES.Sync_DeleteMessage), ), ) + container + .bind(TYPES.Sync_UpdateStorageQuotaUsedInSharedVault) + .toConstantValue(new UpdateStorageQuotaUsedInSharedVault(container.get(TYPES.Sync_SharedVaultRepository))) // Services container @@ -816,6 +822,22 @@ export class ContainerConfigLoader { context.container.get(TYPES.Sync_DomainEventPublisher), ) }) + container + .bind(TYPES.Sync_SharedVaultFileUploadedEventHandler) + .toConstantValue( + new SharedVaultFileUploadedEventHandler( + container.get(TYPES.Sync_UpdateStorageQuotaUsedInSharedVault), + container.get(TYPES.Sync_Logger), + ), + ) + container + .bind(TYPES.Sync_SharedVaultFileRemovedEventHandler) + .toConstantValue( + new SharedVaultFileRemovedEventHandler( + container.get(TYPES.Sync_UpdateStorageQuotaUsedInSharedVault), + container.get(TYPES.Sync_Logger), + ), + ) // Services container.bind(TYPES.Sync_ContentDecoder).toDynamicValue(() => new ContentDecoder()) @@ -859,6 +881,8 @@ export class ContainerConfigLoader { ['DUPLICATE_ITEM_SYNCED', container.get(TYPES.Sync_DuplicateItemSyncedEventHandler)], ['ACCOUNT_DELETION_REQUESTED', container.get(TYPES.Sync_AccountDeletionRequestedEventHandler)], ['ITEM_REVISION_CREATION_REQUESTED', container.get(TYPES.Sync_ItemRevisionCreationRequestedEventHandler)], + ['SHARED_VAULT_FILE_UPLOADED', container.get(TYPES.Sync_SharedVaultFileUploadedEventHandler)], + ['SHARED_VAULT_FILE_REMOVED', container.get(TYPES.Sync_SharedVaultFileRemovedEventHandler)], ]) if (!isConfiguredForHomeServer) { container.bind(TYPES.Sync_AUTH_SERVER_URL).toConstantValue(env.get('AUTH_SERVER_URL')) diff --git a/packages/syncing-server/src/Bootstrap/Types.ts b/packages/syncing-server/src/Bootstrap/Types.ts index 90e5c2b9e..1928aa171 100644 --- a/packages/syncing-server/src/Bootstrap/Types.ts +++ b/packages/syncing-server/src/Bootstrap/Types.ts @@ -78,11 +78,14 @@ const TYPES = { Sync_SaveItems: Symbol.for('Sync_SaveItems'), Sync_GetUserNotifications: Symbol.for('Sync_GetUserNotifications'), Sync_DetermineSharedVaultOperationOnItem: Symbol.for('Sync_DetermineSharedVaultOperationOnItem'), + Sync_UpdateStorageQuotaUsedInSharedVault: Symbol.for('Sync_UpdateStorageQuotaUsedInSharedVault'), // Handlers Sync_AccountDeletionRequestedEventHandler: Symbol.for('Sync_AccountDeletionRequestedEventHandler'), Sync_DuplicateItemSyncedEventHandler: Symbol.for('Sync_DuplicateItemSyncedEventHandler'), Sync_EmailBackupRequestedEventHandler: Symbol.for('Sync_EmailBackupRequestedEventHandler'), Sync_ItemRevisionCreationRequestedEventHandler: Symbol.for('Sync_ItemRevisionCreationRequestedEventHandler'), + Sync_SharedVaultFileRemovedEventHandler: Symbol.for('Sync_SharedVaultFileRemovedEventHandler'), + Sync_SharedVaultFileUploadedEventHandler: Symbol.for('Sync_SharedVaultFileUploadedEventHandler'), // Services Sync_ContentDecoder: Symbol.for('Sync_ContentDecoder'), Sync_DomainEventPublisher: Symbol.for('Sync_DomainEventPublisher'), diff --git a/packages/syncing-server/src/Domain/Handler/SharedVaultFileRemovedEventHandler.ts b/packages/syncing-server/src/Domain/Handler/SharedVaultFileRemovedEventHandler.ts new file mode 100644 index 000000000..a5e997d00 --- /dev/null +++ b/packages/syncing-server/src/Domain/Handler/SharedVaultFileRemovedEventHandler.ts @@ -0,0 +1,22 @@ +import { DomainEventHandlerInterface, SharedVaultFileRemovedEvent } from '@standardnotes/domain-events' +import { Logger } from 'winston' + +import { UpdateStorageQuotaUsedInSharedVault } from '../UseCase/SharedVaults/UpdateStorageQuotaUsedInSharedVault/UpdateStorageQuotaUsedInSharedVault' + +export class SharedVaultFileRemovedEventHandler implements DomainEventHandlerInterface { + constructor( + private updateStorageQuotaUsedInSharedVaultUseCase: UpdateStorageQuotaUsedInSharedVault, + private logger: Logger, + ) {} + + async handle(event: SharedVaultFileRemovedEvent): Promise { + const result = await this.updateStorageQuotaUsedInSharedVaultUseCase.execute({ + sharedVaultUuid: event.payload.sharedVaultUuid, + bytesUsed: -event.payload.fileByteSize, + }) + + if (result.isFailed()) { + this.logger.error(`Failed to update storage quota used in shared vault: ${result.getError()}`) + } + } +} diff --git a/packages/syncing-server/src/Domain/Handler/SharedVaultFileUploadedEventHandler.ts b/packages/syncing-server/src/Domain/Handler/SharedVaultFileUploadedEventHandler.ts new file mode 100644 index 000000000..a7e1a08ac --- /dev/null +++ b/packages/syncing-server/src/Domain/Handler/SharedVaultFileUploadedEventHandler.ts @@ -0,0 +1,22 @@ +import { DomainEventHandlerInterface, SharedVaultFileUploadedEvent } from '@standardnotes/domain-events' +import { Logger } from 'winston' + +import { UpdateStorageQuotaUsedInSharedVault } from '../UseCase/SharedVaults/UpdateStorageQuotaUsedInSharedVault/UpdateStorageQuotaUsedInSharedVault' + +export class SharedVaultFileUploadedEventHandler implements DomainEventHandlerInterface { + constructor( + private updateStorageQuotaUsedInSharedVaultUseCase: UpdateStorageQuotaUsedInSharedVault, + private logger: Logger, + ) {} + + async handle(event: SharedVaultFileUploadedEvent): Promise { + const result = await this.updateStorageQuotaUsedInSharedVaultUseCase.execute({ + sharedVaultUuid: event.payload.sharedVaultUuid, + bytesUsed: event.payload.fileByteSize, + }) + + if (result.isFailed()) { + this.logger.error(`Failed to update storage quota used in shared vault: ${result.getError()}`) + } + } +} diff --git a/packages/syncing-server/src/Domain/UseCase/SharedVaults/UpdateStorageQuotaUsedInSharedVault/UpdateStorageQuotaUsedInSharedVault.spec.ts b/packages/syncing-server/src/Domain/UseCase/SharedVaults/UpdateStorageQuotaUsedInSharedVault/UpdateStorageQuotaUsedInSharedVault.spec.ts new file mode 100644 index 000000000..fa86f8a11 --- /dev/null +++ b/packages/syncing-server/src/Domain/UseCase/SharedVaults/UpdateStorageQuotaUsedInSharedVault/UpdateStorageQuotaUsedInSharedVault.spec.ts @@ -0,0 +1,74 @@ +import { Timestamps, Uuid } from '@standardnotes/domain-core' +import { SharedVault } from '../../../SharedVault/SharedVault' +import { SharedVaultRepositoryInterface } from '../../../SharedVault/SharedVaultRepositoryInterface' +import { UpdateStorageQuotaUsedInSharedVault } from './UpdateStorageQuotaUsedInSharedVault' + +describe('UpdateStorageQuotaUsedInSharedVault', () => { + let sharedVaultRepository: SharedVaultRepositoryInterface + let sharedVault: SharedVault + + const createUseCase = () => new UpdateStorageQuotaUsedInSharedVault(sharedVaultRepository) + + beforeEach(() => { + sharedVault = SharedVault.create({ + fileUploadBytesLimit: 100, + fileUploadBytesUsed: 2, + userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(), + timestamps: Timestamps.create(123, 123).getValue(), + }).getValue() + + sharedVaultRepository = {} as jest.Mocked + sharedVaultRepository.findByUuid = jest.fn().mockResolvedValue(sharedVault) + sharedVaultRepository.save = jest.fn() + }) + + it('should update storage quota used in shared vault', async () => { + const useCase = createUseCase() + + await useCase.execute({ + sharedVaultUuid: '00000000-0000-0000-0000-000000000000', + bytesUsed: 10, + }) + + expect(sharedVaultRepository.save).toBeCalledWith(sharedVault) + expect(sharedVault.props.fileUploadBytesUsed).toEqual(12) + }) + + it('should return error when shared vault is not found', async () => { + sharedVaultRepository.findByUuid = jest.fn().mockResolvedValue(null) + + const useCase = createUseCase() + + const result = await useCase.execute({ + sharedVaultUuid: '00000000-0000-0000-0000-000000000000', + bytesUsed: 10, + }) + + expect(result.isFailed()).toBeTruthy() + expect(result.getError()).toEqual('Shared vault not found for UUID 00000000-0000-0000-0000-000000000000') + }) + + it('should return error when shared vault UUID is invalid', async () => { + const useCase = createUseCase() + + const result = await useCase.execute({ + sharedVaultUuid: 'invalid-uuid', + bytesUsed: 10, + }) + + expect(result.isFailed()).toBeTruthy() + expect(result.getError()).toEqual('Given value is not a valid uuid: invalid-uuid') + }) + + it('should update storage quota with a negative value', async () => { + const useCase = createUseCase() + + await useCase.execute({ + sharedVaultUuid: '00000000-0000-0000-0000-000000000000', + bytesUsed: -1, + }) + + expect(sharedVaultRepository.save).toBeCalledWith(sharedVault) + expect(sharedVault.props.fileUploadBytesUsed).toEqual(1) + }) +}) diff --git a/packages/syncing-server/src/Domain/UseCase/SharedVaults/UpdateStorageQuotaUsedInSharedVault/UpdateStorageQuotaUsedInSharedVault.ts b/packages/syncing-server/src/Domain/UseCase/SharedVaults/UpdateStorageQuotaUsedInSharedVault/UpdateStorageQuotaUsedInSharedVault.ts new file mode 100644 index 000000000..c2fc29f0b --- /dev/null +++ b/packages/syncing-server/src/Domain/UseCase/SharedVaults/UpdateStorageQuotaUsedInSharedVault/UpdateStorageQuotaUsedInSharedVault.ts @@ -0,0 +1,27 @@ +import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core' + +import { SharedVaultRepositoryInterface } from '../../../SharedVault/SharedVaultRepositoryInterface' +import { UpdateStorageQuotaUsedInSharedVaultDTO } from './UpdateStorageQuotaUsedInSharedVaultDTO' + +export class UpdateStorageQuotaUsedInSharedVault implements UseCaseInterface { + constructor(private sharedVaultRepository: SharedVaultRepositoryInterface) {} + + async execute(dto: UpdateStorageQuotaUsedInSharedVaultDTO): Promise> { + const sharedVaultUuidOrError = Uuid.create(dto.sharedVaultUuid) + if (sharedVaultUuidOrError.isFailed()) { + return Result.fail(sharedVaultUuidOrError.getError()) + } + const sharedVaultUuid = sharedVaultUuidOrError.getValue() + + const sharedVault = await this.sharedVaultRepository.findByUuid(sharedVaultUuid) + if (!sharedVault) { + return Result.fail(`Shared vault not found for UUID ${sharedVaultUuid.value}`) + } + + sharedVault.props.fileUploadBytesUsed += dto.bytesUsed + + await this.sharedVaultRepository.save(sharedVault) + + return Result.ok() + } +} diff --git a/packages/syncing-server/src/Domain/UseCase/SharedVaults/UpdateStorageQuotaUsedInSharedVault/UpdateStorageQuotaUsedInSharedVaultDTO.ts b/packages/syncing-server/src/Domain/UseCase/SharedVaults/UpdateStorageQuotaUsedInSharedVault/UpdateStorageQuotaUsedInSharedVaultDTO.ts new file mode 100644 index 000000000..35edfae18 --- /dev/null +++ b/packages/syncing-server/src/Domain/UseCase/SharedVaults/UpdateStorageQuotaUsedInSharedVault/UpdateStorageQuotaUsedInSharedVaultDTO.ts @@ -0,0 +1,4 @@ +export interface UpdateStorageQuotaUsedInSharedVaultDTO { + sharedVaultUuid: string + bytesUsed: number +}