From 7bb698e44222ef128d9642d625e96b7d26ee4dbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20S=C3=B3jko?= Date: Mon, 28 Nov 2022 15:04:26 +0100 Subject: [PATCH] feat(revisions): add copying revisions on duplicated items --- .../Event/RevisionsCopyRequestedEvent.ts | 7 +++ .../RevisionsCopyRequestedEventPayload.ts | 4 ++ packages/domain-events/src/Domain/index.ts | 2 + packages/revisions/src/Bootstrap/Container.ts | 10 ++++ packages/revisions/src/Bootstrap/Types.ts | 2 + ...RevisionsCopyRequestedEventHandler.spec.ts | 42 +++++++++++++ .../RevisionsCopyRequestedEventHandler.ts | 18 ++++++ .../src/Domain/Revision/ContentType.spec.ts | 16 +++++ .../src/Domain/Revision/Revision.spec.ts | 22 +++++++ .../Revision/RevisionRepositoryInterface.ts | 1 + .../CopyRevisions/CopyRevisions.spec.ts | 59 +++++++++++++++++++ .../UseCase/CopyRevisions/CopyRevisions.ts | 43 ++++++++++++++ .../UseCase/CopyRevisions/CopyRevisionsDTO.ts | 4 ++ .../Infra/MySQL/MySQLRevisionRepository.ts | 16 ++++- .../src/Domain/Event/DomainEventFactory.ts | 22 +++++++ .../Event/DomainEventFactoryInterface.ts | 5 ++ .../DuplicateItemSyncedEventHandler.spec.ts | 26 +++++++- .../DuplicateItemSyncedEventHandler.ts | 16 ++++- 18 files changed, 311 insertions(+), 4 deletions(-) create mode 100644 packages/domain-events/src/Domain/Event/RevisionsCopyRequestedEvent.ts create mode 100644 packages/domain-events/src/Domain/Event/RevisionsCopyRequestedEventPayload.ts create mode 100644 packages/revisions/src/Domain/Handler/RevisionsCopyRequestedEventHandler.spec.ts create mode 100644 packages/revisions/src/Domain/Handler/RevisionsCopyRequestedEventHandler.ts create mode 100644 packages/revisions/src/Domain/Revision/ContentType.spec.ts create mode 100644 packages/revisions/src/Domain/Revision/Revision.spec.ts create mode 100644 packages/revisions/src/Domain/UseCase/CopyRevisions/CopyRevisions.spec.ts create mode 100644 packages/revisions/src/Domain/UseCase/CopyRevisions/CopyRevisions.ts create mode 100644 packages/revisions/src/Domain/UseCase/CopyRevisions/CopyRevisionsDTO.ts diff --git a/packages/domain-events/src/Domain/Event/RevisionsCopyRequestedEvent.ts b/packages/domain-events/src/Domain/Event/RevisionsCopyRequestedEvent.ts new file mode 100644 index 000000000..145478ea4 --- /dev/null +++ b/packages/domain-events/src/Domain/Event/RevisionsCopyRequestedEvent.ts @@ -0,0 +1,7 @@ +import { DomainEventInterface } from './DomainEventInterface' +import { RevisionsCopyRequestedEventPayload } from './RevisionsCopyRequestedEventPayload' + +export interface RevisionsCopyRequestedEvent extends DomainEventInterface { + type: 'REVISIONS_COPY_REQUESTED' + payload: RevisionsCopyRequestedEventPayload +} diff --git a/packages/domain-events/src/Domain/Event/RevisionsCopyRequestedEventPayload.ts b/packages/domain-events/src/Domain/Event/RevisionsCopyRequestedEventPayload.ts new file mode 100644 index 000000000..09efb74f7 --- /dev/null +++ b/packages/domain-events/src/Domain/Event/RevisionsCopyRequestedEventPayload.ts @@ -0,0 +1,4 @@ +export interface RevisionsCopyRequestedEventPayload { + newItemUuid: string + originalItemUuid: string +} diff --git a/packages/domain-events/src/Domain/index.ts b/packages/domain-events/src/Domain/index.ts index fa76564a2..c53317721 100644 --- a/packages/domain-events/src/Domain/index.ts +++ b/packages/domain-events/src/Domain/index.ts @@ -74,6 +74,8 @@ export * from './Event/RefundRequestedEvent' export * from './Event/RefundRequestedEventPayload' export * from './Event/RefundProcessedEvent' export * from './Event/RefundProcessedEventPayload' +export * from './Event/RevisionsCopyRequestedEvent' +export * from './Event/RevisionsCopyRequestedEventPayload' export * from './Event/SharedSubscriptionInvitationCanceledEvent' export * from './Event/SharedSubscriptionInvitationCanceledEventPayload' export * from './Event/SharedSubscriptionInvitationCreatedEvent' diff --git a/packages/revisions/src/Bootstrap/Container.ts b/packages/revisions/src/Bootstrap/Container.ts index 0e7d5f31e..fde65b2f1 100644 --- a/packages/revisions/src/Bootstrap/Container.ts +++ b/packages/revisions/src/Bootstrap/Container.ts @@ -39,6 +39,8 @@ import { FSDumpRepository } from '../Infra/FS/FSDumpRepository' import { GetRevision } from '../Domain/UseCase/GetRevision/GetRevision' import { DeleteRevision } from '../Domain/UseCase/DeleteRevision/DeleteRevision' import { AccountDeletionRequestedEventHandler } from '../Domain/Handler/AccountDeletionRequestedEventHandler' +import { RevisionsCopyRequestedEventHandler } from '../Domain/Handler/RevisionsCopyRequestedEventHandler' +import { CopyRevisions } from '../Domain/UseCase/CopyRevisions/CopyRevisions' // eslint-disable-next-line @typescript-eslint/no-var-requires const newrelicFormatter = require('@newrelic/winston-enricher') @@ -161,6 +163,9 @@ export class ContainerConfigLoader { container .bind(TYPES.DeleteRevision) .toConstantValue(new DeleteRevision(container.get(TYPES.RevisionRepository))) + container + .bind(TYPES.CopyRevisions) + .toConstantValue(new CopyRevisions(container.get(TYPES.RevisionRepository))) // Controller container @@ -185,6 +190,11 @@ export class ContainerConfigLoader { .toConstantValue( new AccountDeletionRequestedEventHandler(container.get(TYPES.RevisionRepository), container.get(TYPES.Logger)), ) + container + .bind(TYPES.RevisionsCopyRequestedEventHandler) + .toConstantValue( + new RevisionsCopyRequestedEventHandler(container.get(TYPES.CopyRevisions), container.get(TYPES.Logger)), + ) // Services container diff --git a/packages/revisions/src/Bootstrap/Types.ts b/packages/revisions/src/Bootstrap/Types.ts index cc1c5642d..82a819a08 100644 --- a/packages/revisions/src/Bootstrap/Types.ts +++ b/packages/revisions/src/Bootstrap/Types.ts @@ -27,11 +27,13 @@ const TYPES = { GetRevisionsMetada: Symbol.for('GetRevisionsMetada'), GetRevision: Symbol.for('GetRevision'), DeleteRevision: Symbol.for('DeleteRevision'), + CopyRevisions: Symbol.for('CopyRevisions'), // Controller RevisionsController: Symbol.for('RevisionsController'), // Handlers ItemDumpedEventHandler: Symbol.for('ItemDumpedEventHandler'), AccountDeletionRequestedEventHandler: Symbol.for('AccountDeletionRequestedEventHandler'), + RevisionsCopyRequestedEventHandler: Symbol.for('RevisionsCopyRequestedEventHandler'), // Services CrossServiceTokenDecoder: Symbol.for('CrossServiceTokenDecoder'), DomainEventSubscriberFactory: Symbol.for('DomainEventSubscriberFactory'), diff --git a/packages/revisions/src/Domain/Handler/RevisionsCopyRequestedEventHandler.spec.ts b/packages/revisions/src/Domain/Handler/RevisionsCopyRequestedEventHandler.spec.ts new file mode 100644 index 000000000..d729f17c7 --- /dev/null +++ b/packages/revisions/src/Domain/Handler/RevisionsCopyRequestedEventHandler.spec.ts @@ -0,0 +1,42 @@ +import { Result } from '@standardnotes/domain-core' +import { RevisionsCopyRequestedEvent } from '@standardnotes/domain-events' +import { Logger } from 'winston' + +import { CopyRevisions } from '../UseCase/CopyRevisions/CopyRevisions' +import { RevisionsCopyRequestedEventHandler } from './RevisionsCopyRequestedEventHandler' + +describe('RevisionsCopyRequestedEventHandler', () => { + let copyRevisions: CopyRevisions + let logger: Logger + let event: RevisionsCopyRequestedEvent + + const createHandler = () => new RevisionsCopyRequestedEventHandler(copyRevisions, logger) + + beforeEach(() => { + copyRevisions = {} as jest.Mocked + copyRevisions.execute = jest.fn().mockReturnValue(Result.ok()) + + logger = {} as jest.Mocked + logger.error = jest.fn() + + event = {} as jest.Mocked + event.payload = { + newItemUuid: '1-2-3', + originalItemUuid: '2-3-4', + } + }) + + it('should copy revisions', async () => { + await createHandler().handle(event) + + expect(copyRevisions.execute).toHaveBeenCalled() + }) + + it('should indicate if copying revisions fail', async () => { + copyRevisions.execute = jest.fn().mockReturnValue(Result.fail('Oops')) + + await createHandler().handle(event) + + expect(logger.error).toHaveBeenCalled() + }) +}) diff --git a/packages/revisions/src/Domain/Handler/RevisionsCopyRequestedEventHandler.ts b/packages/revisions/src/Domain/Handler/RevisionsCopyRequestedEventHandler.ts new file mode 100644 index 000000000..dd907e260 --- /dev/null +++ b/packages/revisions/src/Domain/Handler/RevisionsCopyRequestedEventHandler.ts @@ -0,0 +1,18 @@ +import { DomainEventHandlerInterface, RevisionsCopyRequestedEvent } from '@standardnotes/domain-events' +import { Logger } from 'winston' +import { CopyRevisions } from '../UseCase/CopyRevisions/CopyRevisions' + +export class RevisionsCopyRequestedEventHandler implements DomainEventHandlerInterface { + constructor(private copyRevisions: CopyRevisions, private logger: Logger) {} + + async handle(event: RevisionsCopyRequestedEvent): Promise { + const result = await this.copyRevisions.execute({ + newItemUuid: event.payload.newItemUuid, + originalItemUuid: event.payload.originalItemUuid, + }) + + if (result.isFailed()) { + this.logger.error(`Could not copy revisions: ${result.getError()}`) + } + } +} diff --git a/packages/revisions/src/Domain/Revision/ContentType.spec.ts b/packages/revisions/src/Domain/Revision/ContentType.spec.ts new file mode 100644 index 000000000..12a538cc9 --- /dev/null +++ b/packages/revisions/src/Domain/Revision/ContentType.spec.ts @@ -0,0 +1,16 @@ +import { ContentType } from './ContentType' + +describe('ContentType', () => { + it('should create a value obejct', () => { + const valueOrError = ContentType.create('Note') + + expect(valueOrError.isFailed()).toBeFalsy() + expect(valueOrError.getValue().value).not.toBeNull() + }) + + it('should fail to create a value obejct', () => { + const valueOrError = ContentType.create('test') + + expect(valueOrError.isFailed()).toBeTruthy() + }) +}) diff --git a/packages/revisions/src/Domain/Revision/Revision.spec.ts b/packages/revisions/src/Domain/Revision/Revision.spec.ts new file mode 100644 index 000000000..6298e741a --- /dev/null +++ b/packages/revisions/src/Domain/Revision/Revision.spec.ts @@ -0,0 +1,22 @@ +import { Timestamps, Uuid } from '@standardnotes/domain-core' +import { ContentType } from './ContentType' +import { Revision } from './Revision' + +describe('Revision', () => { + it('should create an entity', () => { + const entityOrError = Revision.create({ + itemUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(), + userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(), + content: 'test', + contentType: ContentType.create('Note').getValue(), + itemsKeyId: 'test', + encItemKey: 'test', + authHash: 'test', + creationDate: new Date(1), + timestamps: Timestamps.create(new Date(1), new Date(2)).getValue(), + }) + + expect(entityOrError.isFailed()).toBeFalsy() + expect(entityOrError.getValue().id).not.toBeNull() + }) +}) diff --git a/packages/revisions/src/Domain/Revision/RevisionRepositoryInterface.ts b/packages/revisions/src/Domain/Revision/RevisionRepositoryInterface.ts index 15797020a..341ae45b9 100644 --- a/packages/revisions/src/Domain/Revision/RevisionRepositoryInterface.ts +++ b/packages/revisions/src/Domain/Revision/RevisionRepositoryInterface.ts @@ -7,6 +7,7 @@ export interface RevisionRepositoryInterface { removeByUserUuid(userUuid: Uuid): Promise removeOneByUuid(revisionUuid: Uuid, userUuid: Uuid): Promise findOneByUuid(revisionUuid: Uuid, userUuid: Uuid): Promise + findByItemUuid(itemUuid: Uuid): Promise> findMetadataByItemId(itemUuid: Uuid, userUuid: Uuid): Promise> save(revision: Revision): Promise } diff --git a/packages/revisions/src/Domain/UseCase/CopyRevisions/CopyRevisions.spec.ts b/packages/revisions/src/Domain/UseCase/CopyRevisions/CopyRevisions.spec.ts new file mode 100644 index 000000000..1d0f44357 --- /dev/null +++ b/packages/revisions/src/Domain/UseCase/CopyRevisions/CopyRevisions.spec.ts @@ -0,0 +1,59 @@ +import { Result } from '@standardnotes/domain-core' +import { Revision } from '../../Revision/Revision' +import { RevisionRepositoryInterface } from '../../Revision/RevisionRepositoryInterface' +import { CopyRevisions } from './CopyRevisions' + +describe('CopyRevisions', () => { + let revisionRepository: RevisionRepositoryInterface + + const createUseCase = () => new CopyRevisions(revisionRepository) + + beforeEach(() => { + revisionRepository = {} as jest.Mocked + revisionRepository.findByItemUuid = jest.fn().mockReturnValue([{} as jest.Mocked]) + revisionRepository.save = jest.fn() + }) + + it('should not copy revisions to new item if revision creation fails', async () => { + const revisionMock = jest.spyOn(Revision, 'create') + revisionMock.mockImplementation(() => Result.fail('Oops')) + + const result = await createUseCase().execute({ + originalItemUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d', + newItemUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d', + }) + + expect(result.isFailed()).toBeTruthy() + + revisionMock.mockRestore() + }) + + it('should copy revisions to new item', async () => { + const result = await createUseCase().execute({ + originalItemUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d', + newItemUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d', + }) + + expect(result.isFailed()).toBeFalsy() + expect(revisionRepository.save).toHaveBeenCalled() + expect(result.getValue()).toEqual('Revisions copied') + }) + + it('should not copy revisions for an invalid item uuid', async () => { + const result = await createUseCase().execute({ + originalItemUuid: '1-2-3', + newItemUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d', + }) + + expect(result.isFailed()).toBeTruthy() + }) + + it('should not delete revision for a an invalid new item uuid', async () => { + const result = await createUseCase().execute({ + newItemUuid: '1-2-3', + originalItemUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d', + }) + + expect(result.isFailed()).toBeTruthy() + }) +}) diff --git a/packages/revisions/src/Domain/UseCase/CopyRevisions/CopyRevisions.ts b/packages/revisions/src/Domain/UseCase/CopyRevisions/CopyRevisions.ts new file mode 100644 index 000000000..7a3b502e4 --- /dev/null +++ b/packages/revisions/src/Domain/UseCase/CopyRevisions/CopyRevisions.ts @@ -0,0 +1,43 @@ +import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core' + +import { Revision } from '../../Revision/Revision' +import { RevisionRepositoryInterface } from '../../Revision/RevisionRepositoryInterface' + +import { CopyRevisionsDTO } from './CopyRevisionsDTO' + +export class CopyRevisions implements UseCaseInterface { + constructor(private revisionRepository: RevisionRepositoryInterface) {} + + async execute(dto: CopyRevisionsDTO): Promise> { + const orignalItemUuidOrError = Uuid.create(dto.originalItemUuid) + if (orignalItemUuidOrError.isFailed()) { + return Result.fail(`Could not copy revisions: ${orignalItemUuidOrError.getError()}`) + } + const originalItemUuid = orignalItemUuidOrError.getValue() + + const newItemUuidOrError = Uuid.create(dto.newItemUuid) + if (newItemUuidOrError.isFailed()) { + return Result.fail(`Could not copy revisions: ${newItemUuidOrError.getError()}`) + } + const newItemUuid = newItemUuidOrError.getValue() + + const revisions = await this.revisionRepository.findByItemUuid(originalItemUuid) + + for (const existingRevision of revisions) { + const revisionCopyOrError = Revision.create({ + ...existingRevision.props, + itemUuid: newItemUuid, + }) + + if (revisionCopyOrError.isFailed()) { + return Result.fail(`Could not create revision copy: ${revisionCopyOrError.getError()}`) + } + + const revisionCopy = revisionCopyOrError.getValue() + + await this.revisionRepository.save(revisionCopy) + } + + return Result.ok('Revisions copied') + } +} diff --git a/packages/revisions/src/Domain/UseCase/CopyRevisions/CopyRevisionsDTO.ts b/packages/revisions/src/Domain/UseCase/CopyRevisions/CopyRevisionsDTO.ts new file mode 100644 index 000000000..962a9b949 --- /dev/null +++ b/packages/revisions/src/Domain/UseCase/CopyRevisions/CopyRevisionsDTO.ts @@ -0,0 +1,4 @@ +export interface CopyRevisionsDTO { + originalItemUuid: string + newItemUuid: string +} diff --git a/packages/revisions/src/Infra/MySQL/MySQLRevisionRepository.ts b/packages/revisions/src/Infra/MySQL/MySQLRevisionRepository.ts index 8cf3da231..d36978146 100644 --- a/packages/revisions/src/Infra/MySQL/MySQLRevisionRepository.ts +++ b/packages/revisions/src/Infra/MySQL/MySQLRevisionRepository.ts @@ -1,7 +1,7 @@ import { MapperInterface, Uuid } from '@standardnotes/domain-core' import { Repository } from 'typeorm' -import { Revision } from '../../Domain/Revision/Revision' +import { Revision } from '../../Domain/Revision/Revision' import { RevisionMetadata } from '../../Domain/Revision/RevisionMetadata' import { RevisionRepositoryInterface } from '../../Domain/Revision/RevisionRepositoryInterface' import { TypeORMRevision } from '../TypeORM/TypeORMRevision' @@ -13,6 +13,20 @@ export class MySQLRevisionRepository implements RevisionRepositoryInterface { private revisionMapper: MapperInterface, ) {} + async findByItemUuid(itemUuid: Uuid): Promise { + const typeormRevisions = await this.ormRepository + .createQueryBuilder() + .where('item_uuid = :itemUuid', { itemUuid }) + .getMany() + + const revisions = [] + for (const revision of typeormRevisions) { + revisions.push(this.revisionMapper.toDomain(revision)) + } + + return revisions + } + async removeByUserUuid(userUuid: Uuid): Promise { await this.ormRepository .createQueryBuilder() diff --git a/packages/syncing-server/src/Domain/Event/DomainEventFactory.ts b/packages/syncing-server/src/Domain/Event/DomainEventFactory.ts index 8d362b2a8..8f4613452 100644 --- a/packages/syncing-server/src/Domain/Event/DomainEventFactory.ts +++ b/packages/syncing-server/src/Domain/Event/DomainEventFactory.ts @@ -10,6 +10,7 @@ import { ItemRevisionCreationRequestedEvent, ItemsSyncedEvent, OneDriveBackupFailedEvent, + RevisionsCopyRequestedEvent, UserContentSizeRecalculationRequestedEvent, } from '@standardnotes/domain-events' import { TimerInterface } from '@standardnotes/time' @@ -21,6 +22,27 @@ import { DomainEventFactoryInterface } from './DomainEventFactoryInterface' export class DomainEventFactory implements DomainEventFactoryInterface { constructor(@inject(TYPES.Timer) private timer: TimerInterface) {} + createRevisionsCopyRequestedEvent( + userUuid: string, + dto: { + originalItemUuid: string + newItemUuid: string + }, + ): RevisionsCopyRequestedEvent { + return { + type: 'REVISIONS_COPY_REQUESTED', + createdAt: this.timer.getUTCDate(), + meta: { + correlation: { + userIdentifier: userUuid, + userIdentifierType: 'uuid', + }, + origin: DomainEventService.SyncingServer, + }, + payload: dto, + } + } + createItemDumpedEvent(fileDumpPath: string, userUuid: string): ItemDumpedEvent { return { type: 'ITEM_DUMPED', diff --git a/packages/syncing-server/src/Domain/Event/DomainEventFactoryInterface.ts b/packages/syncing-server/src/Domain/Event/DomainEventFactoryInterface.ts index cb723ee0a..3e49888cb 100644 --- a/packages/syncing-server/src/Domain/Event/DomainEventFactoryInterface.ts +++ b/packages/syncing-server/src/Domain/Event/DomainEventFactoryInterface.ts @@ -8,6 +8,7 @@ import { ItemRevisionCreationRequestedEvent, ItemsSyncedEvent, OneDriveBackupFailedEvent, + RevisionsCopyRequestedEvent, UserContentSizeRecalculationRequestedEvent, } from '@standardnotes/domain-events' @@ -35,4 +36,8 @@ export interface DomainEventFactoryInterface { createDuplicateItemSyncedEvent(itemUuid: string, userUuid: string): DuplicateItemSyncedEvent createItemRevisionCreationRequested(itemUuid: string, userUuid: string): ItemRevisionCreationRequestedEvent createItemDumpedEvent(fileDumpPath: string, userUuid: string): ItemDumpedEvent + createRevisionsCopyRequestedEvent( + userUuid: string, + dto: { originalItemUuid: string; newItemUuid: string }, + ): RevisionsCopyRequestedEvent } diff --git a/packages/syncing-server/src/Domain/Handler/DuplicateItemSyncedEventHandler.spec.ts b/packages/syncing-server/src/Domain/Handler/DuplicateItemSyncedEventHandler.spec.ts index de4ecc93c..8b25ba3c6 100644 --- a/packages/syncing-server/src/Domain/Handler/DuplicateItemSyncedEventHandler.spec.ts +++ b/packages/syncing-server/src/Domain/Handler/DuplicateItemSyncedEventHandler.spec.ts @@ -1,11 +1,16 @@ import 'reflect-metadata' -import { DuplicateItemSyncedEvent } from '@standardnotes/domain-events' +import { + DomainEventPublisherInterface, + DuplicateItemSyncedEvent, + RevisionsCopyRequestedEvent, +} from '@standardnotes/domain-events' import { Logger } from 'winston' import { Item } from '../Item/Item' import { ItemRepositoryInterface } from '../Item/ItemRepositoryInterface' import { DuplicateItemSyncedEventHandler } from './DuplicateItemSyncedEventHandler' import { RevisionServiceInterface } from '../Revision/RevisionServiceInterface' +import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface' describe('DuplicateItemSyncedEventHandler', () => { let itemRepository: ItemRepositoryInterface @@ -14,8 +19,17 @@ describe('DuplicateItemSyncedEventHandler', () => { let duplicateItem: Item let originalItem: Item let event: DuplicateItemSyncedEvent + let domainEventFactory: DomainEventFactoryInterface + let domainEventPublisher: DomainEventPublisherInterface - const createHandler = () => new DuplicateItemSyncedEventHandler(itemRepository, revisionService, logger) + const createHandler = () => + new DuplicateItemSyncedEventHandler( + itemRepository, + revisionService, + domainEventFactory, + domainEventPublisher, + logger, + ) beforeEach(() => { originalItem = { @@ -45,6 +59,14 @@ describe('DuplicateItemSyncedEventHandler', () => { userUuid: '1-2-3', itemUuid: '2-3-4', } + + domainEventFactory = {} as jest.Mocked + domainEventFactory.createRevisionsCopyRequestedEvent = jest + .fn() + .mockReturnValue({} as jest.Mocked) + + domainEventPublisher = {} as jest.Mocked + domainEventPublisher.publish = jest.fn() }) it('should copy revisions from original item to the duplicate item', async () => { diff --git a/packages/syncing-server/src/Domain/Handler/DuplicateItemSyncedEventHandler.ts b/packages/syncing-server/src/Domain/Handler/DuplicateItemSyncedEventHandler.ts index 41f29e3d7..f28782d36 100644 --- a/packages/syncing-server/src/Domain/Handler/DuplicateItemSyncedEventHandler.ts +++ b/packages/syncing-server/src/Domain/Handler/DuplicateItemSyncedEventHandler.ts @@ -1,7 +1,12 @@ -import { DomainEventHandlerInterface, DuplicateItemSyncedEvent } from '@standardnotes/domain-events' +import { + DomainEventHandlerInterface, + DomainEventPublisherInterface, + DuplicateItemSyncedEvent, +} from '@standardnotes/domain-events' import { inject, injectable } from 'inversify' import { Logger } from 'winston' import TYPES from '../../Bootstrap/Types' +import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface' import { ItemRepositoryInterface } from '../Item/ItemRepositoryInterface' import { RevisionServiceInterface } from '../Revision/RevisionServiceInterface' @@ -10,6 +15,8 @@ export class DuplicateItemSyncedEventHandler implements DomainEventHandlerInterf constructor( @inject(TYPES.ItemRepository) private itemRepository: ItemRepositoryInterface, @inject(TYPES.RevisionService) private revisionService: RevisionServiceInterface, + @inject(TYPES.DomainEventFactory) private domainEventFactory: DomainEventFactoryInterface, + @inject(TYPES.DomainEventPublisher) private domainEventPublisher: DomainEventPublisherInterface, @inject(TYPES.Logger) private logger: Logger, ) {} @@ -35,6 +42,13 @@ export class DuplicateItemSyncedEventHandler implements DomainEventHandlerInterf if (existingOriginalItem !== null) { await this.revisionService.copyRevisions(existingOriginalItem.uuid, item.uuid) + + await this.domainEventPublisher.publish( + this.domainEventFactory.createRevisionsCopyRequestedEvent(event.payload.userUuid, { + originalItemUuid: existingOriginalItem.uuid, + newItemUuid: item.uuid, + }), + ) } } }