Compare commits

..

6 Commits

40 changed files with 347 additions and 90 deletions

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.26.12](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.26.11...@standardnotes/analytics@2.26.12) (2023-09-13)
**Note:** Version bump only for package @standardnotes/analytics
## [2.26.11](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.26.10...@standardnotes/analytics@2.26.11) (2023-09-12)
**Note:** Version bump only for package @standardnotes/analytics

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/analytics",
"version": "2.26.11",
"version": "2.26.12",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.74.9](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.74.8...@standardnotes/api-gateway@1.74.9) (2023-09-13)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.74.8](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.74.7...@standardnotes/api-gateway@1.74.8) (2023-09-12)
**Note:** Version bump only for package @standardnotes/api-gateway

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/api-gateway",
"version": "1.74.8",
"version": "1.74.9",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.141.9](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.141.8...@standardnotes/auth-server@1.141.9) (2023-09-13)
### Bug Fixes
* display transition progress in logs ([38685c1](https://github.com/standardnotes/server/commit/38685c1861b13e398dd96aa39f2cf1aece2090fb))
## [1.141.8](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.141.7...@standardnotes/auth-server@1.141.8) (2023-09-12)
### Bug Fixes

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/auth-server",
"version": "1.141.8",
"version": "1.141.9",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -943,6 +943,7 @@ export class ContainerConfigLoader {
new UpdateTransitionStatus(
container.get<TransitionStatusRepositoryInterface>(TYPES.Auth_TransitionStatusRepository),
container.get<RoleServiceInterface>(TYPES.Auth_RoleService),
container.get<winston.Logger>(TYPES.Auth_Logger),
),
)
container

View File

@@ -44,7 +44,10 @@ export class DomainEventFactory implements DomainEventFactoryInterface {
},
origin: DomainEventService.Auth,
},
payload: dto,
payload: {
timestamp: this.timer.getTimestampInMicroseconds(),
...dto,
},
}
}

View File

