mirror of
https://github.com/standardnotes/server
synced 2026-04-25 18:01:21 -04:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a1e654a0d0 | |||
| aa835268ea | |||
| 74b4312928 | |||
| e91a832152 | |||
| 4f95bbee3f | |||
| b9c9f74d0c | |||
| e535cd504c | |||
| db0360860a | |||
| aa2b5f3b74 | |||
| 6241661e27 | |||
| 25047bf46d | |||
| a1820ed212 | |||
| 0a1d1624e8 | |||
| 7367de6832 | |||
| f0abfe89fc | |||
| d1244d165a | |||
| 106d8f9192 | |||
| 1d86ba8fcb | |||
| f20a947f8a | |||
| 19b9de05ae | |||
| 1d751c0fbe |
@@ -53,7 +53,7 @@ services:
|
|||||||
image: mysql:8
|
image: mysql:8
|
||||||
container_name: db-ci
|
container_name: db-ci
|
||||||
env_file: .github/ci.env
|
env_file: .github/ci.env
|
||||||
expose:
|
ports:
|
||||||
- 3306
|
- 3306
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: --default-authentication-plugin=mysql_native_password --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci
|
command: --default-authentication-plugin=mysql_native_password --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci
|
||||||
@@ -66,7 +66,7 @@ services:
|
|||||||
secondary_db:
|
secondary_db:
|
||||||
image: mongo:5.0
|
image: mongo:5.0
|
||||||
container_name: secondary_db-ci
|
container_name: secondary_db-ci
|
||||||
expose:
|
ports:
|
||||||
- 27017
|
- 27017
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
volumes:
|
volumes:
|
||||||
@@ -83,7 +83,7 @@ services:
|
|||||||
container_name: cache-ci
|
container_name: cache-ci
|
||||||
volumes:
|
volumes:
|
||||||
- ./data/redis/:/data
|
- ./data/redis/:/data
|
||||||
expose:
|
ports:
|
||||||
- 6379
|
- 6379
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
networks:
|
networks:
|
||||||
|
|||||||
@@ -3,6 +3,22 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [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
|
||||||
|
|
||||||
|
## [2.26.10](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.26.9...@standardnotes/analytics@2.26.10) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.26.9](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.26.8...@standardnotes/analytics@2.26.9) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.26.8](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.26.7...@standardnotes/analytics@2.26.8) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
## [2.26.7](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.26.6...@standardnotes/analytics@2.26.7) (2023-09-08)
|
## [2.26.7](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.26.6...@standardnotes/analytics@2.26.7) (2023-09-08)
|
||||||
|
|
||||||
**Note:** Version bump only for package @standardnotes/analytics
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/analytics",
|
"name": "@standardnotes/analytics",
|
||||||
"version": "2.26.7",
|
"version": "2.26.11",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,22 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [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
|
||||||
|
|
||||||
|
## [1.74.7](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.74.6...@standardnotes/api-gateway@1.74.7) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.74.6](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.74.5...@standardnotes/api-gateway@1.74.6) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.74.5](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.74.4...@standardnotes/api-gateway@1.74.5) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
## [1.74.4](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.74.3...@standardnotes/api-gateway@1.74.4) (2023-09-11)
|
## [1.74.4](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.74.3...@standardnotes/api-gateway@1.74.4) (2023-09-11)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/api-gateway",
|
"name": "@standardnotes/api-gateway",
|
||||||
"version": "1.74.4",
|
"version": "1.74.8",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,34 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.141.7](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.141.6...@standardnotes/auth-server@1.141.7) (2023-09-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* retry failed revision transitions ([e535cd5](https://github.com/standardnotes/server/commit/e535cd504cf1929539ff7faf13e9c1fdd2b7bfd1))
|
||||||
|
|
||||||
|
## [1.141.6](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.141.5...@standardnotes/auth-server@1.141.6) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.141.5](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.141.4...@standardnotes/auth-server@1.141.5) (2023-09-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* domain event values ([f0abfe8](https://github.com/standardnotes/server/commit/f0abfe89fca0049c47131389683efe2f5aff23f8))
|
||||||
|
|
||||||
|
## [1.141.4](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.141.3...@standardnotes/auth-server@1.141.4) (2023-09-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* adjust transitions to not create revisions during ongoing revisions transition ([106d8f9](https://github.com/standardnotes/server/commit/106d8f9192f630794ca4ddc2c4503f2c6cd196e7))
|
||||||
|
|
||||||
|
## [1.141.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.141.2...@standardnotes/auth-server@1.141.3) (2023-09-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* transition adjustments ([f20a947](https://github.com/standardnotes/server/commit/f20a947f8a555c074d8dc1543c7a8bf61d1d887e))
|
||||||
|
|
||||||
## [1.141.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.141.1...@standardnotes/auth-server@1.141.2) (2023-09-11)
|
## [1.141.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.141.1...@standardnotes/auth-server@1.141.2) (2023-09-11)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import { Env } from '../src/Bootstrap/Env'
|
|||||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||||
import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
|
import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
|
||||||
import { UserRepositoryInterface } from '../src/Domain/User/UserRepositoryInterface'
|
import { UserRepositoryInterface } from '../src/Domain/User/UserRepositoryInterface'
|
||||||
|
import { RoleName } from '@standardnotes/domain-core'
|
||||||
|
import { TransitionStatusRepositoryInterface } from '../src/Domain/Transition/TransitionStatusRepositoryInterface'
|
||||||
|
|
||||||
const inputArgs = process.argv.slice(2)
|
const inputArgs = process.argv.slice(2)
|
||||||
const startDateString = inputArgs[0]
|
const startDateString = inputArgs[0]
|
||||||
@@ -17,6 +19,7 @@ const endDateString = inputArgs[1]
|
|||||||
|
|
||||||
const requestTransition = async (
|
const requestTransition = async (
|
||||||
userRepository: UserRepositoryInterface,
|
userRepository: UserRepositoryInterface,
|
||||||
|
transitionStatusRepository: TransitionStatusRepositoryInterface,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
domainEventFactory: DomainEventFactoryInterface,
|
domainEventFactory: DomainEventFactoryInterface,
|
||||||
domainEventPublisher: DomainEventPublisherInterface,
|
domainEventPublisher: DomainEventPublisherInterface,
|
||||||
@@ -28,8 +31,38 @@ const requestTransition = async (
|
|||||||
|
|
||||||
logger.info(`Found ${users.length} users created between ${startDateString} and ${endDateString}`)
|
logger.info(`Found ${users.length} users created between ${startDateString} and ${endDateString}`)
|
||||||
|
|
||||||
|
let usersTriggered = 0
|
||||||
for (const user of users) {
|
for (const user of users) {
|
||||||
const transitionRequestedEvent = domainEventFactory.createTransitionRequestedEvent({ userUuid: user.uuid })
|
const roles = await user.roles
|
||||||
|
const userHasTransitionUserRole = roles.some((role) => role.name === RoleName.NAMES.TransitionUser) === true
|
||||||
|
if (userHasTransitionUserRole === true) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const transitionRequestedEvent = domainEventFactory.createTransitionRequestedEvent({
|
||||||
|
userUuid: user.uuid,
|
||||||
|
type: 'items',
|
||||||
|
})
|
||||||
|
|
||||||
|
usersTriggered += 1
|
||||||
|
|
||||||
|
await domainEventPublisher.publish(transitionRequestedEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
`Triggered transition for ${usersTriggered} users created between ${startDateString} and ${endDateString}`,
|
||||||
|
)
|
||||||
|
|
||||||
|
const revisionStatuses = await transitionStatusRepository.getStatuses('revisions')
|
||||||
|
const failedStatuses = revisionStatuses.filter((status) => status.status === 'FAILED')
|
||||||
|
|
||||||
|
logger.info(`Found ${failedStatuses.length} failed revision transitions`)
|
||||||
|
|
||||||
|
for (const status of failedStatuses) {
|
||||||
|
const transitionRequestedEvent = domainEventFactory.createTransitionRequestedEvent({
|
||||||
|
userUuid: status.userUuid,
|
||||||
|
type: 'revisions',
|
||||||
|
})
|
||||||
|
|
||||||
await domainEventPublisher.publish(transitionRequestedEvent)
|
await domainEventPublisher.publish(transitionRequestedEvent)
|
||||||
}
|
}
|
||||||
@@ -49,8 +82,13 @@ void container.load().then((container) => {
|
|||||||
const userRepository: UserRepositoryInterface = container.get(TYPES.Auth_UserRepository)
|
const userRepository: UserRepositoryInterface = container.get(TYPES.Auth_UserRepository)
|
||||||
const domainEventFactory: DomainEventFactoryInterface = container.get(TYPES.Auth_DomainEventFactory)
|
const domainEventFactory: DomainEventFactoryInterface = container.get(TYPES.Auth_DomainEventFactory)
|
||||||
const domainEventPublisher: DomainEventPublisherInterface = container.get(TYPES.Auth_DomainEventPublisher)
|
const domainEventPublisher: DomainEventPublisherInterface = container.get(TYPES.Auth_DomainEventPublisher)
|
||||||
|
const transitionStatusRepository: TransitionStatusRepositoryInterface = container.get(
|
||||||
|
TYPES.Auth_TransitionStatusRepository,
|
||||||
|
)
|
||||||
|
|
||||||
Promise.resolve(requestTransition(userRepository, logger, domainEventFactory, domainEventPublisher))
|
Promise.resolve(
|
||||||
|
requestTransition(userRepository, transitionStatusRepository, logger, domainEventFactory, domainEventPublisher),
|
||||||
|
)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
logger.info(`Finished transition request for users created between ${startDateString} and ${endDateString}`)
|
logger.info(`Finished transition request for users created between ${startDateString} and ${endDateString}`)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/auth-server",
|
"name": "@standardnotes/auth-server",
|
||||||
"version": "1.141.2",
|
"version": "1.141.7",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import { DomainEventFactoryInterface } from './DomainEventFactoryInterface'
|
|||||||
export class DomainEventFactory implements DomainEventFactoryInterface {
|
export class DomainEventFactory implements DomainEventFactoryInterface {
|
||||||
constructor(@inject(TYPES.Auth_Timer) private timer: TimerInterface) {}
|
constructor(@inject(TYPES.Auth_Timer) private timer: TimerInterface) {}
|
||||||
|
|
||||||
createTransitionRequestedEvent(dto: { userUuid: string }): TransitionRequestedEvent {
|
createTransitionRequestedEvent(dto: { userUuid: string; type: 'items' | 'revisions' }): TransitionRequestedEvent {
|
||||||
return {
|
return {
|
||||||
type: 'TRANSITION_REQUESTED',
|
type: 'TRANSITION_REQUESTED',
|
||||||
createdAt: this.timer.getUTCDate(),
|
createdAt: this.timer.getUTCDate(),
|
||||||
|
|||||||
@@ -90,5 +90,5 @@ export interface DomainEventFactoryInterface {
|
|||||||
}): StatisticPersistenceRequestedEvent
|
}): StatisticPersistenceRequestedEvent
|
||||||
createSessionCreatedEvent(dto: { userUuid: string }): SessionCreatedEvent
|
createSessionCreatedEvent(dto: { userUuid: string }): SessionCreatedEvent
|
||||||
createSessionRefreshedEvent(dto: { userUuid: string }): SessionRefreshedEvent
|
createSessionRefreshedEvent(dto: { userUuid: string }): SessionRefreshedEvent
|
||||||
createTransitionRequestedEvent(dto: { userUuid: string }): TransitionRequestedEvent
|
createTransitionRequestedEvent(dto: { userUuid: string; type: 'items' | 'revisions' }): TransitionRequestedEvent
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
export interface TransitionStatusRepositoryInterface {
|
export interface TransitionStatusRepositoryInterface {
|
||||||
updateStatus(userUuid: string, transitionType: 'items' | 'revisions', status: 'STARTED' | 'FAILED'): Promise<void>
|
updateStatus(
|
||||||
|
userUuid: string,
|
||||||
|
transitionType: 'items' | 'revisions',
|
||||||
|
status: 'STARTED' | 'IN_PROGRESS' | 'FAILED',
|
||||||
|
): Promise<void>
|
||||||
removeStatus(userUuid: string, transitionType: 'items' | 'revisions'): Promise<void>
|
removeStatus(userUuid: string, transitionType: 'items' | 'revisions'): Promise<void>
|
||||||
getStatus(userUuid: string, transitionType: 'items' | 'revisions'): Promise<'STARTED' | 'FAILED' | null>
|
getStatus(
|
||||||
|
userUuid: string,
|
||||||
|
transitionType: 'items' | 'revisions',
|
||||||
|
): Promise<'STARTED' | 'IN_PROGRESS' | 'FAILED' | null>
|
||||||
|
getStatuses(
|
||||||
|
transitionType: 'items' | 'revisions',
|
||||||
|
): Promise<Array<{ userUuid: string; status: 'STARTED' | 'IN_PROGRESS' | 'FAILED' }>>
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-1
@@ -113,13 +113,14 @@ describe('CreateCrossServiceToken', () => {
|
|||||||
uuid: '00000000-0000-0000-0000-000000000000',
|
uuid: '00000000-0000-0000-0000-000000000000',
|
||||||
},
|
},
|
||||||
ongoing_transition: false,
|
ongoing_transition: false,
|
||||||
|
ongoing_revisions_transition: false,
|
||||||
},
|
},
|
||||||
60,
|
60,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should create a cross service token for user that has an ongoing transaction', async () => {
|
it('should create a cross service token for user that has an ongoing transaction', async () => {
|
||||||
transitionStatusRepository.getStatus = jest.fn().mockReturnValue('STARTED')
|
transitionStatusRepository.getStatus = jest.fn().mockReturnValue('IN_PROGRESS')
|
||||||
|
|
||||||
await createUseCase().execute({
|
await createUseCase().execute({
|
||||||
user,
|
user,
|
||||||
@@ -148,6 +149,7 @@ describe('CreateCrossServiceToken', () => {
|
|||||||
uuid: '00000000-0000-0000-0000-000000000000',
|
uuid: '00000000-0000-0000-0000-000000000000',
|
||||||
},
|
},
|
||||||
ongoing_transition: true,
|
ongoing_transition: true,
|
||||||
|
ongoing_revisions_transition: true,
|
||||||
},
|
},
|
||||||
60,
|
60,
|
||||||
)
|
)
|
||||||
@@ -177,6 +179,7 @@ describe('CreateCrossServiceToken', () => {
|
|||||||
uuid: '00000000-0000-0000-0000-000000000000',
|
uuid: '00000000-0000-0000-0000-000000000000',
|
||||||
},
|
},
|
||||||
ongoing_transition: false,
|
ongoing_transition: false,
|
||||||
|
ongoing_revisions_transition: false,
|
||||||
},
|
},
|
||||||
60,
|
60,
|
||||||
)
|
)
|
||||||
@@ -206,6 +209,7 @@ describe('CreateCrossServiceToken', () => {
|
|||||||
uuid: '00000000-0000-0000-0000-000000000000',
|
uuid: '00000000-0000-0000-0000-000000000000',
|
||||||
},
|
},
|
||||||
ongoing_transition: false,
|
ongoing_transition: false,
|
||||||
|
ongoing_revisions_transition: false,
|
||||||
},
|
},
|
||||||
60,
|
60,
|
||||||
)
|
)
|
||||||
@@ -261,6 +265,7 @@ describe('CreateCrossServiceToken', () => {
|
|||||||
email: 'test@test.te',
|
email: 'test@test.te',
|
||||||
uuid: '00000000-0000-0000-0000-000000000000',
|
uuid: '00000000-0000-0000-0000-000000000000',
|
||||||
},
|
},
|
||||||
|
ongoing_revisions_transition: false,
|
||||||
ongoing_transition: false,
|
ongoing_transition: false,
|
||||||
},
|
},
|
||||||
60,
|
60,
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ export class CreateCrossServiceToken implements UseCaseInterface<string> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const transitionStatus = await this.transitionStatusRepository.getStatus(user.uuid, 'items')
|
const transitionStatus = await this.transitionStatusRepository.getStatus(user.uuid, 'items')
|
||||||
|
const revisionsTransitionStatus = await this.transitionStatusRepository.getStatus(user.uuid, 'revisions')
|
||||||
|
|
||||||
const roles = await user.roles
|
const roles = await user.roles
|
||||||
|
|
||||||
@@ -59,7 +60,9 @@ export class CreateCrossServiceToken implements UseCaseInterface<string> {
|
|||||||
user: this.projectUser(user),
|
user: this.projectUser(user),
|
||||||
roles: this.projectRoles(roles),
|
roles: this.projectRoles(roles),
|
||||||
shared_vault_owner_context: undefined,
|
shared_vault_owner_context: undefined,
|
||||||
ongoing_transition: transitionStatus === 'STARTED',
|
ongoing_transition: transitionStatus === 'IN_PROGRESS',
|
||||||
|
ongoing_revisions_transition:
|
||||||
|
revisionsTransitionStatus === 'STARTED' || revisionsTransitionStatus === 'IN_PROGRESS',
|
||||||
belongs_to_shared_vaults: sharedVaultAssociations.map((association) => ({
|
belongs_to_shared_vaults: sharedVaultAssociations.map((association) => ({
|
||||||
shared_vault_uuid: association.props.sharedVaultUuid.value,
|
shared_vault_uuid: association.props.sharedVaultUuid.value,
|
||||||
permission: association.props.permission.value,
|
permission: association.props.permission.value,
|
||||||
|
|||||||
@@ -4,13 +4,17 @@ import { GetTransitionStatusDTO } from './GetTransitionStatusDTO'
|
|||||||
import { UserRepositoryInterface } from '../../User/UserRepositoryInterface'
|
import { UserRepositoryInterface } from '../../User/UserRepositoryInterface'
|
||||||
import { TransitionStatusRepositoryInterface } from '../../Transition/TransitionStatusRepositoryInterface'
|
import { TransitionStatusRepositoryInterface } from '../../Transition/TransitionStatusRepositoryInterface'
|
||||||
|
|
||||||
export class GetTransitionStatus implements UseCaseInterface<'TO-DO' | 'STARTED' | 'FINISHED' | 'FAILED'> {
|
export class GetTransitionStatus
|
||||||
|
implements UseCaseInterface<'TO-DO' | 'STARTED' | 'IN_PROGRESS' | 'FINISHED' | 'FAILED'>
|
||||||
|
{
|
||||||
constructor(
|
constructor(
|
||||||
private transitionStatusRepository: TransitionStatusRepositoryInterface,
|
private transitionStatusRepository: TransitionStatusRepositoryInterface,
|
||||||
private userRepository: UserRepositoryInterface,
|
private userRepository: UserRepositoryInterface,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(dto: GetTransitionStatusDTO): Promise<Result<'TO-DO' | 'STARTED' | 'FINISHED' | 'FAILED'>> {
|
async execute(
|
||||||
|
dto: GetTransitionStatusDTO,
|
||||||
|
): Promise<Result<'TO-DO' | 'STARTED' | 'IN_PROGRESS' | 'FINISHED' | 'FAILED'>> {
|
||||||
const userUuidOrError = Uuid.create(dto.userUuid)
|
const userUuidOrError = Uuid.create(dto.userUuid)
|
||||||
if (userUuidOrError.isFailed()) {
|
if (userUuidOrError.isFailed()) {
|
||||||
return Result.fail(userUuidOrError.getError())
|
return Result.fail(userUuidOrError.getError())
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
export interface UpdateTransitionStatusDTO {
|
export interface UpdateTransitionStatusDTO {
|
||||||
userUuid: string
|
userUuid: string
|
||||||
transitionType: 'items' | 'revisions'
|
transitionType: 'items' | 'revisions'
|
||||||
status: 'STARTED' | 'FINISHED' | 'FAILED'
|
status: 'STARTED' | 'IN_PROGRESS' | 'FINISHED' | 'FAILED'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,24 @@ export class InMemoryTransitionStatusRepository implements TransitionStatusRepos
|
|||||||
private itemStatuses: Map<string, 'STARTED' | 'FAILED'> = new Map()
|
private itemStatuses: Map<string, 'STARTED' | 'FAILED'> = new Map()
|
||||||
private revisionStatuses: Map<string, 'STARTED' | 'FAILED'> = new Map()
|
private revisionStatuses: Map<string, 'STARTED' | 'FAILED'> = new Map()
|
||||||
|
|
||||||
|
async getStatuses(
|
||||||
|
transitionType: 'items' | 'revisions',
|
||||||
|
): Promise<{ userUuid: string; status: 'STARTED' | 'FAILED' | 'IN_PROGRESS' }[]> {
|
||||||
|
const statuses: { userUuid: string; status: 'STARTED' | 'FAILED' | 'IN_PROGRESS' }[] = []
|
||||||
|
|
||||||
|
if (transitionType === 'items') {
|
||||||
|
for (const [userUuid, status] of this.itemStatuses) {
|
||||||
|
statuses.push({ userUuid, status })
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (const [userUuid, status] of this.revisionStatuses) {
|
||||||
|
statuses.push({ userUuid, status })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return statuses
|
||||||
|
}
|
||||||
|
|
||||||
async updateStatus(
|
async updateStatus(
|
||||||
userUuid: string,
|
userUuid: string,
|
||||||
transitionType: 'items' | 'revisions',
|
transitionType: 'items' | 'revisions',
|
||||||
|
|||||||
@@ -7,21 +7,44 @@ export class RedisTransitionStatusRepository implements TransitionStatusReposito
|
|||||||
|
|
||||||
constructor(private redisClient: IORedis.Redis) {}
|
constructor(private redisClient: IORedis.Redis) {}
|
||||||
|
|
||||||
|
async getStatuses(
|
||||||
|
transitionType: 'items' | 'revisions',
|
||||||
|
): Promise<{ userUuid: string; status: 'STARTED' | 'IN_PROGRESS' | 'FAILED' }[]> {
|
||||||
|
const keys = await this.redisClient.keys(`${this.PREFIX}:${transitionType}:*`)
|
||||||
|
const statuses = await Promise.all(
|
||||||
|
keys.map(async (key) => {
|
||||||
|
const userUuid = key.split(':')[2]
|
||||||
|
const status = (await this.redisClient.get(key)) as 'STARTED' | 'IN_PROGRESS' | 'FAILED'
|
||||||
|
return { userUuid, status }
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
return statuses
|
||||||
|
}
|
||||||
|
|
||||||
async updateStatus(
|
async updateStatus(
|
||||||
userUuid: string,
|
userUuid: string,
|
||||||
transitionType: 'items' | 'revisions',
|
transitionType: 'items' | 'revisions',
|
||||||
status: 'STARTED' | 'FAILED',
|
status: 'STARTED' | 'IN_PROGRESS' | 'FAILED',
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await this.redisClient.set(`${this.PREFIX}:${transitionType}:${userUuid}`, status)
|
if (status === 'IN_PROGRESS') {
|
||||||
|
await this.redisClient.setex(`${this.PREFIX}:${transitionType}:${userUuid}`, 7200, status)
|
||||||
|
} else {
|
||||||
|
await this.redisClient.set(`${this.PREFIX}:${transitionType}:${userUuid}`, status)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeStatus(userUuid: string, transitionType: 'items' | 'revisions'): Promise<void> {
|
async removeStatus(userUuid: string, transitionType: 'items' | 'revisions'): Promise<void> {
|
||||||
await this.redisClient.del(`${this.PREFIX}:${transitionType}:${userUuid}`)
|
await this.redisClient.del(`${this.PREFIX}:${transitionType}:${userUuid}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
async getStatus(userUuid: string, transitionType: 'items' | 'revisions'): Promise<'STARTED' | 'FAILED' | null> {
|
async getStatus(
|
||||||
|
userUuid: string,
|
||||||
|
transitionType: 'items' | 'revisions',
|
||||||
|
): Promise<'STARTED' | 'IN_PROGRESS' | 'FAILED' | null> {
|
||||||
const status = (await this.redisClient.get(`${this.PREFIX}:${transitionType}:${userUuid}`)) as
|
const status = (await this.redisClient.get(`${this.PREFIX}:${transitionType}:${userUuid}`)) as
|
||||||
| 'STARTED'
|
| 'STARTED'
|
||||||
|
| 'IN_PROGRESS'
|
||||||
| 'FAILED'
|
| 'FAILED'
|
||||||
| null
|
| null
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,12 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.28.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.28.0...@standardnotes/domain-core@1.28.1) (2023-09-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* comparing uuids ([0a1d162](https://github.com/standardnotes/server/commit/0a1d1624e818000f2e951f29323a88e6e233c755))
|
||||||
|
|
||||||
# [1.28.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.27.0...@standardnotes/domain-core@1.28.0) (2023-09-07)
|
# [1.28.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.27.0...@standardnotes/domain-core@1.28.0) (2023-09-07)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/domain-core",
|
"name": "@standardnotes/domain-core",
|
||||||
"version": "1.28.0",
|
"version": "1.28.1",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,6 +8,13 @@ describe('Uuid', () => {
|
|||||||
expect(valueOrError.getValue().value).toEqual('84c0f8e8-544a-4c7e-9adf-26209303bc1d')
|
expect(valueOrError.getValue().value).toEqual('84c0f8e8-544a-4c7e-9adf-26209303bc1d')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should create a value object on upper case', () => {
|
||||||
|
const valueOrError = Uuid.create('00B57455-B563-4B50-A2AA-B19762102219')
|
||||||
|
|
||||||
|
expect(valueOrError.isFailed()).toBeFalsy()
|
||||||
|
expect(valueOrError.getValue().value).toEqual('00B57455-B563-4B50-A2AA-B19762102219')
|
||||||
|
})
|
||||||
|
|
||||||
it('should not create an invalid value object', () => {
|
it('should not create an invalid value object', () => {
|
||||||
const valueOrError = Uuid.create('1-2-3')
|
const valueOrError = Uuid.create('1-2-3')
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,18 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [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
|
||||||
|
|
||||||
|
## [1.12.26](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.25...@standardnotes/domain-events-infra@1.12.26) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||||
|
|
||||||
|
## [1.12.25](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.24...@standardnotes/domain-events-infra@1.12.25) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||||
|
|
||||||
## [1.12.24](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.23...@standardnotes/domain-events-infra@1.12.24) (2023-09-08)
|
## [1.12.24](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.23...@standardnotes/domain-events-infra@1.12.24) (2023-09-08)
|
||||||
|
|
||||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/domain-events-infra",
|
"name": "@standardnotes/domain-events-infra",
|
||||||
"version": "1.12.24",
|
"version": "1.12.27",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,22 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [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
|
||||||
|
|
||||||
|
* retry failed revision transitions ([e535cd5](https://github.com/standardnotes/server/commit/e535cd504cf1929539ff7faf13e9c1fdd2b7bfd1))
|
||||||
|
|
||||||
|
## [2.125.2](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.125.1...@standardnotes/domain-events@2.125.2) (2023-09-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* domain event values ([f0abfe8](https://github.com/standardnotes/server/commit/f0abfe89fca0049c47131389683efe2f5aff23f8))
|
||||||
|
|
||||||
|
## [2.125.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.125.0...@standardnotes/domain-events@2.125.1) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/domain-events
|
||||||
|
|
||||||
# [2.125.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.124.0...@standardnotes/domain-events@2.125.0) (2023-09-08)
|
# [2.125.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.124.0...@standardnotes/domain-events@2.125.0) (2023-09-08)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/domain-events",
|
"name": "@standardnotes/domain-events",
|
||||||
"version": "2.125.0",
|
"version": "2.125.3",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
export interface TransitionRequestedEventPayload {
|
export interface TransitionRequestedEventPayload {
|
||||||
userUuid: string
|
userUuid: string
|
||||||
|
type: 'items' | 'revisions'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export interface TransitionStatusUpdatedEventPayload {
|
export interface TransitionStatusUpdatedEventPayload {
|
||||||
userUuid: string
|
userUuid: string
|
||||||
transitionType: 'items' | 'revisions'
|
transitionType: 'items' | 'revisions'
|
||||||
status: 'STARTED' | 'FINISHED' | 'FAILED'
|
status: 'STARTED' | 'IN_PROGRESS' | 'FINISHED' | 'FAILED'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,22 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [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
|
||||||
|
|
||||||
|
## [1.11.38](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.37...@standardnotes/event-store@1.11.38) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/event-store
|
||||||
|
|
||||||
|
## [1.11.37](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.36...@standardnotes/event-store@1.11.37) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/event-store
|
||||||
|
|
||||||
|
## [1.11.36](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.35...@standardnotes/event-store@1.11.36) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/event-store
|
||||||
|
|
||||||
## [1.11.35](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.34...@standardnotes/event-store@1.11.35) (2023-09-08)
|
## [1.11.35](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.34...@standardnotes/event-store@1.11.35) (2023-09-08)
|
||||||
|
|
||||||
**Note:** Version bump only for package @standardnotes/event-store
|
**Note:** Version bump only for package @standardnotes/event-store
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/event-store",
|
"name": "@standardnotes/event-store",
|
||||||
"version": "1.11.35",
|
"version": "1.11.39",
|
||||||
"description": "Event Store Service",
|
"description": "Event Store Service",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "dist/src/index.js",
|
"main": "dist/src/index.js",
|
||||||
|
|||||||
@@ -3,6 +3,22 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [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
|
||||||
|
|
||||||
|
## [1.22.17](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.16...@standardnotes/files-server@1.22.17) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/files-server
|
||||||
|
|
||||||
|
## [1.22.16](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.15...@standardnotes/files-server@1.22.16) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/files-server
|
||||||
|
|
||||||
|
## [1.22.15](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.14...@standardnotes/files-server@1.22.15) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/files-server
|
||||||
|
|
||||||
## [1.22.14](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.13...@standardnotes/files-server@1.22.14) (2023-09-08)
|
## [1.22.14](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.13...@standardnotes/files-server@1.22.14) (2023-09-08)
|
||||||
|
|
||||||
**Note:** Version bump only for package @standardnotes/files-server
|
**Note:** Version bump only for package @standardnotes/files-server
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/files-server",
|
"name": "@standardnotes/files-server",
|
||||||
"version": "1.22.14",
|
"version": "1.22.18",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,46 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.15.45](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.44...@standardnotes/home-server@1.15.45) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/home-server
|
||||||
|
|
||||||
|
## [1.15.44](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.43...@standardnotes/home-server@1.15.44) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/home-server
|
||||||
|
|
||||||
|
## [1.15.43](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.42...@standardnotes/home-server@1.15.43) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/home-server
|
||||||
|
|
||||||
|
## [1.15.42](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.41...@standardnotes/home-server@1.15.42) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/home-server
|
||||||
|
|
||||||
|
## [1.15.41](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.40...@standardnotes/home-server@1.15.41) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/home-server
|
||||||
|
|
||||||
|
## [1.15.40](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.39...@standardnotes/home-server@1.15.40) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/home-server
|
||||||
|
|
||||||
|
## [1.15.39](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.38...@standardnotes/home-server@1.15.39) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/home-server
|
||||||
|
|
||||||
|
## [1.15.38](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.37...@standardnotes/home-server@1.15.38) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/home-server
|
||||||
|
|
||||||
|
## [1.15.37](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.36...@standardnotes/home-server@1.15.37) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/home-server
|
||||||
|
|
||||||
|
## [1.15.36](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.35...@standardnotes/home-server@1.15.36) (2023-09-11)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/home-server
|
||||||
|
|
||||||
## [1.15.35](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.34...@standardnotes/home-server@1.15.35) (2023-09-11)
|
## [1.15.35](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.34...@standardnotes/home-server@1.15.35) (2023-09-11)
|
||||||
|
|
||||||
**Note:** Version bump only for package @standardnotes/home-server
|
**Note:** Version bump only for package @standardnotes/home-server
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/home-server",
|
"name": "@standardnotes/home-server",
|
||||||
"version": "1.15.35",
|
"version": "1.15.45",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,44 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.33.14](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.33.13...@standardnotes/revisions-server@1.33.14) (2023-09-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **revisions:** handle transitions with already existing data in secondary ([aa83526](https://github.com/standardnotes/server/commit/aa835268ea80e3aa74907e449d189e8b2774a859))
|
||||||
|
|
||||||
|
## [1.33.13](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.33.12...@standardnotes/revisions-server@1.33.13) (2023-09-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* retry failed revision transitions ([e535cd5](https://github.com/standardnotes/server/commit/e535cd504cf1929539ff7faf13e9c1fdd2b7bfd1))
|
||||||
|
|
||||||
|
## [1.33.12](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.33.11...@standardnotes/revisions-server@1.33.12) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||||
|
|
||||||
|
## [1.33.11](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.33.10...@standardnotes/revisions-server@1.33.11) (2023-09-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* comparing uuids ([0a1d162](https://github.com/standardnotes/server/commit/0a1d1624e818000f2e951f29323a88e6e233c755))
|
||||||
|
|
||||||
|
## [1.33.10](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.33.9...@standardnotes/revisions-server@1.33.10) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||||
|
|
||||||
|
## [1.33.9](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.33.8...@standardnotes/revisions-server@1.33.9) (2023-09-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* adjust transitions to not create revisions during ongoing revisions transition ([106d8f9](https://github.com/standardnotes/server/commit/106d8f9192f630794ca4ddc2c4503f2c6cd196e7))
|
||||||
|
|
||||||
|
## [1.33.8](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.33.7...@standardnotes/revisions-server@1.33.8) (2023-09-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* transition adjustments ([f20a947](https://github.com/standardnotes/server/commit/f20a947f8a555c074d8dc1543c7a8bf61d1d887e))
|
||||||
|
|
||||||
## [1.33.7](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.33.6...@standardnotes/revisions-server@1.33.7) (2023-09-11)
|
## [1.33.7](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.33.6...@standardnotes/revisions-server@1.33.7) (2023-09-11)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/revisions-server",
|
"name": "@standardnotes/revisions-server",
|
||||||
"version": "1.33.7",
|
"version": "1.33.14",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ import { SQLRevisionMetadataPersistenceMapper } from '../Mapping/Persistence/SQL
|
|||||||
import { SQLRevisionPersistenceMapper } from '../Mapping/Persistence/SQL/SQLRevisionPersistenceMapper'
|
import { SQLRevisionPersistenceMapper } from '../Mapping/Persistence/SQL/SQLRevisionPersistenceMapper'
|
||||||
import { RemoveRevisionsFromSharedVault } from '../Domain/UseCase/RemoveRevisionsFromSharedVault/RemoveRevisionsFromSharedVault'
|
import { RemoveRevisionsFromSharedVault } from '../Domain/UseCase/RemoveRevisionsFromSharedVault/RemoveRevisionsFromSharedVault'
|
||||||
import { ItemRemovedFromSharedVaultEventHandler } from '../Domain/Handler/ItemRemovedFromSharedVaultEventHandler'
|
import { ItemRemovedFromSharedVaultEventHandler } from '../Domain/Handler/ItemRemovedFromSharedVaultEventHandler'
|
||||||
|
import { TransitionRequestedEventHandler } from '../Domain/Handler/TransitionRequestedEventHandler'
|
||||||
|
|
||||||
export class ContainerConfigLoader {
|
export class ContainerConfigLoader {
|
||||||
constructor(private mode: 'server' | 'worker' = 'server') {}
|
constructor(private mode: 'server' | 'worker' = 'server') {}
|
||||||
@@ -419,6 +420,7 @@ export class ContainerConfigLoader {
|
|||||||
new ItemDumpedEventHandler(
|
new ItemDumpedEventHandler(
|
||||||
container.get<DumpRepositoryInterface>(TYPES.Revisions_DumpRepository),
|
container.get<DumpRepositoryInterface>(TYPES.Revisions_DumpRepository),
|
||||||
container.get<RevisionRepositoryResolverInterface>(TYPES.Revisions_RevisionRepositoryResolver),
|
container.get<RevisionRepositoryResolverInterface>(TYPES.Revisions_RevisionRepositoryResolver),
|
||||||
|
container.get<winston.Logger>(TYPES.Revisions_Logger),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
container
|
container
|
||||||
@@ -457,6 +459,16 @@ export class ContainerConfigLoader {
|
|||||||
container.get<winston.Logger>(TYPES.Revisions_Logger),
|
container.get<winston.Logger>(TYPES.Revisions_Logger),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
container
|
||||||
|
.bind<TransitionRequestedEventHandler>(TYPES.Revisions_TransitionRequestedEventHandler)
|
||||||
|
.toConstantValue(
|
||||||
|
new TransitionRequestedEventHandler(
|
||||||
|
container.get<TriggerTransitionFromPrimaryToSecondaryDatabaseForUser>(
|
||||||
|
TYPES.Revisions_TriggerTransitionFromPrimaryToSecondaryDatabaseForUser,
|
||||||
|
),
|
||||||
|
container.get<winston.Logger>(TYPES.Revisions_Logger),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([
|
const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([
|
||||||
['ITEM_DUMPED', container.get(TYPES.Revisions_ItemDumpedEventHandler)],
|
['ITEM_DUMPED', container.get(TYPES.Revisions_ItemDumpedEventHandler)],
|
||||||
@@ -464,6 +476,7 @@ export class ContainerConfigLoader {
|
|||||||
['REVISIONS_COPY_REQUESTED', container.get(TYPES.Revisions_RevisionsCopyRequestedEventHandler)],
|
['REVISIONS_COPY_REQUESTED', container.get(TYPES.Revisions_RevisionsCopyRequestedEventHandler)],
|
||||||
['TRANSITION_STATUS_UPDATED', container.get(TYPES.Revisions_TransitionStatusUpdatedEventHandler)],
|
['TRANSITION_STATUS_UPDATED', container.get(TYPES.Revisions_TransitionStatusUpdatedEventHandler)],
|
||||||
['ITEM_REMOVED_FROM_SHARED_VAULT', container.get(TYPES.Revisions_ItemRemovedFromSharedVaultEventHandler)],
|
['ITEM_REMOVED_FROM_SHARED_VAULT', container.get(TYPES.Revisions_ItemRemovedFromSharedVaultEventHandler)],
|
||||||
|
['TRANSITION_REQUESTED', container.get(TYPES.Revisions_TransitionRequestedEventHandler)],
|
||||||
])
|
])
|
||||||
|
|
||||||
if (isConfiguredForHomeServer) {
|
if (isConfiguredForHomeServer) {
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ const TYPES = {
|
|||||||
Revisions_RevisionsCopyRequestedEventHandler: Symbol.for('Revisions_RevisionsCopyRequestedEventHandler'),
|
Revisions_RevisionsCopyRequestedEventHandler: Symbol.for('Revisions_RevisionsCopyRequestedEventHandler'),
|
||||||
Revisions_TransitionStatusUpdatedEventHandler: Symbol.for('Revisions_TransitionStatusUpdatedEventHandler'),
|
Revisions_TransitionStatusUpdatedEventHandler: Symbol.for('Revisions_TransitionStatusUpdatedEventHandler'),
|
||||||
Revisions_ItemRemovedFromSharedVaultEventHandler: Symbol.for('Revisions_ItemRemovedFromSharedVaultEventHandler'),
|
Revisions_ItemRemovedFromSharedVaultEventHandler: Symbol.for('Revisions_ItemRemovedFromSharedVaultEventHandler'),
|
||||||
|
Revisions_TransitionRequestedEventHandler: Symbol.for('Revisions_TransitionRequestedEventHandler'),
|
||||||
// Services
|
// Services
|
||||||
Revisions_CrossServiceTokenDecoder: Symbol.for('Revisions_CrossServiceTokenDecoder'),
|
Revisions_CrossServiceTokenDecoder: Symbol.for('Revisions_CrossServiceTokenDecoder'),
|
||||||
Revisions_DomainEventSubscriberFactory: Symbol.for('Revisions_DomainEventSubscriberFactory'),
|
Revisions_DomainEventSubscriberFactory: Symbol.for('Revisions_DomainEventSubscriberFactory'),
|
||||||
|
|||||||
@@ -4,6 +4,6 @@ export interface DomainEventFactoryInterface {
|
|||||||
createTransitionStatusUpdatedEvent(dto: {
|
createTransitionStatusUpdatedEvent(dto: {
|
||||||
userUuid: string
|
userUuid: string
|
||||||
transitionType: 'items' | 'revisions'
|
transitionType: 'items' | 'revisions'
|
||||||
status: 'STARTED' | 'FAILED' | 'FINISHED'
|
status: 'STARTED' | 'IN_PROGRESS' | 'FAILED' | 'FINISHED'
|
||||||
}): TransitionStatusUpdatedEvent
|
}): TransitionStatusUpdatedEvent
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import { ItemDumpedEvent } from '@standardnotes/domain-events'
|
import { ItemDumpedEvent } from '@standardnotes/domain-events'
|
||||||
|
import { Logger } from 'winston'
|
||||||
|
import { Uuid, ContentType, Dates } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
import { DumpRepositoryInterface } from '../Dump/DumpRepositoryInterface'
|
import { DumpRepositoryInterface } from '../Dump/DumpRepositoryInterface'
|
||||||
import { Revision } from '../Revision/Revision'
|
import { Revision } from '../Revision/Revision'
|
||||||
import { RevisionRepositoryInterface } from '../Revision/RevisionRepositoryInterface'
|
import { RevisionRepositoryInterface } from '../Revision/RevisionRepositoryInterface'
|
||||||
@@ -11,11 +14,22 @@ describe('ItemDumpedEventHandler', () => {
|
|||||||
let revisionRepositoryResolver: RevisionRepositoryResolverInterface
|
let revisionRepositoryResolver: RevisionRepositoryResolverInterface
|
||||||
let revision: Revision
|
let revision: Revision
|
||||||
let event: ItemDumpedEvent
|
let event: ItemDumpedEvent
|
||||||
|
let logger: Logger
|
||||||
|
|
||||||
const createHandler = () => new ItemDumpedEventHandler(dumpRepository, revisionRepositoryResolver)
|
const createHandler = () => new ItemDumpedEventHandler(dumpRepository, revisionRepositoryResolver, logger)
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
revision = {} as jest.Mocked<Revision>
|
revision = Revision.create({
|
||||||
|
itemUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
||||||
|
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
||||||
|
content: 'test',
|
||||||
|
contentType: ContentType.create('Note').getValue(),
|
||||||
|
itemsKeyId: 'test',
|
||||||
|
encItemKey: 'test',
|
||||||
|
authHash: 'test',
|
||||||
|
creationDate: new Date(1),
|
||||||
|
dates: Dates.create(new Date(1), new Date(2)).getValue(),
|
||||||
|
}).getValue()
|
||||||
|
|
||||||
dumpRepository = {} as jest.Mocked<DumpRepositoryInterface>
|
dumpRepository = {} as jest.Mocked<DumpRepositoryInterface>
|
||||||
dumpRepository.getRevisionFromDumpPath = jest.fn().mockReturnValue(revision)
|
dumpRepository.getRevisionFromDumpPath = jest.fn().mockReturnValue(revision)
|
||||||
@@ -32,6 +46,10 @@ describe('ItemDumpedEventHandler', () => {
|
|||||||
fileDumpPath: 'foobar',
|
fileDumpPath: 'foobar',
|
||||||
roleNames: ['CORE_USER'],
|
roleNames: ['CORE_USER'],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger = {} as jest.Mocked<Logger>
|
||||||
|
logger.debug = jest.fn()
|
||||||
|
logger.error = jest.fn()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should save a revision from file dump', async () => {
|
it('should save a revision from file dump', async () => {
|
||||||
|
|||||||
@@ -3,16 +3,20 @@ import { DomainEventHandlerInterface, ItemDumpedEvent } from '@standardnotes/dom
|
|||||||
import { DumpRepositoryInterface } from '../Dump/DumpRepositoryInterface'
|
import { DumpRepositoryInterface } from '../Dump/DumpRepositoryInterface'
|
||||||
import { RevisionRepositoryResolverInterface } from '../Revision/RevisionRepositoryResolverInterface'
|
import { RevisionRepositoryResolverInterface } from '../Revision/RevisionRepositoryResolverInterface'
|
||||||
import { RoleNameCollection } from '@standardnotes/domain-core'
|
import { RoleNameCollection } from '@standardnotes/domain-core'
|
||||||
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
export class ItemDumpedEventHandler implements DomainEventHandlerInterface {
|
export class ItemDumpedEventHandler implements DomainEventHandlerInterface {
|
||||||
constructor(
|
constructor(
|
||||||
private dumpRepository: DumpRepositoryInterface,
|
private dumpRepository: DumpRepositoryInterface,
|
||||||
private revisionRepositoryResolver: RevisionRepositoryResolverInterface,
|
private revisionRepositoryResolver: RevisionRepositoryResolverInterface,
|
||||||
|
private logger: Logger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async handle(event: ItemDumpedEvent): Promise<void> {
|
async handle(event: ItemDumpedEvent): Promise<void> {
|
||||||
const revision = await this.dumpRepository.getRevisionFromDumpPath(event.payload.fileDumpPath)
|
const revision = await this.dumpRepository.getRevisionFromDumpPath(event.payload.fileDumpPath)
|
||||||
if (revision === null) {
|
if (revision === null) {
|
||||||
|
this.logger.error(`Revision not found for dump path ${event.payload.fileDumpPath}`)
|
||||||
|
|
||||||
await this.dumpRepository.removeDump(event.payload.fileDumpPath)
|
await this.dumpRepository.removeDump(event.payload.fileDumpPath)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -20,6 +24,8 @@ export class ItemDumpedEventHandler implements DomainEventHandlerInterface {
|
|||||||
|
|
||||||
const roleNamesOrError = RoleNameCollection.create(event.payload.roleNames)
|
const roleNamesOrError = RoleNameCollection.create(event.payload.roleNames)
|
||||||
if (roleNamesOrError.isFailed()) {
|
if (roleNamesOrError.isFailed()) {
|
||||||
|
this.logger.error(`Invalid role names ${event.payload.roleNames}`)
|
||||||
|
|
||||||
await this.dumpRepository.removeDump(event.payload.fileDumpPath)
|
await this.dumpRepository.removeDump(event.payload.fileDumpPath)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -28,7 +34,10 @@ export class ItemDumpedEventHandler implements DomainEventHandlerInterface {
|
|||||||
|
|
||||||
const revisionRepository = this.revisionRepositoryResolver.resolve(roleNames)
|
const revisionRepository = this.revisionRepositoryResolver.resolve(roleNames)
|
||||||
|
|
||||||
await revisionRepository.insert(revision)
|
const successfullyInserted = await revisionRepository.insert(revision)
|
||||||
|
if (!successfullyInserted) {
|
||||||
|
this.logger.error(`Could not insert revision ${revision.id.toString()}`)
|
||||||
|
}
|
||||||
|
|
||||||
await this.dumpRepository.removeDump(event.payload.fileDumpPath)
|
await this.dumpRepository.removeDump(event.payload.fileDumpPath)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { DomainEventHandlerInterface, TransitionRequestedEvent } from '@standardnotes/domain-events'
|
||||||
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
|
import { TriggerTransitionFromPrimaryToSecondaryDatabaseForUser } from '../UseCase/Transition/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser'
|
||||||
|
|
||||||
|
export class TransitionRequestedEventHandler implements DomainEventHandlerInterface {
|
||||||
|
constructor(
|
||||||
|
private triggerTransitionFromPrimaryToSecondaryDatabaseForUser: TriggerTransitionFromPrimaryToSecondaryDatabaseForUser,
|
||||||
|
private logger: Logger,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async handle(event: TransitionRequestedEvent): Promise<void> {
|
||||||
|
if (event.payload.type !== 'revisions') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.info(`Handling transition requested event for user ${event.payload.userUuid}`)
|
||||||
|
|
||||||
|
const result = await this.triggerTransitionFromPrimaryToSecondaryDatabaseForUser.execute({
|
||||||
|
userUuid: event.payload.userUuid,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (result.isFailed()) {
|
||||||
|
this.logger.error(`Failed to trigger transition for user ${event.payload.userUuid}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,6 +29,14 @@ export class TransitionStatusUpdatedEventHandler implements DomainEventHandlerIn
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (event.payload.status === 'STARTED' && event.payload.transitionType === 'revisions') {
|
if (event.payload.status === 'STARTED' && event.payload.transitionType === 'revisions') {
|
||||||
|
await this.domainEventPublisher.publish(
|
||||||
|
this.domainEventFactory.createTransitionStatusUpdatedEvent({
|
||||||
|
userUuid: event.payload.userUuid,
|
||||||
|
status: 'IN_PROGRESS',
|
||||||
|
transitionType: 'revisions',
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
const result = await this.transitionRevisionsFromPrimaryToSecondaryDatabaseForUser.execute({
|
const result = await this.transitionRevisionsFromPrimaryToSecondaryDatabaseForUser.execute({
|
||||||
userUuid: event.payload.userUuid,
|
userUuid: event.payload.userUuid,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export class Revision extends Entity<RevisionProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isIdenticalTo(revision: Revision): boolean {
|
isIdenticalTo(revision: Revision): boolean {
|
||||||
if (this._id.toString() !== revision._id.toString()) {
|
if (this._id.toString().toLowerCase() !== revision._id.toString().toLowerCase()) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
-438
@@ -1,438 +0,0 @@
|
|||||||
import { Logger } from 'winston'
|
|
||||||
|
|
||||||
import { RevisionRepositoryInterface } from '../../../Revision/RevisionRepositoryInterface'
|
|
||||||
import { TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser } from './TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser'
|
|
||||||
import { Revision } from '../../../Revision/Revision'
|
|
||||||
import { ContentType, Dates, UniqueEntityId, Uuid } from '@standardnotes/domain-core'
|
|
||||||
import { TimerInterface } from '@standardnotes/time'
|
|
||||||
|
|
||||||
describe('TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser', () => {
|
|
||||||
let primaryRevisionRepository: RevisionRepositoryInterface
|
|
||||||
let secondaryRevisionRepository: RevisionRepositoryInterface | null
|
|
||||||
let logger: Logger
|
|
||||||
let primaryRevision1: Revision
|
|
||||||
let primaryRevision2: Revision
|
|
||||||
let secondaryRevision1: Revision
|
|
||||||
let secondaryRevision2: Revision
|
|
||||||
let timer: TimerInterface
|
|
||||||
|
|
||||||
const createUseCase = () =>
|
|
||||||
new TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser(
|
|
||||||
primaryRevisionRepository,
|
|
||||||
secondaryRevisionRepository,
|
|
||||||
timer,
|
|
||||||
logger,
|
|
||||||
1,
|
|
||||||
)
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
primaryRevision1 = Revision.create(
|
|
||||||
{
|
|
||||||
itemUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
|
||||||
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
|
||||||
content: 'test',
|
|
||||||
contentType: ContentType.create('Note').getValue(),
|
|
||||||
itemsKeyId: 'test',
|
|
||||||
encItemKey: 'test',
|
|
||||||
authHash: 'test',
|
|
||||||
creationDate: new Date(1),
|
|
||||||
dates: Dates.create(new Date(1), new Date(2)).getValue(),
|
|
||||||
},
|
|
||||||
new UniqueEntityId('00000000-0000-0000-0000-000000000000'),
|
|
||||||
).getValue()
|
|
||||||
|
|
||||||
primaryRevision2 = Revision.create(
|
|
||||||
{
|
|
||||||
itemUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc2d').getValue(),
|
|
||||||
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
|
||||||
content: 'test',
|
|
||||||
contentType: ContentType.create('Note').getValue(),
|
|
||||||
itemsKeyId: 'test',
|
|
||||||
encItemKey: 'test',
|
|
||||||
authHash: 'test',
|
|
||||||
creationDate: new Date(1),
|
|
||||||
dates: Dates.create(new Date(1), new Date(2)).getValue(),
|
|
||||||
},
|
|
||||||
new UniqueEntityId('00000000-0000-0000-0000-000000000001'),
|
|
||||||
).getValue()
|
|
||||||
|
|
||||||
secondaryRevision1 = Revision.create(
|
|
||||||
{
|
|
||||||
itemUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
|
||||||
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
|
||||||
content: 'test',
|
|
||||||
contentType: ContentType.create('Note').getValue(),
|
|
||||||
itemsKeyId: 'test',
|
|
||||||
encItemKey: 'test',
|
|
||||||
authHash: 'test',
|
|
||||||
creationDate: new Date(1),
|
|
||||||
dates: Dates.create(new Date(1), new Date(2)).getValue(),
|
|
||||||
},
|
|
||||||
new UniqueEntityId('00000000-0000-0000-0000-000000000000'),
|
|
||||||
).getValue()
|
|
||||||
|
|
||||||
secondaryRevision2 = Revision.create(
|
|
||||||
{
|
|
||||||
itemUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc2d').getValue(),
|
|
||||||
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
|
||||||
content: 'test',
|
|
||||||
contentType: ContentType.create('Note').getValue(),
|
|
||||||
itemsKeyId: 'test',
|
|
||||||
encItemKey: 'test',
|
|
||||||
authHash: 'test',
|
|
||||||
creationDate: new Date(1),
|
|
||||||
dates: Dates.create(new Date(1), new Date(2)).getValue(),
|
|
||||||
},
|
|
||||||
new UniqueEntityId('00000000-0000-0000-0000-000000000001'),
|
|
||||||
).getValue()
|
|
||||||
|
|
||||||
primaryRevisionRepository = {} as jest.Mocked<RevisionRepositoryInterface>
|
|
||||||
primaryRevisionRepository.countByUserUuid = jest.fn().mockResolvedValue(2)
|
|
||||||
primaryRevisionRepository.findByUserUuid = jest
|
|
||||||
.fn()
|
|
||||||
.mockResolvedValueOnce([primaryRevision1])
|
|
||||||
.mockResolvedValueOnce([primaryRevision2])
|
|
||||||
.mockResolvedValueOnce([primaryRevision1])
|
|
||||||
.mockResolvedValueOnce([primaryRevision2])
|
|
||||||
primaryRevisionRepository.removeByUserUuid = jest.fn().mockResolvedValue(undefined)
|
|
||||||
|
|
||||||
secondaryRevisionRepository = {} as jest.Mocked<RevisionRepositoryInterface>
|
|
||||||
secondaryRevisionRepository.insert = jest.fn().mockResolvedValue(true)
|
|
||||||
secondaryRevisionRepository.removeByUserUuid = jest.fn().mockResolvedValue(undefined)
|
|
||||||
secondaryRevisionRepository.countByUserUuid = jest.fn().mockResolvedValue(2)
|
|
||||||
secondaryRevisionRepository.findOneByUuid = jest
|
|
||||||
.fn()
|
|
||||||
.mockResolvedValueOnce(secondaryRevision1)
|
|
||||||
.mockResolvedValueOnce(secondaryRevision2)
|
|
||||||
|
|
||||||
logger = {} as jest.Mocked<Logger>
|
|
||||||
logger.error = jest.fn()
|
|
||||||
logger.info = jest.fn()
|
|
||||||
logger.debug = jest.fn()
|
|
||||||
|
|
||||||
timer = {} as jest.Mocked<TimerInterface>
|
|
||||||
timer.sleep = jest.fn()
|
|
||||||
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(123)
|
|
||||||
timer.convertMicrosecondsToTimeStructure = jest.fn().mockReturnValue({
|
|
||||||
days: 0,
|
|
||||||
hours: 0,
|
|
||||||
minutes: 0,
|
|
||||||
seconds: 0,
|
|
||||||
milliseconds: 0,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('successfull transition', () => {
|
|
||||||
it('should transition Revisions from primary to secondary database', async () => {
|
|
||||||
const useCase = createUseCase()
|
|
||||||
|
|
||||||
const result = await useCase.execute({
|
|
||||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(result.isFailed()).toBeFalsy()
|
|
||||||
|
|
||||||
expect(primaryRevisionRepository.countByUserUuid).toHaveBeenCalledTimes(3)
|
|
||||||
expect(primaryRevisionRepository.countByUserUuid).toHaveBeenCalledWith(
|
|
||||||
Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
|
||||||
)
|
|
||||||
expect(primaryRevisionRepository.findByUserUuid).toHaveBeenCalledTimes(4)
|
|
||||||
expect(primaryRevisionRepository.findByUserUuid).toHaveBeenNthCalledWith(1, {
|
|
||||||
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
|
||||||
limit: 1,
|
|
||||||
offset: 0,
|
|
||||||
})
|
|
||||||
expect(primaryRevisionRepository.findByUserUuid).toHaveBeenNthCalledWith(2, {
|
|
||||||
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
|
||||||
limit: 1,
|
|
||||||
offset: 1,
|
|
||||||
})
|
|
||||||
expect(primaryRevisionRepository.findByUserUuid).toHaveBeenNthCalledWith(3, {
|
|
||||||
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
|
||||||
limit: 1,
|
|
||||||
offset: 0,
|
|
||||||
})
|
|
||||||
expect(primaryRevisionRepository.findByUserUuid).toHaveBeenNthCalledWith(4, {
|
|
||||||
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
|
||||||
limit: 1,
|
|
||||||
offset: 1,
|
|
||||||
})
|
|
||||||
expect((secondaryRevisionRepository as RevisionRepositoryInterface).insert).toHaveBeenCalledTimes(2)
|
|
||||||
expect((secondaryRevisionRepository as RevisionRepositoryInterface).insert).toHaveBeenCalledWith(primaryRevision1)
|
|
||||||
expect((secondaryRevisionRepository as RevisionRepositoryInterface).insert).toHaveBeenCalledWith(primaryRevision2)
|
|
||||||
expect((secondaryRevisionRepository as RevisionRepositoryInterface).removeByUserUuid).not.toHaveBeenCalled()
|
|
||||||
expect(primaryRevisionRepository.removeByUserUuid).toHaveBeenCalledTimes(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should log an error if deleting Revisions from primary database fails', async () => {
|
|
||||||
primaryRevisionRepository.removeByUserUuid = jest.fn().mockRejectedValue(new Error('error'))
|
|
||||||
|
|
||||||
const useCase = createUseCase()
|
|
||||||
|
|
||||||
const result = await useCase.execute({
|
|
||||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(result.isFailed()).toBeFalsy()
|
|
||||||
|
|
||||||
expect(logger.error).toHaveBeenCalledTimes(1)
|
|
||||||
expect(logger.error).toHaveBeenCalledWith(
|
|
||||||
'Failed to clean up primary database revisions for user 00000000-0000-0000-0000-000000000000: Errored when deleting revisions for user 00000000-0000-0000-0000-000000000000: error',
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('failed transition', () => {
|
|
||||||
it('should remove Revisions from secondary database if integrity check fails', async () => {
|
|
||||||
const secondaryRevision2WithDifferentContent = Revision.create({
|
|
||||||
...secondaryRevision2.props,
|
|
||||||
content: 'different-content',
|
|
||||||
}).getValue()
|
|
||||||
|
|
||||||
;(secondaryRevisionRepository as RevisionRepositoryInterface).findOneByUuid = jest
|
|
||||||
.fn()
|
|
||||||
.mockResolvedValueOnce(secondaryRevision1)
|
|
||||||
.mockResolvedValueOnce(secondaryRevision2WithDifferentContent)
|
|
||||||
|
|
||||||
const useCase = createUseCase()
|
|
||||||
|
|
||||||
const result = await useCase.execute({
|
|
||||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(result.isFailed()).toBeTruthy()
|
|
||||||
expect(result.getError()).toEqual(
|
|
||||||
'Revision 00000000-0000-0000-0000-000000000001 is not identical in primary and secondary database',
|
|
||||||
)
|
|
||||||
|
|
||||||
expect((secondaryRevisionRepository as RevisionRepositoryInterface).removeByUserUuid).toHaveBeenCalledTimes(1)
|
|
||||||
expect(primaryRevisionRepository.removeByUserUuid).not.toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should remove Revisions from secondary database if migrating Revisions fails', async () => {
|
|
||||||
primaryRevisionRepository.findByUserUuid = jest
|
|
||||||
.fn()
|
|
||||||
.mockResolvedValueOnce([primaryRevision1])
|
|
||||||
.mockRejectedValueOnce(new Error('error'))
|
|
||||||
|
|
||||||
const useCase = createUseCase()
|
|
||||||
|
|
||||||
const result = await useCase.execute({
|
|
||||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(result.isFailed()).toBeTruthy()
|
|
||||||
expect(result.getError()).toEqual(
|
|
||||||
'Errored when migrating revisions for user 00000000-0000-0000-0000-000000000000: error',
|
|
||||||
)
|
|
||||||
|
|
||||||
expect((secondaryRevisionRepository as RevisionRepositoryInterface).removeByUserUuid).toHaveBeenCalledTimes(1)
|
|
||||||
expect(primaryRevisionRepository.removeByUserUuid).not.toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should return an error for a specific revision if it errors when saving to secondary database', async () => {
|
|
||||||
;(secondaryRevisionRepository as RevisionRepositoryInterface).insert = jest
|
|
||||||
.fn()
|
|
||||||
.mockResolvedValueOnce(true)
|
|
||||||
.mockRejectedValueOnce(new Error('error'))
|
|
||||||
|
|
||||||
const useCase = createUseCase()
|
|
||||||
|
|
||||||
const result = await useCase.execute({
|
|
||||||
userUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(result.isFailed()).toBeTruthy()
|
|
||||||
expect(result.getError()).toEqual(
|
|
||||||
'Errored when saving revision 00000000-0000-0000-0000-000000000001 to secondary database: error',
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should log an error if deleting Revisions from secondary database fails upon migration failure', async () => {
|
|
||||||
primaryRevisionRepository.findByUserUuid = jest
|
|
||||||
.fn()
|
|
||||||
.mockResolvedValueOnce([primaryRevision1])
|
|
||||||
.mockRejectedValueOnce(new Error('error'))
|
|
||||||
;(secondaryRevisionRepository as RevisionRepositoryInterface).removeByUserUuid = jest
|
|
||||||
.fn()
|
|
||||||
.mockRejectedValue(new Error('error'))
|
|
||||||
|
|
||||||
const useCase = createUseCase()
|
|
||||||
|
|
||||||
const result = await useCase.execute({
|
|
||||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(result.isFailed()).toBeTruthy()
|
|
||||||
|
|
||||||
expect(logger.error).toHaveBeenCalledTimes(1)
|
|
||||||
expect(logger.error).toHaveBeenCalledWith(
|
|
||||||
'Failed to clean up secondary database revisions for user 00000000-0000-0000-0000-000000000000: Errored when deleting revisions for user 00000000-0000-0000-0000-000000000000: error',
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should log an error if deleting Revisions from secondary database fails upon integrity check failure', async () => {
|
|
||||||
const secondaryRevision2WithDifferentContent = Revision.create({
|
|
||||||
...secondaryRevision2.props,
|
|
||||||
content: 'different-content',
|
|
||||||
}).getValue()
|
|
||||||
|
|
||||||
;(secondaryRevisionRepository as RevisionRepositoryInterface).findOneByUuid = jest
|
|
||||||
.fn()
|
|
||||||
.mockResolvedValueOnce(secondaryRevision1)
|
|
||||||
.mockResolvedValueOnce(secondaryRevision2WithDifferentContent)
|
|
||||||
;(secondaryRevisionRepository as RevisionRepositoryInterface).removeByUserUuid = jest
|
|
||||||
.fn()
|
|
||||||
.mockRejectedValue(new Error('error'))
|
|
||||||
|
|
||||||
const useCase = createUseCase()
|
|
||||||
|
|
||||||
const result = await useCase.execute({
|
|
||||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(result.isFailed()).toBeTruthy()
|
|
||||||
|
|
||||||
expect(logger.error).toHaveBeenCalledTimes(1)
|
|
||||||
expect(logger.error).toHaveBeenCalledWith(
|
|
||||||
'Failed to clean up secondary database revisions for user 00000000-0000-0000-0000-000000000000: Errored when deleting revisions for user 00000000-0000-0000-0000-000000000000: error',
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should not perform the transition if secondary Revision repository is not set', async () => {
|
|
||||||
secondaryRevisionRepository = null
|
|
||||||
|
|
||||||
const useCase = createUseCase()
|
|
||||||
|
|
||||||
const result = await useCase.execute({
|
|
||||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(result.isFailed()).toBeTruthy()
|
|
||||||
expect(result.getError()).toEqual('Secondary revision repository is not set')
|
|
||||||
|
|
||||||
expect(primaryRevisionRepository.countByUserUuid).not.toHaveBeenCalled()
|
|
||||||
expect(primaryRevisionRepository.findByUserUuid).not.toHaveBeenCalled()
|
|
||||||
expect(primaryRevisionRepository.removeByUserUuid).not.toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should not perform the transition if the user uuid is invalid', async () => {
|
|
||||||
const useCase = createUseCase()
|
|
||||||
|
|
||||||
const result = await useCase.execute({
|
|
||||||
userUuid: 'invalid-uuid',
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(result.isFailed()).toBeTruthy()
|
|
||||||
expect(result.getError()).toEqual('Given value is not a valid uuid: invalid-uuid')
|
|
||||||
|
|
||||||
expect(primaryRevisionRepository.countByUserUuid).not.toHaveBeenCalled()
|
|
||||||
expect(primaryRevisionRepository.findByUserUuid).not.toHaveBeenCalled()
|
|
||||||
expect(primaryRevisionRepository.removeByUserUuid).not.toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should fail integrity check if the Revision count is not the same in both databases', async () => {
|
|
||||||
;(secondaryRevisionRepository as RevisionRepositoryInterface).countByUserUuid = jest.fn().mockResolvedValue(1)
|
|
||||||
|
|
||||||
const useCase = createUseCase()
|
|
||||||
|
|
||||||
const result = await useCase.execute({
|
|
||||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(result.isFailed()).toBeTruthy()
|
|
||||||
expect(result.getError()).toEqual(
|
|
||||||
'Total revisions count for user 00000000-0000-0000-0000-000000000000 in primary database (2) does not match total revisions count in secondary database (1)',
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(primaryRevisionRepository.countByUserUuid).toHaveBeenCalledTimes(3)
|
|
||||||
expect(primaryRevisionRepository.countByUserUuid).toHaveBeenCalledWith(
|
|
||||||
Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
|
||||||
)
|
|
||||||
expect((secondaryRevisionRepository as RevisionRepositoryInterface).countByUserUuid).toHaveBeenCalledTimes(1)
|
|
||||||
expect(primaryRevisionRepository.removeByUserUuid).not.toHaveBeenCalled()
|
|
||||||
expect((secondaryRevisionRepository as RevisionRepositoryInterface).removeByUserUuid).toHaveBeenCalledTimes(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should fail if one Revision is not found in the secondary database', async () => {
|
|
||||||
;(secondaryRevisionRepository as RevisionRepositoryInterface).findOneByUuid = jest
|
|
||||||
.fn()
|
|
||||||
.mockResolvedValueOnce(secondaryRevision1)
|
|
||||||
.mockResolvedValueOnce(null)
|
|
||||||
|
|
||||||
const useCase = createUseCase()
|
|
||||||
|
|
||||||
const result = await useCase.execute({
|
|
||||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(result.isFailed()).toBeTruthy()
|
|
||||||
expect(result.getError()).toEqual('Revision 00000000-0000-0000-0000-000000000001 not found in secondary database')
|
|
||||||
|
|
||||||
expect(primaryRevisionRepository.countByUserUuid).toHaveBeenCalledTimes(3)
|
|
||||||
expect(primaryRevisionRepository.countByUserUuid).toHaveBeenCalledWith(
|
|
||||||
Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
|
||||||
)
|
|
||||||
expect((secondaryRevisionRepository as RevisionRepositoryInterface).countByUserUuid).not.toHaveBeenCalled()
|
|
||||||
expect(primaryRevisionRepository.removeByUserUuid).not.toHaveBeenCalled()
|
|
||||||
expect((secondaryRevisionRepository as RevisionRepositoryInterface).removeByUserUuid).toHaveBeenCalledTimes(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should fail if an error is thrown during integrity check between primary and secondary database', async () => {
|
|
||||||
;(secondaryRevisionRepository as RevisionRepositoryInterface).countByUserUuid = jest
|
|
||||||
.fn()
|
|
||||||
.mockRejectedValue(new Error('error'))
|
|
||||||
|
|
||||||
const useCase = createUseCase()
|
|
||||||
|
|
||||||
const result = await useCase.execute({
|
|
||||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(result.isFailed()).toBeTruthy()
|
|
||||||
expect(result.getError()).toEqual('Errored when checking integrity between primary and secondary database: error')
|
|
||||||
|
|
||||||
expect(primaryRevisionRepository.removeByUserUuid).not.toHaveBeenCalled()
|
|
||||||
expect((secondaryRevisionRepository as RevisionRepositoryInterface).removeByUserUuid).toHaveBeenCalledTimes(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should fail if a revisions did not save in the secondary database', async () => {
|
|
||||||
;(secondaryRevisionRepository as RevisionRepositoryInterface).insert = jest.fn().mockResolvedValue(false)
|
|
||||||
|
|
||||||
const useCase = createUseCase()
|
|
||||||
|
|
||||||
const result = await useCase.execute({
|
|
||||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(result.isFailed()).toBeTruthy()
|
|
||||||
expect(result.getError()).toEqual(
|
|
||||||
'Failed to save revision 00000000-0000-0000-0000-000000000000 to secondary database',
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(primaryRevisionRepository.removeByUserUuid).not.toHaveBeenCalled()
|
|
||||||
expect((secondaryRevisionRepository as RevisionRepositoryInterface).removeByUserUuid).toHaveBeenCalledTimes(1)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should not migrate revisions if there are no revisions in the primary database', async () => {
|
|
||||||
primaryRevisionRepository.countByUserUuid = jest.fn().mockResolvedValue(0)
|
|
||||||
|
|
||||||
const useCase = createUseCase()
|
|
||||||
|
|
||||||
const result = await useCase.execute({
|
|
||||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(result.isFailed()).toBeFalsy()
|
|
||||||
|
|
||||||
expect(primaryRevisionRepository.countByUserUuid).toHaveBeenCalledTimes(1)
|
|
||||||
expect(primaryRevisionRepository.countByUserUuid).toHaveBeenCalledWith(
|
|
||||||
Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
|
||||||
)
|
|
||||||
expect(primaryRevisionRepository.findByUserUuid).not.toHaveBeenCalled()
|
|
||||||
expect((secondaryRevisionRepository as RevisionRepositoryInterface).insert).not.toHaveBeenCalled()
|
|
||||||
expect(primaryRevisionRepository.removeByUserUuid).not.toHaveBeenCalled()
|
|
||||||
expect((secondaryRevisionRepository as RevisionRepositoryInterface).removeByUserUuid).not.toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
+95
-5
@@ -1,9 +1,11 @@
|
|||||||
|
/* istanbul ignore file */
|
||||||
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
|
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
|
||||||
import { TimerInterface } from '@standardnotes/time'
|
import { TimerInterface } from '@standardnotes/time'
|
||||||
import { Logger } from 'winston'
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
import { TransitionRevisionsFromPrimaryToSecondaryDatabaseForUserDTO } from './TransitionRevisionsFromPrimaryToSecondaryDatabaseForUserDTO'
|
import { TransitionRevisionsFromPrimaryToSecondaryDatabaseForUserDTO } from './TransitionRevisionsFromPrimaryToSecondaryDatabaseForUserDTO'
|
||||||
import { RevisionRepositoryInterface } from '../../../Revision/RevisionRepositoryInterface'
|
import { RevisionRepositoryInterface } from '../../../Revision/RevisionRepositoryInterface'
|
||||||
|
import { Revision } from '../../../Revision/Revision'
|
||||||
|
|
||||||
export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements UseCaseInterface<void> {
|
export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements UseCaseInterface<void> {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -15,6 +17,8 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(dto: TransitionRevisionsFromPrimaryToSecondaryDatabaseForUserDTO): Promise<Result<void>> {
|
async execute(dto: TransitionRevisionsFromPrimaryToSecondaryDatabaseForUserDTO): Promise<Result<void>> {
|
||||||
|
this.logger.info(`Transitioning revisions for user ${dto.userUuid}`)
|
||||||
|
|
||||||
if (this.secondRevisionsRepository === null) {
|
if (this.secondRevisionsRepository === null) {
|
||||||
return Result.fail('Secondary revision repository is not set')
|
return Result.fail('Secondary revision repository is not set')
|
||||||
}
|
}
|
||||||
@@ -31,6 +35,21 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
|||||||
return Result.ok()
|
return Result.ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let newRevisionsInSecondaryCount = 0
|
||||||
|
if (await this.hasAlreadyDataInSecondaryDatabase(userUuid)) {
|
||||||
|
const newRevisions = await this.getNewRevisionsCreatedInSecondaryDatabase(userUuid)
|
||||||
|
for (const existingRevision of newRevisions.alreadyExistingInPrimary) {
|
||||||
|
await (this.secondRevisionsRepository as RevisionRepositoryInterface).removeOneByUuid(
|
||||||
|
Uuid.create(existingRevision.id.toString()).getValue(),
|
||||||
|
userUuid,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
newRevisionsInSecondaryCount = newRevisions.newRevisionsInSecondary.length
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.allowForSecondaryDatabaseToCatchUp()
|
||||||
|
|
||||||
const migrationTimeStart = this.timer.getTimestampInMicroseconds()
|
const migrationTimeStart = this.timer.getTimestampInMicroseconds()
|
||||||
|
|
||||||
this.logger.debug(`Transitioning revisions for user ${userUuid.value}`)
|
this.logger.debug(`Transitioning revisions for user ${userUuid.value}`)
|
||||||
@@ -49,7 +68,10 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
|||||||
|
|
||||||
await this.allowForSecondaryDatabaseToCatchUp()
|
await this.allowForSecondaryDatabaseToCatchUp()
|
||||||
|
|
||||||
const integrityCheckResult = await this.checkIntegrityBetweenPrimaryAndSecondaryDatabase(userUuid)
|
const integrityCheckResult = await this.checkIntegrityBetweenPrimaryAndSecondaryDatabase(
|
||||||
|
userUuid,
|
||||||
|
newRevisionsInSecondaryCount,
|
||||||
|
)
|
||||||
if (integrityCheckResult.isFailed()) {
|
if (integrityCheckResult.isFailed()) {
|
||||||
const cleanupResult = await this.deleteRevisionsForUser(userUuid, this.secondRevisionsRepository)
|
const cleanupResult = await this.deleteRevisionsForUser(userUuid, this.secondRevisionsRepository)
|
||||||
if (cleanupResult.isFailed()) {
|
if (cleanupResult.isFailed()) {
|
||||||
@@ -143,13 +165,74 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
|||||||
await this.timer.sleep(twoSecondsInMilliseconds)
|
await this.timer.sleep(twoSecondsInMilliseconds)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async hasAlreadyDataInSecondaryDatabase(userUuid: Uuid): Promise<boolean> {
|
||||||
|
const totalRevisionsCountForUserInSecondary = await (
|
||||||
|
this.secondRevisionsRepository as RevisionRepositoryInterface
|
||||||
|
).countByUserUuid(userUuid)
|
||||||
|
|
||||||
|
return totalRevisionsCountForUserInSecondary > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getNewRevisionsCreatedInSecondaryDatabase(userUuid: Uuid): Promise<{
|
||||||
|
alreadyExistingInPrimary: Revision[]
|
||||||
|
newRevisionsInSecondary: Revision[]
|
||||||
|
}> {
|
||||||
|
const revisions = await (this.secondRevisionsRepository as RevisionRepositoryInterface).findByUserUuid({
|
||||||
|
userUuid: userUuid,
|
||||||
|
})
|
||||||
|
|
||||||
|
const alreadyExistingInPrimary: Revision[] = []
|
||||||
|
const newRevisionsInSecondary: Revision[] = []
|
||||||
|
|
||||||
|
for (const revision of revisions) {
|
||||||
|
const revisionExistsInPrimary = await this.checkIfRevisionExistsInPrimaryDatabase(revision)
|
||||||
|
if (revisionExistsInPrimary) {
|
||||||
|
alreadyExistingInPrimary.push(revision)
|
||||||
|
} else {
|
||||||
|
newRevisionsInSecondary.push(revision)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
alreadyExistingInPrimary: alreadyExistingInPrimary,
|
||||||
|
newRevisionsInSecondary: newRevisionsInSecondary,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async checkIfRevisionExistsInPrimaryDatabase(revision: Revision): Promise<boolean> {
|
||||||
|
const revisionInPrimary = await this.primaryRevisionsRepository.findOneByUuid(
|
||||||
|
Uuid.create(revision.id.toString()).getValue(),
|
||||||
|
revision.props.userUuid as Uuid,
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
|
||||||
|
if (revisionInPrimary === null) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!revision.isIdenticalTo(revisionInPrimary)) {
|
||||||
|
this.logger.error(
|
||||||
|
`Revision ${revision.id.toString()} is not identical in primary and secondary database. Revision in secondary database: ${JSON.stringify(
|
||||||
|
revision,
|
||||||
|
)}, revision in primary database: ${JSON.stringify(revisionInPrimary)}`,
|
||||||
|
)
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
private async isAlreadyMigrated(userUuid: Uuid): Promise<boolean> {
|
private async isAlreadyMigrated(userUuid: Uuid): Promise<boolean> {
|
||||||
const totalRevisionsCountForUserInPrimary = await this.primaryRevisionsRepository.countByUserUuid(userUuid)
|
const totalRevisionsCountForUserInPrimary = await this.primaryRevisionsRepository.countByUserUuid(userUuid)
|
||||||
|
|
||||||
return totalRevisionsCountForUserInPrimary === 0
|
return totalRevisionsCountForUserInPrimary === 0
|
||||||
}
|
}
|
||||||
|
|
||||||
private async checkIntegrityBetweenPrimaryAndSecondaryDatabase(userUuid: Uuid): Promise<Result<boolean>> {
|
private async checkIntegrityBetweenPrimaryAndSecondaryDatabase(
|
||||||
|
userUuid: Uuid,
|
||||||
|
newRevisionsInSecondaryCount: number,
|
||||||
|
): Promise<Result<boolean>> {
|
||||||
try {
|
try {
|
||||||
const totalRevisionsCountForUserInPrimary = await this.primaryRevisionsRepository.countByUserUuid(userUuid)
|
const totalRevisionsCountForUserInPrimary = await this.primaryRevisionsRepository.countByUserUuid(userUuid)
|
||||||
|
|
||||||
@@ -179,7 +262,11 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!revision.isIdenticalTo(revisionInSecondary)) {
|
if (!revision.isIdenticalTo(revisionInSecondary)) {
|
||||||
return Result.fail(`Revision ${revision.id.toString()} is not identical in primary and secondary database`)
|
return Result.fail(
|
||||||
|
`Revision ${revision.id.toString()} is not identical in primary and secondary database. Revision in primary database: ${JSON.stringify(
|
||||||
|
revision,
|
||||||
|
)}, revision in secondary database: ${JSON.stringify(revisionInSecondary)}`,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -188,9 +275,12 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
|||||||
this.secondRevisionsRepository as RevisionRepositoryInterface
|
this.secondRevisionsRepository as RevisionRepositoryInterface
|
||||||
).countByUserUuid(userUuid)
|
).countByUserUuid(userUuid)
|
||||||
|
|
||||||
if (totalRevisionsCountForUserInPrimary !== totalRevisionsCountForUserInSecondary) {
|
if (
|
||||||
|
totalRevisionsCountForUserInPrimary + newRevisionsInSecondaryCount !==
|
||||||
|
totalRevisionsCountForUserInSecondary
|
||||||
|
) {
|
||||||
return Result.fail(
|
return Result.fail(
|
||||||
`Total revisions count for user ${userUuid.value} in primary database (${totalRevisionsCountForUserInPrimary}) does not match total revisions count in secondary database (${totalRevisionsCountForUserInSecondary})`,
|
`Total revisions count for user ${userUuid.value} in primary database (${totalRevisionsCountForUserInPrimary} + ${newRevisionsInSecondaryCount}) does not match total revisions count in secondary database (${totalRevisionsCountForUserInSecondary})`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,22 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [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
|
||||||
|
|
||||||
|
## [1.20.42](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.41...@standardnotes/scheduler-server@1.20.42) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||||
|
|
||||||
|
## [1.20.41](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.40...@standardnotes/scheduler-server@1.20.41) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||||
|
|
||||||
|
## [1.20.40](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.39...@standardnotes/scheduler-server@1.20.40) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||||
|
|
||||||
## [1.20.39](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.38...@standardnotes/scheduler-server@1.20.39) (2023-09-08)
|
## [1.20.39](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.38...@standardnotes/scheduler-server@1.20.39) (2023-09-08)
|
||||||
|
|
||||||
**Note:** Version bump only for package @standardnotes/scheduler-server
|
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/scheduler-server",
|
"name": "@standardnotes/scheduler-server",
|
||||||
"version": "1.20.39",
|
"version": "1.20.43",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,12 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.13.1](https://github.com/standardnotes/server/compare/@standardnotes/security@1.13.0...@standardnotes/security@1.13.1) (2023-09-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* adjust transitions to not create revisions during ongoing revisions transition ([106d8f9](https://github.com/standardnotes/server/commit/106d8f9192f630794ca4ddc2c4503f2c6cd196e7))
|
||||||
|
|
||||||
# [1.13.0](https://github.com/standardnotes/server/compare/@standardnotes/security@1.12.2...@standardnotes/security@1.13.0) (2023-09-06)
|
# [1.13.0](https://github.com/standardnotes/server/compare/@standardnotes/security@1.12.2...@standardnotes/security@1.13.0) (2023-09-06)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/security",
|
"name": "@standardnotes/security",
|
||||||
"version": "1.13.0",
|
"version": "1.13.1",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -25,4 +25,5 @@ export type CrossServiceTokenData = {
|
|||||||
}
|
}
|
||||||
extensionKey?: string
|
extensionKey?: string
|
||||||
ongoing_transition?: boolean
|
ongoing_transition?: boolean
|
||||||
|
ongoing_revisions_transition?: boolean
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.21.31](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.30...@standardnotes/settings@1.21.31) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/settings
|
||||||
|
|
||||||
## [1.21.30](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.29...@standardnotes/settings@1.21.30) (2023-09-07)
|
## [1.21.30](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.29...@standardnotes/settings@1.21.30) (2023-09-07)
|
||||||
|
|
||||||
**Note:** Version bump only for package @standardnotes/settings
|
**Note:** Version bump only for package @standardnotes/settings
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/settings",
|
"name": "@standardnotes/settings",
|
||||||
"version": "1.21.30",
|
"version": "1.21.31",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,57 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.95.11](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.95.10...@standardnotes/syncing-server@1.95.11) (2023-09-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **syncing-server:** binding ([e91a832](https://github.com/standardnotes/syncing-server-js/commit/e91a8321527ac269ba9822ce270184db5bc57099))
|
||||||
|
|
||||||
|
## [1.95.10](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.95.9...@standardnotes/syncing-server@1.95.10) (2023-09-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* retry failed revision transitions ([e535cd5](https://github.com/standardnotes/syncing-server-js/commit/e535cd504cf1929539ff7faf13e9c1fdd2b7bfd1))
|
||||||
|
* **syncing-server:** log syncing errors ([b9c9f74](https://github.com/standardnotes/syncing-server-js/commit/b9c9f74d0c699cf72ea6090627bd5716ac8360d7))
|
||||||
|
|
||||||
|
## [1.95.9](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.95.8...@standardnotes/syncing-server@1.95.9) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/syncing-server
|
||||||
|
|
||||||
|
## [1.95.8](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.95.7...@standardnotes/syncing-server@1.95.8) (2023-09-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **syncing-server:** allow fetching shared vault users for members ([#821](https://github.com/standardnotes/syncing-server-js/issues/821)) ([25047bf](https://github.com/standardnotes/syncing-server-js/commit/25047bf46dfabba7b12eafb59519de3f08822e45))
|
||||||
|
|
||||||
|
## [1.95.7](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.95.6...@standardnotes/syncing-server@1.95.7) (2023-09-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* comparing uuids ([0a1d162](https://github.com/standardnotes/syncing-server-js/commit/0a1d1624e818000f2e951f29323a88e6e233c755))
|
||||||
|
|
||||||
|
## [1.95.6](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.95.5...@standardnotes/syncing-server@1.95.6) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/syncing-server
|
||||||
|
|
||||||
|
## [1.95.5](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.95.4...@standardnotes/syncing-server@1.95.5) (2023-09-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* adjust transitions to not create revisions during ongoing revisions transition ([106d8f9](https://github.com/standardnotes/syncing-server-js/commit/106d8f9192f630794ca4ddc2c4503f2c6cd196e7))
|
||||||
|
|
||||||
|
## [1.95.4](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.95.3...@standardnotes/syncing-server@1.95.4) (2023-09-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* transition adjustments ([f20a947](https://github.com/standardnotes/syncing-server-js/commit/f20a947f8a555c074d8dc1543c7a8bf61d1d887e))
|
||||||
|
|
||||||
|
## [1.95.3](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.95.2...@standardnotes/syncing-server@1.95.3) (2023-09-11)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* debug sync block ([1d751c0](https://github.com/standardnotes/syncing-server-js/commit/1d751c0fbe434f661466dde804a37849f23d9b1b))
|
||||||
|
|
||||||
## [1.95.2](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.95.1...@standardnotes/syncing-server@1.95.2) (2023-09-11)
|
## [1.95.2](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.95.1...@standardnotes/syncing-server@1.95.2) (2023-09-11)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/syncing-server",
|
"name": "@standardnotes/syncing-server",
|
||||||
"version": "1.95.2",
|
"version": "1.95.11",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -666,13 +666,14 @@ export class ContainerConfigLoader {
|
|||||||
.bind<SyncItems>(TYPES.Sync_SyncItems)
|
.bind<SyncItems>(TYPES.Sync_SyncItems)
|
||||||
.toConstantValue(
|
.toConstantValue(
|
||||||
new SyncItems(
|
new SyncItems(
|
||||||
container.get(TYPES.Sync_ItemRepositoryResolver),
|
container.get<ItemRepositoryResolverInterface>(TYPES.Sync_ItemRepositoryResolver),
|
||||||
container.get(TYPES.Sync_GetItems),
|
container.get<GetItems>(TYPES.Sync_GetItems),
|
||||||
container.get(TYPES.Sync_SaveItems),
|
container.get<SaveItems>(TYPES.Sync_SaveItems),
|
||||||
container.get(TYPES.Sync_GetSharedVaults),
|
container.get<GetSharedVaults>(TYPES.Sync_GetSharedVaults),
|
||||||
container.get(TYPES.Sync_GetSharedVaultInvitesSentToUser),
|
container.get<GetSharedVaultInvitesSentToUser>(TYPES.Sync_GetSharedVaultInvitesSentToUser),
|
||||||
container.get(TYPES.Sync_GetMessagesSentToUser),
|
container.get<GetMessagesSentToUser>(TYPES.Sync_GetMessagesSentToUser),
|
||||||
container.get(TYPES.Sync_GetUserNotifications),
|
container.get<GetUserNotifications>(TYPES.Sync_GetUserNotifications),
|
||||||
|
container.get<Logger>(TYPES.Sync_Logger),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
container.bind<CheckIntegrity>(TYPES.Sync_CheckIntegrity).toDynamicValue((context: interfaces.Context) => {
|
container.bind<CheckIntegrity>(TYPES.Sync_CheckIntegrity).toDynamicValue((context: interfaces.Context) => {
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export interface DomainEventFactoryInterface {
|
|||||||
createTransitionStatusUpdatedEvent(dto: {
|
createTransitionStatusUpdatedEvent(dto: {
|
||||||
userUuid: string
|
userUuid: string
|
||||||
transitionType: 'items' | 'revisions'
|
transitionType: 'items' | 'revisions'
|
||||||
status: 'STARTED' | 'FAILED' | 'FINISHED'
|
status: 'STARTED' | 'IN_PROGRESS' | 'FAILED' | 'FINISHED'
|
||||||
}): TransitionStatusUpdatedEvent
|
}): TransitionStatusUpdatedEvent
|
||||||
createEmailRequestedEvent(dto: {
|
createEmailRequestedEvent(dto: {
|
||||||
userEmail: string
|
userEmail: string
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ export class TransitionRequestedEventHandler implements DomainEventHandlerInterf
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async handle(event: TransitionRequestedEvent): Promise<void> {
|
async handle(event: TransitionRequestedEvent): Promise<void> {
|
||||||
|
if (event.payload.type !== 'items') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
this.logger.info(`Handling transition requested event for user ${event.payload.userUuid}`)
|
this.logger.info(`Handling transition requested event for user ${event.payload.userUuid}`)
|
||||||
|
|
||||||
const result = await this.triggerTransitionFromPrimaryToSecondaryDatabaseForUser.execute({
|
const result = await this.triggerTransitionFromPrimaryToSecondaryDatabaseForUser.execute({
|
||||||
|
|||||||
@@ -17,6 +17,14 @@ export class TransitionStatusUpdatedEventHandler implements DomainEventHandlerIn
|
|||||||
|
|
||||||
async handle(event: TransitionStatusUpdatedEvent): Promise<void> {
|
async handle(event: TransitionStatusUpdatedEvent): Promise<void> {
|
||||||
if (event.payload.status === 'STARTED' && event.payload.transitionType === 'items') {
|
if (event.payload.status === 'STARTED' && event.payload.transitionType === 'items') {
|
||||||
|
await this.domainEventPublisher.publish(
|
||||||
|
this.domainEventFactory.createTransitionStatusUpdatedEvent({
|
||||||
|
userUuid: event.payload.userUuid,
|
||||||
|
status: 'IN_PROGRESS',
|
||||||
|
transitionType: 'items',
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
const result = await this.transitionItemsFromPrimaryToSecondaryDatabaseForUser.execute({
|
const result = await this.transitionItemsFromPrimaryToSecondaryDatabaseForUser.execute({
|
||||||
userUuid: event.payload.userUuid,
|
userUuid: event.payload.userUuid,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export class Item extends Aggregate<ItemProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isIdenticalTo(item: Item): boolean {
|
isIdenticalTo(item: Item): boolean {
|
||||||
if (this._id.toString() !== item._id.toString()) {
|
if (this._id.toString().toLowerCase() !== item._id.toString().toLowerCase()) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-8
@@ -58,13 +58,8 @@ describe('GetSharedVaultUsers', () => {
|
|||||||
expect(result.getError()).toBe('Shared vault not found')
|
expect(result.getError()).toBe('Shared vault not found')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns error when originator is not the owner of the shared vault', async () => {
|
it('returns error when originator is not a member of the shared vault', async () => {
|
||||||
sharedVault = SharedVault.create({
|
sharedVaultUsersRepository.findBySharedVaultUuid = jest.fn().mockResolvedValue([])
|
||||||
fileUploadBytesUsed: 2,
|
|
||||||
userUuid: Uuid.create('00000000-0000-0000-0000-000000000001').getValue(),
|
|
||||||
timestamps: Timestamps.create(123, 123).getValue(),
|
|
||||||
}).getValue()
|
|
||||||
sharedVaultRepository.findByUuid = jest.fn().mockResolvedValue(sharedVault)
|
|
||||||
|
|
||||||
const useCase = createUseCase()
|
const useCase = createUseCase()
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
@@ -73,7 +68,7 @@ describe('GetSharedVaultUsers', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
expect(result.isFailed()).toBe(true)
|
expect(result.isFailed()).toBe(true)
|
||||||
expect(result.getError()).toBe('Only the owner can get shared vault users')
|
expect(result.getError()).toBe('Originator is not a member of the shared vault')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns error when shared vault uuid is invalid', async () => {
|
it('returns error when shared vault uuid is invalid', async () => {
|
||||||
|
|||||||
+7
-5
@@ -28,13 +28,15 @@ export class GetSharedVaultUsers implements UseCaseInterface<SharedVaultUser[]>
|
|||||||
return Result.fail('Shared vault not found')
|
return Result.fail('Shared vault not found')
|
||||||
}
|
}
|
||||||
|
|
||||||
const isOriginatorTheOwnerOfTheSharedVault = sharedVault.props.userUuid.equals(originatorUuid)
|
|
||||||
if (!isOriginatorTheOwnerOfTheSharedVault) {
|
|
||||||
return Result.fail('Only the owner can get shared vault users')
|
|
||||||
}
|
|
||||||
|
|
||||||
const sharedVaultUsers = await this.sharedVaultUsersRepository.findBySharedVaultUuid(sharedVaultUuid)
|
const sharedVaultUsers = await this.sharedVaultUsersRepository.findBySharedVaultUuid(sharedVaultUuid)
|
||||||
|
|
||||||
|
const isOriginatorAMember = sharedVaultUsers.some((sharedVaultUser) =>
|
||||||
|
sharedVaultUser.props.userUuid.equals(originatorUuid),
|
||||||
|
)
|
||||||
|
if (!isOriginatorAMember) {
|
||||||
|
return Result.fail('Originator is not a member of the shared vault')
|
||||||
|
}
|
||||||
|
|
||||||
return Result.ok(sharedVaultUsers)
|
return Result.ok(sharedVaultUsers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ describe('SaveItems', () => {
|
|||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
itemHashes: [itemHash1],
|
itemHashes: [itemHash1],
|
||||||
userUuid: 'user-uuid',
|
userUuid: 'user-uuid',
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
apiVersion: '1',
|
apiVersion: '1',
|
||||||
readOnlyAccess: false,
|
readOnlyAccess: false,
|
||||||
sessionUuid: 'session-uuid',
|
sessionUuid: 'session-uuid',
|
||||||
@@ -96,6 +97,7 @@ describe('SaveItems', () => {
|
|||||||
expect(saveNewItem.execute).toHaveBeenCalledWith({
|
expect(saveNewItem.execute).toHaveBeenCalledWith({
|
||||||
itemHash: itemHash1,
|
itemHash: itemHash1,
|
||||||
userUuid: 'user-uuid',
|
userUuid: 'user-uuid',
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
sessionUuid: 'session-uuid',
|
sessionUuid: 'session-uuid',
|
||||||
roleNames: ['CORE_USER'],
|
roleNames: ['CORE_USER'],
|
||||||
})
|
})
|
||||||
@@ -109,6 +111,7 @@ describe('SaveItems', () => {
|
|||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
itemHashes: [itemHash1],
|
itemHashes: [itemHash1],
|
||||||
userUuid: 'user-uuid',
|
userUuid: 'user-uuid',
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
apiVersion: '1',
|
apiVersion: '1',
|
||||||
readOnlyAccess: false,
|
readOnlyAccess: false,
|
||||||
sessionUuid: 'session-uuid',
|
sessionUuid: 'session-uuid',
|
||||||
@@ -133,6 +136,7 @@ describe('SaveItems', () => {
|
|||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
itemHashes: [itemHash1],
|
itemHashes: [itemHash1],
|
||||||
userUuid: 'user-uuid',
|
userUuid: 'user-uuid',
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
apiVersion: '1',
|
apiVersion: '1',
|
||||||
readOnlyAccess: false,
|
readOnlyAccess: false,
|
||||||
sessionUuid: 'session-uuid',
|
sessionUuid: 'session-uuid',
|
||||||
@@ -155,6 +159,7 @@ describe('SaveItems', () => {
|
|||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
itemHashes: [itemHash1],
|
itemHashes: [itemHash1],
|
||||||
userUuid: 'user-uuid',
|
userUuid: 'user-uuid',
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
apiVersion: '1',
|
apiVersion: '1',
|
||||||
readOnlyAccess: true,
|
readOnlyAccess: true,
|
||||||
sessionUuid: 'session-uuid',
|
sessionUuid: 'session-uuid',
|
||||||
@@ -178,6 +183,7 @@ describe('SaveItems', () => {
|
|||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
itemHashes: [itemHash1],
|
itemHashes: [itemHash1],
|
||||||
userUuid: 'user-uuid',
|
userUuid: 'user-uuid',
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
apiVersion: '1',
|
apiVersion: '1',
|
||||||
readOnlyAccess: false,
|
readOnlyAccess: false,
|
||||||
sessionUuid: 'session-uuid',
|
sessionUuid: 'session-uuid',
|
||||||
@@ -197,6 +203,7 @@ describe('SaveItems', () => {
|
|||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
itemHashes: [itemHash1],
|
itemHashes: [itemHash1],
|
||||||
userUuid: 'user-uuid',
|
userUuid: 'user-uuid',
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
apiVersion: '1',
|
apiVersion: '1',
|
||||||
readOnlyAccess: false,
|
readOnlyAccess: false,
|
||||||
sessionUuid: 'session-uuid',
|
sessionUuid: 'session-uuid',
|
||||||
@@ -215,6 +222,7 @@ describe('SaveItems', () => {
|
|||||||
|
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
itemHashes: [itemHash1],
|
itemHashes: [itemHash1],
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
apiVersion: '1',
|
apiVersion: '1',
|
||||||
readOnlyAccess: false,
|
readOnlyAccess: false,
|
||||||
@@ -229,6 +237,7 @@ describe('SaveItems', () => {
|
|||||||
existingItem: savedItem,
|
existingItem: savedItem,
|
||||||
sessionUuid: 'session-uuid',
|
sessionUuid: 'session-uuid',
|
||||||
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
roleNames: ['CORE_USER'],
|
roleNames: ['CORE_USER'],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -242,6 +251,7 @@ describe('SaveItems', () => {
|
|||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
itemHashes: [itemHash1],
|
itemHashes: [itemHash1],
|
||||||
userUuid: 'user-uuid',
|
userUuid: 'user-uuid',
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
apiVersion: '1',
|
apiVersion: '1',
|
||||||
readOnlyAccess: false,
|
readOnlyAccess: false,
|
||||||
sessionUuid: 'session-uuid',
|
sessionUuid: 'session-uuid',
|
||||||
@@ -267,6 +277,7 @@ describe('SaveItems', () => {
|
|||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
itemHashes: [ItemHash.create({ ...itemHash1.props, uuid: 'invalid-uuid' }).getValue()],
|
itemHashes: [ItemHash.create({ ...itemHash1.props, uuid: 'invalid-uuid' }).getValue()],
|
||||||
userUuid: 'user-uuid',
|
userUuid: 'user-uuid',
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
apiVersion: '1',
|
apiVersion: '1',
|
||||||
readOnlyAccess: false,
|
readOnlyAccess: false,
|
||||||
sessionUuid: 'session-uuid',
|
sessionUuid: 'session-uuid',
|
||||||
@@ -313,6 +324,7 @@ describe('SaveItems', () => {
|
|||||||
ItemHash.create({ ...itemHash1.props, uuid: '00000000-0000-0000-0000-000000000003' }).getValue(),
|
ItemHash.create({ ...itemHash1.props, uuid: '00000000-0000-0000-0000-000000000003' }).getValue(),
|
||||||
],
|
],
|
||||||
userUuid: 'user-uuid',
|
userUuid: 'user-uuid',
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
apiVersion: '2',
|
apiVersion: '2',
|
||||||
readOnlyAccess: false,
|
readOnlyAccess: false,
|
||||||
sessionUuid: 'session-uuid',
|
sessionUuid: 'session-uuid',
|
||||||
@@ -330,6 +342,7 @@ describe('SaveItems', () => {
|
|||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
itemHashes: [itemHash1],
|
itemHashes: [itemHash1],
|
||||||
userUuid: 'user-uuid',
|
userUuid: 'user-uuid',
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
apiVersion: '2',
|
apiVersion: '2',
|
||||||
readOnlyAccess: false,
|
readOnlyAccess: false,
|
||||||
sessionUuid: 'session-uuid',
|
sessionUuid: 'session-uuid',
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ export class SaveItems implements UseCaseInterface<SaveItemsResult> {
|
|||||||
sessionUuid: dto.sessionUuid,
|
sessionUuid: dto.sessionUuid,
|
||||||
performingUserUuid: dto.userUuid,
|
performingUserUuid: dto.userUuid,
|
||||||
roleNames: dto.roleNames,
|
roleNames: dto.roleNames,
|
||||||
|
onGoingRevisionsTransition: dto.onGoingRevisionsTransition,
|
||||||
})
|
})
|
||||||
if (udpatedItemOrError.isFailed()) {
|
if (udpatedItemOrError.isFailed()) {
|
||||||
this.logger.error(
|
this.logger.error(
|
||||||
@@ -109,6 +110,7 @@ export class SaveItems implements UseCaseInterface<SaveItemsResult> {
|
|||||||
itemHash,
|
itemHash,
|
||||||
sessionUuid: dto.sessionUuid,
|
sessionUuid: dto.sessionUuid,
|
||||||
roleNames: dto.roleNames,
|
roleNames: dto.roleNames,
|
||||||
|
onGoingRevisionsTransition: dto.onGoingRevisionsTransition,
|
||||||
})
|
})
|
||||||
if (newItemOrError.isFailed()) {
|
if (newItemOrError.isFailed()) {
|
||||||
this.logger.error(
|
this.logger.error(
|
||||||
|
|||||||
@@ -8,4 +8,5 @@ export interface SaveItemsDTO {
|
|||||||
sessionUuid: string | null
|
sessionUuid: string | null
|
||||||
snjsVersion: string
|
snjsVersion: string
|
||||||
roleNames: string[]
|
roleNames: string[]
|
||||||
|
onGoingRevisionsTransition: boolean
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ describe('SaveNewItem', () => {
|
|||||||
|
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
roleNames: [RoleName.NAMES.CoreUser],
|
roleNames: [RoleName.NAMES.CoreUser],
|
||||||
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
||||||
itemHash: itemHash1,
|
itemHash: itemHash1,
|
||||||
@@ -112,6 +113,7 @@ describe('SaveNewItem', () => {
|
|||||||
|
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
onGoingRevisionsTransition: true,
|
||||||
roleNames: [RoleName.NAMES.CoreUser],
|
roleNames: [RoleName.NAMES.CoreUser],
|
||||||
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
||||||
itemHash: itemHash1,
|
itemHash: itemHash1,
|
||||||
@@ -132,6 +134,7 @@ describe('SaveNewItem', () => {
|
|||||||
|
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
roleNames: [RoleName.NAMES.CoreUser],
|
roleNames: [RoleName.NAMES.CoreUser],
|
||||||
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
||||||
itemHash: itemHash1,
|
itemHash: itemHash1,
|
||||||
@@ -150,6 +153,7 @@ describe('SaveNewItem', () => {
|
|||||||
|
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
roleNames: [RoleName.NAMES.CoreUser],
|
roleNames: [RoleName.NAMES.CoreUser],
|
||||||
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
||||||
itemHash: itemHash1,
|
itemHash: itemHash1,
|
||||||
@@ -170,6 +174,7 @@ describe('SaveNewItem', () => {
|
|||||||
|
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
roleNames: [RoleName.NAMES.CoreUser],
|
roleNames: [RoleName.NAMES.CoreUser],
|
||||||
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
||||||
itemHash: itemHash1,
|
itemHash: itemHash1,
|
||||||
@@ -190,6 +195,7 @@ describe('SaveNewItem', () => {
|
|||||||
|
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
roleNames: [RoleName.NAMES.CoreUser],
|
roleNames: [RoleName.NAMES.CoreUser],
|
||||||
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
||||||
itemHash: itemHash1,
|
itemHash: itemHash1,
|
||||||
@@ -208,6 +214,7 @@ describe('SaveNewItem', () => {
|
|||||||
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
||||||
itemHash: itemHash1,
|
itemHash: itemHash1,
|
||||||
roleNames: [RoleName.NAMES.CoreUser],
|
roleNames: [RoleName.NAMES.CoreUser],
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result.isFailed()).toBeTruthy()
|
expect(result.isFailed()).toBeTruthy()
|
||||||
@@ -218,6 +225,7 @@ describe('SaveNewItem', () => {
|
|||||||
|
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
||||||
itemHash: itemHash1,
|
itemHash: itemHash1,
|
||||||
roleNames: ['invalid'],
|
roleNames: ['invalid'],
|
||||||
@@ -231,6 +239,7 @@ describe('SaveNewItem', () => {
|
|||||||
|
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
roleNames: [RoleName.NAMES.CoreUser],
|
roleNames: [RoleName.NAMES.CoreUser],
|
||||||
sessionUuid: '00000000-0000-0000-0000-00000000000',
|
sessionUuid: '00000000-0000-0000-0000-00000000000',
|
||||||
itemHash: itemHash1,
|
itemHash: itemHash1,
|
||||||
@@ -249,6 +258,7 @@ describe('SaveNewItem', () => {
|
|||||||
|
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
roleNames: [RoleName.NAMES.CoreUser],
|
roleNames: [RoleName.NAMES.CoreUser],
|
||||||
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
||||||
itemHash: itemHash1,
|
itemHash: itemHash1,
|
||||||
@@ -267,6 +277,7 @@ describe('SaveNewItem', () => {
|
|||||||
|
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
roleNames: [RoleName.NAMES.CoreUser],
|
roleNames: [RoleName.NAMES.CoreUser],
|
||||||
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
||||||
itemHash: itemHash1,
|
itemHash: itemHash1,
|
||||||
@@ -285,6 +296,7 @@ describe('SaveNewItem', () => {
|
|||||||
|
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
roleNames: [RoleName.NAMES.CoreUser],
|
roleNames: [RoleName.NAMES.CoreUser],
|
||||||
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
||||||
itemHash: itemHash1,
|
itemHash: itemHash1,
|
||||||
@@ -305,6 +317,7 @@ describe('SaveNewItem', () => {
|
|||||||
|
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
roleNames: [RoleName.NAMES.CoreUser],
|
roleNames: [RoleName.NAMES.CoreUser],
|
||||||
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
||||||
itemHash: itemHash1,
|
itemHash: itemHash1,
|
||||||
@@ -328,6 +341,7 @@ describe('SaveNewItem', () => {
|
|||||||
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
||||||
itemHash: itemHash1,
|
itemHash: itemHash1,
|
||||||
roleNames: [RoleName.NAMES.CoreUser],
|
roleNames: [RoleName.NAMES.CoreUser],
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result.isFailed()).toBeTruthy()
|
expect(result.isFailed()).toBeTruthy()
|
||||||
@@ -344,6 +358,7 @@ describe('SaveNewItem', () => {
|
|||||||
|
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
roleNames: [RoleName.NAMES.CoreUser],
|
roleNames: [RoleName.NAMES.CoreUser],
|
||||||
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
||||||
itemHash: itemHash1,
|
itemHash: itemHash1,
|
||||||
@@ -371,6 +386,7 @@ describe('SaveNewItem', () => {
|
|||||||
|
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
roleNames: [RoleName.NAMES.CoreUser],
|
roleNames: [RoleName.NAMES.CoreUser],
|
||||||
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
||||||
itemHash: itemHash1,
|
itemHash: itemHash1,
|
||||||
@@ -393,6 +409,7 @@ describe('SaveNewItem', () => {
|
|||||||
|
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
roleNames: [RoleName.NAMES.CoreUser],
|
roleNames: [RoleName.NAMES.CoreUser],
|
||||||
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
||||||
itemHash: itemHash1,
|
itemHash: itemHash1,
|
||||||
@@ -412,6 +429,7 @@ describe('SaveNewItem', () => {
|
|||||||
|
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
roleNames: [RoleName.NAMES.CoreUser],
|
roleNames: [RoleName.NAMES.CoreUser],
|
||||||
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
||||||
itemHash: itemHash1,
|
itemHash: itemHash1,
|
||||||
@@ -435,6 +453,7 @@ describe('SaveNewItem', () => {
|
|||||||
|
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
roleNames: [RoleName.NAMES.CoreUser],
|
roleNames: [RoleName.NAMES.CoreUser],
|
||||||
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
||||||
itemHash: itemHash1,
|
itemHash: itemHash1,
|
||||||
|
|||||||
@@ -145,13 +145,15 @@ export class SaveNewItem implements UseCaseInterface<Item> {
|
|||||||
await itemRepository.save(newItem)
|
await itemRepository.save(newItem)
|
||||||
|
|
||||||
if (contentType.value !== null && [ContentType.TYPES.Note, ContentType.TYPES.File].includes(contentType.value)) {
|
if (contentType.value !== null && [ContentType.TYPES.Note, ContentType.TYPES.File].includes(contentType.value)) {
|
||||||
await this.domainEventPublisher.publish(
|
if (!dto.onGoingRevisionsTransition) {
|
||||||
this.domainEventFactory.createItemRevisionCreationRequested({
|
await this.domainEventPublisher.publish(
|
||||||
itemUuid: newItem.id.toString(),
|
this.domainEventFactory.createItemRevisionCreationRequested({
|
||||||
userUuid: newItem.props.userUuid.value,
|
itemUuid: newItem.id.toString(),
|
||||||
roleNames: dto.roleNames,
|
userUuid: newItem.props.userUuid.value,
|
||||||
}),
|
roleNames: dto.roleNames,
|
||||||
)
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (duplicateOf) {
|
if (duplicateOf) {
|
||||||
|
|||||||
@@ -5,4 +5,5 @@ export interface SaveNewItemDTO {
|
|||||||
roleNames: string[]
|
roleNames: string[]
|
||||||
itemHash: ItemHash
|
itemHash: ItemHash
|
||||||
sessionUuid: string | null
|
sessionUuid: string | null
|
||||||
|
onGoingRevisionsTransition: boolean
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { GetMessagesSentToUser } from '../../Messaging/GetMessagesSentToUser/Get
|
|||||||
import { GetUserNotifications } from '../../Messaging/GetUserNotifications/GetUserNotifications'
|
import { GetUserNotifications } from '../../Messaging/GetUserNotifications/GetUserNotifications'
|
||||||
import { GetSharedVaultInvitesSentToUser } from '../../SharedVaults/GetSharedVaultInvitesSentToUser/GetSharedVaultInvitesSentToUser'
|
import { GetSharedVaultInvitesSentToUser } from '../../SharedVaults/GetSharedVaultInvitesSentToUser/GetSharedVaultInvitesSentToUser'
|
||||||
import { ItemRepositoryResolverInterface } from '../../../Item/ItemRepositoryResolverInterface'
|
import { ItemRepositoryResolverInterface } from '../../../Item/ItemRepositoryResolverInterface'
|
||||||
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
describe('SyncItems', () => {
|
describe('SyncItems', () => {
|
||||||
let getItemsUseCase: GetItems
|
let getItemsUseCase: GetItems
|
||||||
@@ -28,6 +29,7 @@ describe('SyncItems', () => {
|
|||||||
let getSharedVaultInvitesSentToUserUseCase: GetSharedVaultInvitesSentToUser
|
let getSharedVaultInvitesSentToUserUseCase: GetSharedVaultInvitesSentToUser
|
||||||
let getMessagesSentToUser: GetMessagesSentToUser
|
let getMessagesSentToUser: GetMessagesSentToUser
|
||||||
let getUserNotifications: GetUserNotifications
|
let getUserNotifications: GetUserNotifications
|
||||||
|
let logger: Logger
|
||||||
|
|
||||||
const createUseCase = () =>
|
const createUseCase = () =>
|
||||||
new SyncItems(
|
new SyncItems(
|
||||||
@@ -38,9 +40,15 @@ describe('SyncItems', () => {
|
|||||||
getSharedVaultInvitesSentToUserUseCase,
|
getSharedVaultInvitesSentToUserUseCase,
|
||||||
getMessagesSentToUser,
|
getMessagesSentToUser,
|
||||||
getUserNotifications,
|
getUserNotifications,
|
||||||
|
logger,
|
||||||
)
|
)
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
logger = {} as jest.Mocked<Logger>
|
||||||
|
logger.info = jest.fn()
|
||||||
|
logger.debug = jest.fn()
|
||||||
|
logger.error = jest.fn()
|
||||||
|
|
||||||
item1 = Item.create(
|
item1 = Item.create(
|
||||||
{
|
{
|
||||||
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||||
@@ -143,6 +151,7 @@ describe('SyncItems', () => {
|
|||||||
it('should sync items', async () => {
|
it('should sync items', async () => {
|
||||||
const result = await createUseCase().execute({
|
const result = await createUseCase().execute({
|
||||||
userUuid: '1-2-3',
|
userUuid: '1-2-3',
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
itemHashes: [itemHash],
|
itemHashes: [itemHash],
|
||||||
computeIntegrityHash: false,
|
computeIntegrityHash: false,
|
||||||
syncToken: 'foo',
|
syncToken: 'foo',
|
||||||
@@ -178,6 +187,7 @@ describe('SyncItems', () => {
|
|||||||
expect(saveItemsUseCase.execute).toHaveBeenCalledWith({
|
expect(saveItemsUseCase.execute).toHaveBeenCalledWith({
|
||||||
itemHashes: [itemHash],
|
itemHashes: [itemHash],
|
||||||
userUuid: '1-2-3',
|
userUuid: '1-2-3',
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
apiVersion: '20200115',
|
apiVersion: '20200115',
|
||||||
snjsVersion: '1.2.3',
|
snjsVersion: '1.2.3',
|
||||||
roleNames: [RoleName.NAMES.CoreUser],
|
roleNames: [RoleName.NAMES.CoreUser],
|
||||||
@@ -186,9 +196,39 @@ describe('SyncItems', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should log error if sync items throws an error', async () => {
|
||||||
|
getItemsUseCase.execute = jest.fn().mockImplementation(() => {
|
||||||
|
throw new Error('error')
|
||||||
|
})
|
||||||
|
|
||||||
|
let caughtError = null
|
||||||
|
try {
|
||||||
|
await createUseCase().execute({
|
||||||
|
userUuid: '1-2-3',
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
|
itemHashes: [itemHash],
|
||||||
|
computeIntegrityHash: false,
|
||||||
|
syncToken: 'foo',
|
||||||
|
cursorToken: 'bar',
|
||||||
|
limit: 10,
|
||||||
|
readOnlyAccess: false,
|
||||||
|
contentType: 'Note',
|
||||||
|
apiVersion: ApiVersion.v20200115,
|
||||||
|
sessionUuid: null,
|
||||||
|
snjsVersion: '1.2.3',
|
||||||
|
roleNames: [RoleName.NAMES.CoreUser],
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
caughtError = error
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(caughtError).not.toBeNull()
|
||||||
|
})
|
||||||
|
|
||||||
it('should sync items and return items keys on top for first sync that is not a shared vault exclusive sync', async () => {
|
it('should sync items and return items keys on top for first sync that is not a shared vault exclusive sync', async () => {
|
||||||
const result = await createUseCase().execute({
|
const result = await createUseCase().execute({
|
||||||
userUuid: '1-2-3',
|
userUuid: '1-2-3',
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
itemHashes: [itemHash],
|
itemHashes: [itemHash],
|
||||||
computeIntegrityHash: false,
|
computeIntegrityHash: false,
|
||||||
limit: 10,
|
limit: 10,
|
||||||
@@ -215,6 +255,7 @@ describe('SyncItems', () => {
|
|||||||
it('should sync items and not return items keys on top for first sync that is a shared vault exclusive sync', async () => {
|
it('should sync items and not return items keys on top for first sync that is a shared vault exclusive sync', async () => {
|
||||||
const result = await createUseCase().execute({
|
const result = await createUseCase().execute({
|
||||||
userUuid: '1-2-3',
|
userUuid: '1-2-3',
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
itemHashes: [itemHash],
|
itemHashes: [itemHash],
|
||||||
computeIntegrityHash: false,
|
computeIntegrityHash: false,
|
||||||
limit: 10,
|
limit: 10,
|
||||||
@@ -266,6 +307,7 @@ describe('SyncItems', () => {
|
|||||||
|
|
||||||
const result = await createUseCase().execute({
|
const result = await createUseCase().execute({
|
||||||
userUuid: '1-2-3',
|
userUuid: '1-2-3',
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
itemHashes: [itemHash],
|
itemHashes: [itemHash],
|
||||||
computeIntegrityHash: false,
|
computeIntegrityHash: false,
|
||||||
syncToken: 'foo',
|
syncToken: 'foo',
|
||||||
@@ -306,6 +348,7 @@ describe('SyncItems', () => {
|
|||||||
|
|
||||||
const result = await createUseCase().execute({
|
const result = await createUseCase().execute({
|
||||||
userUuid: '1-2-3',
|
userUuid: '1-2-3',
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
itemHashes: [itemHash],
|
itemHashes: [itemHash],
|
||||||
computeIntegrityHash: false,
|
computeIntegrityHash: false,
|
||||||
syncToken: 'foo',
|
syncToken: 'foo',
|
||||||
@@ -327,6 +370,7 @@ describe('SyncItems', () => {
|
|||||||
|
|
||||||
const result = await createUseCase().execute({
|
const result = await createUseCase().execute({
|
||||||
userUuid: '1-2-3',
|
userUuid: '1-2-3',
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
itemHashes: [itemHash],
|
itemHashes: [itemHash],
|
||||||
computeIntegrityHash: false,
|
computeIntegrityHash: false,
|
||||||
syncToken: 'foo',
|
syncToken: 'foo',
|
||||||
@@ -346,6 +390,7 @@ describe('SyncItems', () => {
|
|||||||
it('should return error if role names are invalid', async () => {
|
it('should return error if role names are invalid', async () => {
|
||||||
const result = await createUseCase().execute({
|
const result = await createUseCase().execute({
|
||||||
userUuid: '1-2-3',
|
userUuid: '1-2-3',
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
itemHashes: [itemHash],
|
itemHashes: [itemHash],
|
||||||
computeIntegrityHash: false,
|
computeIntegrityHash: false,
|
||||||
limit: 10,
|
limit: 10,
|
||||||
@@ -365,6 +410,7 @@ describe('SyncItems', () => {
|
|||||||
|
|
||||||
const result = await createUseCase().execute({
|
const result = await createUseCase().execute({
|
||||||
userUuid: '1-2-3',
|
userUuid: '1-2-3',
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
itemHashes: [itemHash],
|
itemHashes: [itemHash],
|
||||||
computeIntegrityHash: false,
|
computeIntegrityHash: false,
|
||||||
syncToken: 'foo',
|
syncToken: 'foo',
|
||||||
@@ -386,6 +432,7 @@ describe('SyncItems', () => {
|
|||||||
|
|
||||||
const result = await createUseCase().execute({
|
const result = await createUseCase().execute({
|
||||||
userUuid: '1-2-3',
|
userUuid: '1-2-3',
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
itemHashes: [itemHash],
|
itemHashes: [itemHash],
|
||||||
computeIntegrityHash: false,
|
computeIntegrityHash: false,
|
||||||
syncToken: 'foo',
|
syncToken: 'foo',
|
||||||
@@ -407,6 +454,7 @@ describe('SyncItems', () => {
|
|||||||
|
|
||||||
const result = await createUseCase().execute({
|
const result = await createUseCase().execute({
|
||||||
userUuid: '1-2-3',
|
userUuid: '1-2-3',
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
itemHashes: [itemHash],
|
itemHashes: [itemHash],
|
||||||
computeIntegrityHash: false,
|
computeIntegrityHash: false,
|
||||||
syncToken: 'foo',
|
syncToken: 'foo',
|
||||||
@@ -428,6 +476,7 @@ describe('SyncItems', () => {
|
|||||||
|
|
||||||
const result = await createUseCase().execute({
|
const result = await createUseCase().execute({
|
||||||
userUuid: '1-2-3',
|
userUuid: '1-2-3',
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
itemHashes: [itemHash],
|
itemHashes: [itemHash],
|
||||||
computeIntegrityHash: false,
|
computeIntegrityHash: false,
|
||||||
syncToken: 'foo',
|
syncToken: 'foo',
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { GetSharedVaultInvitesSentToUser } from '../../SharedVaults/GetSharedVau
|
|||||||
import { GetMessagesSentToUser } from '../../Messaging/GetMessagesSentToUser/GetMessagesSentToUser'
|
import { GetMessagesSentToUser } from '../../Messaging/GetMessagesSentToUser/GetMessagesSentToUser'
|
||||||
import { GetUserNotifications } from '../../Messaging/GetUserNotifications/GetUserNotifications'
|
import { GetUserNotifications } from '../../Messaging/GetUserNotifications/GetUserNotifications'
|
||||||
import { ItemRepositoryResolverInterface } from '../../../Item/ItemRepositoryResolverInterface'
|
import { ItemRepositoryResolverInterface } from '../../../Item/ItemRepositoryResolverInterface'
|
||||||
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
export class SyncItems implements UseCaseInterface<SyncItemsResponse> {
|
export class SyncItems implements UseCaseInterface<SyncItemsResponse> {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -21,98 +22,111 @@ export class SyncItems implements UseCaseInterface<SyncItemsResponse> {
|
|||||||
private getSharedVaultInvitesSentToUserUseCase: GetSharedVaultInvitesSentToUser,
|
private getSharedVaultInvitesSentToUserUseCase: GetSharedVaultInvitesSentToUser,
|
||||||
private getMessagesSentToUser: GetMessagesSentToUser,
|
private getMessagesSentToUser: GetMessagesSentToUser,
|
||||||
private getUserNotifications: GetUserNotifications,
|
private getUserNotifications: GetUserNotifications,
|
||||||
|
private logger: Logger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(dto: SyncItemsDTO): Promise<Result<SyncItemsResponse>> {
|
async execute(dto: SyncItemsDTO): Promise<Result<SyncItemsResponse>> {
|
||||||
const roleNamesOrError = RoleNameCollection.create(dto.roleNames)
|
try {
|
||||||
if (roleNamesOrError.isFailed()) {
|
const roleNamesOrError = RoleNameCollection.create(dto.roleNames)
|
||||||
return Result.fail(roleNamesOrError.getError())
|
if (roleNamesOrError.isFailed()) {
|
||||||
}
|
return Result.fail(roleNamesOrError.getError())
|
||||||
const roleNames = roleNamesOrError.getValue()
|
}
|
||||||
|
const roleNames = roleNamesOrError.getValue()
|
||||||
|
|
||||||
const getItemsResultOrError = await this.getItemsUseCase.execute({
|
const getItemsResultOrError = await this.getItemsUseCase.execute({
|
||||||
userUuid: dto.userUuid,
|
userUuid: dto.userUuid,
|
||||||
syncToken: dto.syncToken,
|
syncToken: dto.syncToken,
|
||||||
cursorToken: dto.cursorToken,
|
cursorToken: dto.cursorToken,
|
||||||
limit: dto.limit,
|
limit: dto.limit,
|
||||||
contentType: dto.contentType,
|
contentType: dto.contentType,
|
||||||
sharedVaultUuids: dto.sharedVaultUuids,
|
sharedVaultUuids: dto.sharedVaultUuids,
|
||||||
roleNames: dto.roleNames,
|
roleNames: dto.roleNames,
|
||||||
})
|
})
|
||||||
if (getItemsResultOrError.isFailed()) {
|
if (getItemsResultOrError.isFailed()) {
|
||||||
return Result.fail(getItemsResultOrError.getError())
|
return Result.fail(getItemsResultOrError.getError())
|
||||||
}
|
}
|
||||||
const getItemsResult = getItemsResultOrError.getValue()
|
const getItemsResult = getItemsResultOrError.getValue()
|
||||||
|
|
||||||
const saveItemsResultOrError = await this.saveItemsUseCase.execute({
|
const saveItemsResultOrError = await this.saveItemsUseCase.execute({
|
||||||
itemHashes: dto.itemHashes,
|
itemHashes: dto.itemHashes,
|
||||||
userUuid: dto.userUuid,
|
userUuid: dto.userUuid,
|
||||||
apiVersion: dto.apiVersion,
|
apiVersion: dto.apiVersion,
|
||||||
readOnlyAccess: dto.readOnlyAccess,
|
readOnlyAccess: dto.readOnlyAccess,
|
||||||
sessionUuid: dto.sessionUuid,
|
sessionUuid: dto.sessionUuid,
|
||||||
snjsVersion: dto.snjsVersion,
|
snjsVersion: dto.snjsVersion,
|
||||||
roleNames: dto.roleNames,
|
roleNames: dto.roleNames,
|
||||||
})
|
onGoingRevisionsTransition: dto.onGoingRevisionsTransition,
|
||||||
if (saveItemsResultOrError.isFailed()) {
|
})
|
||||||
return Result.fail(saveItemsResultOrError.getError())
|
if (saveItemsResultOrError.isFailed()) {
|
||||||
}
|
return Result.fail(saveItemsResultOrError.getError())
|
||||||
const saveItemsResult = saveItemsResultOrError.getValue()
|
}
|
||||||
|
const saveItemsResult = saveItemsResultOrError.getValue()
|
||||||
|
|
||||||
let retrievedItems = this.filterOutSyncConflictsForConsecutiveSyncs(getItemsResult.items, saveItemsResult.conflicts)
|
let retrievedItems = this.filterOutSyncConflictsForConsecutiveSyncs(
|
||||||
const isSharedVaultExclusiveSync = dto.sharedVaultUuids && dto.sharedVaultUuids.length > 0
|
getItemsResult.items,
|
||||||
if (this.isFirstSync(dto) && !isSharedVaultExclusiveSync) {
|
saveItemsResult.conflicts,
|
||||||
retrievedItems = await this.frontLoadKeysItemsToTop(dto.userUuid, roleNames, retrievedItems)
|
)
|
||||||
}
|
const isSharedVaultExclusiveSync = dto.sharedVaultUuids && dto.sharedVaultUuids.length > 0
|
||||||
|
if (this.isFirstSync(dto) && !isSharedVaultExclusiveSync) {
|
||||||
|
retrievedItems = await this.frontLoadKeysItemsToTop(dto.userUuid, roleNames, retrievedItems)
|
||||||
|
}
|
||||||
|
|
||||||
const sharedVaultsOrError = await this.getSharedVaultsUseCase.execute({
|
const sharedVaultsOrError = await this.getSharedVaultsUseCase.execute({
|
||||||
userUuid: dto.userUuid,
|
userUuid: dto.userUuid,
|
||||||
lastSyncTime: getItemsResult.lastSyncTime ?? undefined,
|
lastSyncTime: getItemsResult.lastSyncTime ?? undefined,
|
||||||
})
|
})
|
||||||
if (sharedVaultsOrError.isFailed()) {
|
if (sharedVaultsOrError.isFailed()) {
|
||||||
return Result.fail(sharedVaultsOrError.getError())
|
return Result.fail(sharedVaultsOrError.getError())
|
||||||
}
|
}
|
||||||
const sharedVaults = sharedVaultsOrError.getValue()
|
const sharedVaults = sharedVaultsOrError.getValue()
|
||||||
|
|
||||||
const sharedVaultInvitesOrError = await this.getSharedVaultInvitesSentToUserUseCase.execute({
|
const sharedVaultInvitesOrError = await this.getSharedVaultInvitesSentToUserUseCase.execute({
|
||||||
userUuid: dto.userUuid,
|
userUuid: dto.userUuid,
|
||||||
lastSyncTime: getItemsResult.lastSyncTime ?? undefined,
|
lastSyncTime: getItemsResult.lastSyncTime ?? undefined,
|
||||||
})
|
})
|
||||||
if (sharedVaultInvitesOrError.isFailed()) {
|
if (sharedVaultInvitesOrError.isFailed()) {
|
||||||
return Result.fail(sharedVaultInvitesOrError.getError())
|
return Result.fail(sharedVaultInvitesOrError.getError())
|
||||||
}
|
}
|
||||||
const sharedVaultInvites = sharedVaultInvitesOrError.getValue()
|
const sharedVaultInvites = sharedVaultInvitesOrError.getValue()
|
||||||
|
|
||||||
const messagesOrError = await this.getMessagesSentToUser.execute({
|
const messagesOrError = await this.getMessagesSentToUser.execute({
|
||||||
recipientUuid: dto.userUuid,
|
recipientUuid: dto.userUuid,
|
||||||
lastSyncTime: getItemsResult.lastSyncTime ?? undefined,
|
lastSyncTime: getItemsResult.lastSyncTime ?? undefined,
|
||||||
})
|
})
|
||||||
if (messagesOrError.isFailed()) {
|
if (messagesOrError.isFailed()) {
|
||||||
return Result.fail(messagesOrError.getError())
|
return Result.fail(messagesOrError.getError())
|
||||||
}
|
}
|
||||||
const messages = messagesOrError.getValue()
|
const messages = messagesOrError.getValue()
|
||||||
|
|
||||||
const notificationsOrError = await this.getUserNotifications.execute({
|
const notificationsOrError = await this.getUserNotifications.execute({
|
||||||
userUuid: dto.userUuid,
|
userUuid: dto.userUuid,
|
||||||
lastSyncTime: getItemsResult.lastSyncTime ?? undefined,
|
lastSyncTime: getItemsResult.lastSyncTime ?? undefined,
|
||||||
})
|
})
|
||||||
if (notificationsOrError.isFailed()) {
|
if (notificationsOrError.isFailed()) {
|
||||||
return Result.fail(notificationsOrError.getError())
|
return Result.fail(notificationsOrError.getError())
|
||||||
}
|
}
|
||||||
const notifications = notificationsOrError.getValue()
|
const notifications = notificationsOrError.getValue()
|
||||||
|
|
||||||
const syncResponse: SyncItemsResponse = {
|
const syncResponse: SyncItemsResponse = {
|
||||||
retrievedItems,
|
retrievedItems,
|
||||||
syncToken: saveItemsResult.syncToken,
|
syncToken: saveItemsResult.syncToken,
|
||||||
savedItems: saveItemsResult.savedItems,
|
savedItems: saveItemsResult.savedItems,
|
||||||
conflicts: saveItemsResult.conflicts,
|
conflicts: saveItemsResult.conflicts,
|
||||||
cursorToken: getItemsResult.cursorToken,
|
cursorToken: getItemsResult.cursorToken,
|
||||||
sharedVaultInvites,
|
sharedVaultInvites,
|
||||||
sharedVaults,
|
sharedVaults,
|
||||||
messages,
|
messages,
|
||||||
notifications,
|
notifications,
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result.ok(syncResponse)
|
return Result.ok(syncResponse)
|
||||||
|
} catch (error) {
|
||||||
|
const itemHashUuids = dto.itemHashes.map((itemHash) => itemHash.props.uuid)
|
||||||
|
this.logger.error(
|
||||||
|
`Sync error for user ${dto.userUuid} syncing items ${itemHashUuids.join(',')}: ${(error as Error).message}`,
|
||||||
|
)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private isFirstSync(dto: SyncItemsDTO): boolean {
|
private isFirstSync(dto: SyncItemsDTO): boolean {
|
||||||
|
|||||||
@@ -14,4 +14,5 @@ export type SyncItemsDTO = {
|
|||||||
snjsVersion: string
|
snjsVersion: string
|
||||||
readOnlyAccess: boolean
|
readOnlyAccess: boolean
|
||||||
sessionUuid: string | null
|
sessionUuid: string | null
|
||||||
|
onGoingRevisionsTransition: boolean
|
||||||
}
|
}
|
||||||
|
|||||||
+40
@@ -140,6 +140,23 @@ describe('UpdateExistingItem', () => {
|
|||||||
|
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
existingItem: item1,
|
existingItem: item1,
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
|
itemHash: itemHash1,
|
||||||
|
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
roleNames: [RoleName.NAMES.CoreUser],
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.isFailed()).toBeFalsy()
|
||||||
|
expect(itemRepository.save).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not create a revision if user has an ongoin revisions transition', async () => {
|
||||||
|
const useCase = createUseCase()
|
||||||
|
|
||||||
|
const result = await useCase.execute({
|
||||||
|
existingItem: item1,
|
||||||
|
onGoingRevisionsTransition: true,
|
||||||
itemHash: itemHash1,
|
itemHash: itemHash1,
|
||||||
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
@@ -155,6 +172,7 @@ describe('UpdateExistingItem', () => {
|
|||||||
|
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
existingItem: item1,
|
existingItem: item1,
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
itemHash: itemHash1,
|
itemHash: itemHash1,
|
||||||
sessionUuid: 'invalid-uuid',
|
sessionUuid: 'invalid-uuid',
|
||||||
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
@@ -169,6 +187,7 @@ describe('UpdateExistingItem', () => {
|
|||||||
|
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
existingItem: item1,
|
existingItem: item1,
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
itemHash: itemHash1,
|
itemHash: itemHash1,
|
||||||
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
@@ -183,6 +202,7 @@ describe('UpdateExistingItem', () => {
|
|||||||
|
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
existingItem: item1,
|
existingItem: item1,
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
itemHash: ItemHash.create({
|
itemHash: ItemHash.create({
|
||||||
...itemHash1.props,
|
...itemHash1.props,
|
||||||
content_type: 'invalid',
|
content_type: 'invalid',
|
||||||
@@ -200,6 +220,7 @@ describe('UpdateExistingItem', () => {
|
|||||||
|
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
existingItem: item1,
|
existingItem: item1,
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
itemHash: ItemHash.create({
|
itemHash: ItemHash.create({
|
||||||
...itemHash1.props,
|
...itemHash1.props,
|
||||||
deleted: true,
|
deleted: true,
|
||||||
@@ -224,6 +245,7 @@ describe('UpdateExistingItem', () => {
|
|||||||
|
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
existingItem: item1,
|
existingItem: item1,
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
itemHash: ItemHash.create({
|
itemHash: ItemHash.create({
|
||||||
...itemHash1.props,
|
...itemHash1.props,
|
||||||
duplicate_of: '00000000-0000-0000-0000-000000000001',
|
duplicate_of: '00000000-0000-0000-0000-000000000001',
|
||||||
@@ -243,6 +265,7 @@ describe('UpdateExistingItem', () => {
|
|||||||
|
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
existingItem: item1,
|
existingItem: item1,
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
itemHash: ItemHash.create({
|
itemHash: ItemHash.create({
|
||||||
...itemHash1.props,
|
...itemHash1.props,
|
||||||
duplicate_of: 'invalid-uuid',
|
duplicate_of: 'invalid-uuid',
|
||||||
@@ -260,6 +283,7 @@ describe('UpdateExistingItem', () => {
|
|||||||
|
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
existingItem: item1,
|
existingItem: item1,
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
itemHash: ItemHash.create({
|
itemHash: ItemHash.create({
|
||||||
...itemHash1.props,
|
...itemHash1.props,
|
||||||
updated_at_timestamp: 123,
|
updated_at_timestamp: 123,
|
||||||
@@ -279,6 +303,7 @@ describe('UpdateExistingItem', () => {
|
|||||||
|
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
existingItem: item1,
|
existingItem: item1,
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
itemHash: ItemHash.create({
|
itemHash: ItemHash.create({
|
||||||
...itemHash1.props,
|
...itemHash1.props,
|
||||||
created_at: undefined,
|
created_at: undefined,
|
||||||
@@ -302,6 +327,7 @@ describe('UpdateExistingItem', () => {
|
|||||||
|
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
existingItem: item1,
|
existingItem: item1,
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
itemHash: ItemHash.create({
|
itemHash: ItemHash.create({
|
||||||
...itemHash1.props,
|
...itemHash1.props,
|
||||||
created_at_timestamp: 123,
|
created_at_timestamp: 123,
|
||||||
@@ -327,6 +353,7 @@ describe('UpdateExistingItem', () => {
|
|||||||
|
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
existingItem: item1,
|
existingItem: item1,
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
itemHash: ItemHash.create({
|
itemHash: ItemHash.create({
|
||||||
...itemHash1.props,
|
...itemHash1.props,
|
||||||
created_at_timestamp: 123,
|
created_at_timestamp: 123,
|
||||||
@@ -346,6 +373,7 @@ describe('UpdateExistingItem', () => {
|
|||||||
|
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
existingItem: item1,
|
existingItem: item1,
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
itemHash: itemHash1,
|
itemHash: itemHash1,
|
||||||
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
performingUserUuid: 'invalid-uuid',
|
performingUserUuid: 'invalid-uuid',
|
||||||
@@ -365,6 +393,7 @@ describe('UpdateExistingItem', () => {
|
|||||||
|
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
existingItem: item1,
|
existingItem: item1,
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
itemHash,
|
itemHash,
|
||||||
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
@@ -392,6 +421,7 @@ describe('UpdateExistingItem', () => {
|
|||||||
|
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
existingItem: item1,
|
existingItem: item1,
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
itemHash,
|
itemHash,
|
||||||
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
@@ -430,6 +460,7 @@ describe('UpdateExistingItem', () => {
|
|||||||
|
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
existingItem: item1,
|
existingItem: item1,
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
itemHash,
|
itemHash,
|
||||||
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
@@ -459,6 +490,7 @@ describe('UpdateExistingItem', () => {
|
|||||||
|
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
existingItem: item1,
|
existingItem: item1,
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
itemHash,
|
itemHash,
|
||||||
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
@@ -480,6 +512,7 @@ describe('UpdateExistingItem', () => {
|
|||||||
|
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
existingItem: item1,
|
existingItem: item1,
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
itemHash,
|
itemHash,
|
||||||
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
@@ -512,6 +545,7 @@ describe('UpdateExistingItem', () => {
|
|||||||
|
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
existingItem: item1,
|
existingItem: item1,
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
itemHash,
|
itemHash,
|
||||||
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
@@ -547,6 +581,7 @@ describe('UpdateExistingItem', () => {
|
|||||||
|
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
existingItem: item1,
|
existingItem: item1,
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
itemHash,
|
itemHash,
|
||||||
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
@@ -569,6 +604,7 @@ describe('UpdateExistingItem', () => {
|
|||||||
|
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
existingItem: item1,
|
existingItem: item1,
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
itemHash,
|
itemHash,
|
||||||
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
@@ -589,6 +625,7 @@ describe('UpdateExistingItem', () => {
|
|||||||
|
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
existingItem: item1,
|
existingItem: item1,
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
itemHash,
|
itemHash,
|
||||||
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
@@ -611,6 +648,7 @@ describe('UpdateExistingItem', () => {
|
|||||||
|
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
existingItem: item1,
|
existingItem: item1,
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
itemHash,
|
itemHash,
|
||||||
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
@@ -632,6 +670,7 @@ describe('UpdateExistingItem', () => {
|
|||||||
|
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
existingItem: item1,
|
existingItem: item1,
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
itemHash,
|
itemHash,
|
||||||
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
@@ -655,6 +694,7 @@ describe('UpdateExistingItem', () => {
|
|||||||
|
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
existingItem: item1,
|
existingItem: item1,
|
||||||
|
onGoingRevisionsTransition: false,
|
||||||
itemHash,
|
itemHash,
|
||||||
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
|||||||
+9
-7
@@ -191,13 +191,15 @@ export class UpdateExistingItem implements UseCaseInterface<Item> {
|
|||||||
dto.existingItem.props.contentType.value !== null &&
|
dto.existingItem.props.contentType.value !== null &&
|
||||||
[ContentType.TYPES.Note, ContentType.TYPES.File].includes(dto.existingItem.props.contentType.value)
|
[ContentType.TYPES.Note, ContentType.TYPES.File].includes(dto.existingItem.props.contentType.value)
|
||||||
) {
|
) {
|
||||||
await this.domainEventPublisher.publish(
|
if (!dto.onGoingRevisionsTransition) {
|
||||||
this.domainEventFactory.createItemRevisionCreationRequested({
|
await this.domainEventPublisher.publish(
|
||||||
itemUuid: dto.existingItem.id.toString(),
|
this.domainEventFactory.createItemRevisionCreationRequested({
|
||||||
userUuid: dto.existingItem.props.userUuid.value,
|
itemUuid: dto.existingItem.id.toString(),
|
||||||
roleNames: dto.roleNames,
|
userUuid: dto.existingItem.props.userUuid.value,
|
||||||
}),
|
roleNames: dto.roleNames,
|
||||||
)
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
@@ -7,4 +7,5 @@ export interface UpdateExistingItemDTO {
|
|||||||
sessionUuid: string | null
|
sessionUuid: string | null
|
||||||
performingUserUuid: string
|
performingUserUuid: string
|
||||||
roleNames: string[]
|
roleNames: string[]
|
||||||
|
onGoingRevisionsTransition: boolean
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-8
@@ -107,7 +107,7 @@ describe('TransitionItemsFromPrimaryToSecondaryDatabaseForUser', () => {
|
|||||||
secondaryItemRepository = {} as jest.Mocked<ItemRepositoryInterface>
|
secondaryItemRepository = {} as jest.Mocked<ItemRepositoryInterface>
|
||||||
secondaryItemRepository.save = jest.fn().mockResolvedValue(undefined)
|
secondaryItemRepository.save = jest.fn().mockResolvedValue(undefined)
|
||||||
secondaryItemRepository.deleteByUserUuid = jest.fn().mockResolvedValue(undefined)
|
secondaryItemRepository.deleteByUserUuid = jest.fn().mockResolvedValue(undefined)
|
||||||
secondaryItemRepository.countAll = jest.fn().mockResolvedValue(2)
|
secondaryItemRepository.countAll = jest.fn().mockReturnValueOnce(0).mockReturnValueOnce(2)
|
||||||
secondaryItemRepository.findByUuid = jest
|
secondaryItemRepository.findByUuid = jest
|
||||||
.fn()
|
.fn()
|
||||||
.mockResolvedValueOnce(secondaryItem1)
|
.mockResolvedValueOnce(secondaryItem1)
|
||||||
@@ -206,9 +206,6 @@ describe('TransitionItemsFromPrimaryToSecondaryDatabaseForUser', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
expect(result.isFailed()).toBeTruthy()
|
expect(result.isFailed()).toBeTruthy()
|
||||||
expect(result.getError()).toEqual(
|
|
||||||
'Item 00000000-0000-0000-0000-000000000001 is not identical in primary and secondary database',
|
|
||||||
)
|
|
||||||
|
|
||||||
expect((secondaryItemRepository as ItemRepositoryInterface).deleteByUserUuid).toHaveBeenCalledTimes(1)
|
expect((secondaryItemRepository as ItemRepositoryInterface).deleteByUserUuid).toHaveBeenCalledTimes(1)
|
||||||
expect(primaryItemRepository.deleteByUserUuid).not.toHaveBeenCalled()
|
expect(primaryItemRepository.deleteByUserUuid).not.toHaveBeenCalled()
|
||||||
@@ -317,7 +314,10 @@ describe('TransitionItemsFromPrimaryToSecondaryDatabaseForUser', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should fail integrity check if the item count is not the same in both databases', async () => {
|
it('should fail integrity check if the item count is not the same in both databases', async () => {
|
||||||
;(secondaryItemRepository as ItemRepositoryInterface).countAll = jest.fn().mockResolvedValue(1)
|
;(secondaryItemRepository as ItemRepositoryInterface).countAll = jest
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValueOnce(0)
|
||||||
|
.mockResolvedValueOnce(1)
|
||||||
|
|
||||||
const useCase = createUseCase()
|
const useCase = createUseCase()
|
||||||
|
|
||||||
@@ -332,7 +332,7 @@ describe('TransitionItemsFromPrimaryToSecondaryDatabaseForUser', () => {
|
|||||||
|
|
||||||
expect(primaryItemRepository.countAll).toHaveBeenCalledTimes(3)
|
expect(primaryItemRepository.countAll).toHaveBeenCalledTimes(3)
|
||||||
expect(primaryItemRepository.countAll).toHaveBeenCalledWith({ userUuid: '00000000-0000-0000-0000-000000000000' })
|
expect(primaryItemRepository.countAll).toHaveBeenCalledWith({ userUuid: '00000000-0000-0000-0000-000000000000' })
|
||||||
expect((secondaryItemRepository as ItemRepositoryInterface).countAll).toHaveBeenCalledTimes(1)
|
expect((secondaryItemRepository as ItemRepositoryInterface).countAll).toHaveBeenCalledTimes(2)
|
||||||
expect(primaryItemRepository.deleteByUserUuid).not.toHaveBeenCalled()
|
expect(primaryItemRepository.deleteByUserUuid).not.toHaveBeenCalled()
|
||||||
expect((secondaryItemRepository as ItemRepositoryInterface).deleteByUserUuid).toHaveBeenCalledTimes(1)
|
expect((secondaryItemRepository as ItemRepositoryInterface).deleteByUserUuid).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
@@ -354,13 +354,16 @@ describe('TransitionItemsFromPrimaryToSecondaryDatabaseForUser', () => {
|
|||||||
|
|
||||||
expect(primaryItemRepository.countAll).toHaveBeenCalledTimes(3)
|
expect(primaryItemRepository.countAll).toHaveBeenCalledTimes(3)
|
||||||
expect(primaryItemRepository.countAll).toHaveBeenCalledWith({ userUuid: '00000000-0000-0000-0000-000000000000' })
|
expect(primaryItemRepository.countAll).toHaveBeenCalledWith({ userUuid: '00000000-0000-0000-0000-000000000000' })
|
||||||
expect((secondaryItemRepository as ItemRepositoryInterface).countAll).toHaveBeenCalledTimes(1)
|
expect((secondaryItemRepository as ItemRepositoryInterface).countAll).toHaveBeenCalledTimes(2)
|
||||||
expect(primaryItemRepository.deleteByUserUuid).not.toHaveBeenCalled()
|
expect(primaryItemRepository.deleteByUserUuid).not.toHaveBeenCalled()
|
||||||
expect((secondaryItemRepository as ItemRepositoryInterface).deleteByUserUuid).toHaveBeenCalledTimes(1)
|
expect((secondaryItemRepository as ItemRepositoryInterface).deleteByUserUuid).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should fail if an error is thrown during integrity check between primary and secondary database', async () => {
|
it('should fail if an error is thrown during integrity check between primary and secondary database', async () => {
|
||||||
;(secondaryItemRepository as ItemRepositoryInterface).countAll = jest.fn().mockRejectedValue(new Error('error'))
|
;(secondaryItemRepository as ItemRepositoryInterface).countAll = jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValueOnce(0)
|
||||||
|
.mockRejectedValue(new Error('error'))
|
||||||
|
|
||||||
const useCase = createUseCase()
|
const useCase = createUseCase()
|
||||||
|
|
||||||
@@ -394,4 +397,21 @@ describe('TransitionItemsFromPrimaryToSecondaryDatabaseForUser', () => {
|
|||||||
expect(primaryItemRepository.deleteByUserUuid).not.toHaveBeenCalled()
|
expect(primaryItemRepository.deleteByUserUuid).not.toHaveBeenCalled()
|
||||||
expect((secondaryItemRepository as ItemRepositoryInterface).deleteByUserUuid).not.toHaveBeenCalled()
|
expect((secondaryItemRepository as ItemRepositoryInterface).deleteByUserUuid).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should not migrate items if there are items in the secondary database', async () => {
|
||||||
|
;(secondaryItemRepository as ItemRepositoryInterface).countAll = jest.fn().mockResolvedValue(1)
|
||||||
|
|
||||||
|
const useCase = createUseCase()
|
||||||
|
|
||||||
|
const result = await useCase.execute({
|
||||||
|
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.isFailed()).toBeTruthy()
|
||||||
|
|
||||||
|
expect(primaryItemRepository.findAll).not.toHaveBeenCalled()
|
||||||
|
expect((secondaryItemRepository as ItemRepositoryInterface).save).not.toHaveBeenCalled()
|
||||||
|
expect(primaryItemRepository.deleteByUserUuid).not.toHaveBeenCalled()
|
||||||
|
expect((secondaryItemRepository as ItemRepositoryInterface).deleteByUserUuid).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
+21
-1
@@ -16,6 +16,8 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(dto: TransitionItemsFromPrimaryToSecondaryDatabaseForUserDTO): Promise<Result<void>> {
|
async execute(dto: TransitionItemsFromPrimaryToSecondaryDatabaseForUserDTO): Promise<Result<void>> {
|
||||||
|
this.logger.info(`Transitioning items for user ${dto.userUuid}`)
|
||||||
|
|
||||||
if (this.secondaryItemRepository === null) {
|
if (this.secondaryItemRepository === null) {
|
||||||
return Result.fail('Secondary item repository is not set')
|
return Result.fail('Secondary item repository is not set')
|
||||||
}
|
}
|
||||||
@@ -32,6 +34,10 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
|
|||||||
return Result.ok()
|
return Result.ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (await this.hasAlreadyDataInSecondaryDatabase(userUuid)) {
|
||||||
|
return Result.fail(`Items for user ${userUuid.value} already exist in secondary database`)
|
||||||
|
}
|
||||||
|
|
||||||
const migrationTimeStart = this.timer.getTimestampInMicroseconds()
|
const migrationTimeStart = this.timer.getTimestampInMicroseconds()
|
||||||
|
|
||||||
const migrationResult = await this.migrateItemsForUser(userUuid)
|
const migrationResult = await this.migrateItemsForUser(userUuid)
|
||||||
@@ -79,6 +85,14 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
|
|||||||
return Result.ok()
|
return Result.ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async hasAlreadyDataInSecondaryDatabase(userUuid: Uuid): Promise<boolean> {
|
||||||
|
const totalItemsCountForUser = await (this.secondaryItemRepository as ItemRepositoryInterface).countAll({
|
||||||
|
userUuid: userUuid.value,
|
||||||
|
})
|
||||||
|
|
||||||
|
return totalItemsCountForUser > 0
|
||||||
|
}
|
||||||
|
|
||||||
private async isAlreadyMigrated(userUuid: Uuid): Promise<boolean> {
|
private async isAlreadyMigrated(userUuid: Uuid): Promise<boolean> {
|
||||||
const totalItemsCountForUser = await this.primaryItemRepository.countAll({ userUuid: userUuid.value })
|
const totalItemsCountForUser = await this.primaryItemRepository.countAll({ userUuid: userUuid.value })
|
||||||
|
|
||||||
@@ -156,7 +170,13 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!item.isIdenticalTo(itemInSecondary)) {
|
if (!item.isIdenticalTo(itemInSecondary)) {
|
||||||
return Result.fail(`Item ${item.uuid.value} is not identical in primary and secondary database`)
|
return Result.fail(
|
||||||
|
`Item ${
|
||||||
|
item.uuid.value
|
||||||
|
} is not identical in primary and secondary database. Item in primary database: ${JSON.stringify(
|
||||||
|
item,
|
||||||
|
)}, item in secondary database: ${JSON.stringify(itemInSecondary)}`,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,12 @@ export class FSItemBackupService implements ItemBackupServiceInterface {
|
|||||||
|
|
||||||
await promises.writeFile(path, contents)
|
await promises.writeFile(path, contents)
|
||||||
|
|
||||||
|
const fileCreated = (await promises.stat(path)).isFile()
|
||||||
|
|
||||||
|
if (!fileCreated) {
|
||||||
|
throw new Error(`Could not create dump file ${path}`)
|
||||||
|
}
|
||||||
|
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export class BaseItemsController extends BaseHttpController {
|
|||||||
|
|
||||||
async sync(request: Request, response: Response): Promise<results.JsonResult> {
|
async sync(request: Request, response: Response): Promise<results.JsonResult> {
|
||||||
if (response.locals.ongoingTransition === true) {
|
if (response.locals.ongoingTransition === true) {
|
||||||
throw new Error('Cannot sync during transition')
|
throw new Error(`Cannot sync user ${response.locals.user.uuid} during transition`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const itemHashes: ItemHash[] = []
|
const itemHashes: ItemHash[] = []
|
||||||
@@ -79,6 +79,7 @@ export class BaseItemsController extends BaseHttpController {
|
|||||||
readOnlyAccess: response.locals.readOnlyAccess,
|
readOnlyAccess: response.locals.readOnlyAccess,
|
||||||
sessionUuid: response.locals.session ? response.locals.session.uuid : null,
|
sessionUuid: response.locals.session ? response.locals.session.uuid : null,
|
||||||
sharedVaultUuids,
|
sharedVaultUuids,
|
||||||
|
onGoingRevisionsTransition: response.locals.onGoingRevisionsTransition,
|
||||||
})
|
})
|
||||||
if (syncResult.isFailed()) {
|
if (syncResult.isFailed()) {
|
||||||
return this.json({ error: { message: syncResult.getError() } }, HttpStatusCode.BadRequest)
|
return this.json({ error: { message: syncResult.getError() } }, HttpStatusCode.BadRequest)
|
||||||
|
|||||||
+1
@@ -30,6 +30,7 @@ export class InversifyExpressAuthMiddleware extends BaseMiddleware {
|
|||||||
response.locals.readOnlyAccess = decodedToken.session?.readonly_access ?? false
|
response.locals.readOnlyAccess = decodedToken.session?.readonly_access ?? false
|
||||||
response.locals.sharedVaultOwnerContext = decodedToken.shared_vault_owner_context
|
response.locals.sharedVaultOwnerContext = decodedToken.shared_vault_owner_context
|
||||||
response.locals.ongoingTransition = decodedToken.ongoing_transition
|
response.locals.ongoingTransition = decodedToken.ongoing_transition
|
||||||
|
response.locals.onGoingRevisionsTransition = decodedToken.ongoing_revisions_transition
|
||||||
|
|
||||||
return next()
|
return next()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export class TypeORMSharedVaultInviteRepository implements SharedVaultInviteRepo
|
|||||||
const persistence = await this.ormRepository
|
const persistence = await this.ormRepository
|
||||||
.createQueryBuilder('shared_vault_invite')
|
.createQueryBuilder('shared_vault_invite')
|
||||||
.where('shared_vault_invite.sender_uuid = :uuid', {
|
.where('shared_vault_invite.sender_uuid = :uuid', {
|
||||||
senderUuid: dto.senderUuid.value,
|
uuid: dto.senderUuid.value,
|
||||||
})
|
})
|
||||||
.andWhere('shared_vault_invite.shared_vault_uuid = :sharedVaultUuid', {
|
.andWhere('shared_vault_invite.shared_vault_uuid = :sharedVaultUuid', {
|
||||||
sharedVaultUuid: dto.sharedVaultUuid.value,
|
sharedVaultUuid: dto.sharedVaultUuid.value,
|
||||||
|
|||||||
@@ -3,6 +3,22 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [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
|
||||||
|
|
||||||
|
## [1.10.39](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.10.38...@standardnotes/websockets-server@1.10.39) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/websockets-server
|
||||||
|
|
||||||
|
## [1.10.38](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.10.37...@standardnotes/websockets-server@1.10.38) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/websockets-server
|
||||||
|
|
||||||
|
## [1.10.37](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.10.36...@standardnotes/websockets-server@1.10.37) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/websockets-server
|
||||||
|
|
||||||
## [1.10.36](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.10.35...@standardnotes/websockets-server@1.10.36) (2023-09-08)
|
## [1.10.36](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.10.35...@standardnotes/websockets-server@1.10.36) (2023-09-08)
|
||||||
|
|
||||||
**Note:** Version bump only for package @standardnotes/websockets-server
|
**Note:** Version bump only for package @standardnotes/websockets-server
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/websockets-server",
|
"name": "@standardnotes/websockets-server",
|
||||||
"version": "1.10.36",
|
"version": "1.10.40",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user