Browse Source

fix(syncing-server): update storage quota used in a shared vault (#691)

Karol Sójko 1 năm trước cách đây
mục cha
commit
3415cae093

+ 24 - 0
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<UpdateStorageQuotaUsedInSharedVault>(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<SharedVaultFileUploadedEventHandler>(TYPES.Sync_SharedVaultFileUploadedEventHandler)
+      .toConstantValue(
+        new SharedVaultFileUploadedEventHandler(
+          container.get(TYPES.Sync_UpdateStorageQuotaUsedInSharedVault),
+          container.get(TYPES.Sync_Logger),
+        ),
+      )
+    container
+      .bind<SharedVaultFileRemovedEventHandler>(TYPES.Sync_SharedVaultFileRemovedEventHandler)
+      .toConstantValue(
+        new SharedVaultFileRemovedEventHandler(
+          container.get(TYPES.Sync_UpdateStorageQuotaUsedInSharedVault),
+          container.get(TYPES.Sync_Logger),
+        ),
+      )
 
     // Services
     container.bind<ContentDecoder>(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'))

+ 3 - 0
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'),

+ 22 - 0
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<void> {
+    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()}`)
+    }
+  }
+}

+ 22 - 0
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<void> {
+    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()}`)
+    }
+  }
+}

+ 74 - 0
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<SharedVaultRepositoryInterface>
+    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)
+  })
+})

+ 27 - 0
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<void> {
+  constructor(private sharedVaultRepository: SharedVaultRepositoryInterface) {}
+
+  async execute(dto: UpdateStorageQuotaUsedInSharedVaultDTO): Promise<Result<void>> {
+    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()
+  }
+}

+ 4 - 0
packages/syncing-server/src/Domain/UseCase/SharedVaults/UpdateStorageQuotaUsedInSharedVault/UpdateStorageQuotaUsedInSharedVaultDTO.ts

@@ -0,0 +1,4 @@
+export interface UpdateStorageQuotaUsedInSharedVaultDTO {
+  sharedVaultUuid: string
+  bytesUsed: number
+}