@@ -13,6 +13,7 @@ export class TransitionStatusUpdatedEventHandler implements DomainEventHandlerIn
status: event.payload.status,
userUuid: event.payload.userUuid,
transitionType: event.payload.transitionType,
transitionTimestamp: event.payload.transitionTimestamp,
})
if (result.isFailed()) {

View File

@@ -3,17 +3,36 @@ import { RoleName, Uuid } from '@standardnotes/domain-core'
import { RoleServiceInterface } from '../../Role/RoleServiceInterface'
import { TransitionStatusRepositoryInterface } from '../../Transition/TransitionStatusRepositoryInterface'
import { UpdateTransitionStatus } from './UpdateTransitionStatus'
import { Logger } from 'winston'
describe('UpdateTransitionStatus', () => {
let transitionStatusRepository: TransitionStatusRepositoryInterface
let roleService: RoleServiceInterface
let logger: Logger
const createUseCase = () => new UpdateTransitionStatus(transitionStatusRepository, roleService)
const createUseCase = () => new UpdateTransitionStatus(transitionStatusRepository, roleService, logger)
beforeEach(() => {
logger = {} as jest.Mocked<Logger>
logger.info = jest.fn()
transitionStatusRepository = {} as jest.Mocked<TransitionStatusRepositoryInterface>
transitionStatusRepository.removeStatus = jest.fn()
transitionStatusRepository.updateStatus = jest.fn()
transitionStatusRepository.getStatuses = jest.fn().mockResolvedValue([
{
userUuid: '00000000-0000-0000-0000-000000000000',
status: 'STARTED',
},
{
userUuid: '00000000-0000-0000-0000-000000000001',
status: 'IN_PROGRESS',
},
{
userUuid: '00000000-0000-0000-0000-000000000002',
status: 'FAILED',
},
])
roleService = {} as jest.Mocked<RoleServiceInterface>
roleService.addRoleToUser = jest.fn()
@@ -26,6 +45,7 @@ describe('UpdateTransitionStatus', () => {
userUuid: '00000000-0000-0000-0000-000000000000',
status: 'FINISHED',
transitionType: 'items',
transitionTimestamp: 123,
})
expect(result.isFailed()).toBeFalsy()
@@ -46,6 +66,7 @@ describe('UpdateTransitionStatus', () => {
userUuid: '00000000-0000-0000-0000-000000000000',
status: 'FINISHED',
transitionType: 'revisions',
transitionTimestamp: 123,
})
expect(result.isFailed()).toBeFalsy()
@@ -63,6 +84,7 @@ describe('UpdateTransitionStatus', () => {
userUuid: '00000000-0000-0000-0000-000000000000',
status: 'STARTED',
transitionType: 'items',
transitionTimestamp: 123,
})
expect(result.isFailed()).toBeFalsy()
@@ -80,6 +102,7 @@ describe('UpdateTransitionStatus', () => {
userUuid: 'invalid',
status: 'STARTED',
transitionType: 'items',
transitionTimestamp: 123,
})
expect(result.isFailed()).toBeTruthy()

View File

@@ -2,11 +2,13 @@ import { Result, RoleName, UseCaseInterface, Uuid } from '@standardnotes/domain-
import { TransitionStatusRepositoryInterface } from '../../Transition/TransitionStatusRepositoryInterface'
import { UpdateTransitionStatusDTO } from './UpdateTransitionStatusDTO'
import { RoleServiceInterface } from '../../Role/RoleServiceInterface'
import { Logger } from 'winston'
export class UpdateTransitionStatus implements UseCaseInterface<void> {
constructor(
private transitionStatusRepository: TransitionStatusRepositoryInterface,
private roleService: RoleServiceInterface,
private logger: Logger,
) {}
async execute(dto: UpdateTransitionStatusDTO): Promise<Result<void>> {
@@ -16,17 +18,35 @@ export class UpdateTransitionStatus implements UseCaseInterface<void> {
}
const userUuid = userUuidOrError.getValue()
if (dto.status === 'FINISHED') {
await this.transitionStatusRepository.removeStatus(dto.userUuid, dto.transitionType)
if (dto.transitionType === 'items') {
await this.roleService.addRoleToUser(userUuid, RoleName.create(RoleName.NAMES.TransitionUser).getValue())
}
if (dto.status !== 'FINISHED') {
await this.transitionStatusRepository.updateStatus(dto.userUuid, dto.transitionType, dto.status)
return Result.ok()
}
await this.transitionStatusRepository.updateStatus(dto.userUuid, dto.transitionType, dto.status)
await this.transitionStatusRepository.removeStatus(dto.userUuid, dto.transitionType)
if (dto.transitionType === 'items') {
await this.roleService.addRoleToUser(userUuid, RoleName.create(RoleName.NAMES.TransitionUser).getValue())
}
const itemStatuses = await this.transitionStatusRepository.getStatuses('items')
const itemsStartedStatusesCount = itemStatuses.filter((status) => status.status === 'STARTED').length
const itemsInProgressStatusesCount = itemStatuses.filter((status) => status.status === 'IN_PROGRESS').length
const itemsFailedStatusesCount = itemStatuses.filter((status) => status.status === 'FAILED').length
this.logger.info(
`[TRANSITION ${dto.transitionTimestamp}] Items transition statuses: ${itemsStartedStatusesCount} started, ${itemsInProgressStatusesCount} in progress, ${itemsFailedStatusesCount} failed`,
)
const revisionStatuses = await this.transitionStatusRepository.getStatuses('revisions')
const revisionsStartedStatusesCount = revisionStatuses.filter((status) => status.status === 'STARTED').length
const revisionsInProgressStatusesCount = revisionStatuses.filter((status) => status.status === 'IN_PROGRESS').length
const revisionsFailedStatusesCount = revisionStatuses.filter((status) => status.status === 'FAILED').length
this.logger.info(
`[TRANSITION ${dto.transitionTimestamp}] Revisions transition statuses: ${revisionsStartedStatusesCount} started, ${revisionsInProgressStatusesCount} in progress, ${revisionsFailedStatusesCount} failed`,
)
return Result.ok()
}

View File

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

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.12.28](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.27...@standardnotes/domain-events-infra@1.12.28) (2023-09-13)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.12.27](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.26...@standardnotes/domain-events-infra@1.12.27) (2023-09-12)
**Note:** Version bump only for package @standardnotes/domain-events-infra

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/domain-events-infra",
"version": "1.12.27",
"version": "1.12.28",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.125.4](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.125.3...@standardnotes/domain-events@2.125.4) (2023-09-13)
### Bug Fixes
* display transition progress in logs ([38685c1](https://github.com/standardnotes/server/commit/38685c1861b13e398dd96aa39f2cf1aece2090fb))
## [2.125.3](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.125.2...@standardnotes/domain-events@2.125.3) (2023-09-12)
### Bug Fixes

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/domain-events",
"version": "2.125.3",
"version": "2.125.4",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -1,4 +1,5 @@
export interface TransitionRequestedEventPayload {
userUuid: string
type: 'items' | 'revisions'
timestamp: number
}

View File

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

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.11.40](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.39...@standardnotes/event-store@1.11.40) (2023-09-13)
**Note:** Version bump only for package @standardnotes/event-store
## [1.11.39](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.38...@standardnotes/event-store@1.11.39) (2023-09-12)
**Note:** Version bump only for package @standardnotes/event-store

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/event-store",
"version": "1.11.39",
"version": "1.11.40",
"description": "Event Store Service",
"private": true,
"main": "dist/src/index.js",

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.22.19](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.18...@standardnotes/files-server@1.22.19) (2023-09-13)
**Note:** Version bump only for package @standardnotes/files-server
## [1.22.18](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.17...@standardnotes/files-server@1.22.18) (2023-09-12)
**Note:** Version bump only for package @standardnotes/files-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/files-server",
"version": "1.22.18",
"version": "1.22.19",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.15.50](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.49...@standardnotes/home-server@1.15.50) (2023-09-13)
**Note:** Version bump only for package @standardnotes/home-server
## [1.15.49](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.48...@standardnotes/home-server@1.15.49) (2023-09-13)
**Note:** Version bump only for package @standardnotes/home-server
## [1.15.48](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.47...@standardnotes/home-server@1.15.48) (2023-09-13)
**Note:** Version bump only for package @standardnotes/home-server
## [1.15.47](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.46...@standardnotes/home-server@1.15.47) (2023-09-13)
**Note:** Version bump only for package @standardnotes/home-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/home-server",
"version": "1.15.47",
"version": "1.15.50",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -3,6 +3,24 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.33.19](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.33.18...@standardnotes/revisions-server@1.33.19) (2023-09-13)
### Bug Fixes
* include handling updated items in revisions in secondary ([fbcb45c](https://github.com/standardnotes/server/commit/fbcb45c3a23fde09702fae7bfcb409bdbb610191))
## [1.33.18](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.33.17...@standardnotes/revisions-server@1.33.18) (2023-09-13)
### Bug Fixes
* display transition progress in logs ([38685c1](https://github.com/standardnotes/server/commit/38685c1861b13e398dd96aa39f2cf1aece2090fb))
## [1.33.17](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.33.16...@standardnotes/revisions-server@1.33.17) (2023-09-13)
### Bug Fixes
* setting status for already migrated users ([9be4c00](https://github.com/standardnotes/server/commit/9be4c002b755fea057489b6077b297162223aefe))
## [1.33.16](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.33.15...@standardnotes/revisions-server@1.33.16) (2023-09-13)
### Bug Fixes

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/revisions-server",
"version": "1.33.16",
"version": "1.33.19",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -443,6 +443,7 @@ export class ContainerConfigLoader {
.bind<TransitionStatusUpdatedEventHandler>(TYPES.Revisions_TransitionStatusUpdatedEventHandler)
.toConstantValue(
new TransitionStatusUpdatedEventHandler(
container.get<RevisionRepositoryInterface>(TYPES.Revisions_SQLRevisionRepository),
container.get<TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser>(
TYPES.Revisions_TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser,
),

View File

@@ -21,7 +21,10 @@ export class DomainEventFactory implements DomainEventFactoryInterface {
},
origin: DomainEventService.SyncingServer,
},
payload: dto,
payload: {
transitionTimestamp: this.timer.getTimestampInMicroseconds(),
...dto,
},
}
}
}

View File

@@ -6,9 +6,12 @@ import {
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,
@@ -29,6 +32,34 @@ export class TransitionStatusUpdatedEventHandler implements DomainEventHandlerIn
}
if (event.payload.status === 'STARTED' && event.payload.transitionType === 'revisions') {
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',
}),
)
return
}
if (await this.isAlreadyMigrated(userUuidOrError.getValue())) {
await this.domainEventPublisher.publish(
this.domainEventFactory.createTransitionStatusUpdatedEvent({
userUuid: event.payload.userUuid,
status: 'FINISHED',
transitionType: 'revisions',
}),
)
return
}
await this.domainEventPublisher.publish(
this.domainEventFactory.createTransitionStatusUpdatedEvent({
userUuid: event.payload.userUuid,
@@ -66,4 +97,16 @@ export class TransitionStatusUpdatedEventHandler implements DomainEventHandlerIn
return
}
}
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
}
}

View File

@@ -29,16 +29,13 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
}
const userUuid = userUuidOrError.getValue()
if (await this.isAlreadyMigrated(userUuid)) {
this.logger.info(`Revisions for user ${userUuid.value} are already migrated`)
return Result.ok()
}
let newRevisionsInSecondaryCount = 0
let updatedRevisionsInSecondary: Revision[] = []
if (await this.hasAlreadyDataInSecondaryDatabase(userUuid)) {
const newRevisions = await this.getNewRevisionsCreatedInSecondaryDatabase(userUuid)
for (const existingRevision of newRevisions.alreadyExistingInPrimary) {
const { alreadyExistingInPrimary, newRevisionsInSecondary, updatedInSecondary } =
await this.getNewRevisionsCreatedInSecondaryDatabase(userUuid)
for (const existingRevision of alreadyExistingInPrimary) {
this.logger.info(`Removing revision ${existingRevision.id.toString()} from secondary database`)
await (this.secondRevisionsRepository as RevisionRepositoryInterface).removeOneByUuid(
Uuid.create(existingRevision.id.toString()).getValue(),
@@ -46,24 +43,34 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
)
}
if (newRevisions.newRevisionsInSecondary.length > 0) {
if (newRevisionsInSecondary.length > 0) {
this.logger.info(
`Found ${newRevisions.newRevisionsInSecondary.length} new revisions in secondary database for user ${userUuid.value}`,
`Found ${newRevisionsInSecondary.length} new revisions in secondary database for user ${userUuid.value}`,
)
}
newRevisionsInSecondaryCount = newRevisions.newRevisionsInSecondary.length
newRevisionsInSecondaryCount = newRevisionsInSecondary.length
if (updatedInSecondary.length > 0) {
this.logger.info(
`Found ${updatedInSecondary.length} updated revisions in secondary database for user ${userUuid.value}`,
)
}
updatedRevisionsInSecondary = updatedInSecondary
}
const updatedRevisionsInSecondaryCount = updatedRevisionsInSecondary.length
await this.allowForSecondaryDatabaseToCatchUp()
const migrationTimeStart = this.timer.getTimestampInMicroseconds()
this.logger.debug(`Transitioning revisions for user ${userUuid.value}`)
const migrationResult = await this.migrateRevisionsForUser(userUuid)
const migrationResult = await this.migrateRevisionsForUser(userUuid, updatedRevisionsInSecondary)
if (migrationResult.isFailed()) {
if (newRevisionsInSecondaryCount === 0) {
if (newRevisionsInSecondaryCount === 0 && updatedRevisionsInSecondaryCount === 0) {
const cleanupResult = await this.deleteRevisionsForUser(userUuid, this.secondRevisionsRepository)
if (cleanupResult.isFailed()) {
this.logger.error(
@@ -82,7 +89,7 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
newRevisionsInSecondaryCount,
)
if (integrityCheckResult.isFailed()) {
if (newRevisionsInSecondaryCount === 0) {
if (newRevisionsInSecondaryCount === 0 && updatedRevisionsInSecondaryCount === 0) {
const cleanupResult = await this.deleteRevisionsForUser(userUuid, this.secondRevisionsRepository)
if (cleanupResult.isFailed()) {
this.logger.error(
@@ -113,7 +120,10 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
return Result.ok()
}
private async migrateRevisionsForUser(userUuid: Uuid): Promise<Result<void>> {
private async migrateRevisionsForUser(
userUuid: Uuid,
updatedRevisionsInSecondary: Revision[],
): Promise<Result<void>> {
try {
const totalRevisionsCountForUser = await this.primaryRevisionsRepository.countByUserUuid(userUuid)
let totalRevisionsCountTransitionedToSecondary = 0
@@ -129,6 +139,14 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
for (const revision of revisions) {
try {
if (
updatedRevisionsInSecondary.find(
(updatedRevision) => updatedRevision.id.toString() === revision.id.toString(),
)
) {
continue
}
this.logger.debug(
`Transitioning revision #${
totalRevisionsCountTransitionedToSecondary + 1
@@ -194,6 +212,7 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
private async getNewRevisionsCreatedInSecondaryDatabase(userUuid: Uuid): Promise<{
alreadyExistingInPrimary: Revision[]
newRevisionsInSecondary: Revision[]
updatedInSecondary: Revision[]
}> {
const revisions = await (this.secondRevisionsRepository as RevisionRepositoryInterface).findByUserUuid({
userUuid: userUuid,
@@ -201,23 +220,35 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
const alreadyExistingInPrimary: Revision[] = []
const newRevisionsInSecondary: Revision[] = []
const updatedInSecondary: Revision[] = []
for (const revision of revisions) {
const revisionExistsInPrimary = await this.checkIfRevisionExistsInPrimaryDatabase(revision)
if (revisionExistsInPrimary) {
const { revisionInPrimary, newerRevisionInSecondary } =
await this.checkIfRevisionExistsInPrimaryDatabase(revision)
if (revisionInPrimary !== null) {
alreadyExistingInPrimary.push(revision)
} else {
continue
}
if (newerRevisionInSecondary !== null) {
updatedInSecondary.push(newerRevisionInSecondary)
continue
}
if (revisionInPrimary === null && newerRevisionInSecondary === null) {
newRevisionsInSecondary.push(revision)
continue
}
}
return {
alreadyExistingInPrimary: alreadyExistingInPrimary,
newRevisionsInSecondary: newRevisionsInSecondary,
updatedInSecondary: updatedInSecondary,
}
}
private async checkIfRevisionExistsInPrimaryDatabase(revision: Revision): Promise<boolean> {
private async checkIfRevisionExistsInPrimaryDatabase(
revision: Revision,
): Promise<{ revisionInPrimary: Revision | null; newerRevisionInSecondary: Revision | null }> {
const revisionInPrimary = await this.primaryRevisionsRepository.findOneByUuid(
Uuid.create(revision.id.toString()).getValue(),
revision.props.userUuid as Uuid,
@@ -225,7 +256,10 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
)
if (revisionInPrimary === null) {
return false
return {
revisionInPrimary: null,
newerRevisionInSecondary: null,
}
}
if (!revision.isIdenticalTo(revisionInPrimary)) {
@@ -235,22 +269,17 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
)}, revision in primary database: ${JSON.stringify(revisionInPrimary)}`,
)
return false
return {
revisionInPrimary: null,
newerRevisionInSecondary:
revision.props.dates.updatedAt > revisionInPrimary.props.dates.updatedAt ? revision : null,
}
}
return true
}
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 {
revisionInPrimary: revisionInPrimary,
newerRevisionInSecondary: null,
}
return totalRevisionsCountForUserInPrimary === 0
}
private async checkIntegrityBetweenPrimaryAndSecondaryDatabase(

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.20.44](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.43...@standardnotes/scheduler-server@1.20.44) (2023-09-13)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.20.43](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.42...@standardnotes/scheduler-server@1.20.43) (2023-09-12)
**Note:** Version bump only for package @standardnotes/scheduler-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/scheduler-server",
"version": "1.20.43",
"version": "1.20.44",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -3,6 +3,24 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.95.16](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.95.15...@standardnotes/syncing-server@1.95.16) (2023-09-13)
### Bug Fixes
* include handling updated items in revisions in secondary ([fbcb45c](https://github.com/standardnotes/syncing-server-js/commit/fbcb45c3a23fde09702fae7bfcb409bdbb610191))
## [1.95.15](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.95.14...@standardnotes/syncing-server@1.95.15) (2023-09-13)
### Bug Fixes
* display transition progress in logs ([38685c1](https://github.com/standardnotes/syncing-server-js/commit/38685c1861b13e398dd96aa39f2cf1aece2090fb))
## [1.95.14](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.95.13...@standardnotes/syncing-server@1.95.14) (2023-09-13)
### Bug Fixes
* setting status for already migrated users ([9be4c00](https://github.com/standardnotes/syncing-server-js/commit/9be4c002b755fea057489b6077b297162223aefe))
## [1.95.13](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.95.12...@standardnotes/syncing-server@1.95.13) (2023-09-13)
### Bug Fixes

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/syncing-server",
"version": "1.95.13",
"version": "1.95.16",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -952,6 +952,7 @@ export class ContainerConfigLoader {
.bind<TransitionStatusUpdatedEventHandler>(TYPES.Sync_TransitionStatusUpdatedEventHandler)
.toConstantValue(
new TransitionStatusUpdatedEventHandler(
container.get<ItemRepositoryInterface>(TYPES.Sync_SQLItemRepository),
container.get<TransitionItemsFromPrimaryToSecondaryDatabaseForUser>(
TYPES.Sync_TransitionItemsFromPrimaryToSecondaryDatabaseForUser,
),

View File

@@ -184,7 +184,10 @@ export class DomainEventFactory implements DomainEventFactoryInterface {
},
origin: DomainEventService.SyncingServer,
},
payload: dto,
payload: {
transitionTimestamp: this.timer.getTimestampInMicroseconds(),
...dto,
},
}
}

View File

@@ -6,9 +6,11 @@ import {
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,
@@ -17,6 +19,18 @@ export class TransitionStatusUpdatedEventHandler implements DomainEventHandlerIn
async handle(event: TransitionStatusUpdatedEvent): Promise<void> {
if (event.payload.status === 'STARTED' && event.payload.transitionType === 'items') {
if (await this.isAlreadyMigrated(event.payload.userUuid)) {
await this.domainEventPublisher.publish(
this.domainEventFactory.createTransitionStatusUpdatedEvent({
userUuid: event.payload.userUuid,
status: 'FINISHED',
transitionType: 'items',
}),
)
return
}
await this.domainEventPublisher.publish(
this.domainEventFactory.createTransitionStatusUpdatedEvent({
userUuid: event.payload.userUuid,
@@ -52,4 +66,14 @@ export class TransitionStatusUpdatedEventHandler implements DomainEventHandlerIn
)
}
}
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
}
}

View File

@@ -30,36 +30,42 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
}
const userUuid = userUuidOrError.getValue()
if (await this.isAlreadyMigrated(userUuid)) {
this.logger.info(`Items for user ${userUuid.value} are already migrated`)
return Result.ok()
}
let newItemsInSecondaryCount = 0
let updatedItemsInSecondary: Item[] = []
if (await this.hasAlreadyDataInSecondaryDatabase(userUuid)) {
const newItems = await this.getNewItemsCreatedInSecondaryDatabase(userUuid)
for (const existingItem of newItems.alreadyExistingInPrimary) {
const { alreadyExistingInPrimary, newItemsInSecondary, updatedInSecondary } =
await this.getNewItemsCreatedInSecondaryDatabase(userUuid)
for (const existingItem of alreadyExistingInPrimary) {
this.logger.info(`Removing item ${existingItem.uuid.value} from secondary database`)
await (this.secondaryItemRepository as ItemRepositoryInterface).remove(existingItem)
}
if (newItems.newItemsInSecondary.length > 0) {
if (newItemsInSecondary.length > 0) {
this.logger.info(
`Found ${newItems.newItemsInSecondary.length} new items in secondary database for user ${userUuid.value}`,
`Found ${newItemsInSecondary.length} new items in secondary database for user ${userUuid.value}`,
)
}
newItemsInSecondaryCount = newItems.newItemsInSecondary.length
newItemsInSecondaryCount = newItemsInSecondary.length
if (updatedInSecondary.length > 0) {
this.logger.info(
`Found ${updatedInSecondary.length} updated items in secondary database for user ${userUuid.value}`,
)
}
updatedItemsInSecondary = updatedInSecondary
}
const updatedItemsInSecondaryCount = updatedItemsInSecondary.length
await this.allowForSecondaryDatabaseToCatchUp()
const migrationTimeStart = this.timer.getTimestampInMicroseconds()
const migrationResult = await this.migrateItemsForUser(userUuid)
const migrationResult = await this.migrateItemsForUser(userUuid, updatedItemsInSecondary)
if (migrationResult.isFailed()) {
if (newItemsInSecondaryCount === 0) {
if (newItemsInSecondaryCount === 0 && updatedItemsInSecondaryCount === 0) {
const cleanupResult = await this.deleteItemsForUser(userUuid, this.secondaryItemRepository)
if (cleanupResult.isFailed()) {
this.logger.error(
@@ -78,7 +84,7 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
newItemsInSecondaryCount,
)
if (integrityCheckResult.isFailed()) {
if (newItemsInSecondaryCount === 0) {
if (newItemsInSecondaryCount === 0 && updatedItemsInSecondaryCount === 0) {
const cleanupResult = await this.deleteItemsForUser(userUuid, this.secondaryItemRepository)
if (cleanupResult.isFailed()) {
this.logger.error(
@@ -122,16 +128,6 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
return hasAlreadyDataInSecondaryDatabase
}
private async isAlreadyMigrated(userUuid: Uuid): Promise<boolean> {
const totalItemsCountForUser = await this.primaryItemRepository.countAll({ userUuid: userUuid.value })
if (totalItemsCountForUser > 0) {
this.logger.info(`User ${userUuid.value} has ${totalItemsCountForUser} items in primary database.`)
}
return totalItemsCountForUser === 0
}
private async allowForSecondaryDatabaseToCatchUp(): Promise<void> {
const twoSecondsInMilliseconds = 2_000
await this.timer.sleep(twoSecondsInMilliseconds)
@@ -140,34 +136,46 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
private async getNewItemsCreatedInSecondaryDatabase(userUuid: Uuid): Promise<{
alreadyExistingInPrimary: Item[]
newItemsInSecondary: Item[]
updatedInSecondary: Item[]
}> {
const items = await (this.secondaryItemRepository as ItemRepositoryInterface).findAll({
userUuid: userUuid.value,
})
const alreadyExistingInPrimary: Item[] = []
const updatedInSecondary: Item[] = []
const newItemsInSecondary: Item[] = []
for (const item of items) {
const itemExistsInPrimary = await this.checkIfItemExistsInPrimaryDatabase(item)
if (itemExistsInPrimary) {
const { itemInPrimary, newerItemInSecondary } = await this.checkIfItemExistsInPrimaryDatabase(item)
if (itemInPrimary !== null) {
alreadyExistingInPrimary.push(item)
} else {
continue
}
if (newerItemInSecondary !== null) {
updatedInSecondary.push(newerItemInSecondary)
continue
}
if (itemInPrimary === null && newerItemInSecondary === null) {
newItemsInSecondary.push(item)
continue
}
}
return {
alreadyExistingInPrimary: alreadyExistingInPrimary,
newItemsInSecondary: newItemsInSecondary,
alreadyExistingInPrimary,
newItemsInSecondary,
updatedInSecondary,
}
}
private async checkIfItemExistsInPrimaryDatabase(item: Item): Promise<boolean> {
private async checkIfItemExistsInPrimaryDatabase(
item: Item,
): Promise<{ itemInPrimary: Item | null; newerItemInSecondary: Item | null }> {
const itemInPrimary = await this.primaryItemRepository.findByUuid(item.uuid)
if (itemInPrimary === null) {
return false
return { itemInPrimary: null, newerItemInSecondary: null }
}
if (!item.isIdenticalTo(itemInPrimary)) {
@@ -177,13 +185,16 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
)}, revision in primary database: ${JSON.stringify(itemInPrimary)}`,
)
return false
return {
itemInPrimary: null,
newerItemInSecondary: item.props.timestamps.updatedAt > itemInPrimary.props.timestamps.updatedAt ? item : null,
}
}
return true
return { itemInPrimary: itemInPrimary, newerItemInSecondary: null }
}
private async migrateItemsForUser(userUuid: Uuid): Promise<Result<void>> {
private async migrateItemsForUser(userUuid: Uuid, updatedItemsInSecondary: Item[]): Promise<Result<void>> {
try {
const totalItemsCountForUser = await this.primaryItemRepository.countAll({ userUuid: userUuid.value })
const totalPages = Math.ceil(totalItemsCountForUser / this.pageSize)
@@ -197,6 +208,9 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
const items = await this.primaryItemRepository.findAll(query)
for (const item of items) {
if (updatedItemsInSecondary.find((updatedItem) => updatedItem.uuid.equals(item.uuid))) {
continue
}
await (this.secondaryItemRepository as ItemRepositoryInterface).save(item)
}
}

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.10.41](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.10.40...@standardnotes/websockets-server@1.10.41) (2023-09-13)
**Note:** Version bump only for package @standardnotes/websockets-server
## [1.10.40](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.10.39...@standardnotes/websockets-server@1.10.40) (2023-09-12)
**Note:** Version bump only for package @standardnotes/websockets-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/websockets-server",
"version": "1.10.40",
"version": "1.10.41",
"engines": {
"node": ">=18.0.0 <21.0.0"
},