chore: display shared vault file usage (#2399)

* chore: display shared vault file usage

* fix: specs

* fix: reshape filtering result

* fix: resolving invalid server items

* fix: get revisions specs

* fix: processing issue

* fix: tests

---------

Co-authored-by: Mo <mo@standardnotes.com>
This commit is contained in:
Karol Sójko
2023-08-11 08:59:16 +02:00
committed by GitHub
parent 05f3672526
commit 5bca53736b
87 changed files with 505 additions and 169 deletions

View File

@@ -22,8 +22,7 @@ import { AsymmetricMessageServiceInterface } from './../AsymmetricMessage/Asymme
import { ImportDataResult } from '../Import/ImportDataResult'
import { ChallengeServiceInterface } from './../Challenge/ChallengeServiceInterface'
import { VaultServiceInterface } from '../Vault/VaultServiceInterface'
import { ApplicationIdentifier } from '@standardnotes/common'
import { BackupFile, Environment, Platform, PrefKey, PrefValue } from '@standardnotes/models'
import { BackupFile, Environment, Platform, PrefKey, PrefValue, ApplicationIdentifier } from '@standardnotes/models'
import { BackupServiceInterface, FilesClientInterface } from '@standardnotes/files'
import { AlertService } from '../Alert/AlertService'

View File

@@ -1,5 +1,4 @@
import { Environment, Platform } from '@standardnotes/models'
import { ApplicationIdentifier } from '@standardnotes/common'
import { Environment, Platform, ApplicationIdentifier } from '@standardnotes/models'
import { AlertService, DeviceInterface } from '@standardnotes/services'
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'

View File

@@ -38,6 +38,7 @@ describe('ProcessAcceptedVaultInvite', () => {
metadata: {
name: 'test-name',
iconString: 'safe-square',
fileBytesUsed: 0,
},
trustedContacts: [],
},

View File

@@ -36,6 +36,7 @@ export class ProcessAcceptedVaultInvite {
sharing: {
sharedVaultUuid: sharedVaultUuid,
ownerUserUuid: ownerUuid,
fileBytesUsed: metadata.fileBytesUsed,
},
}

View File

@@ -1,5 +1,5 @@
import { ApplicationIdentifier } from '@standardnotes/common'
import {
ApplicationIdentifier,
FullyFormedTransferPayload,
TransferPayload,
NamespacedRootKeyInKeychain,

View File

@@ -3,10 +3,10 @@ import {
ContactPublicKeySetInterface,
PublicKeyTrustStatus,
TrustedContactInterface,
ProtocolVersion,
} from '@standardnotes/models'
import { DecryptMessage } from './DecryptMessage'
import { OperatorInterface, EncryptionOperatorsInterface } from '@standardnotes/encryption'
import { ProtocolVersion } from '@standardnotes/common'
function createMockPublicKeySetChain(): ContactPublicKeySetInterface {
const nMinusOne = new ContactPublicKeySet({

View File

@@ -1,5 +1,4 @@
import { FindDefaultItemsKey } from './../Encryption/UseCase/ItemsKey/FindDefaultItemsKey'
import { ProtocolVersion } from '@standardnotes/common'
import {
DecryptedParameters,
ErrorDecryptingParameters,
@@ -23,6 +22,7 @@ import {
PayloadEmitSource,
KeySystemItemsKeyInterface,
SureFindPayload,
ProtocolVersion,
ContentTypeUsesRootKeyEncryption,
} from '@standardnotes/models'
import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface'

View File

@@ -1,7 +1,6 @@
import { UserRegistrationResponseBody } from '@standardnotes/api'
import { ProtocolVersion } from '@standardnotes/common'
import { SNRootKey } from '@standardnotes/encryption'
import { RootKeyInterface } from '@standardnotes/models'
import { RootKeyInterface, ProtocolVersion } from '@standardnotes/models'
import {
SessionBody,
SignInResponse,

View File

@@ -14,9 +14,11 @@ import { SyncServiceInterface } from '../Sync/SyncServiceInterface'
import { ItemManagerInterface } from '../Item/ItemManagerInterface'
import { SessionsClientInterface } from '../Session/SessionsClientInterface'
import { ContactPublicKeySetInterface, TrustedContactInterface } from '@standardnotes/models'
import { SyncLocalVaultsWithRemoteSharedVaults } from './UseCase/SyncLocalVaultsWithRemoteSharedVaults'
describe('SharedVaultService', () => {
let service: SharedVaultService
let syncLocalVaultsWithRemoteSharedVaults: SyncLocalVaultsWithRemoteSharedVaults
beforeEach(() => {
const sync = {} as jest.Mocked<SyncServiceInterface>
@@ -37,12 +39,16 @@ describe('SharedVaultService', () => {
const deleteSharedVaultUseCase = {} as jest.Mocked<DeleteSharedVault>
const discardItemsLocally = {} as jest.Mocked<DiscardItemsLocally>
syncLocalVaultsWithRemoteSharedVaults = {} as jest.Mocked<SyncLocalVaultsWithRemoteSharedVaults>
syncLocalVaultsWithRemoteSharedVaults.execute = jest.fn()
const eventBus = {} as jest.Mocked<InternalEventBusInterface>
eventBus.addEventHandler = jest.fn()
service = new SharedVaultService(
items,
session,
syncLocalVaultsWithRemoteSharedVaults,
getVault,
getOwnedVaults,
createSharedVaultUseCase,

View File

@@ -32,6 +32,7 @@ import { ContentType, NotificationType, Uuid } from '@standardnotes/domain-core'
import { HandleKeyPairChange } from '../Contacts/UseCase/HandleKeyPairChange'
import { FindContact } from '../Contacts/UseCase/FindContact'
import { GetOwnedSharedVaults } from './UseCase/GetOwnedSharedVaults'
import { SyncLocalVaultsWithRemoteSharedVaults } from './UseCase/SyncLocalVaultsWithRemoteSharedVaults'
export class SharedVaultService
extends AbstractService<SharedVaultServiceEvent, SharedVaultServiceEventPayload>
@@ -40,6 +41,7 @@ export class SharedVaultService
constructor(
private items: ItemManagerInterface,
private session: SessionsClientInterface,
private _syncLocalVaultsWithRemoteSharedVaults: SyncLocalVaultsWithRemoteSharedVaults,
private _getVault: GetVault,
private _getOwnedSharedVaults: GetOwnedSharedVaults,
private _createSharedVault: CreateSharedVault,
@@ -67,6 +69,7 @@ export class SharedVaultService
super.deinit()
;(this.items as unknown) = undefined
;(this.session as unknown) = undefined
;(this._syncLocalVaultsWithRemoteSharedVaults as unknown) = undefined
;(this._getVault as unknown) = undefined
;(this._createSharedVault as unknown) = undefined
;(this._handleKeyPairChange as unknown) = undefined
@@ -88,7 +91,7 @@ export class SharedVaultService
break
}
case NotificationServiceEvent.NotificationReceived:
await this.handleUserEvent(event.payload as NotificationServiceEventPayload)
await this.handleNotification(event.payload as NotificationServiceEventPayload)
break
case SyncEvent.ReceivedRemoteSharedVaults:
void this.notifyEventSync(SharedVaultServiceEvent.SharedVaultStatusChanged)
@@ -96,7 +99,7 @@ export class SharedVaultService
}
}
private async handleUserEvent(event: NotificationServiceEventPayload): Promise<void> {
private async handleNotification(event: NotificationServiceEventPayload): Promise<void> {
switch (event.eventPayload.props.type.value) {
case NotificationType.TYPES.RemovedFromSharedVault: {
const vault = this._getVault.execute<SharedVaultListingInterface>({
@@ -114,6 +117,19 @@ export class SharedVaultService
}
break
}
case NotificationType.TYPES.SharedVaultFileRemoved:
case NotificationType.TYPES.SharedVaultFileUploaded: {
const vaultOrError = this._getVault.execute<SharedVaultListingInterface>({
sharedVaultUuid: event.eventPayload.props.sharedVaultUuid.value,
})
if (!vaultOrError.isFailed()) {
await this._syncLocalVaultsWithRemoteSharedVaults.execute([vaultOrError.getValue()])
void this.notifyEventSync(SharedVaultServiceEvent.SharedVaultStatusChanged)
}
break
}
}
}

View File

@@ -2,6 +2,7 @@ import { KeySystemIdentifier } from '@standardnotes/models'
export enum SharedVaultServiceEvent {
SharedVaultStatusChanged = 'SharedVaultStatusChanged',
SharedVaultFileStorageUsageChanged = 'SharedVaultFileStorageUsageChanged',
}
export type SharedVaultServiceEventPayload = {

View File

@@ -31,6 +31,7 @@ export class ConvertToSharedVault {
mutator.sharing = {
sharedVaultUuid: serverVaultHash.uuid,
ownerUserUuid: serverVaultHash.user_uuid,
fileBytesUsed: serverVaultHash.file_upload_bytes_used,
}
},
)

View File

@@ -50,6 +50,7 @@ export class CreateSharedVault {
mutator.sharing = {
sharedVaultUuid: serverVaultHash.uuid,
ownerUserUuid: serverVaultHash.user_uuid,
fileBytesUsed: serverVaultHash.file_upload_bytes_used,
}
},
)

View File

@@ -0,0 +1,80 @@
import { MutatorClientInterface, SharedVaultServerInterface, VaultListingInterface } from '@standardnotes/snjs'
import { SyncLocalVaultsWithRemoteSharedVaults } from './SyncLocalVaultsWithRemoteSharedVaults'
describe('SyncLocalVaultsWithRemoteSharedVaults', () => {
let sharedVaultServer: SharedVaultServerInterface
let mutator: MutatorClientInterface
const createUseCase = () => new SyncLocalVaultsWithRemoteSharedVaults(sharedVaultServer, mutator)
beforeEach(() => {
sharedVaultServer = {} as jest.Mocked<SharedVaultServerInterface>
sharedVaultServer.getSharedVaults = jest.fn().mockResolvedValue({ data: { sharedVaults: [{
uuid: '1-2-3',
user_uuid: '2-3-4',
file_upload_bytes_used: 123,
file_upload_bytes_limit: 10000000,
created_at_timestamp: 123,
updated_at_timestamp: 123,
}] } })
mutator = {} as jest.Mocked<MutatorClientInterface>
mutator.changeItem = jest.fn()
})
it('should sync local vaults with remote shared vaults to update file storage usage', async () => {
const localVaults = [{
uuid: '1-2-3',
name: 'Vault',
isSharedVaultListing: () => true,
sharing: {
sharedVaultUuid: '1-2-3',
ownerUserUuid: '2-3-4',
fileBytesUsed: 0,
},
} as jest.Mocked<VaultListingInterface>]
const useCase = createUseCase()
await useCase.execute(localVaults)
expect(mutator.changeItem).toHaveBeenCalledWith(localVaults[0], expect.any(Function))
})
it('should fail if shared vault server returns error', async () => {
sharedVaultServer.getSharedVaults = jest.fn().mockResolvedValue({ data: { error: { message: 'test-error' } } })
const localVaults = [{
uuid: '1-2-3',
name: 'Vault',
isSharedVaultListing: () => true,
sharing: {
sharedVaultUuid: '1-2-3',
ownerUserUuid: '2-3-4',
fileBytesUsed: 0,
},
} as jest.Mocked<VaultListingInterface>]
const useCase = createUseCase()
const result = await useCase.execute(localVaults)
expect(result.isFailed()).toBe(true)
})
it('should not sync local vaults with remote shared vaults if local vault is not shared', async () => {
const localVaults = [{
uuid: '1-2-3',
name: 'Vault',
isSharedVaultListing: () => false,
sharing: {
sharedVaultUuid: '1-2-3',
ownerUserUuid: '2-3-4',
fileBytesUsed: 0,
},
} as jest.Mocked<VaultListingInterface>]
const useCase = createUseCase()
await useCase.execute(localVaults)
expect(mutator.changeItem).not.toHaveBeenCalled()
})
})

View File

@@ -0,0 +1,40 @@
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
import { SharedVaultServerInterface } from '@standardnotes/api'
import { HttpError, isErrorResponse } from '@standardnotes/responses'
import { SharedVaultListingInterface, VaultListingInterface, VaultListingMutator } from '@standardnotes/models'
import { MutatorClientInterface } from '../../Mutator/MutatorClientInterface'
export class SyncLocalVaultsWithRemoteSharedVaults implements UseCaseInterface<void> {
constructor(
private sharedVaultServer: SharedVaultServerInterface,
private mutator: MutatorClientInterface,
) {}
async execute(localVaults: VaultListingInterface[]): Promise<Result<void>> {
const remoteVaultsResponse = await this.sharedVaultServer.getSharedVaults()
if (isErrorResponse(remoteVaultsResponse)) {
return Result.fail((remoteVaultsResponse.data.error as HttpError).message as string)
}
const remoteVaults = remoteVaultsResponse.data.sharedVaults
for (const localVault of localVaults) {
if (!localVault.isSharedVaultListing()) {
continue
}
const remoteVault = remoteVaults.find((vault) => vault.uuid === localVault.sharing.sharedVaultUuid)
if (remoteVault) {
await this.mutator.changeItem<VaultListingMutator, SharedVaultListingInterface>(localVault, (mutator) => {
/* istanbul ignore next */
mutator.sharing = {
sharedVaultUuid: remoteVault.uuid,
ownerUserUuid: remoteVault.user_uuid,
fileBytesUsed: remoteVault.file_upload_bytes_used,
}
})
}
}
return Result.ok()
}
}

View File

@@ -1,4 +1,4 @@
import { ProtocolVersion } from '@standardnotes/common'
import { ProtocolVersion } from '@standardnotes/models'
export const API_MESSAGE_GENERIC_INVALID_LOGIN = 'A server error occurred while trying to sign in. Please try again.'
export const API_MESSAGE_GENERIC_REGISTRATION_FAIL =

View File

@@ -123,6 +123,7 @@ export class InviteToVault implements UseCaseInterface<SharedVaultInviteServerHa
name: params.sharedVault.name,
description: params.sharedVault.description,
iconString: params.sharedVault.iconString,
fileBytesUsed: params.sharedVault.sharing.fileBytesUsed,
},
},
},

View File

@@ -161,6 +161,7 @@ export * from './SharedVaults/UseCase/NotifyVaultUsersOfKeyRotation'
export * from './SharedVaults/UseCase/SendVaultDataChangedMessage'
export * from './SharedVaults/UseCase/SendVaultKeyChangedMessage'
export * from './SharedVaults/UseCase/ShareContactWithVault'
export * from './SharedVaults/UseCase/SyncLocalVaultsWithRemoteSharedVaults'
export * from './Singleton/SingletonManagerInterface'
export * from './Status/StatusService'
export * from './Status/StatusServiceInterface'