소스 검색

feat: add unassigning items and revisions upon shared vault removal (#839)

* feat: add unassigning items and revisions upon shared vault removal

* fix(syncing-server): update event payload creation
Karol Sójko 1 년 전
부모
커밋
378ecedfcc
30개의 변경된 파일295개의 추가작업 그리고 71개의 파일을 삭제
  1. 0 1
      packages/domain-events/src/Domain/Event/ItemRemovedFromSharedVaultEventPayload.ts
  2. 7 0
      packages/domain-events/src/Domain/Event/SharedVaultRemovedEvent.ts
  3. 3 0
      packages/domain-events/src/Domain/Event/SharedVaultRemovedEventPayload.ts
  4. 2 0
      packages/domain-events/src/Domain/index.ts
  5. 13 1
      packages/revisions/src/Bootstrap/Container.ts
  6. 1 0
      packages/revisions/src/Bootstrap/Types.ts
  7. 6 1
      packages/revisions/src/Domain/Handler/ItemRemovedFromSharedVaultEventHandler.ts
  8. 21 0
      packages/revisions/src/Domain/Handler/SharedVaultRemovedEventHandler.ts
  9. 1 1
      packages/revisions/src/Domain/Revision/RevisionRepositoryInterface.ts
  10. 10 20
      packages/revisions/src/Domain/UseCase/RemoveRevisionsFromSharedVault/RemoveRevisionsFromSharedVault.spec.ts
  11. 15 16
      packages/revisions/src/Domain/UseCase/RemoveRevisionsFromSharedVault/RemoveRevisionsFromSharedVault.ts
  12. 1 2
      packages/revisions/src/Domain/UseCase/RemoveRevisionsFromSharedVault/RemoveRevisionsFromSharedVaultDTO.ts
  13. 19 13
      packages/revisions/src/Infra/TypeORM/MongoDB/MongoDBRevisionRepository.ts
  14. 1 1
      packages/revisions/src/Infra/TypeORM/SQL/SQLLegacyRevisionRepository.ts
  15. 19 11
      packages/revisions/src/Infra/TypeORM/SQL/SQLRevisionRepository.ts
  16. 25 0
      packages/syncing-server/src/Bootstrap/Container.ts
  17. 2 0
      packages/syncing-server/src/Bootstrap/Types.ts
  18. 16 1
      packages/syncing-server/src/Domain/Event/DomainEventFactory.ts
  19. 2 1
      packages/syncing-server/src/Domain/Event/DomainEventFactoryInterface.ts
  20. 22 0
      packages/syncing-server/src/Domain/Handler/SharedVaultRemovedEventHandler.ts
  21. 1 0
      packages/syncing-server/src/Domain/Item/ItemRepositoryInterface.ts
  22. 17 0
      packages/syncing-server/src/Domain/UseCase/SharedVaults/DeleteSharedVault/DeleteSharedVault.spec.ts
  23. 10 0
      packages/syncing-server/src/Domain/UseCase/SharedVaults/DeleteSharedVault/DeleteSharedVault.ts
  24. 33 0
      packages/syncing-server/src/Domain/UseCase/SharedVaults/RemoveItemsFromSharedVault/RemoveItemsFromSharedVault.spec.ts
  25. 20 0
      packages/syncing-server/src/Domain/UseCase/SharedVaults/RemoveItemsFromSharedVault/RemoveItemsFromSharedVault.ts
  26. 3 0
      packages/syncing-server/src/Domain/UseCase/SharedVaults/RemoveItemsFromSharedVault/RemoveItemsFromSharedVaultDTO.ts
  27. 0 1
      packages/syncing-server/src/Domain/UseCase/Syncing/UpdateExistingItem/UpdateExistingItem.ts
  28. 7 0
      packages/syncing-server/src/Infra/TypeORM/MongoDBItemRepository.ts
  29. 14 1
      packages/syncing-server/src/Infra/TypeORM/SQLItemRepository.ts
  30. 4 0
      packages/syncing-server/src/Infra/TypeORM/SQLLegacyItemRepository.ts

+ 0 - 1
packages/domain-events/src/Domain/Event/ItemRemovedFromSharedVaultEventPayload.ts

@@ -2,5 +2,4 @@ export interface ItemRemovedFromSharedVaultEventPayload {
   userUuid: string
   userUuid: string
   itemUuid: string
   itemUuid: string
   sharedVaultUuid: string
   sharedVaultUuid: string
-  roleNames: string[]
 }
 }

+ 7 - 0
packages/domain-events/src/Domain/Event/SharedVaultRemovedEvent.ts

@@ -0,0 +1,7 @@
+import { DomainEventInterface } from './DomainEventInterface'
+import { SharedVaultRemovedEventPayload } from './SharedVaultRemovedEventPayload'
+
+export interface SharedVaultRemovedEvent extends DomainEventInterface {
+  type: 'SHARED_VAULT_REMOVED'
+  payload: SharedVaultRemovedEventPayload
+}

+ 3 - 0
packages/domain-events/src/Domain/Event/SharedVaultRemovedEventPayload.ts

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

+ 2 - 0
packages/domain-events/src/Domain/index.ts

@@ -76,6 +76,8 @@ export * from './Event/SharedVaultFileRemovedEvent'
 export * from './Event/SharedVaultFileRemovedEventPayload'
 export * from './Event/SharedVaultFileRemovedEventPayload'
 export * from './Event/SharedVaultFileUploadedEvent'
 export * from './Event/SharedVaultFileUploadedEvent'
 export * from './Event/SharedVaultFileUploadedEventPayload'
 export * from './Event/SharedVaultFileUploadedEventPayload'
+export * from './Event/SharedVaultRemovedEvent'
+export * from './Event/SharedVaultRemovedEventPayload'
 export * from './Event/StatisticPersistenceRequestedEvent'
 export * from './Event/StatisticPersistenceRequestedEvent'
 export * from './Event/StatisticPersistenceRequestedEventPayload'
 export * from './Event/StatisticPersistenceRequestedEventPayload'
 export * from './Event/SubscriptionCancelledEvent'
 export * from './Event/SubscriptionCancelledEvent'

+ 13 - 1
packages/revisions/src/Bootstrap/Container.ts

@@ -67,6 +67,7 @@ import { SQLRevisionPersistenceMapper } from '../Mapping/Persistence/SQL/SQLRevi
 import { RemoveRevisionsFromSharedVault } from '../Domain/UseCase/RemoveRevisionsFromSharedVault/RemoveRevisionsFromSharedVault'
 import { RemoveRevisionsFromSharedVault } from '../Domain/UseCase/RemoveRevisionsFromSharedVault/RemoveRevisionsFromSharedVault'
 import { ItemRemovedFromSharedVaultEventHandler } from '../Domain/Handler/ItemRemovedFromSharedVaultEventHandler'
 import { ItemRemovedFromSharedVaultEventHandler } from '../Domain/Handler/ItemRemovedFromSharedVaultEventHandler'
 import { TransitionRequestedEventHandler } from '../Domain/Handler/TransitionRequestedEventHandler'
 import { TransitionRequestedEventHandler } from '../Domain/Handler/TransitionRequestedEventHandler'
