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
This commit is contained in:
Karol Sójko 2023-09-25 12:56:31 +02:00 committed by GitHub
parent a58262d584
commit 7b1eec21e5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 74 additions and 173 deletions

View file

@ -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"

View file

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

View file

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

View file

@ -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()
})
})

View file

@ -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,

View file

@ -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()
})
})

View file

@ -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,

View file

@ -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,
}),
)
}
}
}

View file

@ -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)
})
})

View file

@ -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')
}
}
}

View file

@ -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(),

View file

@ -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

View file

@ -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,
}),
)