diff --git a/packages/domain-events/src/Domain/Event/ItemRemovedFromSharedVaultEvent.ts b/packages/domain-events/src/Domain/Event/ItemRemovedFromSharedVaultEvent.ts new file mode 100644 index 000000000..21e19023a --- /dev/null +++ b/packages/domain-events/src/Domain/Event/ItemRemovedFromSharedVaultEvent.ts @@ -0,0 +1,7 @@ +import { DomainEventInterface } from './DomainEventInterface' +import { ItemRemovedFromSharedVaultEventPayload } from './ItemRemovedFromSharedVaultEventPayload' + +export interface ItemRemovedFromSharedVaultEvent extends DomainEventInterface { + type: 'ITEM_REMOVED_FROM_SHARED_VAULT' + payload: ItemRemovedFromSharedVaultEventPayload +} diff --git a/packages/domain-events/src/Domain/Event/ItemRemovedFromSharedVaultEventPayload.ts b/packages/domain-events/src/Domain/Event/ItemRemovedFromSharedVaultEventPayload.ts new file mode 100644 index 000000000..e7ecff31c --- /dev/null +++ b/packages/domain-events/src/Domain/Event/ItemRemovedFromSharedVaultEventPayload.ts @@ -0,0 +1,6 @@ +export interface ItemRemovedFromSharedVaultEventPayload { + userUuid: string + itemUuid: string + sharedVaultUuid: string + roleNames: string[] +} diff --git a/packages/domain-events/src/Domain/index.ts b/packages/domain-events/src/Domain/index.ts index afd8339c6..a5120023a 100644 --- a/packages/domain-events/src/Domain/index.ts +++ b/packages/domain-events/src/Domain/index.ts @@ -32,6 +32,8 @@ export * from './Event/FileUploadedEvent' export * from './Event/FileUploadedEventPayload' export * from './Event/ItemDumpedEvent' export * from './Event/ItemDumpedEventPayload' +export * from './Event/ItemRemovedFromSharedVaultEvent' +export * from './Event/ItemRemovedFromSharedVaultEventPayload' export * from './Event/ItemRevisionCreationRequestedEvent' export * from './Event/ItemRevisionCreationRequestedEventPayload' export * from './Event/ListedAccountCreatedEvent' diff --git a/packages/revisions/src/Bootstrap/Container.ts b/packages/revisions/src/Bootstrap/Container.ts index fc9aac139..e0e82f007 100644 --- a/packages/revisions/src/Bootstrap/Container.ts +++ b/packages/revisions/src/Bootstrap/Container.ts @@ -66,6 +66,8 @@ import { SQLRevision } from '../Infra/TypeORM/SQL/SQLRevision' import { SQLRevisionRepository } from '../Infra/TypeORM/SQL/SQLRevisionRepository' import { SQLRevisionMetadataPersistenceMapper } from '../Mapping/Persistence/SQL/SQLRevisionMetadataPersistenceMapper' import { SQLRevisionPersistenceMapper } from '../Mapping/Persistence/SQL/SQLRevisionPersistenceMapper' +import { RemoveRevisionsFromSharedVault } from '../Domain/UseCase/RemoveRevisionsFromSharedVault/RemoveRevisionsFromSharedVault' +import { ItemRemovedFromSharedVaultEventHandler } from '../Domain/Handler/ItemRemovedFromSharedVaultEventHandler' export class ContainerConfigLoader { async load(configuration?: { @@ -358,6 +360,13 @@ export class ContainerConfigLoader { container.get(TYPES.Revisions_DomainEventFactory), ), ) + container + .bind(TYPES.Revisions_RemoveRevisionsFromSharedVault) + .toConstantValue( + new RemoveRevisionsFromSharedVault( + container.get(TYPES.Revisions_RevisionRepositoryResolver), + ), + ) // env vars container.bind(TYPES.Revisions_AUTH_JWT_SECRET).toConstantValue(env.get('AUTH_JWT_SECRET')) @@ -437,12 +446,21 @@ export class ContainerConfigLoader { container.get(TYPES.Revisions_Logger), ), ) + container + .bind(TYPES.Revisions_ItemRemovedFromSharedVaultEventHandler) + .toConstantValue( + new ItemRemovedFromSharedVaultEventHandler( + container.get(TYPES.Revisions_RemoveRevisionsFromSharedVault), + container.get(TYPES.Revisions_Logger), + ), + ) const eventHandlers: Map = new Map([ ['ITEM_DUMPED', container.get(TYPES.Revisions_ItemDumpedEventHandler)], ['ACCOUNT_DELETION_REQUESTED', container.get(TYPES.Revisions_AccountDeletionRequestedEventHandler)], ['REVISIONS_COPY_REQUESTED', container.get(TYPES.Revisions_RevisionsCopyRequestedEventHandler)], ['TRANSITION_STATUS_UPDATED', container.get(TYPES.Revisions_TransitionStatusUpdatedEventHandler)], + ['ITEM_REMOVED_FROM_SHARED_VAULT', container.get(TYPES.Revisions_ItemRemovedFromSharedVaultEventHandler)], ]) if (isConfiguredForHomeServer) { diff --git a/packages/revisions/src/Bootstrap/Types.ts b/packages/revisions/src/Bootstrap/Types.ts index e907fe1f5..3f549ef95 100644 --- a/packages/revisions/src/Bootstrap/Types.ts +++ b/packages/revisions/src/Bootstrap/Types.ts @@ -49,6 +49,7 @@ const TYPES = { Revisions_TriggerTransitionFromPrimaryToSecondaryDatabaseForUser: Symbol.for( 'Revisions_TriggerTransitionFromPrimaryToSecondaryDatabaseForUser', ), + Revisions_RemoveRevisionsFromSharedVault: Symbol.for('Revisions_RemoveRevisionsFromSharedVault'), // Controller Revisions_ControllerContainer: Symbol.for('Revisions_ControllerContainer'), Revisions_RevisionsController: Symbol.for('Revisions_RevisionsController'), @@ -58,6 +59,7 @@ const TYPES = { Revisions_AccountDeletionRequestedEventHandler: Symbol.for('Revisions_AccountDeletionRequestedEventHandler'), Revisions_RevisionsCopyRequestedEventHandler: Symbol.for('Revisions_RevisionsCopyRequestedEventHandler'), Revisions_TransitionStatusUpdatedEventHandler: Symbol.for('Revisions_TransitionStatusUpdatedEventHandler'), + Revisions_ItemRemovedFromSharedVaultEventHandler: Symbol.for('Revisions_ItemRemovedFromSharedVaultEventHandler'), // Services Revisions_CrossServiceTokenDecoder: Symbol.for('Revisions_CrossServiceTokenDecoder'), Revisions_DomainEventSubscriberFactory: Symbol.for('Revisions_DomainEventSubscriberFactory'), diff --git a/packages/revisions/src/Domain/Handler/ItemRemovedFromSharedVaultEventHandler.ts b/packages/revisions/src/Domain/Handler/ItemRemovedFromSharedVaultEventHandler.ts new file mode 100644 index 000000000..e33d50591 --- /dev/null +++ b/packages/revisions/src/Domain/Handler/ItemRemovedFromSharedVaultEventHandler.ts @@ -0,0 +1,22 @@ +import { DomainEventHandlerInterface, ItemRemovedFromSharedVaultEvent } from '@standardnotes/domain-events' +import { RemoveRevisionsFromSharedVault } from '../UseCase/RemoveRevisionsFromSharedVault/RemoveRevisionsFromSharedVault' +import { Logger } from 'winston' + +export class ItemRemovedFromSharedVaultEventHandler implements DomainEventHandlerInterface { + constructor( + private removeRevisionsFromSharedVault: RemoveRevisionsFromSharedVault, + private logger: Logger, + ) {} + + async handle(event: ItemRemovedFromSharedVaultEvent): Promise { + const result = await this.removeRevisionsFromSharedVault.execute({ + sharedVaultUuid: event.payload.sharedVaultUuid, + itemUuid: event.payload.itemUuid, + roleNames: event.payload.roleNames, + }) + + if (result.isFailed()) { + this.logger.error(`Failed to remove revisions from shared vault: ${result.getError()}`) + } + } +} diff --git a/packages/revisions/src/Domain/Revision/RevisionRepositoryInterface.ts b/packages/revisions/src/Domain/Revision/RevisionRepositoryInterface.ts index d69b336f3..a0a2d3d32 100644 --- a/packages/revisions/src/Domain/Revision/RevisionRepositoryInterface.ts +++ b/packages/revisions/src/Domain/Revision/RevisionRepositoryInterface.ts @@ -13,4 +13,5 @@ export interface RevisionRepositoryInterface { updateUserUuid(itemUuid: Uuid, userUuid: Uuid): Promise findByUserUuid(dto: { userUuid: Uuid; offset?: number; limit?: number }): Promise> insert(revision: Revision): Promise + clearSharedVaultAndKeySystemAssociations(itemUuid: Uuid, sharedVaultUuid: Uuid): Promise } diff --git a/packages/revisions/src/Domain/UseCase/RemoveRevisionsFromSharedVault/RemoveRevisionsFromSharedVault.spec.ts b/packages/revisions/src/Domain/UseCase/RemoveRevisionsFromSharedVault/RemoveRevisionsFromSharedVault.spec.ts new file mode 100644 index 000000000..061b8346b --- /dev/null +++ b/packages/revisions/src/Domain/UseCase/RemoveRevisionsFromSharedVault/RemoveRevisionsFromSharedVault.spec.ts @@ -0,0 +1,66 @@ +import { RevisionRepositoryInterface } from '../../Revision/RevisionRepositoryInterface' +import { RevisionRepositoryResolverInterface } from '../../Revision/RevisionRepositoryResolverInterface' +import { RemoveRevisionsFromSharedVault } from './RemoveRevisionsFromSharedVault' + +describe('RemoveRevisionsFromSharedVault', () => { + let revisionRepositoryResolver: RevisionRepositoryResolverInterface + let revisionRepository: RevisionRepositoryInterface + + const createUseCase = () => new RemoveRevisionsFromSharedVault(revisionRepositoryResolver) + + beforeEach(() => { + revisionRepository = {} as jest.Mocked + revisionRepository.clearSharedVaultAndKeySystemAssociations = jest.fn() + + revisionRepositoryResolver = {} as jest.Mocked + revisionRepositoryResolver.resolve = jest.fn().mockReturnValue(revisionRepository) + }) + + it('should clear shared vault and key system associations', async () => { + const useCase = createUseCase() + + await useCase.execute({ + itemUuid: '00000000-0000-0000-0000-000000000000', + sharedVaultUuid: '00000000-0000-0000-0000-000000000001', + roleNames: ['CORE_USER'], + }) + + expect(revisionRepository.clearSharedVaultAndKeySystemAssociations).toHaveBeenCalled() + }) + + it('should return error when shared vault uuid is invalid', async () => { + const useCase = createUseCase() + + const result = await useCase.execute({ + itemUuid: '00000000-0000-0000-0000-000000000000', + sharedVaultUuid: 'invalid', + roleNames: ['CORE_USER'], + }) + + expect(result.isFailed()).toBe(true) + }) + + it('should return error when item uuid is invalid', async () => { + const useCase = createUseCase() + + const result = await useCase.execute({ + itemUuid: 'invalid', + sharedVaultUuid: '00000000-0000-0000-0000-000000000001', + roleNames: ['CORE_USER'], + }) + + expect(result.isFailed()).toBe(true) + }) + + it('should return error when role names are invalid', async () => { + const useCase = createUseCase() + + const result = await useCase.execute({ + itemUuid: '00000000-0000-0000-0000-000000000000', + sharedVaultUuid: '00000000-0000-0000-0000-000000000001', + roleNames: ['invalid'], + }) + + expect(result.isFailed()).toBe(true) + }) +}) diff --git a/packages/revisions/src/Domain/UseCase/RemoveRevisionsFromSharedVault/RemoveRevisionsFromSharedVault.ts b/packages/revisions/src/Domain/UseCase/RemoveRevisionsFromSharedVault/RemoveRevisionsFromSharedVault.ts new file mode 100644 index 000000000..4d0e69a97 --- /dev/null +++ b/packages/revisions/src/Domain/UseCase/RemoveRevisionsFromSharedVault/RemoveRevisionsFromSharedVault.ts @@ -0,0 +1,33 @@ +import { Result, RoleNameCollection, UseCaseInterface, Uuid } from '@standardnotes/domain-core' +import { RevisionRepositoryResolverInterface } from '../../Revision/RevisionRepositoryResolverInterface' +import { RemoveRevisionsFromSharedVaultDTO } from './RemoveRevisionsFromSharedVaultDTO' + +export class RemoveRevisionsFromSharedVault implements UseCaseInterface { + constructor(private revisionRepositoryResolver: RevisionRepositoryResolverInterface) {} + + async execute(dto: RemoveRevisionsFromSharedVaultDTO): Promise> { + const sharedVaultUuidOrError = Uuid.create(dto.sharedVaultUuid) + if (sharedVaultUuidOrError.isFailed()) { + return Result.fail(sharedVaultUuidOrError.getError()) + } + const sharedVaultUuid = sharedVaultUuidOrError.getValue() + + const itemUuidOrError = Uuid.create(dto.itemUuid) + if (itemUuidOrError.isFailed()) { + return Result.fail(itemUuidOrError.getError()) + } + const itemUuid = itemUuidOrError.getValue() + + const roleNamesOrError = RoleNameCollection.create(dto.roleNames) + if (roleNamesOrError.isFailed()) { + return Result.fail(roleNamesOrError.getError()) + } + const roleNames = roleNamesOrError.getValue() + + const revisionRepository = this.revisionRepositoryResolver.resolve(roleNames) + + await revisionRepository.clearSharedVaultAndKeySystemAssociations(itemUuid, sharedVaultUuid) + + return Result.ok() + } +} diff --git a/packages/revisions/src/Domain/UseCase/RemoveRevisionsFromSharedVault/RemoveRevisionsFromSharedVaultDTO.ts b/packages/revisions/src/Domain/UseCase/RemoveRevisionsFromSharedVault/RemoveRevisionsFromSharedVaultDTO.ts new file mode 100644 index 000000000..f44fedfbe --- /dev/null +++ b/packages/revisions/src/Domain/UseCase/RemoveRevisionsFromSharedVault/RemoveRevisionsFromSharedVaultDTO.ts @@ -0,0 +1,5 @@ +export interface RemoveRevisionsFromSharedVaultDTO { + itemUuid: string + sharedVaultUuid: string + roleNames: string[] +} diff --git a/packages/revisions/src/Infra/TypeORM/MongoDB/MongoDBRevisionRepository.ts b/packages/revisions/src/Infra/TypeORM/MongoDB/MongoDBRevisionRepository.ts index ee88debf0..ab663abb2 100644 --- a/packages/revisions/src/Infra/TypeORM/MongoDB/MongoDBRevisionRepository.ts +++ b/packages/revisions/src/Infra/TypeORM/MongoDB/MongoDBRevisionRepository.ts @@ -16,6 +16,21 @@ export class MongoDBRevisionRepository implements RevisionRepositoryInterface { private logger: Logger, ) {} + async clearSharedVaultAndKeySystemAssociations(itemUuid: Uuid, sharedVaultUuid: Uuid): Promise { + await this.mongoRepository.updateMany( + { + itemUuid: { $eq: itemUuid.value }, + sharedVaultUuid: { $eq: sharedVaultUuid.value }, + }, + { + $set: { + sharedVaultUuid: null, + keySystemIdentifier: null, + }, + }, + ) + } + async countByUserUuid(userUuid: Uuid): Promise { return this.mongoRepository.count({ userUuid: { $eq: userUuid.value } }) } diff --git a/packages/revisions/src/Infra/TypeORM/SQL/SQLLegacyRevisionRepository.ts b/packages/revisions/src/Infra/TypeORM/SQL/SQLLegacyRevisionRepository.ts index dfc392fea..9cba8e984 100644 --- a/packages/revisions/src/Infra/TypeORM/SQL/SQLLegacyRevisionRepository.ts +++ b/packages/revisions/src/Infra/TypeORM/SQL/SQLLegacyRevisionRepository.ts @@ -15,6 +15,10 @@ export class SQLLegacyRevisionRepository implements RevisionRepositoryInterface protected logger: Logger, ) {} + async clearSharedVaultAndKeySystemAssociations(_itemUuid: Uuid, _sharedVaultUuid: Uuid): Promise { + this.logger.error('Method clearSharedVaultAndKeySystemAssociations not implemented.') + } + async countByUserUuid(userUuid: Uuid): Promise { return this.ormRepository .createQueryBuilder() diff --git a/packages/revisions/src/Infra/TypeORM/SQL/SQLRevisionRepository.ts b/packages/revisions/src/Infra/TypeORM/SQL/SQLRevisionRepository.ts index 97930f675..6160afd14 100644 --- a/packages/revisions/src/Infra/TypeORM/SQL/SQLRevisionRepository.ts +++ b/packages/revisions/src/Infra/TypeORM/SQL/SQLRevisionRepository.ts @@ -45,6 +45,21 @@ export class SQLRevisionRepository extends SQLLegacyRevisionRepository { return this.revisionMapper.toDomain(sqlRevision) } + override async clearSharedVaultAndKeySystemAssociations(itemUuid: Uuid, sharedVaultUuid: Uuid): Promise { + await this.ormRepository + .createQueryBuilder() + .update() + .set({ + sharedVaultUuid: null, + keySystemIdentifier: null, + }) + .where('item_uuid = :itemUuid AND shared_vault_uuid = :sharedVaultUuid', { + itemUuid: itemUuid.value, + sharedVaultUuid: sharedVaultUuid.value, + }) + .execute() + } + override async findMetadataByItemId( itemUuid: Uuid, userUuid: Uuid, diff --git a/packages/syncing-server/src/Domain/Event/DomainEventFactory.ts b/packages/syncing-server/src/Domain/Event/DomainEventFactory.ts index 199375221..d97e4ec23 100644 --- a/packages/syncing-server/src/Domain/Event/DomainEventFactory.ts +++ b/packages/syncing-server/src/Domain/Event/DomainEventFactory.ts @@ -4,6 +4,7 @@ import { DuplicateItemSyncedEvent, EmailRequestedEvent, ItemDumpedEvent, + ItemRemovedFromSharedVaultEvent, ItemRevisionCreationRequestedEvent, MessageSentToUserEvent, NotificationAddedForUserEvent, @@ -20,6 +21,26 @@ import { DomainEventFactoryInterface } from './DomainEventFactoryInterface' export class DomainEventFactory implements DomainEventFactoryInterface { constructor(private timer: TimerInterface) {} + createItemRemovedFromSharedVaultEvent(dto: { + sharedVaultUuid: string + itemUuid: string + userUuid: string + roleNames: string[] + }): ItemRemovedFromSharedVaultEvent { + return { + type: 'ITEM_REMOVED_FROM_SHARED_VAULT', + createdAt: this.timer.getUTCDate(), + meta: { + correlation: { + userIdentifier: dto.userUuid, + userIdentifierType: 'uuid', + }, + origin: DomainEventService.SyncingServer, + }, + payload: dto, + } + } + createUserRemovedFromSharedVaultEvent(dto: { sharedVaultUuid: string userUuid: string diff --git a/packages/syncing-server/src/Domain/Event/DomainEventFactoryInterface.ts b/packages/syncing-server/src/Domain/Event/DomainEventFactoryInterface.ts index 8aea7f561..fd9cdfb09 100644 --- a/packages/syncing-server/src/Domain/Event/DomainEventFactoryInterface.ts +++ b/packages/syncing-server/src/Domain/Event/DomainEventFactoryInterface.ts @@ -2,6 +2,7 @@ import { DuplicateItemSyncedEvent, EmailRequestedEvent, ItemDumpedEvent, + ItemRemovedFromSharedVaultEvent, ItemRevisionCreationRequestedEvent, MessageSentToUserEvent, NotificationAddedForUserEvent, @@ -93,4 +94,10 @@ export interface DomainEventFactoryInterface { sharedVaultUuid: string userUuid: string }): UserRemovedFromSharedVaultEvent + createItemRemovedFromSharedVaultEvent(dto: { + sharedVaultUuid: string + itemUuid: string + userUuid: string + roleNames: string[] + }): ItemRemovedFromSharedVaultEvent } diff --git a/packages/syncing-server/src/Domain/UseCase/Syncing/UpdateExistingItem/UpdateExistingItem.spec.ts b/packages/syncing-server/src/Domain/UseCase/Syncing/UpdateExistingItem/UpdateExistingItem.spec.ts index deacfcd61..d5287f70f 100644 --- a/packages/syncing-server/src/Domain/UseCase/Syncing/UpdateExistingItem/UpdateExistingItem.spec.ts +++ b/packages/syncing-server/src/Domain/UseCase/Syncing/UpdateExistingItem/UpdateExistingItem.spec.ts @@ -111,6 +111,9 @@ describe('UpdateExistingItem', () => { domainEventFactory.createItemRevisionCreationRequested = jest .fn() .mockReturnValue({} as jest.Mocked) + domainEventFactory.createItemRemovedFromSharedVaultEvent = jest + .fn() + .mockReturnValue({} as jest.Mocked) determineSharedVaultOperationOnItem = {} as jest.Mocked determineSharedVaultOperationOnItem.execute = jest.fn().mockResolvedValue( @@ -400,6 +403,47 @@ describe('UpdateExistingItem', () => { expect(item1.props.sharedVaultAssociation).not.toBeUndefined() }) + it('should remove a shared vault association and publish an event that item has been removed from shared vault', async () => { + const useCase = createUseCase() + + item1.props.sharedVaultAssociation = SharedVaultAssociation.create({ + sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(), + lastEditedBy: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(), + }).getValue() + + const itemHash = ItemHash.create({ + ...itemHash1.props, + shared_vault_uuid: null, + }).getValue() + + determineSharedVaultOperationOnItem.execute = jest.fn().mockReturnValue( + Result.ok( + SharedVaultOperationOnItem.create({ + existingItem: item1, + incomingItemHash: itemHash1, + sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(), + type: SharedVaultOperationOnItem.TYPES.RemoveFromSharedVault, + userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(), + }).getValue(), + ), + ) + + const result = await useCase.execute({ + existingItem: item1, + itemHash, + sessionUuid: '00000000-0000-0000-0000-000000000000', + performingUserUuid: '00000000-0000-0000-0000-000000000000', + roleNames: [RoleName.NAMES.CoreUser], + }) + + expect(result.isFailed()).toBeFalsy() + + expect(item1.props.sharedVaultAssociation).toBeUndefined() + + expect(domainEventFactory.createItemRemovedFromSharedVaultEvent).toHaveBeenCalled() + expect(domainEventPublisher.publish).toHaveBeenCalled() + }) + it('should return error if shared vault association could not be created', async () => { const useCase = createUseCase() diff --git a/packages/syncing-server/src/Domain/UseCase/Syncing/UpdateExistingItem/UpdateExistingItem.ts b/packages/syncing-server/src/Domain/UseCase/Syncing/UpdateExistingItem/UpdateExistingItem.ts index 842eb7caa..e4c2ad203 100644 --- a/packages/syncing-server/src/Domain/UseCase/Syncing/UpdateExistingItem/UpdateExistingItem.ts +++ b/packages/syncing-server/src/Domain/UseCase/Syncing/UpdateExistingItem/UpdateExistingItem.ts @@ -37,16 +37,6 @@ export class UpdateExistingItem implements UseCaseInterface { ) {} async execute(dto: UpdateExistingItemDTO): Promise> { - let sessionUuid = null - if (dto.sessionUuid) { - const sessionUuidOrError = Uuid.create(dto.sessionUuid) - if (sessionUuidOrError.isFailed()) { - return Result.fail(sessionUuidOrError.getError()) - } - sessionUuid = sessionUuidOrError.getValue() - } - dto.existingItem.props.updatedWithSession = sessionUuid - const userUuidOrError = Uuid.create(dto.performingUserUuid) if (userUuidOrError.isFailed()) { return Result.fail(userUuidOrError.getError()) @@ -59,6 +49,29 @@ export class UpdateExistingItem implements UseCaseInterface { } const roleNames = roleNamesOrError.getValue() + let sharedVaultOperation: SharedVaultOperationOnItem | null = null + if (dto.itemHash.representsASharedVaultItem() || dto.existingItem.isAssociatedWithASharedVault()) { + const sharedVaultOperationOrError = await this.determineSharedVaultOperationOnItem.execute({ + existingItem: dto.existingItem, + itemHash: dto.itemHash, + userUuid: userUuid.value, + }) + if (sharedVaultOperationOrError.isFailed()) { + return Result.fail(sharedVaultOperationOrError.getError()) + } + sharedVaultOperation = sharedVaultOperationOrError.getValue() + } + + let sessionUuid = null + if (dto.sessionUuid) { + const sessionUuidOrError = Uuid.create(dto.sessionUuid) + if (sessionUuidOrError.isFailed()) { + return Result.fail(sessionUuidOrError.getError()) + } + sessionUuid = sessionUuidOrError.getValue() + } + dto.existingItem.props.updatedWithSession = sessionUuid + if (dto.itemHash.props.content) { dto.existingItem.props.content = dto.itemHash.props.content } @@ -128,7 +141,6 @@ export class UpdateExistingItem implements UseCaseInterface { dto.existingItem.props.contentSize = Buffer.byteLength(JSON.stringify(dto.existingItem)) - let sharedVaultOperation: SharedVaultOperationOnItem | null = null if (dto.itemHash.representsASharedVaultItem()) { const sharedVaultAssociationOrError = SharedVaultAssociation.create({ lastEditedBy: userUuid, @@ -140,16 +152,6 @@ export class UpdateExistingItem implements UseCaseInterface { } dto.existingItem.props.sharedVaultAssociation = sharedVaultAssociationOrError.getValue() - - const sharedVaultOperationOrError = await this.determineSharedVaultOperationOnItem.execute({ - existingItem: dto.existingItem, - itemHash: dto.itemHash, - userUuid: userUuid.value, - }) - if (sharedVaultOperationOrError.isFailed()) { - return Result.fail(sharedVaultOperationOrError.getError()) - } - sharedVaultOperation = sharedVaultOperationOrError.getValue() } else { dto.existingItem.props.sharedVaultAssociation = undefined } @@ -209,7 +211,7 @@ export class UpdateExistingItem implements UseCaseInterface { ) } - const notificationsResult = await this.addNotifications(dto.existingItem.uuid, userUuid, sharedVaultOperation) + const notificationsResult = await this.addNotificationsAndPublishEvents(userUuid, sharedVaultOperation, dto) if (notificationsResult.isFailed()) { return Result.fail(notificationsResult.getError()) } @@ -217,10 +219,10 @@ export class UpdateExistingItem implements UseCaseInterface { return Result.ok(dto.existingItem) } - private async addNotifications( - itemUuid: Uuid, + private async addNotificationsAndPublishEvents( userUuid: Uuid, sharedVaultOperation: SharedVaultOperationOnItem | null, + dto: UpdateExistingItemDTO, ): Promise> { if ( sharedVaultOperation && @@ -229,7 +231,7 @@ export class UpdateExistingItem implements UseCaseInterface { const notificationPayloadOrError = NotificationPayload.create({ sharedVaultUuid: sharedVaultOperation.props.sharedVaultUuid, type: NotificationType.create(NotificationType.TYPES.SharedVaultItemRemoved).getValue(), - itemUuid: itemUuid, + itemUuid: dto.existingItem.uuid, version: '1.0', }) if (notificationPayloadOrError.isFailed()) { @@ -246,6 +248,15 @@ export class UpdateExistingItem implements UseCaseInterface { if (result.isFailed()) { return Result.fail(result.getError()) } + + await this.domainEventPublisher.publish( + this.domainEventFactory.createItemRemovedFromSharedVaultEvent({ + sharedVaultUuid: sharedVaultOperation.props.sharedVaultUuid.value, + itemUuid: dto.existingItem.uuid.value, + userUuid: userUuid.value, + roleNames: dto.roleNames, + }), + ) } if (sharedVaultOperation && sharedVaultOperation.props.type === SharedVaultOperationOnItem.TYPES.AddToSharedVault) { diff --git a/packages/syncing-server/src/Infra/TypeORM/MongoDBItemRepository.ts b/packages/syncing-server/src/Infra/TypeORM/MongoDBItemRepository.ts index efeae8285..0e3b90444 100644 --- a/packages/syncing-server/src/Infra/TypeORM/MongoDBItemRepository.ts +++ b/packages/syncing-server/src/Infra/TypeORM/MongoDBItemRepository.ts @@ -155,15 +155,23 @@ export class MongoDBItemRepository implements ItemRepositoryInterface { async markItemsAsDeleted(itemUuids: string[], updatedAtTimestamp: number): Promise { await this.mongoRepository.updateMany( - { where: { _id: { $in: itemUuids.map((uuid) => BSON.UUID.createFromHexString(uuid)) } } }, - { deleted: true, content: null, encItemKey: null, authHash: null, updatedAtTimestamp }, + { _id: { $in: itemUuids.map((uuid) => BSON.UUID.createFromHexString(uuid)) } }, + { + $set: { + deleted: true, + content: null, + encItemKey: null, + authHash: null, + updatedAtTimestamp, + }, + }, ) } async updateContentSize(itemUuid: string, contentSize: number): Promise { await this.mongoRepository.updateOne( - { where: { _id: { $eq: BSON.UUID.createFromHexString(itemUuid) } } }, - { contentSize }, + { _id: { $eq: BSON.UUID.createFromHexString(itemUuid) } }, + { $set: { contentSize } }, ) }