+import { SharedVaultRemovedEventHandler } from '../Domain/Handler/SharedVaultRemovedEventHandler'
 
 
 export class ContainerConfigLoader {
 export class ContainerConfigLoader {
   constructor(private mode: 'server' | 'worker' = 'server') {}
   constructor(private mode: 'server' | 'worker' = 'server') {}
@@ -356,7 +357,9 @@ export class ContainerConfigLoader {
       .bind<RemoveRevisionsFromSharedVault>(TYPES.Revisions_RemoveRevisionsFromSharedVault)
       .bind<RemoveRevisionsFromSharedVault>(TYPES.Revisions_RemoveRevisionsFromSharedVault)
       .toConstantValue(
       .toConstantValue(
         new RemoveRevisionsFromSharedVault(
         new RemoveRevisionsFromSharedVault(
-          container.get<RevisionRepositoryResolverInterface>(TYPES.Revisions_RevisionRepositoryResolver),
+          isSecondaryDatabaseEnabled
+            ? container.get<RevisionRepositoryInterface>(TYPES.Revisions_MongoDBRevisionRepository)
+            : container.get<RevisionRepositoryInterface>(TYPES.Revisions_SQLRevisionRepository),
         ),
         ),
       )
       )
 
 
@@ -448,6 +451,14 @@ export class ContainerConfigLoader {
           container.get<winston.Logger>(TYPES.Revisions_Logger),
           container.get<winston.Logger>(TYPES.Revisions_Logger),
         ),
         ),
       )
       )
