Explorar el Código

feat: refactor transition to minimize status changes (#828)

Karol Sójko hace 1 año
padre
commit
36f07c691a
Se han modificado 32 ficheros con 383 adiciones y 522 borrados
  1. 26 9
      packages/auth/bin/transition.ts
  2. 5 9
      packages/auth/src/Domain/Transition/TransitionStatusRepositoryInterface.ts
  3. 14 3
      packages/auth/src/Domain/UseCase/CreateCrossServiceToken/CreateCrossServiceToken.spec.ts
  4. 3 4
      packages/auth/src/Domain/UseCase/CreateCrossServiceToken/CreateCrossServiceToken.ts
  5. 40 7
      packages/auth/src/Domain/UseCase/UpdateTransitionStatus/UpdateTransitionStatus.spec.ts
  6. 13 4
      packages/auth/src/Domain/UseCase/UpdateTransitionStatus/UpdateTransitionStatus.ts
  7. 1 1
      packages/auth/src/Domain/UseCase/UpdateTransitionStatus/UpdateTransitionStatusDTO.ts
  8. 14 13
      packages/auth/src/Infra/InMemory/InMemoryTransitionStatusRepository.ts
  9. 18 21
      packages/auth/src/Infra/Redis/RedisTransitionStatusRepository.ts
  10. 28 0
      packages/domain-core/src/Domain/Transition/TransitionStatus.ts
  11. 3 0
      packages/domain-core/src/Domain/Transition/TransitionStatusProps.ts
  12. 3 0
      packages/domain-core/src/Domain/index.ts
  13. 1 1
      packages/domain-events/src/Domain/Event/TransitionStatusUpdatedEventPayload.ts
  14. 5 28
      packages/revisions/src/Bootstrap/Container.ts
  15. 0 4
      packages/revisions/src/Bootstrap/Types.ts
  16. 1 1
      packages/revisions/src/Domain/Event/DomainEventFactory.ts
  17. 1 1
      packages/revisions/src/Domain/Event/DomainEventFactoryInterface.ts
  18. 97 7
      packages/revisions/src/Domain/Handler/TransitionRequestedEventHandler.ts
  19. 0 147
      packages/revisions/src/Domain/Handler/TransitionStatusUpdatedEventHandler.ts
  20. 0 31
      packages/revisions/src/Domain/UseCase/Transition/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser.spec.ts
  21. 0 25
      packages/revisions/src/Domain/UseCase/Transition/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser.ts
  22. 0 4
      packages/revisions/src/Domain/UseCase/Transition/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser/TriggerTransitionFromPrimaryToSecondaryDatabaseForUserDTO.ts
  23. 2 28
      packages/syncing-server/src/Bootstrap/Container.ts
  24. 0 4
      packages/syncing-server/src/Bootstrap/Types.ts
  25. 1 1
      packages/syncing-server/src/Domain/Event/DomainEventFactory.ts
  26. 1 1
      packages/syncing-server/src/Domain/Event/DomainEventFactoryInterface.ts
  27. 95 5
      packages/syncing-server/src/Domain/Handler/TransitionRequestedEventHandler.ts
  28. 0 100
      packages/syncing-server/src/Domain/Handler/TransitionStatusUpdatedEventHandler.ts
  29. 0 31
      packages/syncing-server/src/Domain/UseCase/Transition/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser.spec.ts
  30. 0 25
      packages/syncing-server/src/Domain/UseCase/Transition/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser.ts
  31. 0 4
      packages/syncing-server/src/Domain/UseCase/Transition/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser/TriggerTransitionFromPrimaryToSecondaryDatabaseForUserDTO.ts
  32. 11 3
      packages/syncing-server/src/Infra/TypeORM/SQLLegacyItemRepository.ts

+ 26 - 9
packages/auth/bin/transition.ts

@@ -12,7 +12,7 @@ import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFact
 import { UserRepositoryInterface } from '../src/Domain/User/UserRepositoryInterface'
 import { TimerInterface } from '@standardnotes/time'
 import { TransitionStatusRepositoryInterface } from '../src/Domain/Transition/TransitionStatusRepositoryInterface'
-import { RoleName } from '@standardnotes/domain-core'
+import { RoleName, TransitionStatus } from '@standardnotes/domain-core'
 
 const inputArgs = process.argv.slice(2)
 const startDateString = inputArgs[0]
@@ -46,21 +46,38 @@ const requestTransition = async (
 
     const userHasTransitionRole = userRoles.some((role) => role.name === RoleName.NAMES.TransitionUser)
     const bothTransitionStatusesAreVerified =
-      itemsTransitionStatus === 'VERIFIED' && revisionsTransitionStatus === 'VERIFIED'
+      itemsTransitionStatus?.value === TransitionStatus.STATUSES.Verified &&
+      revisionsTransitionStatus?.value === TransitionStatus.STATUSES.Verified
 
     if (userHasTransitionRole && bothTransitionStatusesAreVerified) {
       continue
     }
 
-    const transitionRequestedEvent = domainEventFactory.createTransitionRequestedEvent({
-      userUuid: user.uuid,
-      type: 'items',
-      timestamp,
-    })
+    if (itemsTransitionStatus?.value !== TransitionStatus.STATUSES.Verified) {
+      await transitionStatusRepository.remove(user.uuid, 'items')
 
-    usersTriggered += 1
+      await domainEventPublisher.publish(
+        domainEventFactory.createTransitionRequestedEvent({
+          userUuid: user.uuid,
+          type: 'items',
+          timestamp,
+        }),
+      )
+    }
+
+    if (revisionsTransitionStatus?.value !== TransitionStatus.STATUSES.Verified) {
+      await transitionStatusRepository.remove(user.uuid, 'revisions')
 
-    await domainEventPublisher.publish(transitionRequestedEvent)
+      await domainEventPublisher.publish(
+        domainEventFactory.createTransitionRequestedEvent({
+          userUuid: user.uuid,
+          type: 'revisions',
+          timestamp,
+        }),
+      )
+    }
+
+    usersTriggered += 1
   }
 
   logger.info(

+ 5 - 9
packages/auth/src/Domain/Transition/TransitionStatusRepositoryInterface.ts

@@ -1,11 +1,7 @@
+import { TransitionStatus } from '@standardnotes/domain-core'
+
 export interface TransitionStatusRepositoryInterface {
-  updateStatus(
-    userUuid: string,
-    transitionType: 'items' | 'revisions',
-    status: 'STARTED' | 'IN_PROGRESS' | 'FINISHED' | 'FAILED' | 'VERIFIED',
-  ): Promise<void>
-  getStatus(
-    userUuid: string,
-    transitionType: 'items' | 'revisions',
-  ): Promise<'STARTED' | 'IN_PROGRESS' | 'FINISHED' | 'FAILED' | 'VERIFIED' | null>
+  updateStatus(userUuid: string, transitionType: 'items' | 'revisions', status: TransitionStatus): Promise<void>
+  getStatus(userUuid: string, transitionType: 'items' | 'revisions'): Promise<TransitionStatus | null>
+  remove(userUuid: string, transitionType: 'items' | 'revisions'): Promise<void>
 }

+ 14 - 3
packages/auth/src/Domain/UseCase/CreateCrossServiceToken/CreateCrossServiceToken.spec.ts

@@ -9,7 +9,14 @@ import { UserRepositoryInterface } from '../../User/UserRepositoryInterface'
 
 import { CreateCrossServiceToken } from './CreateCrossServiceToken'
 import { GetSetting } from '../GetSetting/GetSetting'
-import { Result, SharedVaultUser, SharedVaultUserPermission, Timestamps, Uuid } from '@standardnotes/domain-core'
+import {
+  Result,
+  SharedVaultUser,
+  SharedVaultUserPermission,
+  Timestamps,
+  TransitionStatus,
+  Uuid,
+} from '@standardnotes/domain-core'
 import { TransitionStatusRepositoryInterface } from '../../Transition/TransitionStatusRepositoryInterface'
 import { SharedVaultUserRepositoryInterface } from '../../SharedVault/SharedVaultUserRepositoryInterface'
 
@@ -72,7 +79,9 @@ describe('CreateCrossServiceToken', () => {
     getSettingUseCase.execute = jest.fn().mockReturnValue(Result.ok({ setting: { value: '100' } }))
 
     transitionStatusRepository = {} as jest.Mocked<TransitionStatusRepositoryInterface>
-    transitionStatusRepository.getStatus = jest.fn().mockReturnValue('TO-DO')
+    transitionStatusRepository.getStatus = jest
+      .fn()
+      .mockReturnValue(TransitionStatus.create(TransitionStatus.STATUSES.Verified).getValue())
 
     sharedVaultUserRepository = {} as jest.Mocked<SharedVaultUserRepositoryInterface>
     sharedVaultUserRepository.findByUserUuid = jest.fn().mockReturnValue([
@@ -120,7 +129,9 @@ describe('CreateCrossServiceToken', () => {
   })
 
   it('should create a cross service token for user that has an ongoing transaction', async () => {
-    transitionStatusRepository.getStatus = jest.fn().mockReturnValue('IN_PROGRESS')
+    transitionStatusRepository.getStatus = jest
+      .fn()
+      .mockReturnValue(TransitionStatus.create(TransitionStatus.STATUSES.InProgress).getValue())
 
     await createUseCase().execute({
       user,

+ 3 - 4
packages/auth/src/Domain/UseCase/CreateCrossServiceToken/CreateCrossServiceToken.ts

@@ -1,6 +1,6 @@
 import { TokenEncoderInterface, CrossServiceTokenData } from '@standardnotes/security'
 import { inject, injectable } from 'inversify'
-import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
+import { Result, TransitionStatus, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
 
 import TYPES from '../../../Bootstrap/Types'
 import { ProjectorInterface } from '../../../Projection/ProjectorInterface'
@@ -60,9 +60,8 @@ export class CreateCrossServiceToken implements UseCaseInterface<string> {
       user: this.projectUser(user),
       roles: this.projectRoles(roles),
       shared_vault_owner_context: undefined,
-      ongoing_transition: transitionStatus === 'IN_PROGRESS',
-      ongoing_revisions_transition:
-        revisionsTransitionStatus === 'STARTED' || revisionsTransitionStatus === 'IN_PROGRESS',
+      ongoing_transition: transitionStatus?.value === TransitionStatus.STATUSES.InProgress,
+      ongoing_revisions_transition: revisionsTransitionStatus?.value === TransitionStatus.STATUSES.InProgress,
       belongs_to_shared_vaults: sharedVaultAssociations.map((association) => ({
         shared_vault_uuid: association.props.sharedVaultUuid.value,
         permission: association.props.permission.value,

+ 40 - 7
packages/auth/src/Domain/UseCase/UpdateTransitionStatus/UpdateTransitionStatus.spec.ts

@@ -1,4 +1,4 @@
-import { RoleName, Uuid } from '@standardnotes/domain-core'
+import { RoleName, TransitionStatus, Uuid } from '@standardnotes/domain-core'
 
 import { RoleServiceInterface } from '../../Role/RoleServiceInterface'
 import { TransitionStatusRepositoryInterface } from '../../Transition/TransitionStatusRepositoryInterface'
@@ -18,6 +18,7 @@ describe('UpdateTransitionStatus', () => {
 
     transitionStatusRepository = {} as jest.Mocked<TransitionStatusRepositoryInterface>
     transitionStatusRepository.updateStatus = jest.fn()
+    transitionStatusRepository.getStatus = jest.fn().mockResolvedValue(null)
 
     roleService = {} as jest.Mocked<RoleServiceInterface>
     roleService.addRoleToUser = jest.fn()
@@ -45,17 +46,13 @@ describe('UpdateTransitionStatus', () => {
 
     const result = await useCase.execute({
       userUuid: '00000000-0000-0000-0000-000000000000',
-      status: 'STARTED',
+      status: TransitionStatus.STATUSES.InProgress,
       transitionType: 'items',
       transitionTimestamp: 123,
     })
 
     expect(result.isFailed()).toBeFalsy()
-    expect(transitionStatusRepository.updateStatus).toHaveBeenCalledWith(
-      '00000000-0000-0000-0000-000000000000',
-      'items',
-      'STARTED',
-    )
+    expect(transitionStatusRepository.updateStatus).toHaveBeenCalled()
   })
 
   it('should return error when user uuid is invalid', async () => {
@@ -71,4 +68,40 @@ describe('UpdateTransitionStatus', () => {
     expect(result.isFailed()).toBeTruthy()
     expect(result.getError()).toEqual('Given value is not a valid uuid: invalid')
   })
+
+  it('should not update status if transition is already verified', async () => {
+    transitionStatusRepository.getStatus = jest
+      .fn()
+      .mockResolvedValue(TransitionStatus.create(TransitionStatus.STATUSES.Verified).getValue())
+
+    const useCase = createUseCase()
+
+    const result = await useCase.execute({
+      userUuid: '00000000-0000-0000-0000-000000000000',
+      status: TransitionStatus.STATUSES.InProgress,
+      transitionType: 'items',
+      transitionTimestamp: 123,
+    })
+
+    expect(result.isFailed()).toBeFalsy()
+    expect(transitionStatusRepository.updateStatus).not.toHaveBeenCalled()
+  })
+
+  it('should not update status if transition is already failed', async () => {
+    transitionStatusRepository.getStatus = jest
+      .fn()
+      .mockResolvedValue(TransitionStatus.create(TransitionStatus.STATUSES.Failed).getValue())
+
+    const useCase = createUseCase()
+
+    const result = await useCase.execute({
+      userUuid: '00000000-0000-0000-0000-000000000000',
+      status: TransitionStatus.STATUSES.InProgress,
+      transitionType: 'items',
+      transitionTimestamp: 123,
+    })
+
+    expect(result.isFailed()).toBeFalsy()
+    expect(transitionStatusRepository.updateStatus).not.toHaveBeenCalled()
+  })
 })

+ 13 - 4
packages/auth/src/Domain/UseCase/UpdateTransitionStatus/UpdateTransitionStatus.ts

@@ -1,4 +1,4 @@
-import { Result, RoleName, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
+import { Result, RoleName, TransitionStatus, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
 import { TransitionStatusRepositoryInterface } from '../../Transition/TransitionStatusRepositoryInterface'
 import { UpdateTransitionStatusDTO } from './UpdateTransitionStatusDTO'
 import { RoleServiceInterface } from '../../Role/RoleServiceInterface'
@@ -18,11 +18,20 @@ export class UpdateTransitionStatus implements UseCaseInterface<void> {
     }
     const userUuid = userUuidOrError.getValue()
 
-    this.logger.info(`Received transition status updated event to ${dto.status} for user ${dto.userUuid}`)
+    const currentStatus = await this.transitionStatusRepository.getStatus(dto.userUuid, dto.transitionType)
+    if (
+      [TransitionStatus.STATUSES.Verified, TransitionStatus.STATUSES.Failed].includes(currentStatus?.value as string)
+    ) {
+      this.logger.info(`User ${dto.userUuid} transition already finished.`)
 
-    await this.transitionStatusRepository.updateStatus(dto.userUuid, dto.transitionType, dto.status)
+      return Result.ok()
+    }
+
+    const transitionStatus = TransitionStatus.create(dto.status).getValue()
+
+    await this.transitionStatusRepository.updateStatus(dto.userUuid, dto.transitionType, transitionStatus)
 
-    if (dto.transitionType === 'items' && dto.status === 'VERIFIED') {
+    if (dto.transitionType === 'items' && transitionStatus.value === TransitionStatus.STATUSES.Verified) {
       await this.roleService.addRoleToUser(userUuid, RoleName.create(RoleName.NAMES.TransitionUser).getValue())
     }
 

+ 1 - 1
packages/auth/src/Domain/UseCase/UpdateTransitionStatus/UpdateTransitionStatusDTO.ts

@@ -2,5 +2,5 @@ export interface UpdateTransitionStatusDTO {
   userUuid: string
   transitionType: 'items' | 'revisions'
   transitionTimestamp: number
-  status: 'STARTED' | 'IN_PROGRESS' | 'FINISHED' | 'FAILED' | 'VERIFIED'
+  status: string
 }

+ 14 - 13
packages/auth/src/Infra/InMemory/InMemoryTransitionStatusRepository.ts

@@ -1,22 +1,19 @@
+import { TransitionStatus } from '@standardnotes/domain-core'
 import { TransitionStatusRepositoryInterface } from '../../Domain/Transition/TransitionStatusRepositoryInterface'
 
 export class InMemoryTransitionStatusRepository implements TransitionStatusRepositoryInterface {
-  private itemStatuses: Map<string, 'STARTED' | 'FAILED'> = new Map()
-  private revisionStatuses: Map<string, 'STARTED' | 'FAILED'> = new Map()
+  private itemStatuses: Map<string, string> = new Map()
+  private revisionStatuses: Map<string, string> = new Map()
 
-  async updateStatus(
-    userUuid: string,
-    transitionType: 'items' | 'revisions',
-    status: 'STARTED' | 'FAILED',
-  ): Promise<void> {
+  async updateStatus(userUuid: string, transitionType: 'items' | 'revisions', status: TransitionStatus): Promise<void> {
     if (transitionType === 'items') {
-      this.itemStatuses.set(userUuid, status)
+      this.itemStatuses.set(userUuid, status.value)
     } else {
-      this.revisionStatuses.set(userUuid, status)
+      this.revisionStatuses.set(userUuid, status.value)
     }
   }
 
-  async removeStatus(userUuid: string, transitionType: 'items' | 'revisions'): Promise<void> {
+  async remove(userUuid: string, transitionType: 'items' | 'revisions'): Promise<void> {
     if (transitionType === 'items') {
       this.itemStatuses.delete(userUuid)
     } else {
@@ -24,8 +21,8 @@ export class InMemoryTransitionStatusRepository implements TransitionStatusRepos
     }
   }
 
-  async getStatus(userUuid: string, transitionType: 'items' | 'revisions'): Promise<'STARTED' | 'FAILED' | null> {
-    let status: 'STARTED' | 'FAILED' | null = null
+  async getStatus(userUuid: string, transitionType: 'items' | 'revisions'): Promise<TransitionStatus | null> {
+    let status: string | null
 
     if (transitionType === 'items') {
       status = this.itemStatuses.get(userUuid) ?? null
@@ -33,6 +30,10 @@ export class InMemoryTransitionStatusRepository implements TransitionStatusRepos
       status = this.revisionStatuses.get(userUuid) ?? null
     }
 
-    return status
+    if (status === null) {
+      return null
+    }
+
+    return TransitionStatus.create(status).getValue()
   }
 }

+ 18 - 21
packages/auth/src/Infra/Redis/RedisTransitionStatusRepository.ts

@@ -1,41 +1,38 @@
 import * as IORedis from 'ioredis'
 
 import { TransitionStatusRepositoryInterface } from '../../Domain/Transition/TransitionStatusRepositoryInterface'
+import { TransitionStatus } from '@standardnotes/domain-core'
 
 export class RedisTransitionStatusRepository implements TransitionStatusRepositoryInterface {
   private readonly PREFIX = 'transition'
 
   constructor(private redisClient: IORedis.Redis) {}
 
-  async updateStatus(
-    userUuid: string,
-    transitionType: 'items' | 'revisions',
-    status: 'STARTED' | 'IN_PROGRESS' | 'FINISHED' | 'FAILED' | 'VERIFIED',
-  ): Promise<void> {
-    switch (status) {
-      case 'FAILED':
-      case 'VERIFIED':
-        await this.redisClient.set(`${this.PREFIX}:${transitionType}:${userUuid}`, status)
+  async remove(userUuid: string, transitionType: 'items' | 'revisions'): Promise<void> {
+    await this.redisClient.del(`${this.PREFIX}:${transitionType}:${userUuid}`)
+  }
+
+  async updateStatus(userUuid: string, transitionType: 'items' | 'revisions', status: TransitionStatus): Promise<void> {
+    switch (status.value) {
+      case TransitionStatus.STATUSES.Failed:
+      case TransitionStatus.STATUSES.Verified:
+        await this.redisClient.set(`${this.PREFIX}:${transitionType}:${userUuid}`, status.value)
         break
-      case 'IN_PROGRESS': {
+      case TransitionStatus.STATUSES.InProgress: {
         const ttl2Hourse = 7_200
-        await this.redisClient.setex(`${this.PREFIX}:${transitionType}:${userUuid}`, ttl2Hourse, status)
-        break
-      }
-      default: {
-        const ttl10Hours = 36_000
-        await this.redisClient.setex(`${this.PREFIX}:${transitionType}:${userUuid}`, ttl10Hours, status)
+        await this.redisClient.setex(`${this.PREFIX}:${transitionType}:${userUuid}`, ttl2Hourse, status.value)
         break
       }
     }
   }
 
-  async getStatus(
-    userUuid: string,
-    transitionType: 'items' | 'revisions',
-  ): Promise<'STARTED' | 'IN_PROGRESS' | 'FINISHED' | 'FAILED' | 'VERIFIED' | null> {
+  async getStatus(userUuid: string, transitionType: 'items' | 'revisions'): Promise<TransitionStatus | null> {
     const status = await this.redisClient.get(`${this.PREFIX}:${transitionType}:${userUuid}`)
 
-    return status as 'STARTED' | 'IN_PROGRESS' | 'FINISHED' | 'FAILED' | 'VERIFIED' | null
+    if (status === null) {
+      return null
+    }
+
+    return TransitionStatus.create(status).getValue()
   }
 }

+ 28 - 0
packages/domain-core/src/Domain/Transition/TransitionStatus.ts

@@ -0,0 +1,28 @@
+import { Result } from '../Core/Result'
+import { ValueObject } from '../Core/ValueObject'
+import { TransitionStatusProps } from './TransitionStatusProps'
+
+export class TransitionStatus extends ValueObject<TransitionStatusProps> {
+  static readonly STATUSES = {
+    InProgress: 'IN_PROGRESS',
+    Failed: 'FAILED',
+    Verified: 'VERIFIED',
+  }
+
+  get value(): string {
+    return this.props.value
+  }
+
+  private constructor(props: TransitionStatusProps) {
+    super(props)
+  }
+
+  static create(name: string): Result<TransitionStatus> {
+    const isValidName = Object.values(this.STATUSES).includes(name)
+    if (!isValidName) {
+      return Result.fail<TransitionStatus>('Invalid transition status name.')
+    } else {
+      return Result.ok<TransitionStatus>(new TransitionStatus({ value: name }))
+    }
+  }
+}

+ 3 - 0
packages/domain-core/src/Domain/Transition/TransitionStatusProps.ts

@@ -0,0 +1,3 @@
+export interface TransitionStatusProps {
+  value: string
+}

+ 3 - 0
packages/domain-core/src/Domain/index.ts

@@ -67,5 +67,8 @@ export * from './SharedVault/SharedVaultUserProps'
 export * from './Subscription/SubscriptionPlanName'
 export * from './Subscription/SubscriptionPlanNameProps'
 
+export * from './Transition/TransitionStatus'
+export * from './Transition/TransitionStatusProps'
+
 export * from './UseCase/SyncUseCaseInterface'
 export * from './UseCase/UseCaseInterface'

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

@@ -2,5 +2,5 @@ export interface TransitionStatusUpdatedEventPayload {
   userUuid: string
   transitionType: 'items' | 'revisions'
   transitionTimestamp: number
-  status: 'STARTED' | 'IN_PROGRESS' | 'FINISHED' | 'FAILED' | 'VERIFIED'
+  status: string
 }

+ 5 - 28
packages/revisions/src/Bootstrap/Container.ts

@@ -60,8 +60,6 @@ import { RevisionHttpRepresentation } from '../Mapping/Http/RevisionHttpRepresen
 import { TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser } from '../Domain/UseCase/Transition/TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser/TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser'
 import { DomainEventFactoryInterface } from '../Domain/Event/DomainEventFactoryInterface'
 import { DomainEventFactory } from '../Domain/Event/DomainEventFactory'
-import { TransitionStatusUpdatedEventHandler } from '../Domain/Handler/TransitionStatusUpdatedEventHandler'
-import { TriggerTransitionFromPrimaryToSecondaryDatabaseForUser } from '../Domain/UseCase/Transition/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser'
 import { SQLRevision } from '../Infra/TypeORM/SQL/SQLRevision'
 import { SQLRevisionRepository } from '../Infra/TypeORM/SQL/SQLRevisionRepository'
 import { SQLRevisionMetadataPersistenceMapper } from '../Mapping/Persistence/SQL/SQLRevisionMetadataPersistenceMapper'
@@ -354,16 +352,6 @@ export class ContainerConfigLoader {
           env.get('MIGRATION_BATCH_SIZE', true) ? +env.get('MIGRATION_BATCH_SIZE', true) : 100,
         ),
       )
-    container
-      .bind<TriggerTransitionFromPrimaryToSecondaryDatabaseForUser>(
-        TYPES.Revisions_TriggerTransitionFromPrimaryToSecondaryDatabaseForUser,
-      )
-      .toConstantValue(
-        new TriggerTransitionFromPrimaryToSecondaryDatabaseForUser(
-          container.get<DomainEventPublisherInterface>(TYPES.Revisions_DomainEventPublisher),
-          container.get<DomainEventFactoryInterface>(TYPES.Revisions_DomainEventFactory),
-        ),
-      )
     container
       .bind<RemoveRevisionsFromSharedVault>(TYPES.Revisions_RemoveRevisionsFromSharedVault)
       .toConstantValue(
@@ -439,19 +427,6 @@ export class ContainerConfigLoader {
           container.get<winston.Logger>(TYPES.Revisions_Logger),
         ),
       )
-    container
-      .bind<TransitionStatusUpdatedEventHandler>(TYPES.Revisions_TransitionStatusUpdatedEventHandler)
-      .toConstantValue(
-        new TransitionStatusUpdatedEventHandler(
-          container.get<RevisionRepositoryInterface>(TYPES.Revisions_SQLRevisionRepository),
-          container.get<TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser>(
-            TYPES.Revisions_TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser,
-          ),
-          container.get<DomainEventPublisherInterface>(TYPES.Revisions_DomainEventPublisher),
-          container.get<DomainEventFactoryInterface>(TYPES.Revisions_DomainEventFactory),
-          container.get<winston.Logger>(TYPES.Revisions_Logger),
-        ),
-      )
     container
       .bind<ItemRemovedFromSharedVaultEventHandler>(TYPES.Revisions_ItemRemovedFromSharedVaultEventHandler)
       .toConstantValue(
@@ -464,9 +439,12 @@ export class ContainerConfigLoader {
       .bind<TransitionRequestedEventHandler>(TYPES.Revisions_TransitionRequestedEventHandler)
       .toConstantValue(
         new TransitionRequestedEventHandler(
-          container.get<TriggerTransitionFromPrimaryToSecondaryDatabaseForUser>(
-            TYPES.Revisions_TriggerTransitionFromPrimaryToSecondaryDatabaseForUser,
+          container.get<TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser>(
+            TYPES.Revisions_TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser,
           ),
+          container.get<RevisionRepositoryInterface>(TYPES.Revisions_SQLRevisionRepository),
+          container.get<DomainEventPublisherInterface>(TYPES.Revisions_DomainEventPublisher),
+          container.get<DomainEventFactoryInterface>(TYPES.Revisions_DomainEventFactory),
           container.get<winston.Logger>(TYPES.Revisions_Logger),
         ),
       )
@@ -475,7 +453,6 @@ export class ContainerConfigLoader {
       ['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)],
       ['TRANSITION_REQUESTED', container.get(TYPES.Revisions_TransitionRequestedEventHandler)],
     ])

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

@@ -46,9 +46,6 @@ const TYPES = {
   Revisions_TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser: Symbol.for(
     'Revisions_TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser',
   ),
-  Revisions_TriggerTransitionFromPrimaryToSecondaryDatabaseForUser: Symbol.for(
-    'Revisions_TriggerTransitionFromPrimaryToSecondaryDatabaseForUser',
-  ),
   Revisions_RemoveRevisionsFromSharedVault: Symbol.for('Revisions_RemoveRevisionsFromSharedVault'),
   // Controller
   Revisions_ControllerContainer: Symbol.for('Revisions_ControllerContainer'),
@@ -58,7 +55,6 @@ const TYPES = {
   Revisions_ItemDumpedEventHandler: Symbol.for('Revisions_ItemDumpedEventHandler'),
   Revisions_AccountDeletionRequestedEventHandler: Symbol.for('Revisions_AccountDeletionRequestedEventHandler'),
   Revisions_RevisionsCopyRequestedEventHandler: Symbol.for('Revisions_RevisionsCopyRequestedEventHandler'),
-  Revisions_TransitionStatusUpdatedEventHandler: Symbol.for('Revisions_TransitionStatusUpdatedEventHandler'),
   Revisions_ItemRemovedFromSharedVaultEventHandler: Symbol.for('Revisions_ItemRemovedFromSharedVaultEventHandler'),
   Revisions_TransitionRequestedEventHandler: Symbol.for('Revisions_TransitionRequestedEventHandler'),
   // Services

+ 1 - 1
packages/revisions/src/Domain/Event/DomainEventFactory.ts

@@ -10,7 +10,7 @@ export class DomainEventFactory implements DomainEventFactoryInterface {
     userUuid: string
     transitionType: 'items' | 'revisions'
     transitionTimestamp: number
-    status: 'STARTED' | 'IN_PROGRESS' | 'FAILED' | 'FINISHED' | 'VERIFIED'
+    status: string
   }): TransitionStatusUpdatedEvent {
     return {
       type: 'TRANSITION_STATUS_UPDATED',

+ 1 - 1
packages/revisions/src/Domain/Event/DomainEventFactoryInterface.ts

@@ -5,6 +5,6 @@ export interface DomainEventFactoryInterface {
     userUuid: string
     transitionType: 'items' | 'revisions'
     transitionTimestamp: number
-    status: 'STARTED' | 'IN_PROGRESS' | 'FAILED' | 'FINISHED' | 'VERIFIED'
+    status: string
   }): TransitionStatusUpdatedEvent
 }

+ 97 - 7
packages/revisions/src/Domain/Handler/TransitionRequestedEventHandler.ts

@@ -1,11 +1,20 @@
-import { DomainEventHandlerInterface, TransitionRequestedEvent } from '@standardnotes/domain-events'
+import {
+  DomainEventHandlerInterface,
+  DomainEventPublisherInterface,
+  TransitionRequestedEvent,
+} from '@standardnotes/domain-events'
 import { Logger } from 'winston'
-
-import { TriggerTransitionFromPrimaryToSecondaryDatabaseForUser } from '../UseCase/Transition/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser'
+import { TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser } from '../UseCase/Transition/TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser/TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser'
+import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
+import { RevisionRepositoryInterface } from '../Revision/RevisionRepositoryInterface'
+import { TransitionStatus, Uuid } from '@standardnotes/domain-core'
 
 export class TransitionRequestedEventHandler implements DomainEventHandlerInterface {
   constructor(
-    private triggerTransitionFromPrimaryToSecondaryDatabaseForUser: TriggerTransitionFromPrimaryToSecondaryDatabaseForUser,
+    private transitionRevisionsFromPrimaryToSecondaryDatabaseForUser: TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser,
+    private primaryRevisionsRepository: RevisionRepositoryInterface,
+    private domainEventPublisher: DomainEventPublisherInterface,
+    private domainEventFactory: DomainEventFactoryInterface,
     private logger: Logger,
   ) {}
 
@@ -14,15 +23,96 @@ export class TransitionRequestedEventHandler implements DomainEventHandlerInterf
       return
     }
 
+    const userUuid = await this.getUserUuidFromEvent(event)
+    if (!userUuid) {
+      return
+    }
+
+    if (await this.isAlreadyMigrated(userUuid)) {
+      this.logger.info(`User ${event.payload.userUuid} already migrated.`)
+
+      await this.domainEventPublisher.publish(
+        this.domainEventFactory.createTransitionStatusUpdatedEvent({
+          userUuid: event.payload.userUuid,
+          status: TransitionStatus.STATUSES.Verified,
+          transitionType: 'revisions',
+          transitionTimestamp: event.payload.timestamp,
+        }),
+      )
+
+      return
+    }
+
     this.logger.info(`Handling transition requested event for user ${event.payload.userUuid}`)
 
-    const result = await this.triggerTransitionFromPrimaryToSecondaryDatabaseForUser.execute({
+    await this.domainEventPublisher.publish(
+      this.domainEventFactory.createTransitionStatusUpdatedEvent({
+        userUuid: event.payload.userUuid,
+        status: TransitionStatus.STATUSES.InProgress,
+        transitionType: 'revisions',
+        transitionTimestamp: event.payload.timestamp,
+      }),
+    )
+
+    const result = await this.transitionRevisionsFromPrimaryToSecondaryDatabaseForUser.execute({
       userUuid: event.payload.userUuid,
-      transitionTimestamp: event.payload.timestamp,
     })
 
     if (result.isFailed()) {
-      this.logger.error(`Failed to trigger transition for user ${event.payload.userUuid}`)
+      this.logger.error(`Failed to transition for user ${event.payload.userUuid}`)
+
+      await this.domainEventPublisher.publish(
+        this.domainEventFactory.createTransitionStatusUpdatedEvent({
+          userUuid: event.payload.userUuid,
+          status: TransitionStatus.STATUSES.Failed,
+          transitionType: 'revisions',
+          transitionTimestamp: event.payload.timestamp,
+        }),
+      )
+
+      return
+    }
+
+    await this.domainEventPublisher.publish(
+      this.domainEventFactory.createTransitionStatusUpdatedEvent({
+        userUuid: event.payload.userUuid,
+        status: TransitionStatus.STATUSES.Verified,
+        transitionType: 'revisions',
+        transitionTimestamp: event.payload.timestamp,
+      }),
+    )
+  }
+
+  private async isAlreadyMigrated(userUuid: Uuid): Promise<boolean> {
+    const totalRevisionsCountForUserInPrimary = await this.primaryRevisionsRepository.countByUserUuid(userUuid)
+
+    if (totalRevisionsCountForUserInPrimary > 0) {
+      this.logger.info(
+        `User ${userUuid.value} has ${totalRevisionsCountForUserInPrimary} revisions in primary database.`,
+      )
     }
+
+    return totalRevisionsCountForUserInPrimary === 0
+  }
+
+  private async getUserUuidFromEvent(event: TransitionRequestedEvent): Promise<Uuid | null> {
+    const userUuidOrError = Uuid.create(event.payload.userUuid)
+    if (userUuidOrError.isFailed()) {
+      this.logger.error(
+        `Failed to transition revisions for user ${event.payload.userUuid}: ${userUuidOrError.getError()}`,
+      )
+      await this.domainEventPublisher.publish(
+        this.domainEventFactory.createTransitionStatusUpdatedEvent({
+          userUuid: event.payload.userUuid,
+          status: TransitionStatus.STATUSES.Failed,
+          transitionType: 'revisions',
+          transitionTimestamp: event.payload.timestamp,
+        }),
+      )
+
+      return null
+    }
+
+    return userUuidOrError.getValue()
   }
 }

+ 0 - 147
packages/revisions/src/Domain/Handler/TransitionStatusUpdatedEventHandler.ts

@@ -1,147 +0,0 @@
-import {
-  DomainEventHandlerInterface,
-  DomainEventPublisherInterface,
-  TransitionStatusUpdatedEvent,
-} from '@standardnotes/domain-events'
-import { Logger } from 'winston'
-import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
-import { TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser } from '../UseCase/Transition/TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser/TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser'
-import { Uuid } from '@standardnotes/domain-core'
-import { RevisionRepositoryInterface } from '../Revision/RevisionRepositoryInterface'
-
-export class TransitionStatusUpdatedEventHandler implements DomainEventHandlerInterface {
-  constructor(
-    private primaryRevisionsRepository: RevisionRepositoryInterface,
-    private transitionRevisionsFromPrimaryToSecondaryDatabaseForUser: TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser,
-    private domainEventPublisher: DomainEventPublisherInterface,
-    private domainEventFactory: DomainEventFactoryInterface,
-    private logger: Logger,
-  ) {}
-
-  async handle(event: TransitionStatusUpdatedEvent): Promise<void> {
-    if (event.payload.status === 'STARTED' && event.payload.transitionType === 'items') {
-      await this.domainEventPublisher.publish(
-        this.domainEventFactory.createTransitionStatusUpdatedEvent({
-          userUuid: event.payload.userUuid,
-          status: 'STARTED',
-          transitionType: 'revisions',
-          transitionTimestamp: event.payload.transitionTimestamp,
-        }),
-      )
-
-      return
-    }
-
-    this.logger.info(
-      `Received transition status updated event to ${event.payload.status} for user ${event.payload.userUuid}`,
-    )
-
-    if (event.payload.status === 'STARTED' && event.payload.transitionType === 'revisions') {
-      const userUuid = await this.getUserUuidFromEvent(event)
-      if (userUuid === null) {
-        return
-      }
-
-      if (await this.isAlreadyMigrated(userUuid)) {
-        await this.domainEventPublisher.publish(
-          this.domainEventFactory.createTransitionStatusUpdatedEvent({
-            userUuid: event.payload.userUuid,
-            status: 'FINISHED',
-            transitionType: 'revisions',
-            transitionTimestamp: event.payload.transitionTimestamp,
-          }),
-        )
-
-        return
-      }
-
-      await this.domainEventPublisher.publish(
-        this.domainEventFactory.createTransitionStatusUpdatedEvent({
-          userUuid: event.payload.userUuid,
-          status: 'IN_PROGRESS',
-          transitionType: 'revisions',
-          transitionTimestamp: event.payload.transitionTimestamp,
-        }),
-      )
-
-      const result = await this.transitionRevisionsFromPrimaryToSecondaryDatabaseForUser.execute({
-        userUuid: event.payload.userUuid,
-      })
-
-      if (result.isFailed()) {
-        this.logger.error(`Failed to transition revisions for user ${event.payload.userUuid}: ${result.getError()}`)
-
-        await this.domainEventPublisher.publish(
-          this.domainEventFactory.createTransitionStatusUpdatedEvent({
-            userUuid: event.payload.userUuid,
-            status: 'FAILED',
-            transitionType: 'revisions',
-            transitionTimestamp: event.payload.transitionTimestamp,
-          }),
-        )
-
-        return
-      }
-
-      await this.domainEventPublisher.publish(
-        this.domainEventFactory.createTransitionStatusUpdatedEvent({
-          userUuid: event.payload.userUuid,
-          status: 'FINISHED',
-          transitionType: 'revisions',
-          transitionTimestamp: event.payload.transitionTimestamp,
-        }),
-      )
-
-      return
-    }
-
-    if (event.payload.status === 'FINISHED' && event.payload.transitionType === 'revisions') {
-      const userUuid = await this.getUserUuidFromEvent(event)
-      if (userUuid === null) {
-        return
-      }
-
-      if (await this.isAlreadyMigrated(userUuid)) {
-        this.domainEventFactory.createTransitionStatusUpdatedEvent({
-          userUuid: event.payload.userUuid,
-          status: 'VERIFIED',
-          transitionType: 'revisions',
-          transitionTimestamp: event.payload.transitionTimestamp,
-        })
-      }
-    }
-  }
-
-  private async isAlreadyMigrated(userUuid: Uuid): Promise<boolean> {
-    const totalRevisionsCountForUserInPrimary = await this.primaryRevisionsRepository.countByUserUuid(userUuid)
-
-    if (totalRevisionsCountForUserInPrimary > 0) {
-      this.logger.info(
-        `User ${userUuid.value} has ${totalRevisionsCountForUserInPrimary} revisions in primary database.`,
-      )
-    }
-
-    return totalRevisionsCountForUserInPrimary === 0
-  }
-
-  private async getUserUuidFromEvent(event: TransitionStatusUpdatedEvent): Promise<Uuid | null> {
-    const userUuidOrError = Uuid.create(event.payload.userUuid)
-    if (userUuidOrError.isFailed()) {
-      this.logger.error(
-        `Failed to transition revisions for user ${event.payload.userUuid}: ${userUuidOrError.getError()}`,
-      )
-      await this.domainEventPublisher.publish(
-        this.domainEventFactory.createTransitionStatusUpdatedEvent({
-          userUuid: event.payload.userUuid,
-          status: 'FAILED',
-          transitionType: 'revisions',
-          transitionTimestamp: event.payload.transitionTimestamp,
-        }),
-      )
-
-      return null
-    }
-
-    return userUuidOrError.getValue()
-  }
-}

+ 0 - 31
packages/revisions/src/Domain/UseCase/Transition/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser.spec.ts

@@ -1,31 +0,0 @@
-import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
-
-import { TriggerTransitionFromPrimaryToSecondaryDatabaseForUser } from './TriggerTransitionFromPrimaryToSecondaryDatabaseForUser'
-import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
-
-describe('TriggerTransitionFromPrimaryToSecondaryDatabaseForUser', () => {
-  let domainEventPubliser: DomainEventPublisherInterface
-  let domainEventFactory: DomainEventFactoryInterface
-
-  const createUseCase = () =>
-    new TriggerTransitionFromPrimaryToSecondaryDatabaseForUser(domainEventPubliser, domainEventFactory)
-
-  beforeEach(() => {
-    domainEventPubliser = {} as jest.Mocked<DomainEventPublisherInterface>
-    domainEventPubliser.publish = jest.fn()
-
-    domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
-    domainEventFactory.createTransitionStatusUpdatedEvent = jest.fn()
-  })
-
-  it('should publish transition status updated event', async () => {
-    const useCase = createUseCase()
-
-    await useCase.execute({
-      userUuid: '00000000-0000-0000-0000-000000000000',
-      transitionTimestamp: 123,
-    })
-
-    expect(domainEventPubliser.publish).toHaveBeenCalled()
-  })
-})

+ 0 - 25
packages/revisions/src/Domain/UseCase/Transition/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser.ts

@@ -1,25 +0,0 @@
-import { Result, UseCaseInterface } from '@standardnotes/domain-core'
-import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
-
-import { TriggerTransitionFromPrimaryToSecondaryDatabaseForUserDTO } from './TriggerTransitionFromPrimaryToSecondaryDatabaseForUserDTO'
-import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
-
-export class TriggerTransitionFromPrimaryToSecondaryDatabaseForUser implements UseCaseInterface<void> {
-  constructor(
-    private domainEventPubliser: DomainEventPublisherInterface,
-    private domainEventFactory: DomainEventFactoryInterface,
-  ) {}
-
-  async execute(dto: TriggerTransitionFromPrimaryToSecondaryDatabaseForUserDTO): Promise<Result<void>> {
-    const event = this.domainEventFactory.createTransitionStatusUpdatedEvent({
-      userUuid: dto.userUuid,
-      status: 'STARTED',
-      transitionType: 'revisions',
-      transitionTimestamp: dto.transitionTimestamp,
-    })
-
-    await this.domainEventPubliser.publish(event)
-
-    return Result.ok()
-  }
-}

+ 0 - 4
packages/revisions/src/Domain/UseCase/Transition/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser/TriggerTransitionFromPrimaryToSecondaryDatabaseForUserDTO.ts

@@ -1,4 +0,0 @@
-export interface TriggerTransitionFromPrimaryToSecondaryDatabaseForUserDTO {
-  userUuid: string
-  transitionTimestamp: number
-}

+ 2 - 28
packages/syncing-server/src/Bootstrap/Container.ts

@@ -160,8 +160,6 @@ import { ItemRepositoryResolverInterface } from '../Domain/Item/ItemRepositoryRe
 import { TypeORMItemRepositoryResolver } from '../Infra/TypeORM/TypeORMItemRepositoryResolver'
 import { TransitionItemsFromPrimaryToSecondaryDatabaseForUser } from '../Domain/UseCase/Transition/TransitionItemsFromPrimaryToSecondaryDatabaseForUser/TransitionItemsFromPrimaryToSecondaryDatabaseForUser'
 import { SharedVaultFileMovedEventHandler } from '../Domain/Handler/SharedVaultFileMovedEventHandler'
-import { TransitionStatusUpdatedEventHandler } from '../Domain/Handler/TransitionStatusUpdatedEventHandler'
-import { TriggerTransitionFromPrimaryToSecondaryDatabaseForUser } from '../Domain/UseCase/Transition/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser'
 import { SQLItem } from '../Infra/TypeORM/SQLItem'
 import { SQLItemPersistenceMapper } from '../Mapping/Persistence/SQLItemPersistenceMapper'
 import { SQLItemRepository } from '../Infra/TypeORM/SQLItemRepository'
@@ -837,16 +835,6 @@ export class ContainerConfigLoader {
           env.get('MIGRATION_BATCH_SIZE', true) ? +env.get('MIGRATION_BATCH_SIZE', true) : 100,
         ),
       )
-    container
-      .bind<TriggerTransitionFromPrimaryToSecondaryDatabaseForUser>(
-        TYPES.Sync_TriggerTransitionFromPrimaryToSecondaryDatabaseForUser,
-      )
-      .toConstantValue(
-        new TriggerTransitionFromPrimaryToSecondaryDatabaseForUser(
-          container.get<DomainEventPublisherInterface>(TYPES.Sync_DomainEventPublisher),
-          container.get<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory),
-        ),
-      )
 
     // Services
     container
@@ -949,9 +937,9 @@ export class ContainerConfigLoader {
         ),
       )
     container
-      .bind<TransitionStatusUpdatedEventHandler>(TYPES.Sync_TransitionStatusUpdatedEventHandler)
+      .bind<TransitionRequestedEventHandler>(TYPES.Sync_TransitionRequestedEventHandler)
       .toConstantValue(
-        new TransitionStatusUpdatedEventHandler(
+        new TransitionRequestedEventHandler(
           container.get<ItemRepositoryInterface>(TYPES.Sync_SQLItemRepository),
           container.get<TransitionItemsFromPrimaryToSecondaryDatabaseForUser>(
             TYPES.Sync_TransitionItemsFromPrimaryToSecondaryDatabaseForUser,
@@ -961,16 +949,6 @@ export class ContainerConfigLoader {
           container.get<Logger>(TYPES.Sync_Logger),
         ),
       )
-    container
-      .bind<TransitionRequestedEventHandler>(TYPES.Sync_TransitionRequestedEventHandler)
-      .toConstantValue(
-        new TransitionRequestedEventHandler(
-          container.get<TriggerTransitionFromPrimaryToSecondaryDatabaseForUser>(
-            TYPES.Sync_TriggerTransitionFromPrimaryToSecondaryDatabaseForUser,
-          ),
-          container.get<Logger>(TYPES.Sync_Logger),
-        ),
-      )
 
     // Services
     container.bind<ContentDecoder>(TYPES.Sync_ContentDecoder).toDynamicValue(() => new ContentDecoder())
@@ -1005,10 +983,6 @@ export class ContainerConfigLoader {
         'SHARED_VAULT_FILE_MOVED',
         container.get<SharedVaultFileMovedEventHandler>(TYPES.Sync_SharedVaultFileMovedEventHandler),
       ],
-      [
-        'TRANSITION_STATUS_UPDATED',
-        container.get<TransitionStatusUpdatedEventHandler>(TYPES.Sync_TransitionStatusUpdatedEventHandler),
-      ],
       [
         'TRANSITION_REQUESTED',
         container.get<TransitionRequestedEventHandler>(TYPES.Sync_TransitionRequestedEventHandler),

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

@@ -84,9 +84,6 @@ const TYPES = {
   Sync_TransitionItemsFromPrimaryToSecondaryDatabaseForUser: Symbol.for(
     'Sync_TransitionItemsFromPrimaryToSecondaryDatabaseForUser',
   ),
-  Sync_TriggerTransitionFromPrimaryToSecondaryDatabaseForUser: Symbol.for(
-    'Sync_TriggerTransitionFromPrimaryToSecondaryDatabaseForUser',
-  ),
   Sync_SendEventToClient: Symbol.for('Sync_SendEventToClient'),
   // Handlers
   Sync_AccountDeletionRequestedEventHandler: Symbol.for('Sync_AccountDeletionRequestedEventHandler'),
@@ -96,7 +93,6 @@ const TYPES = {
   Sync_SharedVaultFileRemovedEventHandler: Symbol.for('Sync_SharedVaultFileRemovedEventHandler'),
   Sync_SharedVaultFileUploadedEventHandler: Symbol.for('Sync_SharedVaultFileUploadedEventHandler'),
   Sync_SharedVaultFileMovedEventHandler: Symbol.for('Sync_SharedVaultFileMovedEventHandler'),
-  Sync_TransitionStatusUpdatedEventHandler: Symbol.for('Sync_TransitionStatusUpdatedEventHandler'),
   Sync_TransitionRequestedEventHandler: Symbol.for('Sync_TransitionRequestedEventHandler'),
   // Services
   Sync_ContentDecoder: Symbol.for('Sync_ContentDecoder'),

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

@@ -173,7 +173,7 @@ export class DomainEventFactory implements DomainEventFactoryInterface {
     userUuid: string
     transitionType: 'items' | 'revisions'
     transitionTimestamp: number
-    status: 'STARTED' | 'IN_PROGRESS' | 'FAILED' | 'FINISHED' | 'VERIFIED'
+    status: string
   }): TransitionStatusUpdatedEvent {
     return {
       type: 'TRANSITION_STATUS_UPDATED',

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

@@ -53,7 +53,7 @@ export interface DomainEventFactoryInterface {
     userUuid: string
     transitionType: 'items' | 'revisions'
     transitionTimestamp: number
-    status: 'STARTED' | 'IN_PROGRESS' | 'FAILED' | 'FINISHED' | 'VERIFIED'
+    status: string
   }): TransitionStatusUpdatedEvent
   createEmailRequestedEvent(dto: {
     userEmail: string

+ 95 - 5
packages/syncing-server/src/Domain/Handler/TransitionRequestedEventHandler.ts

@@ -1,11 +1,21 @@
-import { DomainEventHandlerInterface, TransitionRequestedEvent } from '@standardnotes/domain-events'
+import {
+  DomainEventHandlerInterface,
+  DomainEventPublisherInterface,
+  TransitionRequestedEvent,
+} from '@standardnotes/domain-events'
 import { Logger } from 'winston'
+import { TransitionStatus, Uuid } from '@standardnotes/domain-core'
 
-import { TriggerTransitionFromPrimaryToSecondaryDatabaseForUser } from '../UseCase/Transition/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser'
+import { TransitionItemsFromPrimaryToSecondaryDatabaseForUser } from '../UseCase/Transition/TransitionItemsFromPrimaryToSecondaryDatabaseForUser/TransitionItemsFromPrimaryToSecondaryDatabaseForUser'
+import { ItemRepositoryInterface } from '../Item/ItemRepositoryInterface'
+import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
 
 export class TransitionRequestedEventHandler implements DomainEventHandlerInterface {
   constructor(
-    private triggerTransitionFromPrimaryToSecondaryDatabaseForUser: TriggerTransitionFromPrimaryToSecondaryDatabaseForUser,
+    private primaryItemRepository: ItemRepositoryInterface,
+    private transitionItemsFromPrimaryToSecondaryDatabaseForUser: TransitionItemsFromPrimaryToSecondaryDatabaseForUser,
+    private domainEventPublisher: DomainEventPublisherInterface,
+    private domainEventFactory: DomainEventFactoryInterface,
     private logger: Logger,
   ) {}
 
@@ -14,15 +24,95 @@ export class TransitionRequestedEventHandler implements DomainEventHandlerInterf
       return
     }
 
+    const userUuid = await this.getUserUuidFromEvent(event)
+    if (!userUuid) {
+      return
+    }
+
+    if (await this.isAlreadyMigrated(userUuid)) {
+      this.logger.info(`User ${event.payload.userUuid} already migrated.`)
+
+      await this.domainEventPublisher.publish(
+        this.domainEventFactory.createTransitionStatusUpdatedEvent({
+          userUuid: event.payload.userUuid,
+          status: TransitionStatus.STATUSES.Verified,
+          transitionType: 'items',
+          transitionTimestamp: event.payload.timestamp,
+        }),
+      )
+
+      return
+    }
+
     this.logger.info(`Handling transition requested event for user ${event.payload.userUuid}`)
 
-    const result = await this.triggerTransitionFromPrimaryToSecondaryDatabaseForUser.execute({
+    await this.domainEventPublisher.publish(
+      this.domainEventFactory.createTransitionStatusUpdatedEvent({
+        userUuid: event.payload.userUuid,
+        status: TransitionStatus.STATUSES.InProgress,
+        transitionType: 'items',
+        transitionTimestamp: event.payload.timestamp,
+      }),
+    )
+
+    const result = await this.transitionItemsFromPrimaryToSecondaryDatabaseForUser.execute({
       userUuid: event.payload.userUuid,
-      transitionTimestamp: event.payload.timestamp,
     })
 
     if (result.isFailed()) {
       this.logger.error(`Failed to trigger transition for user ${event.payload.userUuid}`)
+
+      await this.domainEventPublisher.publish(
+        this.domainEventFactory.createTransitionStatusUpdatedEvent({
+          userUuid: event.payload.userUuid,
+          status: TransitionStatus.STATUSES.Failed,
+          transitionType: 'items',
+          transitionTimestamp: event.payload.timestamp,
+        }),
+      )
+
+      return
     }
+
+    await this.domainEventPublisher.publish(
+      this.domainEventFactory.createTransitionStatusUpdatedEvent({
+        userUuid: event.payload.userUuid,
+        status: TransitionStatus.STATUSES.Verified,
+        transitionType: 'items',
+        transitionTimestamp: event.payload.timestamp,
+      }),
+    )
+  }
+
+  private async isAlreadyMigrated(userUuid: Uuid): Promise<boolean> {
+    const totalItemsCountForUserInPrimary = await this.primaryItemRepository.countAll({
+      userUuid: userUuid.value,
+    })
+
+    if (totalItemsCountForUserInPrimary > 0) {
+      this.logger.info(`User ${userUuid.value} has ${totalItemsCountForUserInPrimary} items in primary database.`)
+    }
+
+    return totalItemsCountForUserInPrimary === 0
+  }
+
+  private async getUserUuidFromEvent(event: TransitionRequestedEvent): Promise<Uuid | null> {
+    const userUuidOrError = Uuid.create(event.payload.userUuid)
+    if (userUuidOrError.isFailed()) {
+      this.logger.error(`Failed to transition items for user ${event.payload.userUuid}: ${userUuidOrError.getError()}`)
+
+      await this.domainEventPublisher.publish(
+        this.domainEventFactory.createTransitionStatusUpdatedEvent({
+          userUuid: event.payload.userUuid,
+          status: TransitionStatus.STATUSES.Failed,
+          transitionType: 'items',
+          transitionTimestamp: event.payload.timestamp,
+        }),
+      )
+
+      return null
+    }
+
+    return userUuidOrError.getValue()
   }
 }

+ 0 - 100
packages/syncing-server/src/Domain/Handler/TransitionStatusUpdatedEventHandler.ts

@@ -1,100 +0,0 @@
-import {
-  DomainEventHandlerInterface,
-  DomainEventPublisherInterface,
-  TransitionStatusUpdatedEvent,
-} from '@standardnotes/domain-events'
-import { Logger } from 'winston'
-import { TransitionItemsFromPrimaryToSecondaryDatabaseForUser } from '../UseCase/Transition/TransitionItemsFromPrimaryToSecondaryDatabaseForUser/TransitionItemsFromPrimaryToSecondaryDatabaseForUser'
-import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
-import { ItemRepositoryInterface } from '../Item/ItemRepositoryInterface'
-
-export class TransitionStatusUpdatedEventHandler implements DomainEventHandlerInterface {
-  constructor(
-    private primaryItemRepository: ItemRepositoryInterface,
-    private transitionItemsFromPrimaryToSecondaryDatabaseForUser: TransitionItemsFromPrimaryToSecondaryDatabaseForUser,
-    private domainEventPublisher: DomainEventPublisherInterface,
-    private domainEventFactory: DomainEventFactoryInterface,
-    private logger: Logger,
-  ) {}
-
-  async handle(event: TransitionStatusUpdatedEvent): Promise<void> {
-    if (event.payload.transitionType !== 'items') {
-      return
-    }
-
-    this.logger.info(
-      `Received transition status updated event to ${event.payload.status} for user ${event.payload.userUuid}`,
-    )
-
-    if (event.payload.status === 'STARTED') {
-      if (await this.isAlreadyMigrated(event.payload.userUuid)) {
-        await this.domainEventPublisher.publish(
-          this.domainEventFactory.createTransitionStatusUpdatedEvent({
-            userUuid: event.payload.userUuid,
-            status: 'FINISHED',
-            transitionType: 'items',
-            transitionTimestamp: event.payload.transitionTimestamp,
-          }),
-        )
-
-        return
-      }
-
-      await this.domainEventPublisher.publish(
-        this.domainEventFactory.createTransitionStatusUpdatedEvent({
-          userUuid: event.payload.userUuid,
-          status: 'IN_PROGRESS',
-          transitionType: 'items',
-          transitionTimestamp: event.payload.transitionTimestamp,
-        }),
-      )
-
-      const result = await this.transitionItemsFromPrimaryToSecondaryDatabaseForUser.execute({
-        userUuid: event.payload.userUuid,
-      })
-
-      if (result.isFailed()) {
-        this.logger.error(`Failed to transition items for user ${event.payload.userUuid}: ${result.getError()}`)
-
-        await this.domainEventPublisher.publish(
-          this.domainEventFactory.createTransitionStatusUpdatedEvent({
-            userUuid: event.payload.userUuid,
-            status: 'FAILED',
-            transitionType: 'items',
-            transitionTimestamp: event.payload.transitionTimestamp,
-          }),
-        )
-
-        return
-      }
-
-      await this.domainEventPublisher.publish(
-        this.domainEventFactory.createTransitionStatusUpdatedEvent({
-          userUuid: event.payload.userUuid,
-          status: 'FINISHED',
-          transitionType: 'items',
-          transitionTimestamp: event.payload.transitionTimestamp,
-        }),
-      )
-    } else if (event.payload.status === 'FINISHED') {
-      if (await this.isAlreadyMigrated(event.payload.userUuid)) {
-        this.domainEventFactory.createTransitionStatusUpdatedEvent({
-          userUuid: event.payload.userUuid,
-          status: 'VERIFIED',
-          transitionType: 'items',
-          transitionTimestamp: event.payload.transitionTimestamp,
-        })
-      }
-    }
-  }
-
-  private async isAlreadyMigrated(userUuid: string): Promise<boolean> {
-    const totalItemsCountForUser = await this.primaryItemRepository.countAll({ userUuid })
-
-    if (totalItemsCountForUser > 0) {
-      this.logger.info(`User ${userUuid} has ${totalItemsCountForUser} items in primary database.`)
-    }
-
-    return totalItemsCountForUser === 0
-  }
-}

+ 0 - 31
packages/syncing-server/src/Domain/UseCase/Transition/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser.spec.ts

@@ -1,31 +0,0 @@
-import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
-
-import { TriggerTransitionFromPrimaryToSecondaryDatabaseForUser } from './TriggerTransitionFromPrimaryToSecondaryDatabaseForUser'
-import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
-
-describe('TriggerTransitionFromPrimaryToSecondaryDatabaseForUser', () => {
-  let domainEventPubliser: DomainEventPublisherInterface
-  let domainEventFactory: DomainEventFactoryInterface
-
-  const createUseCase = () =>
-    new TriggerTransitionFromPrimaryToSecondaryDatabaseForUser(domainEventPubliser, domainEventFactory)
-
-  beforeEach(() => {
-    domainEventPubliser = {} as jest.Mocked<DomainEventPublisherInterface>
-    domainEventPubliser.publish = jest.fn()
-
-    domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
-    domainEventFactory.createTransitionStatusUpdatedEvent = jest.fn()
-  })
-
-  it('should publish transition status updated event', async () => {
-    const useCase = createUseCase()
-
-    await useCase.execute({
-      userUuid: '00000000-0000-0000-0000-000000000000',
-      transitionTimestamp: 123,
-    })
-
-    expect(domainEventPubliser.publish).toHaveBeenCalled()
-  })
-})

+ 0 - 25
packages/syncing-server/src/Domain/UseCase/Transition/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser.ts

@@ -1,25 +0,0 @@
-import { Result, UseCaseInterface } from '@standardnotes/domain-core'
-import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
-
-import { TriggerTransitionFromPrimaryToSecondaryDatabaseForUserDTO } from './TriggerTransitionFromPrimaryToSecondaryDatabaseForUserDTO'
-import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
-
-export class TriggerTransitionFromPrimaryToSecondaryDatabaseForUser implements UseCaseInterface<void> {
-  constructor(
-    private domainEventPubliser: DomainEventPublisherInterface,
-    private domainEventFactory: DomainEventFactoryInterface,
-  ) {}
-
-  async execute(dto: TriggerTransitionFromPrimaryToSecondaryDatabaseForUserDTO): Promise<Result<void>> {
-    const event = this.domainEventFactory.createTransitionStatusUpdatedEvent({
-      userUuid: dto.userUuid,
-      status: 'STARTED',
-      transitionType: 'items',
-      transitionTimestamp: dto.transitionTimestamp,
-    })
-
-    await this.domainEventPubliser.publish(event)
-
-    return Result.ok()
-  }
-}

+ 0 - 4
packages/syncing-server/src/Domain/UseCase/Transition/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser/TriggerTransitionFromPrimaryToSecondaryDatabaseForUserDTO.ts

@@ -1,4 +0,0 @@
-export interface TriggerTransitionFromPrimaryToSecondaryDatabaseForUserDTO {
-  userUuid: string
-  transitionTimestamp: number
-}

+ 11 - 3
packages/syncing-server/src/Infra/TypeORM/SQLLegacyItemRepository.ts

@@ -89,7 +89,9 @@ export class SQLLegacyItemRepository implements ItemRepositoryInterface {
 
       return item
     } catch (error) {
-      this.logger.error(`Failed to find item ${uuid.value} by uuid: ${(error as Error).message}`)
+      this.logger.error(
+        `Failed to map item ${uuid.value} for user ${persistence.userUuid} by uuid: ${(error as Error).message}`,
+      )
 
       return null
     }
@@ -137,7 +139,9 @@ export class SQLLegacyItemRepository implements ItemRepositoryInterface {
 
       return item
     } catch (error) {
-      this.logger.error(`Failed to find item ${uuid} by uuid and userUuid: ${(error as Error).message}`)
+      this.logger.error(
+        `Failed to map item ${uuid} for user ${persistence.userUuid} by uuid and userUuid: ${(error as Error).message}`,
+      )
 
       return null
     }
@@ -151,7 +155,11 @@ export class SQLLegacyItemRepository implements ItemRepositoryInterface {
       try {
         domainItems.push(this.mapper.toDomain(persistencItem))
       } catch (error) {
-        this.logger.error(`Failed to map item ${persistencItem.uuid} to domain: ${(error as Error).message}`)
+        this.logger.error(
+          `Failed to map item ${persistencItem.uuid} for user ${persistencItem.userUuid} to domain: ${
+            (error as Error).message
+          }`,
+        )
       }
     }