+    container
+      .bind<SharedVaultRemovedEventHandler>(TYPES.Revisions_SharedVaultRemovedEventHandler)
+      .toConstantValue(
+        new SharedVaultRemovedEventHandler(
+          container.get<RemoveRevisionsFromSharedVault>(TYPES.Revisions_RemoveRevisionsFromSharedVault),
+          container.get<winston.Logger>(TYPES.Revisions_Logger),
+        ),
+      )
 
 
     const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([
     const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([
       ['ITEM_DUMPED', container.get(TYPES.Revisions_ItemDumpedEventHandler)],
       ['ITEM_DUMPED', container.get(TYPES.Revisions_ItemDumpedEventHandler)],
@@ -455,6 +466,7 @@ export class ContainerConfigLoader {
       ['REVISIONS_COPY_REQUESTED', container.get(TYPES.Revisions_RevisionsCopyRequestedEventHandler)],
       ['REVISIONS_COPY_REQUESTED', container.get(TYPES.Revisions_RevisionsCopyRequestedEventHandler)],
       ['ITEM_REMOVED_FROM_SHARED_VAULT', container.get(TYPES.Revisions_ItemRemovedFromSharedVaultEventHandler)],
       ['ITEM_REMOVED_FROM_SHARED_VAULT', container.get(TYPES.Revisions_ItemRemovedFromSharedVaultEventHandler)],
       ['TRANSITION_REQUESTED', container.get(TYPES.Revisions_TransitionRequestedEventHandler)],
       ['TRANSITION_REQUESTED', container.get(TYPES.Revisions_TransitionRequestedEventHandler)],
+      ['SHARED_VAULT_REMOVED', container.get(TYPES.Revisions_SharedVaultRemovedEventHandler)],
     ])
     ])
 
 
     if (isConfiguredForHomeServer) {
     if (isConfiguredForHomeServer) {

+ 1 - 0
packages/revisions/src/Bootstrap/Types.ts

@@ -57,6 +57,7 @@ const TYPES = {
   Revisions_RevisionsCopyRequestedEventHandler: Symbol.for('Revisions_RevisionsCopyRequestedEventHandler'),
   Revisions_RevisionsCopyRequestedEventHandler: Symbol.for('Revisions_RevisionsCopyRequestedEventHandler'),
   Revisions_ItemRemovedFromSharedVaultEventHandler: Symbol.for('Revisions_ItemRemovedFromSharedVaultEventHandler'),
   Revisions_ItemRemovedFromSharedVaultEventHandler: Symbol.for('Revisions_ItemRemovedFromSharedVaultEventHandler'),
   Revisions_TransitionRequestedEventHandler: Symbol.for('Revisions_TransitionRequestedEventHandler'),
   Revisions_TransitionRequestedEventHandler: Symbol.for('Revisions_TransitionRequestedEventHandler'),
+  Revisions_SharedVaultRemovedEventHandler: Symbol.for('Revisions_SharedVaultRemovedEventHandler'),
   // Services
   // Services
   Revisions_CrossServiceTokenDecoder: Symbol.for('Revisions_CrossServiceTokenDecoder'),
   Revisions_CrossServiceTokenDecoder: Symbol.for('Revisions_CrossServiceTokenDecoder'),
   Revisions_DomainEventSubscriberFactory: Symbol.for('Revisions_DomainEventSubscriberFactory'),
   Revisions_DomainEventSubscriberFactory: Symbol.for('Revisions_DomainEventSubscriberFactory'),

+ 6 - 1
packages/revisions/src/Domain/Handler/ItemRemovedFromSharedVaultEventHandler.ts

@@ -9,10 +9,15 @@ export class ItemRemovedFromSharedVaultEventHandler implements DomainEventHandle
   ) {}
   ) {}
 
 
   async handle(event: ItemRemovedFromSharedVaultEvent): Promise<void> {
   async handle(event: ItemRemovedFromSharedVaultEvent): Promise<void> {
+    if (!event.payload.itemUuid) {
+      this.logger.error('ItemRemovedFromSharedVaultEvent is missing itemUuid')
+
+      return
+    }
+
     const result = await this.removeRevisionsFromSharedVault.execute({
     const result = await this.removeRevisionsFromSharedVault.execute({
       sharedVaultUuid: event.payload.sharedVaultUuid,
       sharedVaultUuid: event.payload.sharedVaultUuid,
       itemUuid: event.payload.itemUuid,
       itemUuid: event.payload.itemUuid,
-      roleNames: event.payload.roleNames,
     })
     })
 
 
     if (result.isFailed()) {
     if (result.isFailed()) {

+ 21 - 0
packages/revisions/src/Domain/Handler/SharedVaultRemovedEventHandler.ts

@@ -0,0 +1,21 @@
+import { DomainEventHandlerInterface, SharedVaultRemovedEvent } from '@standardnotes/domain-events'
+import { Logger } from 'winston'
+
+import { RemoveRevisionsFromSharedVault } from '../UseCase/RemoveRevisionsFromSharedVault/RemoveRevisionsFromSharedVault'
+
+export class SharedVaultRemovedEventHandler implements DomainEventHandlerInterface {
+  constructor(
+    private removeRevisionsFromSharedVault: RemoveRevisionsFromSharedVault,
+    private logger: Logger,
+  ) {}
+
+  async handle(event: SharedVaultRemovedEvent): Promise<void> {
+    const result = await this.removeRevisionsFromSharedVault.execute({
+      sharedVaultUuid: event.payload.sharedVaultUuid,
+    })
+
+    if (result.isFailed()) {
+      this.logger.error(`Failed to remove revisions from shared vault: ${result.getError()}`)
+    }
+  }
+}

+ 1 - 1
packages/revisions/src/Domain/Revision/RevisionRepositoryInterface.ts

@@ -13,5 +13,5 @@ export interface RevisionRepositoryInterface {
   updateUserUuid(itemUuid: Uuid, userUuid: Uuid): Promise<void>
   updateUserUuid(itemUuid: Uuid, userUuid: Uuid): Promise<void>
   findByUserUuid(dto: { userUuid: Uuid; offset?: number; limit?: number }): Promise<Array<Revision>>
   findByUserUuid(dto: { userUuid: Uuid; offset?: number; limit?: number }): Promise<Array<Revision>>
   insert(revision: Revision): Promise<boolean>
   insert(revision: Revision): Promise<boolean>
-  clearSharedVaultAndKeySystemAssociations(itemUuid: Uuid, sharedVaultUuid: Uuid): Promise<void>
+  clearSharedVaultAndKeySystemAssociations(dto: { itemUuid?: Uuid; sharedVaultUuid: Uuid }): Promise<void>
 }
 }

+ 10 - 20
packages/revisions/src/Domain/UseCase/RemoveRevisionsFromSharedVault/RemoveRevisionsFromSharedVault.spec.ts

@@ -1,19 +1,14 @@
 import { RevisionRepositoryInterface } from '../../Revision/RevisionRepositoryInterface'
 import { RevisionRepositoryInterface } from '../../Revision/RevisionRepositoryInterface'
-import { RevisionRepositoryResolverInterface } from '../../Revision/RevisionRepositoryResolverInterface'
 import { RemoveRevisionsFromSharedVault } from './RemoveRevisionsFromSharedVault'
 import { RemoveRevisionsFromSharedVault } from './RemoveRevisionsFromSharedVault'
 
 
 describe('RemoveRevisionsFromSharedVault', () => {
 describe('RemoveRevisionsFromSharedVault', () => {
-  let revisionRepositoryResolver: RevisionRepositoryResolverInterface
   let revisionRepository: RevisionRepositoryInterface
   let revisionRepository: RevisionRepositoryInterface
 
 
-  const createUseCase = () => new RemoveRevisionsFromSharedVault(revisionRepositoryResolver)
+  const createUseCase = () => new RemoveRevisionsFromSharedVault(revisionRepository)
 
 
   beforeEach(() => {
   beforeEach(() => {
     revisionRepository = {} as jest.Mocked<RevisionRepositoryInterface>
     revisionRepository = {} as jest.Mocked<RevisionRepositoryInterface>
     revisionRepository.clearSharedVaultAndKeySystemAssociations = jest.fn()
     revisionRepository.clearSharedVaultAndKeySystemAssociations = jest.fn()
-
-    revisionRepositoryResolver = {} as jest.Mocked<RevisionRepositoryResolverInterface>
-    revisionRepositoryResolver.resolve = jest.fn().mockReturnValue(revisionRepository)
   })
   })
 
 
   it('should clear shared vault and key system associations', async () => {
   it('should clear shared vault and key system associations', async () => {
@@ -22,43 +17,38 @@ describe('RemoveRevisionsFromSharedVault', () => {
     await useCase.execute({
     await useCase.execute({
       itemUuid: '00000000-0000-0000-0000-000000000000',
       itemUuid: '00000000-0000-0000-0000-000000000000',
       sharedVaultUuid: '00000000-0000-0000-0000-000000000001',
       sharedVaultUuid: '00000000-0000-0000-0000-000000000001',
-      roleNames: ['CORE_USER'],
     })
     })
 
 
     expect(revisionRepository.clearSharedVaultAndKeySystemAssociations).toHaveBeenCalled()
     expect(revisionRepository.clearSharedVaultAndKeySystemAssociations).toHaveBeenCalled()
   })
   })
 
 
-  it('should return error when shared vault uuid is invalid', async () => {
+  it('should clear shared vault and key system associations for all items in a vault when item uuid is not provided', async () => {
     const useCase = createUseCase()
     const useCase = createUseCase()
 
 
-    const result = await useCase.execute({
-      itemUuid: '00000000-0000-0000-0000-000000000000',
-      sharedVaultUuid: 'invalid',
-      roleNames: ['CORE_USER'],
+    await useCase.execute({
+      sharedVaultUuid: '00000000-0000-0000-0000-000000000001',
     })
     })
 
 
-    expect(result.isFailed()).toBe(true)
+    expect(revisionRepository.clearSharedVaultAndKeySystemAssociations).toHaveBeenCalled()
   })
   })
 
 
-  it('should return error when item uuid is invalid', async () => {
+  it('should return error when shared vault uuid is invalid', async () => {
     const useCase = createUseCase()
     const useCase = createUseCase()
 
 
     const result = await useCase.execute({
     const result = await useCase.execute({
-      itemUuid: 'invalid',
-      sharedVaultUuid: '00000000-0000-0000-0000-000000000001',
-      roleNames: ['CORE_USER'],
+      itemUuid: '00000000-0000-0000-0000-000000000000',
+      sharedVaultUuid: 'invalid',
     })
     })
 
 
     expect(result.isFailed()).toBe(true)
     expect(result.isFailed()).toBe(true)
   })
   })
 
 
-  it('should return error when role names are invalid', async () => {
+  it('should return error when item uuid is invalid', async () => {
     const useCase = createUseCase()
     const useCase = createUseCase()
 
 
     const result = await useCase.execute({
     const result = await useCase.execute({
-      itemUuid: '00000000-0000-0000-0000-000000000000',
+      itemUuid: 'invalid',
       sharedVaultUuid: '00000000-0000-0000-0000-000000000001',
       sharedVaultUuid: '00000000-0000-0000-0000-000000000001',
-      roleNames: ['invalid'],
     })
     })
 
 
     expect(result.isFailed()).toBe(true)
     expect(result.isFailed()).toBe(true)

+ 15 - 16
packages/revisions/src/Domain/UseCase/RemoveRevisionsFromSharedVault/RemoveRevisionsFromSharedVault.ts

@@ -1,9 +1,10 @@
-import { Result, RoleNameCollection, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
-import { RevisionRepositoryResolverInterface } from '../../Revision/RevisionRepositoryResolverInterface'
+import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
+
 import { RemoveRevisionsFromSharedVaultDTO } from './RemoveRevisionsFromSharedVaultDTO'
 import { RemoveRevisionsFromSharedVaultDTO } from './RemoveRevisionsFromSharedVaultDTO'
+import { RevisionRepositoryInterface } from '../../Revision/RevisionRepositoryInterface'
 
 
 export class RemoveRevisionsFromSharedVault implements UseCaseInterface<void> {
 export class RemoveRevisionsFromSharedVault implements UseCaseInterface<void> {
-  constructor(private revisionRepositoryResolver: RevisionRepositoryResolverInterface) {}
+  constructor(private revisionRepository: RevisionRepositoryInterface) {}
 
 
   async execute(dto: RemoveRevisionsFromSharedVaultDTO): Promise<Result<void>> {
   async execute(dto: RemoveRevisionsFromSharedVaultDTO): Promise<Result<void>> {
     const sharedVaultUuidOrError = Uuid.create(dto.sharedVaultUuid)
     const sharedVaultUuidOrError = Uuid.create(dto.sharedVaultUuid)
@@ -12,21 +13,19 @@ export class RemoveRevisionsFromSharedVault implements UseCaseInterface<void> {
     }
     }
     const sharedVaultUuid = sharedVaultUuidOrError.getValue()
     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())
+    let itemUuid: Uuid | undefined
+    if (dto.itemUuid !== undefined) {
+      const itemUuidOrError = Uuid.create(dto.itemUuid)
+      if (itemUuidOrError.isFailed()) {
+        return Result.fail(itemUuidOrError.getError())
+      }
+      itemUuid = itemUuidOrError.getValue()
     }
     }
-    const roleNames = roleNamesOrError.getValue()
-
-    const revisionRepository = this.revisionRepositoryResolver.resolve(roleNames)
 
 
-    await revisionRepository.clearSharedVaultAndKeySystemAssociations(itemUuid, sharedVaultUuid)
+    await this.revisionRepository.clearSharedVaultAndKeySystemAssociations({
+      itemUuid,
+      sharedVaultUuid,
+    })
 
 
     return Result.ok()
     return Result.ok()
   }
   }

+ 1 - 2
packages/revisions/src/Domain/UseCase/RemoveRevisionsFromSharedVault/RemoveRevisionsFromSharedVaultDTO.ts

@@ -1,5 +1,4 @@
 export interface RemoveRevisionsFromSharedVaultDTO {
 export interface RemoveRevisionsFromSharedVaultDTO {
-  itemUuid: string
+  itemUuid?: string
   sharedVaultUuid: string
   sharedVaultUuid: string
-  roleNames: string[]
 }
 }

+ 19 - 13
packages/revisions/src/Infra/TypeORM/MongoDB/MongoDBRevisionRepository.ts

@@ -1,5 +1,5 @@
 import { MapperInterface, Uuid } from '@standardnotes/domain-core'
 import { MapperInterface, Uuid } from '@standardnotes/domain-core'
-import { MongoRepository } from 'typeorm'
+import { MongoRepository, ObjectLiteral } from 'typeorm'
 import { BSON } from 'mongodb'
 import { BSON } from 'mongodb'
 import { Logger } from 'winston'
 import { Logger } from 'winston'
 
 
@@ -16,19 +16,25 @@ export class MongoDBRevisionRepository implements RevisionRepositoryInterface {
     private logger: Logger,
     private logger: Logger,
   ) {}
   ) {}
 
 
-  async clearSharedVaultAndKeySystemAssociations(itemUuid: Uuid, sharedVaultUuid: Uuid): Promise<void> {
-    await this.mongoRepository.updateMany(
-      {
-        itemUuid: { $eq: itemUuid.value },
-        sharedVaultUuid: { $eq: sharedVaultUuid.value },
-      },
-      {
-        $set: {
-          sharedVaultUuid: null,
-          keySystemIdentifier: null,
-        },
+  async clearSharedVaultAndKeySystemAssociations(dto: { itemUuid?: Uuid; sharedVaultUuid: Uuid }): Promise<void> {
+    let query: ObjectLiteral
+    if (dto.itemUuid !== undefined) {
+      query = {
+        itemUuid: { $eq: dto.itemUuid.value },
+        sharedVaultUuid: { $eq: dto.sharedVaultUuid.value },
+      }
+    } else {
+      query = {
+        sharedVaultUuid: { $eq: dto.sharedVaultUuid.value },
+      }
+    }
+
+    await this.mongoRepository.updateMany(query, {
+      $set: {
+        sharedVaultUuid: null,
+        keySystemIdentifier: null,
       },
       },
-    )
+    })
   }
   }
 
 
   async countByUserUuid(userUuid: Uuid): Promise<number> {
   async countByUserUuid(userUuid: Uuid): Promise<number> {

+ 1 - 1
packages/revisions/src/Infra/TypeORM/SQL/SQLLegacyRevisionRepository.ts

@@ -15,7 +15,7 @@ export class SQLLegacyRevisionRepository implements RevisionRepositoryInterface
     protected logger: Logger,
     protected logger: Logger,
   ) {}
   ) {}
 
 
-  async clearSharedVaultAndKeySystemAssociations(_itemUuid: Uuid, _sharedVaultUuid: Uuid): Promise<void> {
+  async clearSharedVaultAndKeySystemAssociations(_dto: { itemUuid?: Uuid; sharedVaultUuid: Uuid }): Promise<void> {
     this.logger.error('Method clearSharedVaultAndKeySystemAssociations not implemented.')
     this.logger.error('Method clearSharedVaultAndKeySystemAssociations not implemented.')
   }
   }
 
 

+ 19 - 11
packages/revisions/src/Infra/TypeORM/SQL/SQLRevisionRepository.ts

@@ -66,19 +66,27 @@ export class SQLRevisionRepository extends SQLLegacyRevisionRepository {
     return this.revisionMapper.toDomain(sqlRevision)
     return this.revisionMapper.toDomain(sqlRevision)
   }
   }
 
 
-  override async clearSharedVaultAndKeySystemAssociations(itemUuid: Uuid, sharedVaultUuid: Uuid): Promise<void> {
-    await this.ormRepository
-      .createQueryBuilder()
-      .update()
-      .set({
-        sharedVaultUuid: null,
-        keySystemIdentifier: null,
+  override async clearSharedVaultAndKeySystemAssociations(dto: {
+    itemUuid?: Uuid
+    sharedVaultUuid: Uuid
+  }): Promise<void> {
+    const queryBuilder = this.ormRepository.createQueryBuilder().update().set({
+      sharedVaultUuid: null,
+      keySystemIdentifier: null,
+    })
+
+    if (dto.itemUuid !== undefined) {
+      queryBuilder.where('item_uuid = :itemUuid AND shared_vault_uuid = :sharedVaultUuid', {
+        itemUuid: dto.itemUuid.value,
+        sharedVaultUuid: dto.sharedVaultUuid.value,
       })
       })
-      .where('item_uuid = :itemUuid AND shared_vault_uuid = :sharedVaultUuid', {
-        itemUuid: itemUuid.value,
-        sharedVaultUuid: sharedVaultUuid.value,
+    } else {
+      queryBuilder.where('shared_vault_uuid = :sharedVaultUuid', {
+        sharedVaultUuid: dto.sharedVaultUuid.value,
       })
       })
-      .execute()
+    }
+
+    await queryBuilder.execute()
   }
   }
 
 
   override async findMetadataByItemId(
   override async findMetadataByItemId(

+ 25 - 0
packages/syncing-server/src/Bootstrap/Container.ts

@@ -166,6 +166,8 @@ import { SQLItemRepository } from '../Infra/TypeORM/SQLItemRepository'
 import { SendEventToClient } from '../Domain/UseCase/Syncing/SendEventToClient/SendEventToClient'
 import { SendEventToClient } from '../Domain/UseCase/Syncing/SendEventToClient/SendEventToClient'
 import { TransitionRequestedEventHandler } from '../Domain/Handler/TransitionRequestedEventHandler'
 import { TransitionRequestedEventHandler } from '../Domain/Handler/TransitionRequestedEventHandler'
 import { DeleteSharedVaults } from '../Domain/UseCase/SharedVaults/DeleteSharedVaults/DeleteSharedVaults'
 import { DeleteSharedVaults } from '../Domain/UseCase/SharedVaults/DeleteSharedVaults/DeleteSharedVaults'
+import { RemoveItemsFromSharedVault } from '../Domain/UseCase/SharedVaults/RemoveItemsFromSharedVault/RemoveItemsFromSharedVault'
+import { SharedVaultRemovedEventHandler } from '../Domain/Handler/SharedVaultRemovedEventHandler'
 
 
 export class ContainerConfigLoader {
 export class ContainerConfigLoader {
   private readonly DEFAULT_CONTENT_SIZE_TRANSFER_LIMIT = 10_000_000
   private readonly DEFAULT_CONTENT_SIZE_TRANSFER_LIMIT = 10_000_000
@@ -786,6 +788,8 @@ export class ContainerConfigLoader {
           container.get<SharedVaultInviteRepositoryInterface>(TYPES.Sync_SharedVaultInviteRepository),
           container.get<SharedVaultInviteRepositoryInterface>(TYPES.Sync_SharedVaultInviteRepository),
           container.get<RemoveUserFromSharedVault>(TYPES.Sync_RemoveSharedVaultUser),
           container.get<RemoveUserFromSharedVault>(TYPES.Sync_RemoveSharedVaultUser),
           container.get<DeclineInviteToSharedVault>(TYPES.Sync_DeclineInviteToSharedVault),
           container.get<DeclineInviteToSharedVault>(TYPES.Sync_DeclineInviteToSharedVault),
+          container.get<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory),
+          container.get<DomainEventPublisherInterface>(TYPES.Sync_DomainEventPublisher),
         ),
         ),
       )
       )
     container
     container
@@ -851,6 +855,15 @@ export class ContainerConfigLoader {
           env.get('MIGRATION_BATCH_SIZE', true) ? +env.get('MIGRATION_BATCH_SIZE', true) : 100,
           env.get('MIGRATION_BATCH_SIZE', true) ? +env.get('MIGRATION_BATCH_SIZE', true) : 100,
         ),
         ),
       )
       )
+    container
+      .bind<RemoveItemsFromSharedVault>(TYPES.Sync_RemoveItemsFromSharedVault)
+      .toConstantValue(
+        new RemoveItemsFromSharedVault(
+          isSecondaryDatabaseEnabled
+            ? container.get<ItemRepositoryInterface>(TYPES.Sync_MongoDBItemRepository)
+            : container.get<ItemRepositoryInterface>(TYPES.Sync_SQLItemRepository),
+        ),
+      )
 
 
     // Services
     // Services
     container
     container
@@ -966,6 +979,14 @@ export class ContainerConfigLoader {
           container.get<Logger>(TYPES.Sync_Logger),
           container.get<Logger>(TYPES.Sync_Logger),
         ),
         ),
       )
       )
+    container
+      .bind<SharedVaultRemovedEventHandler>(TYPES.Sync_SharedVaultRemovedEventHandler)
+      .toConstantValue(
+        new SharedVaultRemovedEventHandler(
+          container.get<RemoveItemsFromSharedVault>(TYPES.Sync_RemoveItemsFromSharedVault),
+          container.get<Logger>(TYPES.Sync_Logger),
+        ),
+      )
 
 
     // Services
     // Services
     container.bind<ContentDecoder>(TYPES.Sync_ContentDecoder).toDynamicValue(() => new ContentDecoder())
     container.bind<ContentDecoder>(TYPES.Sync_ContentDecoder).toDynamicValue(() => new ContentDecoder())
@@ -1004,6 +1025,10 @@ export class ContainerConfigLoader {
         'TRANSITION_REQUESTED',
         'TRANSITION_REQUESTED',
         container.get<TransitionRequestedEventHandler>(TYPES.Sync_TransitionRequestedEventHandler),
         container.get<TransitionRequestedEventHandler>(TYPES.Sync_TransitionRequestedEventHandler),
       ],
       ],
+      [
+        'SHARED_VAULT_REMOVED',
+        container.get<SharedVaultRemovedEventHandler>(TYPES.Sync_SharedVaultRemovedEventHandler),
+      ],
     ])
     ])
     if (!isConfiguredForHomeServer) {
     if (!isConfiguredForHomeServer) {
       container.bind(TYPES.Sync_AUTH_SERVER_URL).toConstantValue(env.get('AUTH_SERVER_URL'))
       container.bind(TYPES.Sync_AUTH_SERVER_URL).toConstantValue(env.get('AUTH_SERVER_URL'))

+ 2 - 0
packages/syncing-server/src/Bootstrap/Types.ts

@@ -86,6 +86,7 @@ const TYPES = {
     'Sync_TransitionItemsFromPrimaryToSecondaryDatabaseForUser',
     'Sync_TransitionItemsFromPrimaryToSecondaryDatabaseForUser',
   ),
   ),
   Sync_SendEventToClient: Symbol.for('Sync_SendEventToClient'),
   Sync_SendEventToClient: Symbol.for('Sync_SendEventToClient'),
+  Sync_RemoveItemsFromSharedVault: Symbol.for('Sync_RemoveItemsFromSharedVault'),
   // Handlers
   // Handlers
   Sync_AccountDeletionRequestedEventHandler: Symbol.for('Sync_AccountDeletionRequestedEventHandler'),
   Sync_AccountDeletionRequestedEventHandler: Symbol.for('Sync_AccountDeletionRequestedEventHandler'),
   Sync_DuplicateItemSyncedEventHandler: Symbol.for('Sync_DuplicateItemSyncedEventHandler'),
   Sync_DuplicateItemSyncedEventHandler: Symbol.for('Sync_DuplicateItemSyncedEventHandler'),
@@ -95,6 +96,7 @@ const TYPES = {
   Sync_SharedVaultFileUploadedEventHandler: Symbol.for('Sync_SharedVaultFileUploadedEventHandler'),
   Sync_SharedVaultFileUploadedEventHandler: Symbol.for('Sync_SharedVaultFileUploadedEventHandler'),
   Sync_SharedVaultFileMovedEventHandler: Symbol.for('Sync_SharedVaultFileMovedEventHandler'),
   Sync_SharedVaultFileMovedEventHandler: Symbol.for('Sync_SharedVaultFileMovedEventHandler'),
   Sync_TransitionRequestedEventHandler: Symbol.for('Sync_TransitionRequestedEventHandler'),
   Sync_TransitionRequestedEventHandler: Symbol.for('Sync_TransitionRequestedEventHandler'),
+  Sync_SharedVaultRemovedEventHandler: Symbol.for('Sync_SharedVaultRemovedEventHandler'),
   // Services
   // Services
   Sync_ContentDecoder: Symbol.for('Sync_ContentDecoder'),
   Sync_ContentDecoder: Symbol.for('Sync_ContentDecoder'),
   Sync_DomainEventPublisher: Symbol.for('Sync_DomainEventPublisher'),
   Sync_DomainEventPublisher: Symbol.for('Sync_DomainEventPublisher'),

+ 16 - 1
packages/syncing-server/src/Domain/Event/DomainEventFactory.ts

@@ -9,6 +9,7 @@ import {
   MessageSentToUserEvent,
   MessageSentToUserEvent,
   NotificationAddedForUserEvent,
   NotificationAddedForUserEvent,
   RevisionsCopyRequestedEvent,
   RevisionsCopyRequestedEvent,
+  SharedVaultRemovedEvent,
   TransitionStatusUpdatedEvent,
   TransitionStatusUpdatedEvent,
   UserAddedToSharedVaultEvent,
   UserAddedToSharedVaultEvent,
   UserInvitedToSharedVaultEvent,
   UserInvitedToSharedVaultEvent,
@@ -21,11 +22,25 @@ import { DomainEventFactoryInterface } from './DomainEventFactoryInterface'
 export class DomainEventFactory implements DomainEventFactoryInterface {
 export class DomainEventFactory implements DomainEventFactoryInterface {
   constructor(private timer: TimerInterface) {}
   constructor(private timer: TimerInterface) {}
 
 
+  createSharedVaultRemovedEvent(dto: { sharedVaultUuid: string }): SharedVaultRemovedEvent {
+    return {
+      type: 'SHARED_VAULT_REMOVED',
+      createdAt: this.timer.getUTCDate(),
+      meta: {
+        correlation: {
+          userIdentifier: dto.sharedVaultUuid,
+          userIdentifierType: 'shared-vault-uuid',
+        },
+        origin: DomainEventService.SyncingServer,
+      },
+      payload: dto,
+    }
+  }
+
   createItemRemovedFromSharedVaultEvent(dto: {
   createItemRemovedFromSharedVaultEvent(dto: {
     sharedVaultUuid: string
     sharedVaultUuid: string
     itemUuid: string
     itemUuid: string
     userUuid: string
     userUuid: string
-    roleNames: string[]
   }): ItemRemovedFromSharedVaultEvent {
   }): ItemRemovedFromSharedVaultEvent {
     return {
     return {
       type: 'ITEM_REMOVED_FROM_SHARED_VAULT',
       type: 'ITEM_REMOVED_FROM_SHARED_VAULT',

+ 2 - 1
packages/syncing-server/src/Domain/Event/DomainEventFactoryInterface.ts

@@ -7,6 +7,7 @@ import {
   MessageSentToUserEvent,
   MessageSentToUserEvent,
   NotificationAddedForUserEvent,
   NotificationAddedForUserEvent,
   RevisionsCopyRequestedEvent,
   RevisionsCopyRequestedEvent,
+  SharedVaultRemovedEvent,
   TransitionStatusUpdatedEvent,
   TransitionStatusUpdatedEvent,
   UserAddedToSharedVaultEvent,
   UserAddedToSharedVaultEvent,
   UserInvitedToSharedVaultEvent,
   UserInvitedToSharedVaultEvent,
@@ -99,6 +100,6 @@ export interface DomainEventFactoryInterface {
     sharedVaultUuid: string
     sharedVaultUuid: string
     itemUuid: string
     itemUuid: string
     userUuid: string
     userUuid: string
-    roleNames: string[]
   }): ItemRemovedFromSharedVaultEvent
   }): ItemRemovedFromSharedVaultEvent
+  createSharedVaultRemovedEvent(dto: { sharedVaultUuid: string }): SharedVaultRemovedEvent
 }
 }

+ 22 - 0
packages/syncing-server/src/Domain/Handler/SharedVaultRemovedEventHandler.ts

@@ -0,0 +1,22 @@
+import { DomainEventHandlerInterface, SharedVaultRemovedEvent } from '@standardnotes/domain-events'
+import { RemoveItemsFromSharedVault } from '../UseCase/SharedVaults/RemoveItemsFromSharedVault/RemoveItemsFromSharedVault'
+import { Logger } from 'winston'
+
+export class SharedVaultRemovedEventHandler implements DomainEventHandlerInterface {
+  constructor(
+    private removeItemsFromSharedVault: RemoveItemsFromSharedVault,
+    private logger: Logger,
+  ) {}
+
+  async handle(event: SharedVaultRemovedEvent): Promise<void> {
+    const result = await this.removeItemsFromSharedVault.execute({
+      sharedVaultUuid: event.payload.sharedVaultUuid,
+    })
+
+    if (result.isFailed()) {
+      this.logger.error(
+        `Failed to remove items from shared vault ${event.payload.sharedVaultUuid}: ${result.getError()}`,
+      )
+    }
+  }
+}

+ 1 - 0
packages/syncing-server/src/Domain/Item/ItemRepositoryInterface.ts

@@ -19,4 +19,5 @@ export interface ItemRepositoryInterface {
   save(item: Item): Promise<void>
   save(item: Item): Promise<void>
   markItemsAsDeleted(itemUuids: Array<string>, updatedAtTimestamp: number): Promise<void>
   markItemsAsDeleted(itemUuids: Array<string>, updatedAtTimestamp: number): Promise<void>
   updateContentSize(itemUuid: string, contentSize: number): Promise<void>
   updateContentSize(itemUuid: string, contentSize: number): Promise<void>
+  unassignFromSharedVault(sharedVaultUuid: Uuid): Promise<void>
 }
 }

+ 17 - 0
packages/syncing-server/src/Domain/UseCase/SharedVaults/DeleteSharedVault/DeleteSharedVault.spec.ts

@@ -1,4 +1,5 @@
 import { Uuid, Timestamps, Result, SharedVaultUserPermission, SharedVaultUser } from '@standardnotes/domain-core'
 import { Uuid, Timestamps, Result, SharedVaultUserPermission, SharedVaultUser } from '@standardnotes/domain-core'
+import { DomainEventInterface, DomainEventPublisherInterface } from '@standardnotes/domain-events'
 
 
 import { SharedVaultRepositoryInterface } from '../../../SharedVault/SharedVaultRepositoryInterface'
 import { SharedVaultRepositoryInterface } from '../../../SharedVault/SharedVaultRepositoryInterface'
 import { SharedVaultInviteRepositoryInterface } from '../../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
 import { SharedVaultInviteRepositoryInterface } from '../../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
@@ -8,6 +9,7 @@ import { SharedVault } from '../../../SharedVault/SharedVault'
 import { RemoveUserFromSharedVault } from '../RemoveUserFromSharedVault/RemoveUserFromSharedVault'
 import { RemoveUserFromSharedVault } from '../RemoveUserFromSharedVault/RemoveUserFromSharedVault'
 import { DeclineInviteToSharedVault } from '../DeclineInviteToSharedVault/DeclineInviteToSharedVault'
 import { DeclineInviteToSharedVault } from '../DeclineInviteToSharedVault/DeclineInviteToSharedVault'
 import { SharedVaultInvite } from '../../../SharedVault/User/Invite/SharedVaultInvite'
 import { SharedVaultInvite } from '../../../SharedVault/User/Invite/SharedVaultInvite'
+import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
 
 
 describe('DeleteSharedVault', () => {
 describe('DeleteSharedVault', () => {
   let sharedVaultRepository: SharedVaultRepositoryInterface
   let sharedVaultRepository: SharedVaultRepositoryInterface
@@ -18,6 +20,8 @@ describe('DeleteSharedVault', () => {
   let sharedVault: SharedVault
   let sharedVault: SharedVault
   let sharedVaultUser: SharedVaultUser
   let sharedVaultUser: SharedVaultUser
   let sharedVaultInvite: SharedVaultInvite
   let sharedVaultInvite: SharedVaultInvite
+  let domainEventFactory: DomainEventFactoryInterface
+  let domainEventPublisher: DomainEventPublisherInterface
 
 
   const createUseCase = () =>
   const createUseCase = () =>
     new DeleteSharedVault(
     new DeleteSharedVault(
@@ -26,6 +30,8 @@ describe('DeleteSharedVault', () => {
       sharedVaultInviteRepository,
       sharedVaultInviteRepository,
       removeUserFromSharedVault,
       removeUserFromSharedVault,
       declineInviteToSharedVault,
       declineInviteToSharedVault,
+      domainEventFactory,
+      domainEventPublisher,
     )
     )
 
 
   beforeEach(() => {
   beforeEach(() => {
@@ -63,6 +69,17 @@ describe('DeleteSharedVault', () => {
 
 
     removeUserFromSharedVault = {} as jest.Mocked<RemoveUserFromSharedVault>
     removeUserFromSharedVault = {} as jest.Mocked<RemoveUserFromSharedVault>
     removeUserFromSharedVault.execute = jest.fn().mockReturnValue(Result.ok())
     removeUserFromSharedVault.execute = jest.fn().mockReturnValue(Result.ok())
+
+    domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
+    domainEventFactory.createUserRemovedFromSharedVaultEvent = jest
+      .fn()
+      .mockReturnValue({} as jest.Mocked<DomainEventInterface>)
+    domainEventFactory.createSharedVaultRemovedEvent = jest
+      .fn()
+      .mockReturnValue({} as jest.Mocked<DomainEventInterface>)
+
+    domainEventPublisher = {} as jest.Mocked<DomainEventPublisherInterface>
+    domainEventPublisher.publish = jest.fn()
   })
   })
 
 
   it('should remove shared vault', async () => {
   it('should remove shared vault', async () => {

+ 10 - 0
packages/syncing-server/src/Domain/UseCase/SharedVaults/DeleteSharedVault/DeleteSharedVault.ts

@@ -1,4 +1,5 @@
 import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
 import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
+import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
 
 
 import { DeleteSharedVaultDTO } from './DeleteSharedVaultDTO'
 import { DeleteSharedVaultDTO } from './DeleteSharedVaultDTO'
 import { SharedVaultRepositoryInterface } from '../../../SharedVault/SharedVaultRepositoryInterface'
 import { SharedVaultRepositoryInterface } from '../../../SharedVault/SharedVaultRepositoryInterface'
@@ -6,6 +7,7 @@ import { SharedVaultUserRepositoryInterface } from '../../../SharedVault/User/Sh
 import { SharedVaultInviteRepositoryInterface } from '../../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
 import { SharedVaultInviteRepositoryInterface } from '../../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
 import { RemoveUserFromSharedVault } from '../RemoveUserFromSharedVault/RemoveUserFromSharedVault'
 import { RemoveUserFromSharedVault } from '../RemoveUserFromSharedVault/RemoveUserFromSharedVault'
 import { DeclineInviteToSharedVault } from '../DeclineInviteToSharedVault/DeclineInviteToSharedVault'
 import { DeclineInviteToSharedVault } from '../DeclineInviteToSharedVault/DeclineInviteToSharedVault'
+import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
 
 
 export class DeleteSharedVault implements UseCaseInterface<void> {
 export class DeleteSharedVault implements UseCaseInterface<void> {
   constructor(
   constructor(
@@ -14,6 +16,8 @@ export class DeleteSharedVault implements UseCaseInterface<void> {
     private sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface,
     private sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface,
     private removeUserFromSharedVault: RemoveUserFromSharedVault,
     private removeUserFromSharedVault: RemoveUserFromSharedVault,
     private declineInviteToSharedVault: DeclineInviteToSharedVault,
     private declineInviteToSharedVault: DeclineInviteToSharedVault,
+    private domainEventFactory: DomainEventFactoryInterface,
+    private domainEventPublisher: DomainEventPublisherInterface,
   ) {}
   ) {}
 
 
   async execute(dto: DeleteSharedVaultDTO): Promise<Result<void>> {
   async execute(dto: DeleteSharedVaultDTO): Promise<Result<void>> {
@@ -66,6 +70,12 @@ export class DeleteSharedVault implements UseCaseInterface<void> {
 
 
     await this.sharedVaultRepository.remove(sharedVault)
     await this.sharedVaultRepository.remove(sharedVault)
 
 
+    await this.domainEventPublisher.publish(
+      this.domainEventFactory.createSharedVaultRemovedEvent({
+        sharedVaultUuid: sharedVaultUuid.value,
+      }),
+    )
+
     return Result.ok()
     return Result.ok()
   }
   }
 }
 }

+ 33 - 0
packages/syncing-server/src/Domain/UseCase/SharedVaults/RemoveItemsFromSharedVault/RemoveItemsFromSharedVault.spec.ts

@@ -0,0 +1,33 @@
+import { ItemRepositoryInterface } from '../../../Item/ItemRepositoryInterface'
+import { RemoveItemsFromSharedVault } from './RemoveItemsFromSharedVault'
+
+describe('RemoveItemsFromSharedVault', () => {
+  let itemRepository: ItemRepositoryInterface
+
+  const createUseCase = () => new RemoveItemsFromSharedVault(itemRepository)
+
+  beforeEach(() => {
+    itemRepository = {} as jest.Mocked<ItemRepositoryInterface>
+    itemRepository.unassignFromSharedVault = jest.fn()
+  })
+
+  it('should unassign items from shared vault', async () => {
+    const useCase = createUseCase()
+
+    await useCase.execute({
+      sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
+    })
+
+    expect(itemRepository.unassignFromSharedVault).toHaveBeenCalled()
+  })
+
+  it('should return error when shared vault uuid is invalid', async () => {
+    const useCase = createUseCase()
+
+    const result = await useCase.execute({
+      sharedVaultUuid: 'invalid-uuid',
+    })
+
+    expect(result.isFailed()).toBe(true)
+  })
+})

+ 20 - 0
packages/syncing-server/src/Domain/UseCase/SharedVaults/RemoveItemsFromSharedVault/RemoveItemsFromSharedVault.ts

@@ -0,0 +1,20 @@
+import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
+
+import { ItemRepositoryInterface } from '../../../Item/ItemRepositoryInterface'
+import { RemoveItemsFromSharedVaultDTO } from './RemoveItemsFromSharedVaultDTO'
+
+export class RemoveItemsFromSharedVault implements UseCaseInterface<void> {
+  constructor(private itemRepository: ItemRepositoryInterface) {}
+
+  async execute(dto: RemoveItemsFromSharedVaultDTO): Promise<Result<void>> {
+    const sharedVaultUuidOrError = Uuid.create(dto.sharedVaultUuid)
+    if (sharedVaultUuidOrError.isFailed()) {
+      return Result.fail(sharedVaultUuidOrError.getError())
+    }
+    const sharedVaultUuid = sharedVaultUuidOrError.getValue()
+
+    await this.itemRepository.unassignFromSharedVault(sharedVaultUuid)
+
+    return Result.ok()
+  }
+}

+ 3 - 0
packages/syncing-server/src/Domain/UseCase/SharedVaults/RemoveItemsFromSharedVault/RemoveItemsFromSharedVaultDTO.ts

@@ -0,0 +1,3 @@
+export interface RemoveItemsFromSharedVaultDTO {
+  sharedVaultUuid: string
+}

+ 0 - 1
packages/syncing-server/src/Domain/UseCase/Syncing/UpdateExistingItem/UpdateExistingItem.ts

@@ -270,7 +270,6 @@ export class UpdateExistingItem implements UseCaseInterface<Item> {
           sharedVaultUuid: sharedVaultOperation.props.sharedVaultUuid.value,
           sharedVaultUuid: sharedVaultOperation.props.sharedVaultUuid.value,
           itemUuid: dto.existingItem.uuid.value,
           itemUuid: dto.existingItem.uuid.value,
           userUuid: userUuid.value,
           userUuid: userUuid.value,
-          roleNames: dto.roleNames,
         }),
         }),
       )
       )
     }
     }

+ 7 - 0
packages/syncing-server/src/Infra/TypeORM/MongoDBItemRepository.ts

@@ -17,6 +17,13 @@ export class MongoDBItemRepository implements ItemRepositoryInterface {
     private logger: Logger,
     private logger: Logger,
   ) {}
   ) {}
 
 
+  async unassignFromSharedVault(sharedVaultUuid: Uuid): Promise<void> {
+    await this.mongoRepository.updateMany(
+      { sharedVaultUuid: { $eq: sharedVaultUuid.value } },
+      { $set: { sharedVaultUuid: null } },
+    )
+  }
+
   async removeByUuid(uuid: Uuid): Promise<void> {
   async removeByUuid(uuid: Uuid): Promise<void> {
     await this.mongoRepository.deleteOne({ _id: { $eq: BSON.UUID.createFromHexString(uuid.value) } })
     await this.mongoRepository.deleteOne({ _id: { $eq: BSON.UUID.createFromHexString(uuid.value) } })
   }
   }

+ 14 - 1
packages/syncing-server/src/Infra/TypeORM/SQLItemRepository.ts

@@ -1,5 +1,5 @@
 import { Repository, SelectQueryBuilder } from 'typeorm'
 import { Repository, SelectQueryBuilder } from 'typeorm'
-import { MapperInterface } from '@standardnotes/domain-core'
+import { MapperInterface, Uuid } from '@standardnotes/domain-core'
 import { Logger } from 'winston'
 import { Logger } from 'winston'
 
 
 import { Item } from '../../Domain/Item/Item'
 import { Item } from '../../Domain/Item/Item'
@@ -16,6 +16,19 @@ export class SQLItemRepository extends SQLLegacyItemRepository {
     super(ormRepository, mapper, logger)
     super(ormRepository, mapper, logger)
   }
   }
 
 
+  override async unassignFromSharedVault(sharedVaultUuid: Uuid): Promise<void> {
+    await this.ormRepository
+      .createQueryBuilder('item')
+      .update()
+      .set({
+        sharedVaultUuid: null,
+      })
+      .where('shared_vault_uuid = :sharedVaultUuid', {
+        sharedVaultUuid: sharedVaultUuid.value,
+      })
+      .execute()
+  }
+
   protected override createFindAllQueryBuilder(query: ItemQuery): SelectQueryBuilder<SQLItem> {
   protected override createFindAllQueryBuilder(query: ItemQuery): SelectQueryBuilder<SQLItem> {
     const queryBuilder = this.ormRepository.createQueryBuilder('item')
     const queryBuilder = this.ormRepository.createQueryBuilder('item')
 
 

+ 4 - 0
packages/syncing-server/src/Infra/TypeORM/SQLLegacyItemRepository.ts

@@ -16,6 +16,10 @@ export class SQLLegacyItemRepository implements ItemRepositoryInterface {
     protected logger: Logger,
     protected logger: Logger,
   ) {}
   ) {}
 
 
+  async unassignFromSharedVault(_sharedVaultUuid: Uuid): Promise<void> {
+    this.logger.error('Method unassignFromSharedVault not supported.')
+  }
+
   async removeByUuid(uuid: Uuid): Promise<void> {
   async removeByUuid(uuid: Uuid): Promise<void> {
     await this.ormRepository
     await this.ormRepository
       .createQueryBuilder('item')
       .createQueryBuilder('item')