mirror of
https://github.com/standardnotes/server
synced 2026-01-17 05:04:27 -05:00
Compare commits
23 Commits
@standardn
...
@standardn
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7367de6832 | ||
|
|
f0abfe89fc | ||
|
|
d1244d165a | ||
|
|
106d8f9192 | ||
|
|
1d86ba8fcb | ||
|
|
f20a947f8a | ||
|
|
19b9de05ae | ||
|
|
1d751c0fbe | ||
|
|
fa2564e164 | ||
|
|
330bff0124 | ||
|
|
ca57c8e7b5 | ||
|
|
a82b9a0c8a | ||
|
|
ea7e9d73c4 | ||
|
|
117b7b4b99 | ||
|
|
b4bf11d9da | ||
|
|
0306e10469 | ||
|
|
0ab47013f2 | ||
|
|
836883b82d | ||
|
|
ed671be9c5 | ||
|
|
9676a2586c | ||
|
|
e95ba61c7f | ||
|
|
a0718aea26 | ||
|
|
156fa7a618 |
1
.github/ci.env
vendored
1
.github/ci.env
vendored
@@ -4,6 +4,7 @@ DB_USERNAME=std_notes_user
|
||||
DB_PASSWORD=changeme123
|
||||
DB_DATABASE=standard_notes_db
|
||||
DB_PORT=3306
|
||||
DB_DEBUG_LEVEL=all
|
||||
DB_SQLITE_DATABASE_PATH=standard_notes_db
|
||||
REDIS_PORT=6379
|
||||
REDIS_HOST=cache
|
||||
|
||||
1
.github/workflows/common-e2e.yml
vendored
1
.github/workflows/common-e2e.yml
vendored
@@ -141,6 +141,7 @@ jobs:
|
||||
echo "DB_USERNAME=standardnotes" >> packages/home-server/.env
|
||||
echo "DB_PASSWORD=standardnotes" >> packages/home-server/.env
|
||||
echo "DB_TYPE=${{ matrix.db_type }}" >> packages/home-server/.env
|
||||
echo "DB_DEBUG_LEVEL=all" >> packages/home-server/.env
|
||||
echo "REDIS_URL=redis://localhost:6379" >> packages/home-server/.env
|
||||
echo "CACHE_TYPE=${{ matrix.cache_type }}" >> packages/home-server/.env
|
||||
echo "SECONDARY_DB_ENABLED=${{ matrix.secondary_db_enabled }}" >> packages/home-server/.env
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/analytics",
|
||||
"version": "2.26.7",
|
||||
"version": "2.26.9",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,20 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api-gateway:** awaiting for other services to start ([0ab4701](https://github.com/standardnotes/api-gateway/commit/0ab47013f210dca7aa404966798011947fb5c362))
|
||||
|
||||
## [1.74.3](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.74.2...@standardnotes/api-gateway@1.74.3) (2023-09-08)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/api-gateway",
|
||||
"version": "1.74.3",
|
||||
"version": "1.74.6",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -2,5 +2,8 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
sh supervisor/wait-for.sh localhost $AUTH_SERVER_PORT
|
||||
sh supervisor/wait-for.sh localhost $FILES_SERVER_PORT
|
||||
sh supervisor/wait-for.sh localhost $REVISIONS_SERVER_PORT
|
||||
sh supervisor/wait-for.sh localhost $SYNCING_SERVER_PORT
|
||||
node docker/entrypoint-server.js
|
||||
|
||||
@@ -3,6 +3,42 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** remove transitioning upon sign out ([#819](https://github.com/standardnotes/server/issues/819)) ([330bff0](https://github.com/standardnotes/server/commit/330bff0124f5f49c3441304d166ea43c21fea7bc))
|
||||
|
||||
## [1.141.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.141.0...@standardnotes/auth-server@1.141.1) (2023-09-11)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* disable running migrations in worker mode of a given service ([a82b9a0](https://github.com/standardnotes/server/commit/a82b9a0c8a023ba8a450ff9e34bcd62f928fcab3))
|
||||
|
||||
# [1.141.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.140.0...@standardnotes/auth-server@1.141.0) (2023-09-11)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add procedure to transition users created between dates ([#816](https://github.com/standardnotes/server/issues/816)) ([e95ba61](https://github.com/standardnotes/server/commit/e95ba61c7f769736698ebbc38179d6dc05a8cc5e))
|
||||
|
||||
# [1.140.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.139.0...@standardnotes/auth-server@1.140.0) (2023-09-08)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -75,7 +75,7 @@ const requestBackups = async (
|
||||
})
|
||||
}
|
||||
|
||||
const container = new ContainerConfigLoader()
|
||||
const container = new ContainerConfigLoader('worker')
|
||||
void container.load().then((container) => {
|
||||
dayjs.extend(utc)
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ const cleanup = async (
|
||||
await cleanupExpiredSessions.execute({ date })
|
||||
}
|
||||
|
||||
const container = new ContainerConfigLoader()
|
||||
const container = new ContainerConfigLoader('worker')
|
||||
void container.load().then((container) => {
|
||||
const env: Env = new Env()
|
||||
env.load()
|
||||
|
||||
@@ -8,7 +8,7 @@ import TYPES from '../src/Bootstrap/Types'
|
||||
import { Env } from '../src/Bootstrap/Env'
|
||||
import { PersistStatistics } from '../src/Domain/UseCase/PersistStatistics/PersistStatistics'
|
||||
|
||||
const container = new ContainerConfigLoader()
|
||||
const container = new ContainerConfigLoader('worker')
|
||||
void container.load().then((container) => {
|
||||
const env: Env = new Env()
|
||||
env.load()
|
||||
|
||||
80
packages/auth/bin/transition.ts
Normal file
80
packages/auth/bin/transition.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { Logger } from 'winston'
|
||||
import * as dayjs from 'dayjs'
|
||||
import * as utc from 'dayjs/plugin/utc'
|
||||
|
||||
import { ContainerConfigLoader } from '../src/Bootstrap/Container'
|
||||
import TYPES from '../src/Bootstrap/Types'
|
||||
import { Env } from '../src/Bootstrap/Env'
|
||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||
import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
|
||||
import { UserRepositoryInterface } from '../src/Domain/User/UserRepositoryInterface'
|
||||
import { RoleName } from '@standardnotes/domain-core'
|
||||
|
||||
const inputArgs = process.argv.slice(2)
|
||||
const startDateString = inputArgs[0]
|
||||
const endDateString = inputArgs[1]
|
||||
|
||||
const requestTransition = async (
|
||||
userRepository: UserRepositoryInterface,
|
||||
logger: Logger,
|
||||
domainEventFactory: DomainEventFactoryInterface,
|
||||
domainEventPublisher: DomainEventPublisherInterface,
|
||||
): Promise<void> => {
|
||||
const startDate = new Date(startDateString)
|
||||
const endDate = new Date(endDateString)
|
||||
|
||||
const users = await userRepository.findAllCreatedBetween(startDate, endDate)
|
||||
|
||||
logger.info(`Found ${users.length} users created between ${startDateString} and ${endDateString}`)
|
||||
|
||||
let usersTriggered = 0
|
||||
for (const user of users) {
|
||||
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 })
|
||||
|
||||
usersTriggered += 1
|
||||
|
||||
await domainEventPublisher.publish(transitionRequestedEvent)
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`Triggered transition for ${usersTriggered} users created between ${startDateString} and ${endDateString}`,
|
||||
)
|
||||
}
|
||||
|
||||
const container = new ContainerConfigLoader('worker')
|
||||
void container.load().then((container) => {
|
||||
dayjs.extend(utc)
|
||||
|
||||
const env: Env = new Env()
|
||||
env.load()
|
||||
|
||||
const logger: Logger = container.get(TYPES.Auth_Logger)
|
||||
|
||||
logger.info(`Starting transition request for users created between ${startDateString} and ${endDateString}`)
|
||||
|
||||
const userRepository: UserRepositoryInterface = container.get(TYPES.Auth_UserRepository)
|
||||
const domainEventFactory: DomainEventFactoryInterface = container.get(TYPES.Auth_DomainEventFactory)
|
||||
const domainEventPublisher: DomainEventPublisherInterface = container.get(TYPES.Auth_DomainEventPublisher)
|
||||
|
||||
Promise.resolve(requestTransition(userRepository, logger, domainEventFactory, domainEventPublisher))
|
||||
.then(() => {
|
||||
logger.info(`Finished transition request for users created between ${startDateString} and ${endDateString}`)
|
||||
|
||||
process.exit(0)
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error(
|
||||
`Error while requesting transition for users created between ${startDateString} and ${endDateString}: ${error}`,
|
||||
)
|
||||
|
||||
process.exit(1)
|
||||
})
|
||||
})
|
||||
@@ -63,7 +63,7 @@ const requestBackups = async (
|
||||
return
|
||||
}
|
||||
|
||||
const container = new ContainerConfigLoader()
|
||||
const container = new ContainerConfigLoader('worker')
|
||||
void container.load().then((container) => {
|
||||
dayjs.extend(utc)
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import { DomainEventSubscriberFactoryInterface } from '@standardnotes/domain-eve
|
||||
import * as dayjs from 'dayjs'
|
||||
import * as utc from 'dayjs/plugin/utc'
|
||||
|
||||
const container = new ContainerConfigLoader()
|
||||
const container = new ContainerConfigLoader('worker')
|
||||
void container.load().then((container) => {
|
||||
dayjs.extend(utc)
|
||||
|
||||
|
||||
11
packages/auth/docker/entrypoint-transition.js
Normal file
11
packages/auth/docker/entrypoint-transition.js
Normal file
@@ -0,0 +1,11 @@
|
||||
'use strict'
|
||||
|
||||
const path = require('path')
|
||||
|
||||
const pnp = require(path.normalize(path.resolve(__dirname, '../../..', '.pnp.cjs'))).setup()
|
||||
|
||||
const index = require(path.normalize(path.resolve(__dirname, '../dist/bin/transition.js')))
|
||||
|
||||
Object.defineProperty(exports, '__esModule', { value: true })
|
||||
|
||||
exports.default = index
|
||||
@@ -55,6 +55,13 @@ case "$COMMAND" in
|
||||
node docker/entrypoint-backup.js one_drive daily
|
||||
;;
|
||||
|
||||
'transition' )
|
||||
START_DATE=$1 && shift 1
|
||||
END_DATE=$1 && shift 1
|
||||
echo "[Docker] Starting Transition..."
|
||||
node docker/entrypoint-transition.js $START_DATE $END_DATE
|
||||
;;
|
||||
|
||||
* )
|
||||
echo "[Docker] Unknown command"
|
||||
;;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/auth-server",
|
||||
"version": "1.140.0",
|
||||
"version": "1.141.5",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -274,6 +274,8 @@ import { UserAddedToSharedVaultEventHandler } from '../Domain/Handler/UserAddedT
|
||||
import { UserRemovedFromSharedVaultEventHandler } from '../Domain/Handler/UserRemovedFromSharedVaultEventHandler'
|
||||
|
||||
export class ContainerConfigLoader {
|
||||
constructor(private mode: 'server' | 'worker' = 'server') {}
|
||||
|
||||
async load(configuration?: {
|
||||
controllerConatiner?: ControllerContainerInterface
|
||||
directCallDomainEventPublisher?: DirectCallDomainEventPublisher
|
||||
@@ -310,7 +312,7 @@ export class ContainerConfigLoader {
|
||||
}
|
||||
container.bind<winston.Logger>(TYPES.Auth_Logger).toConstantValue(logger)
|
||||
|
||||
const appDataSource = new AppDataSource(env)
|
||||
const appDataSource = new AppDataSource({ env, runMigrations: this.mode === 'server' })
|
||||
await appDataSource.initialize()
|
||||
|
||||
logger.debug('Database initialized')
|
||||
@@ -981,7 +983,6 @@ export class ContainerConfigLoader {
|
||||
container.get(TYPES.Auth_GenerateRecoveryCodes),
|
||||
container.get(TYPES.Auth_Logger),
|
||||
container.get(TYPES.Auth_SessionService),
|
||||
container.get(TYPES.Auth_TRANSITION_MODE_ENABLED),
|
||||
),
|
||||
)
|
||||
container
|
||||
|
||||
@@ -23,7 +23,12 @@ import { TypeORMSharedVaultUser } from '../Infra/TypeORM/TypeORMSharedVaultUser'
|
||||
export class AppDataSource {
|
||||
private _dataSource: DataSource | undefined
|
||||
|
||||
constructor(private env: Env) {}
|
||||
constructor(
|
||||
private configuration: {
|
||||
env: Env
|
||||
runMigrations: boolean
|
||||
},
|
||||
) {}
|
||||
|
||||
getRepository<Entity extends ObjectLiteral>(target: EntityTarget<Entity>): Repository<Entity> {
|
||||
if (!this._dataSource) {
|
||||
@@ -38,12 +43,12 @@ export class AppDataSource {
|
||||
}
|
||||
|
||||
get dataSource(): DataSource {
|
||||
this.env.load()
|
||||
this.configuration.env.load()
|
||||
|
||||
const isConfiguredForMySQL = this.env.get('DB_TYPE') === 'mysql'
|
||||
const isConfiguredForMySQL = this.configuration.env.get('DB_TYPE') === 'mysql'
|
||||
|
||||
const maxQueryExecutionTime = this.env.get('DB_MAX_QUERY_EXECUTION_TIME', true)
|
||||
? +this.env.get('DB_MAX_QUERY_EXECUTION_TIME', true)
|
||||
const maxQueryExecutionTime = this.configuration.env.get('DB_MAX_QUERY_EXECUTION_TIME', true)
|
||||
? +this.configuration.env.get('DB_MAX_QUERY_EXECUTION_TIME', true)
|
||||
: 45_000
|
||||
|
||||
const commonDataSourceOptions = {
|
||||
@@ -68,28 +73,28 @@ export class AppDataSource {
|
||||
TypeORMSharedVaultUser,
|
||||
],
|
||||
migrations: [`${__dirname}/../../migrations/${isConfiguredForMySQL ? 'mysql' : 'sqlite'}/*.js`],
|
||||
migrationsRun: true,
|
||||
logging: <LoggerOptions>this.env.get('DB_DEBUG_LEVEL', true) ?? 'info',
|
||||
migrationsRun: this.configuration.runMigrations,
|
||||
logging: <LoggerOptions>this.configuration.env.get('DB_DEBUG_LEVEL', true) ?? 'info',
|
||||
}
|
||||
|
||||
if (isConfiguredForMySQL) {
|
||||
const inReplicaMode = this.env.get('DB_REPLICA_HOST', true) ? true : false
|
||||
const inReplicaMode = this.configuration.env.get('DB_REPLICA_HOST', true) ? true : false
|
||||
|
||||
const replicationConfig = {
|
||||
master: {
|
||||
host: this.env.get('DB_HOST'),
|
||||
port: parseInt(this.env.get('DB_PORT')),
|
||||
username: this.env.get('DB_USERNAME'),
|
||||
password: this.env.get('DB_PASSWORD'),
|
||||
database: this.env.get('DB_DATABASE'),
|
||||
host: this.configuration.env.get('DB_HOST'),
|
||||
port: parseInt(this.configuration.env.get('DB_PORT')),
|
||||
username: this.configuration.env.get('DB_USERNAME'),
|
||||
password: this.configuration.env.get('DB_PASSWORD'),
|
||||
database: this.configuration.env.get('DB_DATABASE'),
|
||||
},
|
||||
slaves: [
|
||||
{
|
||||
host: this.env.get('DB_REPLICA_HOST', true),
|
||||
port: parseInt(this.env.get('DB_PORT')),
|
||||
username: this.env.get('DB_USERNAME'),
|
||||
password: this.env.get('DB_PASSWORD'),
|
||||
database: this.env.get('DB_DATABASE'),
|
||||
host: this.configuration.env.get('DB_REPLICA_HOST', true),
|
||||
port: parseInt(this.configuration.env.get('DB_PORT')),
|
||||
username: this.configuration.env.get('DB_USERNAME'),
|
||||
password: this.configuration.env.get('DB_PASSWORD'),
|
||||
database: this.configuration.env.get('DB_DATABASE'),
|
||||
},
|
||||
],
|
||||
removeNodeErrorCount: 10,
|
||||
@@ -103,11 +108,11 @@ export class AppDataSource {
|
||||
supportBigNumbers: true,
|
||||
bigNumberStrings: false,
|
||||
replication: inReplicaMode ? replicationConfig : undefined,
|
||||
host: inReplicaMode ? undefined : this.env.get('DB_HOST'),
|
||||
port: inReplicaMode ? undefined : parseInt(this.env.get('DB_PORT')),
|
||||
username: inReplicaMode ? undefined : this.env.get('DB_USERNAME'),
|
||||
password: inReplicaMode ? undefined : this.env.get('DB_PASSWORD'),
|
||||
database: inReplicaMode ? undefined : this.env.get('DB_DATABASE'),
|
||||
host: inReplicaMode ? undefined : this.configuration.env.get('DB_HOST'),
|
||||
port: inReplicaMode ? undefined : parseInt(this.configuration.env.get('DB_PORT')),
|
||||
username: inReplicaMode ? undefined : this.configuration.env.get('DB_USERNAME'),
|
||||
password: inReplicaMode ? undefined : this.configuration.env.get('DB_PASSWORD'),
|
||||
database: inReplicaMode ? undefined : this.configuration.env.get('DB_DATABASE'),
|
||||
}
|
||||
|
||||
this._dataSource = new DataSource(mySQLDataSourceOptions)
|
||||
@@ -115,7 +120,7 @@ export class AppDataSource {
|
||||
const sqliteDataSourceOptions: SqliteConnectionOptions = {
|
||||
...commonDataSourceOptions,
|
||||
type: 'sqlite',
|
||||
database: this.env.get('DB_SQLITE_DATABASE_PATH'),
|
||||
database: this.configuration.env.get('DB_SQLITE_DATABASE_PATH'),
|
||||
enableWAL: true,
|
||||
busyErrorRetry: 2000,
|
||||
}
|
||||
|
||||
@@ -4,4 +4,4 @@ import { Env } from './Env'
|
||||
const env: Env = new Env()
|
||||
env.load()
|
||||
|
||||
export const MigrationsDataSource = new AppDataSource(env).dataSource
|
||||
export const MigrationsDataSource = new AppDataSource({ env, runMigrations: true }).dataSource
|
||||
|
||||
@@ -27,7 +27,6 @@ describe('AuthController', () => {
|
||||
let doGenerateRecoveryCodes: GenerateRecoveryCodes
|
||||
let logger: Logger
|
||||
let sessionService: SessionServiceInterface
|
||||
let transitionModeEnabled: boolean
|
||||
|
||||
const createController = () =>
|
||||
new AuthController(
|
||||
@@ -40,7 +39,6 @@ describe('AuthController', () => {
|
||||
doGenerateRecoveryCodes,
|
||||
logger,
|
||||
sessionService,
|
||||
transitionModeEnabled,
|
||||
)
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -66,8 +64,6 @@ describe('AuthController', () => {
|
||||
|
||||
sessionService = {} as jest.Mocked<SessionServiceInterface>
|
||||
sessionService.deleteSessionByToken = jest.fn().mockReturnValue('1-2-3')
|
||||
|
||||
transitionModeEnabled = false
|
||||
})
|
||||
|
||||
it('should register a user', async () => {
|
||||
|
||||
@@ -37,7 +37,6 @@ export class AuthController implements UserServerInterface {
|
||||
private doGenerateRecoveryCodes: GenerateRecoveryCodes,
|
||||
private logger: Logger,
|
||||
private sessionService: SessionServiceInterface,
|
||||
private transitionModeEnabled: boolean,
|
||||
) {}
|
||||
|
||||
async update(_params: UserUpdateRequestParams): Promise<HttpResponse<UserUpdateResponse>> {
|
||||
@@ -227,14 +226,6 @@ export class AuthController implements UserServerInterface {
|
||||
let headers = undefined
|
||||
if (userUuid !== null) {
|
||||
headers = new Map([['x-invalidate-cache', userUuid]])
|
||||
|
||||
if (this.transitionModeEnabled) {
|
||||
await this.domainEventPublisher.publish(
|
||||
this.domainEventFactory.createTransitionRequestedEvent({
|
||||
userUuid,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
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>
|
||||
getStatus(userUuid: string, transitionType: 'items' | 'revisions'): Promise<'STARTED' | 'FAILED' | null>
|
||||
getStatus(
|
||||
userUuid: string,
|
||||
transitionType: 'items' | 'revisions',
|
||||
): Promise<'STARTED' | 'IN_PROGRESS' | 'FAILED' | null>
|
||||
}
|
||||
|
||||
@@ -113,13 +113,14 @@ describe('CreateCrossServiceToken', () => {
|
||||
uuid: '00000000-0000-0000-0000-000000000000',
|
||||
},
|
||||
ongoing_transition: false,
|
||||
ongoing_revisions_transition: false,
|
||||
},
|
||||
60,
|
||||
)
|
||||
})
|
||||
|
||||
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({
|
||||
user,
|
||||
@@ -148,6 +149,7 @@ describe('CreateCrossServiceToken', () => {
|
||||
uuid: '00000000-0000-0000-0000-000000000000',
|
||||
},
|
||||
ongoing_transition: true,
|
||||
ongoing_revisions_transition: true,
|
||||
},
|
||||
60,
|
||||
)
|
||||
@@ -177,6 +179,7 @@ describe('CreateCrossServiceToken', () => {
|
||||
uuid: '00000000-0000-0000-0000-000000000000',
|
||||
},
|
||||
ongoing_transition: false,
|
||||
ongoing_revisions_transition: false,
|
||||
},
|
||||
60,
|
||||
)
|
||||
@@ -206,6 +209,7 @@ describe('CreateCrossServiceToken', () => {
|
||||
uuid: '00000000-0000-0000-0000-000000000000',
|
||||
},
|
||||
ongoing_transition: false,
|
||||
ongoing_revisions_transition: false,
|
||||
},
|
||||
60,
|
||||
)
|
||||
@@ -261,6 +265,7 @@ describe('CreateCrossServiceToken', () => {
|
||||
email: 'test@test.te',
|
||||
uuid: '00000000-0000-0000-0000-000000000000',
|
||||
},
|
||||
ongoing_revisions_transition: false,
|
||||
ongoing_transition: false,
|
||||
},
|
||||
60,
|
||||
|
||||
@@ -48,6 +48,7 @@ export class CreateCrossServiceToken implements UseCaseInterface<string> {
|
||||
}
|
||||
|
||||
const transitionStatus = await this.transitionStatusRepository.getStatus(user.uuid, 'items')
|
||||
const revisionsTransitionStatus = await this.transitionStatusRepository.getStatus(user.uuid, 'revisions')
|
||||
|
||||
const roles = await user.roles
|
||||
|
||||
@@ -59,7 +60,9 @@ export class CreateCrossServiceToken implements UseCaseInterface<string> {
|
||||
user: this.projectUser(user),
|
||||
roles: this.projectRoles(roles),
|
||||
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) => ({
|
||||
shared_vault_uuid: association.props.sharedVaultUuid.value,
|
||||
permission: association.props.permission.value,
|
||||
|
||||
@@ -4,13 +4,17 @@ import { GetTransitionStatusDTO } from './GetTransitionStatusDTO'
|
||||
import { UserRepositoryInterface } from '../../User/UserRepositoryInterface'
|
||||
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(
|
||||
private transitionStatusRepository: TransitionStatusRepositoryInterface,
|
||||
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)
|
||||
if (userUuidOrError.isFailed()) {
|
||||
return Result.fail(userUuidOrError.getError())
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export interface UpdateTransitionStatusDTO {
|
||||
userUuid: string
|
||||
transitionType: 'items' | 'revisions'
|
||||
status: 'STARTED' | 'FINISHED' | 'FAILED'
|
||||
status: 'STARTED' | 'IN_PROGRESS' | 'FINISHED' | 'FAILED'
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ export interface UserRepositoryInterface {
|
||||
streamTeam(memberEmail?: Email): Promise<ReadStream>
|
||||
findOneByUuid(uuid: Uuid): Promise<User | null>
|
||||
findOneByUsernameOrEmail(usernameOrEmail: Email | Username): Promise<User | null>
|
||||
findAllCreatedBetween(start: Date, end: Date): Promise<User[]>
|
||||
save(user: User): Promise<User>
|
||||
remove(user: User): Promise<User>
|
||||
}
|
||||
|
||||
@@ -10,18 +10,26 @@ export class RedisTransitionStatusRepository implements TransitionStatusReposito
|
||||
async updateStatus(
|
||||
userUuid: string,
|
||||
transitionType: 'items' | 'revisions',
|
||||
status: 'STARTED' | 'FAILED',
|
||||
status: 'STARTED' | 'IN_PROGRESS' | 'FAILED',
|
||||
): 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> {
|
||||
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
|
||||
| 'STARTED'
|
||||
| 'IN_PROGRESS'
|
||||
| 'FAILED'
|
||||
| null
|
||||
|
||||
|
||||
@@ -14,6 +14,13 @@ export class TypeORMUserRepository implements UserRepositoryInterface {
|
||||
private ormRepository: Repository<User>,
|
||||
) {}
|
||||
|
||||
async findAllCreatedBetween(start: Date, end: Date): Promise<User[]> {
|
||||
return this.ormRepository
|
||||
.createQueryBuilder('user')
|
||||
.where('user.created_at BETWEEN :start AND :end', { start, end })
|
||||
.getMany()
|
||||
}
|
||||
|
||||
async save(user: User): Promise<User> {
|
||||
return this.ormRepository.save(user)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/domain-events-infra",
|
||||
"version": "1.12.24",
|
||||
"version": "1.12.26",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,16 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/domain-events",
|
||||
"version": "2.125.0",
|
||||
"version": "2.125.2",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export interface TransitionStatusUpdatedEventPayload {
|
||||
userUuid: string
|
||||
transitionType: 'items' | 'revisions'
|
||||
status: 'STARTED' | 'FINISHED' | 'FAILED'
|
||||
status: 'STARTED' | 'IN_PROGRESS' | 'FINISHED' | 'FAILED'
|
||||
}
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/event-store",
|
||||
"version": "1.11.35",
|
||||
"version": "1.11.37",
|
||||
"description": "Event Store Service",
|
||||
"private": true,
|
||||
"main": "dist/src/index.js",
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/files-server",
|
||||
"version": "1.22.14",
|
||||
"version": "1.22.16",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,38 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.15.34](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.33...@standardnotes/home-server@1.15.34) (2023-09-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.15.33](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.32...@standardnotes/home-server@1.15.33) (2023-09-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.15.32](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.31...@standardnotes/home-server@1.15.32) (2023-09-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.15.31](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.30...@standardnotes/home-server@1.15.31) (2023-09-08)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/home-server",
|
||||
"version": "1.15.31",
|
||||
"version": "1.15.39",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,50 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** remove transitioning upon sign out ([#819](https://github.com/standardnotes/server/issues/819)) ([330bff0](https://github.com/standardnotes/server/commit/330bff0124f5f49c3441304d166ea43c21fea7bc))
|
||||
|
||||
## [1.33.6](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.33.5...@standardnotes/revisions-server@1.33.6) (2023-09-11)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* disable running migrations in worker mode of a given service ([a82b9a0](https://github.com/standardnotes/server/commit/a82b9a0c8a023ba8a450ff9e34bcd62f928fcab3))
|
||||
|
||||
## [1.33.5](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.33.4...@standardnotes/revisions-server@1.33.5) (2023-09-11)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **revisions:** add item_uuid to revisions metadata http representation ([9676a25](https://github.com/standardnotes/server/commit/9676a2586cabc344f3c244a5a20d4e1d57ac4c35))
|
||||
* **revisions:** conflict with table naming ([ed671be](https://github.com/standardnotes/server/commit/ed671be9c56eb8d54b8dd52a69d5e30e07203aa4))
|
||||
* **revisions:** legacy table syncing and select for metadata ([b4bf11d](https://github.com/standardnotes/server/commit/b4bf11d9da44cc1d980b6922f532ff487fe582df))
|
||||
* **revisions:** removing queries ([117b7b4](https://github.com/standardnotes/server/commit/117b7b4b99e09aebfd081433646ec05c2ebf308f))
|
||||
* **revisions:** rename table only if exists ([836883b](https://github.com/standardnotes/server/commit/836883b82dd436d0e9b3ae3b70f734ceb508e227))
|
||||
|
||||
## [1.33.4](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.33.3...@standardnotes/revisions-server@1.33.4) (2023-09-11)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **revisions:** add shared vault uuid to revision metadata http representation ([156fa7a](https://github.com/standardnotes/server/commit/156fa7a618b9f33a839580019d0398c1b838697d))
|
||||
|
||||
## [1.33.3](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.33.2...@standardnotes/revisions-server@1.33.3) (2023-09-08)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Env } from '../src/Bootstrap/Env'
|
||||
import { DomainEventSubscriberFactoryInterface } from '@standardnotes/domain-events'
|
||||
import { ContainerConfigLoader } from '../src/Bootstrap/Container'
|
||||
|
||||
const container = new ContainerConfigLoader()
|
||||
const container = new ContainerConfigLoader('worker')
|
||||
void container.load().then((container) => {
|
||||
const env: Env = new Env()
|
||||
env.load()
|
||||
|
||||
@@ -4,19 +4,19 @@ export class init1669113322388 implements MigrationInterface {
|
||||
name = 'init1669113322388'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await this.syncSchemaBetweenLegacyRevisions(queryRunner)
|
||||
|
||||
await queryRunner.query(
|
||||
'CREATE TABLE IF NOT EXISTS `revisions` (`uuid` varchar(36) NOT NULL, `item_uuid` varchar(36) NOT NULL, `user_uuid` varchar(36) NOT NULL, `content` mediumtext NULL, `content_type` varchar(255) NULL, `items_key_id` varchar(255) NULL, `enc_item_key` text NULL, `auth_hash` varchar(255) NULL, `creation_date` date NULL, `created_at` datetime(6) NULL, `updated_at` datetime(6) NULL, INDEX `item_uuid` (`item_uuid`), INDEX `user_uuid` (`user_uuid`), INDEX `creation_date` (`creation_date`), INDEX `created_at` (`created_at`), PRIMARY KEY (`uuid`)) ENGINE=InnoDB',
|
||||
'CREATE TABLE `revisions_revisions` (`uuid` varchar(36) NOT NULL, `item_uuid` varchar(36) NOT NULL, `user_uuid` varchar(36) NULL, `content` mediumtext NULL, `content_type` varchar(255) NULL, `items_key_id` varchar(255) NULL, `enc_item_key` text NULL, `auth_hash` varchar(255) NULL, `creation_date` date NULL, `created_at` datetime(6) NULL, `updated_at` datetime(6) NULL, INDEX `item_uuid` (`item_uuid`), INDEX `user_uuid` (`user_uuid`), INDEX `creation_date` (`creation_date`), INDEX `created_at` (`created_at`), PRIMARY KEY (`uuid`)) ENGINE=InnoDB',
|
||||
)
|
||||
|
||||
await this.syncSchemaBetweenLegacyRevisions(queryRunner)
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query('DROP INDEX `created_at` ON `revisions`')
|
||||
await queryRunner.query('DROP INDEX `creation_date` ON `revisions`')
|
||||
await queryRunner.query('DROP INDEX `user_uuid` ON `revisions`')
|
||||
await queryRunner.query('DROP INDEX `item_uuid` ON `revisions`')
|
||||
await queryRunner.query('DROP TABLE `revisions`')
|
||||
await queryRunner.query('DROP INDEX `created_at` ON `revisions_revisions`')
|
||||
await queryRunner.query('DROP INDEX `creation_date` ON `revisions_revisions`')
|
||||
await queryRunner.query('DROP INDEX `user_uuid` ON `revisions_revisions`')
|
||||
await queryRunner.query('DROP INDEX `item_uuid` ON `revisions_revisions`')
|
||||
await queryRunner.query('DROP TABLE `revisions_revisions`')
|
||||
}
|
||||
|
||||
private async syncSchemaBetweenLegacyRevisions(queryRunner: QueryRunner): Promise<void> {
|
||||
@@ -28,14 +28,10 @@ export class init1669113322388 implements MigrationInterface {
|
||||
return
|
||||
}
|
||||
|
||||
const revisionsTableHasUserUuidColumnQueryResult = await queryRunner.manager.query(
|
||||
'SELECT COUNT(*) as count FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = "revisions" AND column_name = "user_uuid"',
|
||||
await queryRunner.query(
|
||||
'INSERT INTO `revisions_revisions`(`uuid`, `item_uuid`, `user_uuid`, `content`, `content_type`, `items_key_id`, `enc_item_key`, `auth_hash`, `creation_date`, `created_at`, `updated_at`) SELECT `uuid`, `item_uuid`, NULL, `content`, `content_type`, `items_key_id`, `enc_item_key`, `auth_hash`, `creation_date`, `created_at`, `updated_at` FROM `revisions`',
|
||||
)
|
||||
const revisionsTableHasUserUuidColumn = revisionsTableHasUserUuidColumnQueryResult[0].count === 1
|
||||
if (revisionsTableHasUserUuidColumn) {
|
||||
return
|
||||
}
|
||||
|
||||
await queryRunner.query('ALTER TABLE `revisions` ADD COLUMN `user_uuid` varchar(36) NULL')
|
||||
await queryRunner.query('DROP TABLE `revisions`')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,24 +5,24 @@ export class removeDateIndexes1669636497932 implements MigrationInterface {
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
const indexRevisionsOnCreatedAt = await queryRunner.manager.query(
|
||||
'SHOW INDEX FROM `revisions` where `key_name` = "created_at"',
|
||||
'SHOW INDEX FROM `revisions_revisions` where `key_name` = "created_at"',
|
||||
)
|
||||
const indexRevisionsOnCreatedAtExist = indexRevisionsOnCreatedAt && indexRevisionsOnCreatedAt.length > 0
|
||||
if (indexRevisionsOnCreatedAtExist) {
|
||||
await queryRunner.query('DROP INDEX `created_at` ON `revisions`')
|
||||
await queryRunner.query('DROP INDEX `created_at` ON `revisions_revisions`')
|
||||
}
|
||||
|
||||
const indexRevisionsOnCreationDate = await queryRunner.manager.query(
|
||||
'SHOW INDEX FROM `revisions` where `key_name` = "creation_date"',
|
||||
'SHOW INDEX FROM `revisions_revisions` where `key_name` = "creation_date"',
|
||||
)
|
||||
const indexRevisionsOnCreationDateAtExist = indexRevisionsOnCreationDate && indexRevisionsOnCreationDate.length > 0
|
||||
if (indexRevisionsOnCreationDateAtExist) {
|
||||
await queryRunner.query('DROP INDEX `creation_date` ON `revisions`')
|
||||
await queryRunner.query('DROP INDEX `creation_date` ON `revisions_revisions`')
|
||||
}
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query('CREATE INDEX `creation_date` ON `revisions` (`creation_date`)')
|
||||
await queryRunner.query('CREATE INDEX `created_at` ON `revisions` (`created_at`)')
|
||||
await queryRunner.query('CREATE INDEX `creation_date` ON `revisions_revisions` (`creation_date`)')
|
||||
await queryRunner.query('CREATE INDEX `created_at` ON `revisions_revisions` (`created_at`)')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@ export class makeUserUuidNullable1669735585016 implements MigrationInterface {
|
||||
name = 'makeUserUuidNullable1669735585016'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query('ALTER TABLE `revisions` CHANGE `user_uuid` `user_uuid` varchar(36) NULL')
|
||||
await queryRunner.query('ALTER TABLE `revisions_revisions` CHANGE `user_uuid` `user_uuid` varchar(36) NULL')
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query('ALTER TABLE `revisions` CHANGE `user_uuid` `user_uuid` varchar(36) NOT NULL')
|
||||
await queryRunner.query('ALTER TABLE `revisions_revisions` CHANGE `user_uuid` `user_uuid` varchar(36) NOT NULL')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,16 +2,18 @@ import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class AddSharedVaultInformation1693915383950 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query('ALTER TABLE `revisions` ADD `edited_by` varchar(36) NULL')
|
||||
await queryRunner.query('ALTER TABLE `revisions` ADD `shared_vault_uuid` varchar(36) NULL')
|
||||
await queryRunner.query('ALTER TABLE `revisions` ADD `key_system_identifier` varchar(36) NULL')
|
||||
await queryRunner.query('CREATE INDEX `index_revisions_on_shared_vault_uuid` ON `revisions` (`shared_vault_uuid`)')
|
||||
await queryRunner.query('ALTER TABLE `revisions_revisions` ADD `edited_by` varchar(36) NULL')
|
||||
await queryRunner.query('ALTER TABLE `revisions_revisions` ADD `shared_vault_uuid` varchar(36) NULL')
|
||||
await queryRunner.query('ALTER TABLE `revisions_revisions` ADD `key_system_identifier` varchar(36) NULL')
|
||||
await queryRunner.query(
|
||||
'CREATE INDEX `index_revisions_on_shared_vault_uuid` ON `revisions_revisions` (`shared_vault_uuid`)',
|
||||
)
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query('DROP INDEX `index_revisions_on_shared_vault_uuid` ON `revisions`')
|
||||
await queryRunner.query('ALTER TABLE `revisions` DROP COLUMN `key_system_identifier`')
|
||||
await queryRunner.query('ALTER TABLE `revisions` DROP COLUMN `shared_vault_uuid`')
|
||||
await queryRunner.query('ALTER TABLE `revisions` DROP COLUMN `last_edited_by`')
|
||||
await queryRunner.query('DROP INDEX `index_revisions_on_shared_vault_uuid` ON `revisions_revisions`')
|
||||
await queryRunner.query('ALTER TABLE `revisions_revisions` DROP COLUMN `key_system_identifier`')
|
||||
await queryRunner.query('ALTER TABLE `revisions_revisions` DROP COLUMN `shared_vault_uuid`')
|
||||
await queryRunner.query('ALTER TABLE `revisions_revisions` DROP COLUMN `last_edited_by`')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class RenameRevisionsTable1694425333972 implements MigrationInterface {
|
||||
name = 'RenameRevisionsTable1694425333972'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
const revisionsTableExistsQueryResult = await queryRunner.manager.query(
|
||||
'SELECT COUNT(*) as count FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = "revisions"',
|
||||
)
|
||||
const revisionsTableExists = revisionsTableExistsQueryResult[0].count === 1
|
||||
if (revisionsTableExists) {
|
||||
await queryRunner.query('RENAME TABLE `revisions` TO `revisions_revisions`')
|
||||
}
|
||||
}
|
||||
|
||||
public async down(): Promise<void> {
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -5,15 +5,15 @@ export class initialBoilerplate1682678053275 implements MigrationInterface {
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
'CREATE TABLE "revisions" ("uuid" varchar PRIMARY KEY NOT NULL, "item_uuid" varchar(36) NOT NULL, "user_uuid" varchar(36), "content" text, "content_type" varchar(255), "items_key_id" varchar(255), "enc_item_key" text, "auth_hash" varchar(255), "creation_date" date, "created_at" datetime(6), "updated_at" datetime(6))',
|
||||
'CREATE TABLE "revisions_revisions" ("uuid" varchar PRIMARY KEY NOT NULL, "item_uuid" varchar(36) NOT NULL, "user_uuid" varchar(36), "content" text, "content_type" varchar(255), "items_key_id" varchar(255), "enc_item_key" text, "auth_hash" varchar(255), "creation_date" date, "created_at" datetime(6), "updated_at" datetime(6))',
|
||||
)
|
||||
await queryRunner.query('CREATE INDEX "item_uuid" ON "revisions" ("item_uuid") ')
|
||||
await queryRunner.query('CREATE INDEX "user_uuid" ON "revisions" ("user_uuid") ')
|
||||
await queryRunner.query('CREATE INDEX "item_uuid" ON "revisions_revisions" ("item_uuid") ')
|
||||
await queryRunner.query('CREATE INDEX "user_uuid" ON "revisions_revisions" ("user_uuid") ')
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query('DROP INDEX "user_uuid"')
|
||||
await queryRunner.query('DROP INDEX "item_uuid"')
|
||||
await queryRunner.query('DROP TABLE "revisions"')
|
||||
await queryRunner.query('DROP TABLE "revisions_revisions"')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,28 +10,30 @@ export class AddSharedVaultInformation1693915775491 implements MigrationInterfac
|
||||
'CREATE TABLE "temporary_revisions" ("uuid" varchar PRIMARY KEY NOT NULL, "item_uuid" varchar(36) NOT NULL, "user_uuid" varchar(36), "content" text, "content_type" varchar(255), "items_key_id" varchar(255), "enc_item_key" text, "auth_hash" varchar(255), "creation_date" date, "created_at" datetime(6), "updated_at" datetime(6), "edited_by" varchar(36), "shared_vault_uuid" varchar(36), "key_system_identifier" varchar(36))',
|
||||
)
|
||||
await queryRunner.query(
|
||||
'INSERT INTO "temporary_revisions"("uuid", "item_uuid", "user_uuid", "content", "content_type", "items_key_id", "enc_item_key", "auth_hash", "creation_date", "created_at", "updated_at") SELECT "uuid", "item_uuid", "user_uuid", "content", "content_type", "items_key_id", "enc_item_key", "auth_hash", "creation_date", "created_at", "updated_at" FROM "revisions"',
|
||||
'INSERT INTO "temporary_revisions"("uuid", "item_uuid", "user_uuid", "content", "content_type", "items_key_id", "enc_item_key", "auth_hash", "creation_date", "created_at", "updated_at") SELECT "uuid", "item_uuid", "user_uuid", "content", "content_type", "items_key_id", "enc_item_key", "auth_hash", "creation_date", "created_at", "updated_at" FROM "revisions_revisions"',
|
||||
)
|
||||
await queryRunner.query('DROP TABLE "revisions_revisions"')
|
||||
await queryRunner.query('ALTER TABLE "temporary_revisions" RENAME TO "revisions_revisions"')
|
||||
await queryRunner.query('CREATE INDEX "user_uuid" ON "revisions_revisions" ("user_uuid") ')
|
||||
await queryRunner.query('CREATE INDEX "item_uuid" ON "revisions_revisions" ("item_uuid") ')
|
||||
await queryRunner.query(
|
||||
'CREATE INDEX "index_revisions_on_shared_vault_uuid" ON "revisions_revisions" ("shared_vault_uuid") ',
|
||||
)
|
||||
await queryRunner.query('DROP TABLE "revisions"')
|
||||
await queryRunner.query('ALTER TABLE "temporary_revisions" RENAME TO "revisions"')
|
||||
await queryRunner.query('CREATE INDEX "user_uuid" ON "revisions" ("user_uuid") ')
|
||||
await queryRunner.query('CREATE INDEX "item_uuid" ON "revisions" ("item_uuid") ')
|
||||
await queryRunner.query('CREATE INDEX "index_revisions_on_shared_vault_uuid" ON "revisions" ("shared_vault_uuid") ')
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query('DROP INDEX "index_revisions_on_shared_vault_uuid"')
|
||||
await queryRunner.query('DROP INDEX "item_uuid"')
|
||||
await queryRunner.query('DROP INDEX "user_uuid"')
|
||||
await queryRunner.query('ALTER TABLE "revisions" RENAME TO "temporary_revisions"')
|
||||
await queryRunner.query('ALTER TABLE "revisions_revisions" RENAME TO "temporary_revisions"')
|
||||
await queryRunner.query(
|
||||
'CREATE TABLE "revisions" ("uuid" varchar PRIMARY KEY NOT NULL, "item_uuid" varchar(36) NOT NULL, "user_uuid" varchar(36), "content" text, "content_type" varchar(255), "items_key_id" varchar(255), "enc_item_key" text, "auth_hash" varchar(255), "creation_date" date, "created_at" datetime(6), "updated_at" datetime(6))',
|
||||
'CREATE TABLE "revisions_revisions" ("uuid" varchar PRIMARY KEY NOT NULL, "item_uuid" varchar(36) NOT NULL, "user_uuid" varchar(36), "content" text, "content_type" varchar(255), "items_key_id" varchar(255), "enc_item_key" text, "auth_hash" varchar(255), "creation_date" date, "created_at" datetime(6), "updated_at" datetime(6))',
|
||||
)
|
||||
await queryRunner.query(
|
||||
'INSERT INTO "revisions"("uuid", "item_uuid", "user_uuid", "content", "content_type", "items_key_id", "enc_item_key", "auth_hash", "creation_date", "created_at", "updated_at") SELECT "uuid", "item_uuid", "user_uuid", "content", "content_type", "items_key_id", "enc_item_key", "auth_hash", "creation_date", "created_at", "updated_at" FROM "temporary_revisions"',
|
||||
'INSERT INTO "revisions_revisions"("uuid", "item_uuid", "user_uuid", "content", "content_type", "items_key_id", "enc_item_key", "auth_hash", "creation_date", "created_at", "updated_at") SELECT "uuid", "item_uuid", "user_uuid", "content", "content_type", "items_key_id", "enc_item_key", "auth_hash", "creation_date", "created_at", "updated_at" FROM "temporary_revisions"',
|
||||
)
|
||||
await queryRunner.query('DROP TABLE "temporary_revisions"')
|
||||
await queryRunner.query('CREATE INDEX "item_uuid" ON "revisions" ("item_uuid") ')
|
||||
await queryRunner.query('CREATE INDEX "user_uuid" ON "revisions" ("user_uuid") ')
|
||||
await queryRunner.query('CREATE INDEX "item_uuid" ON "revisions_revisions" ("item_uuid") ')
|
||||
await queryRunner.query('CREATE INDEX "user_uuid" ON "revisions_revisions" ("user_uuid") ')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/revisions-server",
|
||||
"version": "1.33.3",
|
||||
"version": "1.33.10",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -70,6 +70,8 @@ import { RemoveRevisionsFromSharedVault } from '../Domain/UseCase/RemoveRevision
|
||||
import { ItemRemovedFromSharedVaultEventHandler } from '../Domain/Handler/ItemRemovedFromSharedVaultEventHandler'
|
||||
|
||||
export class ContainerConfigLoader {
|
||||
constructor(private mode: 'server' | 'worker' = 'server') {}
|
||||
|
||||
async load(configuration?: {
|
||||
controllerConatiner?: ControllerContainerInterface
|
||||
directCallDomainEventPublisher?: DirectCallDomainEventPublisher
|
||||
@@ -115,7 +117,7 @@ export class ContainerConfigLoader {
|
||||
|
||||
container.bind<TimerInterface>(TYPES.Revisions_Timer).toDynamicValue(() => new Timer())
|
||||
|
||||
const appDataSource = new AppDataSource(env)
|
||||
const appDataSource = new AppDataSource({ env, runMigrations: this.mode === 'server' })
|
||||
await appDataSource.initialize()
|
||||
|
||||
logger.debug('Database initialized')
|
||||
@@ -348,6 +350,7 @@ export class ContainerConfigLoader {
|
||||
: null,
|
||||
container.get<TimerInterface>(TYPES.Revisions_Timer),
|
||||
container.get<winston.Logger>(TYPES.Revisions_Logger),
|
||||
env.get('MIGRATION_BATCH_SIZE', true) ? +env.get('MIGRATION_BATCH_SIZE', true) : 100,
|
||||
),
|
||||
)
|
||||
container
|
||||
|
||||
@@ -12,7 +12,12 @@ export class AppDataSource {
|
||||
private _dataSource: DataSource | undefined
|
||||
private _secondaryDataSource: DataSource | undefined
|
||||
|
||||
constructor(private env: Env) {}
|
||||
constructor(
|
||||
private configuration: {
|
||||
env: Env
|
||||
runMigrations: boolean
|
||||
},
|
||||
) {}
|
||||
|
||||
getRepository<Entity extends ObjectLiteral>(target: EntityTarget<Entity>): Repository<Entity> {
|
||||
if (!this._dataSource) {
|
||||
@@ -39,20 +44,20 @@ export class AppDataSource {
|
||||
}
|
||||
|
||||
get secondaryDataSource(): DataSource | undefined {
|
||||
this.env.load()
|
||||
this.configuration.env.load()
|
||||
|
||||
if (this.env.get('SECONDARY_DB_ENABLED', true) !== 'true') {
|
||||
if (this.configuration.env.get('SECONDARY_DB_ENABLED', true) !== 'true') {
|
||||
return undefined
|
||||
}
|
||||
|
||||
this._secondaryDataSource = new DataSource({
|
||||
type: 'mongodb',
|
||||
host: this.env.get('MONGO_HOST'),
|
||||
host: this.configuration.env.get('MONGO_HOST'),
|
||||
authSource: 'admin',
|
||||
port: parseInt(this.env.get('MONGO_PORT')),
|
||||
username: this.env.get('MONGO_USERNAME'),
|
||||
password: this.env.get('MONGO_PASSWORD', true),
|
||||
database: this.env.get('MONGO_DATABASE'),
|
||||
port: parseInt(this.configuration.env.get('MONGO_PORT')),
|
||||
username: this.configuration.env.get('MONGO_USERNAME'),
|
||||
password: this.configuration.env.get('MONGO_PASSWORD', true),
|
||||
database: this.configuration.env.get('MONGO_DATABASE'),
|
||||
entities: [MongoDBRevision],
|
||||
retryWrites: false,
|
||||
synchronize: true,
|
||||
@@ -62,15 +67,16 @@ export class AppDataSource {
|
||||
}
|
||||
|
||||
get dataSource(): DataSource {
|
||||
this.env.load()
|
||||
this.configuration.env.load()
|
||||
|
||||
const isConfiguredForMySQL = this.env.get('DB_TYPE') === 'mysql'
|
||||
const isConfiguredForMySQL = this.configuration.env.get('DB_TYPE') === 'mysql'
|
||||
|
||||
const isConfiguredForHomeServerOrSelfHosting =
|
||||
this.env.get('MODE', true) === 'home-server' || this.env.get('MODE', true) === 'self-hosted'
|
||||
this.configuration.env.get('MODE', true) === 'home-server' ||
|
||||
this.configuration.env.get('MODE', true) === 'self-hosted'
|
||||
|
||||
const maxQueryExecutionTime = this.env.get('DB_MAX_QUERY_EXECUTION_TIME', true)
|
||||
? +this.env.get('DB_MAX_QUERY_EXECUTION_TIME', true)
|
||||
const maxQueryExecutionTime = this.configuration.env.get('DB_MAX_QUERY_EXECUTION_TIME', true)
|
||||
? +this.configuration.env.get('DB_MAX_QUERY_EXECUTION_TIME', true)
|
||||
: 45_000
|
||||
|
||||
const migrationsSourceDirectoryName = isConfiguredForMySQL
|
||||
@@ -83,28 +89,28 @@ export class AppDataSource {
|
||||
maxQueryExecutionTime,
|
||||
entities: [isConfiguredForHomeServerOrSelfHosting ? SQLRevision : SQLLegacyRevision],
|
||||
migrations: [`${__dirname}/../../migrations/${migrationsSourceDirectoryName}/*.js`],
|
||||
migrationsRun: true,
|
||||
logging: <LoggerOptions>this.env.get('DB_DEBUG_LEVEL', true) ?? 'info',
|
||||
migrationsRun: this.configuration.runMigrations,
|
||||
logging: <LoggerOptions>this.configuration.env.get('DB_DEBUG_LEVEL', true) ?? 'info',
|
||||
}
|
||||
|
||||
if (isConfiguredForMySQL) {
|
||||
const inReplicaMode = this.env.get('DB_REPLICA_HOST', true) ? true : false
|
||||
const inReplicaMode = this.configuration.env.get('DB_REPLICA_HOST', true) ? true : false
|
||||
|
||||
const replicationConfig = {
|
||||
master: {
|
||||
host: this.env.get('DB_HOST'),
|
||||
port: parseInt(this.env.get('DB_PORT')),
|
||||
username: this.env.get('DB_USERNAME'),
|
||||
password: this.env.get('DB_PASSWORD'),
|
||||
database: this.env.get('DB_DATABASE'),
|
||||
host: this.configuration.env.get('DB_HOST'),
|
||||
port: parseInt(this.configuration.env.get('DB_PORT')),
|
||||
username: this.configuration.env.get('DB_USERNAME'),
|
||||
password: this.configuration.env.get('DB_PASSWORD'),
|
||||
database: this.configuration.env.get('DB_DATABASE'),
|
||||
},
|
||||
slaves: [
|
||||
{
|
||||
host: this.env.get('DB_REPLICA_HOST', true),
|
||||
port: parseInt(this.env.get('DB_PORT')),
|
||||
username: this.env.get('DB_USERNAME'),
|
||||
password: this.env.get('DB_PASSWORD'),
|
||||
database: this.env.get('DB_DATABASE'),
|
||||
host: this.configuration.env.get('DB_REPLICA_HOST', true),
|
||||
port: parseInt(this.configuration.env.get('DB_PORT')),
|
||||
username: this.configuration.env.get('DB_USERNAME'),
|
||||
password: this.configuration.env.get('DB_PASSWORD'),
|
||||
database: this.configuration.env.get('DB_DATABASE'),
|
||||
},
|
||||
],
|
||||
removeNodeErrorCount: 10,
|
||||
@@ -118,11 +124,11 @@ export class AppDataSource {
|
||||
supportBigNumbers: true,
|
||||
bigNumberStrings: false,
|
||||
replication: inReplicaMode ? replicationConfig : undefined,
|
||||
host: inReplicaMode ? undefined : this.env.get('DB_HOST'),
|
||||
port: inReplicaMode ? undefined : parseInt(this.env.get('DB_PORT')),
|
||||
username: inReplicaMode ? undefined : this.env.get('DB_USERNAME'),
|
||||
password: inReplicaMode ? undefined : this.env.get('DB_PASSWORD'),
|
||||
database: inReplicaMode ? undefined : this.env.get('DB_DATABASE'),
|
||||
host: inReplicaMode ? undefined : this.configuration.env.get('DB_HOST'),
|
||||
port: inReplicaMode ? undefined : parseInt(this.configuration.env.get('DB_PORT')),
|
||||
username: inReplicaMode ? undefined : this.configuration.env.get('DB_USERNAME'),
|
||||
password: inReplicaMode ? undefined : this.configuration.env.get('DB_PASSWORD'),
|
||||
database: inReplicaMode ? undefined : this.configuration.env.get('DB_DATABASE'),
|
||||
}
|
||||
|
||||
this._dataSource = new DataSource(mySQLDataSourceOptions)
|
||||
@@ -130,7 +136,7 @@ export class AppDataSource {
|
||||
const sqliteDataSourceOptions: SqliteConnectionOptions = {
|
||||
...commonDataSourceOptions,
|
||||
type: 'sqlite',
|
||||
database: this.env.get('DB_SQLITE_DATABASE_PATH'),
|
||||
database: this.configuration.env.get('DB_SQLITE_DATABASE_PATH'),
|
||||
enableWAL: true,
|
||||
busyErrorRetry: 2000,
|
||||
}
|
||||
|
||||
@@ -4,4 +4,4 @@ import { Env } from './Env'
|
||||
const env: Env = new Env()
|
||||
env.load()
|
||||
|
||||
export const MigrationsDataSource = new AppDataSource(env).dataSource
|
||||
export const MigrationsDataSource = new AppDataSource({ env, runMigrations: true }).dataSource
|
||||
|
||||
@@ -4,6 +4,6 @@ export interface DomainEventFactoryInterface {
|
||||
createTransitionStatusUpdatedEvent(dto: {
|
||||
userUuid: string
|
||||
transitionType: 'items' | 'revisions'
|
||||
status: 'STARTED' | 'FAILED' | 'FINISHED'
|
||||
status: 'STARTED' | 'IN_PROGRESS' | 'FAILED' | 'FINISHED'
|
||||
}): TransitionStatusUpdatedEvent
|
||||
}
|
||||
|
||||
@@ -29,6 +29,14 @@ export class TransitionStatusUpdatedEventHandler implements DomainEventHandlerIn
|
||||
}
|
||||
|
||||
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({
|
||||
userUuid: event.payload.userUuid,
|
||||
})
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { ContentType, Dates } from '@standardnotes/domain-core'
|
||||
import { ContentType, Dates, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
export interface RevisionMetadataProps {
|
||||
contentType: ContentType
|
||||
itemUuid: Uuid
|
||||
sharedVaultUuid: Uuid | null
|
||||
dates: Dates
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ describe('TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser', () => {
|
||||
secondaryRevisionRepository,
|
||||
timer,
|
||||
logger,
|
||||
1,
|
||||
)
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -98,7 +99,7 @@ describe('TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser', () => {
|
||||
secondaryRevisionRepository = {} as jest.Mocked<RevisionRepositoryInterface>
|
||||
secondaryRevisionRepository.insert = jest.fn().mockResolvedValue(true)
|
||||
secondaryRevisionRepository.removeByUserUuid = jest.fn().mockResolvedValue(undefined)
|
||||
secondaryRevisionRepository.countByUserUuid = jest.fn().mockResolvedValue(2)
|
||||
secondaryRevisionRepository.countByUserUuid = jest.fn().mockResolvedValueOnce(0).mockResolvedValueOnce(2)
|
||||
secondaryRevisionRepository.findOneByUuid = jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce(secondaryRevision1)
|
||||
@@ -200,9 +201,6 @@ describe('TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser', () => {
|
||||
})
|
||||
|
||||
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()
|
||||
@@ -331,7 +329,10 @@ describe('TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser', () => {
|
||||
})
|
||||
|
||||
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)
|
||||
;(secondaryRevisionRepository as RevisionRepositoryInterface).countByUserUuid = jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce(0)
|
||||
.mockResolvedValueOnce(1)
|
||||
|
||||
const useCase = createUseCase()
|
||||
|
||||
@@ -348,7 +349,7 @@ describe('TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser', () => {
|
||||
expect(primaryRevisionRepository.countByUserUuid).toHaveBeenCalledWith(
|
||||
Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
)
|
||||
expect((secondaryRevisionRepository as RevisionRepositoryInterface).countByUserUuid).toHaveBeenCalledTimes(1)
|
||||
expect((secondaryRevisionRepository as RevisionRepositoryInterface).countByUserUuid).toHaveBeenCalledTimes(2)
|
||||
expect(primaryRevisionRepository.removeByUserUuid).not.toHaveBeenCalled()
|
||||
expect((secondaryRevisionRepository as RevisionRepositoryInterface).removeByUserUuid).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
@@ -372,7 +373,6 @@ describe('TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser', () => {
|
||||
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)
|
||||
})
|
||||
@@ -380,6 +380,7 @@ describe('TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser', () => {
|
||||
it('should fail if an error is thrown during integrity check between primary and secondary database', async () => {
|
||||
;(secondaryRevisionRepository as RevisionRepositoryInterface).countByUserUuid = jest
|
||||
.fn()
|
||||
.mockReturnValueOnce(0)
|
||||
.mockRejectedValue(new Error('error'))
|
||||
|
||||
const useCase = createUseCase()
|
||||
@@ -434,4 +435,21 @@ describe('TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser', () => {
|
||||
expect(primaryRevisionRepository.removeByUserUuid).not.toHaveBeenCalled()
|
||||
expect((secondaryRevisionRepository as RevisionRepositoryInterface).removeByUserUuid).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not migrate revisions if there are already revisions in the secondary database', async () => {
|
||||
;(secondaryRevisionRepository as RevisionRepositoryInterface).countByUserUuid = jest.fn().mockResolvedValueOnce(1)
|
||||
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
|
||||
expect(primaryRevisionRepository.findByUserUuid).not.toHaveBeenCalled()
|
||||
expect((secondaryRevisionRepository as RevisionRepositoryInterface).insert).not.toHaveBeenCalled()
|
||||
expect(primaryRevisionRepository.removeByUserUuid).not.toHaveBeenCalled()
|
||||
expect((secondaryRevisionRepository as RevisionRepositoryInterface).removeByUserUuid).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -11,9 +11,12 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
||||
private secondRevisionsRepository: RevisionRepositoryInterface | null,
|
||||
private timer: TimerInterface,
|
||||
private logger: Logger,
|
||||
private pageSize: number,
|
||||
) {}
|
||||
|
||||
async execute(dto: TransitionRevisionsFromPrimaryToSecondaryDatabaseForUserDTO): Promise<Result<void>> {
|
||||
this.logger.info(`Transitioning revisions for user ${dto.userUuid}`)
|
||||
|
||||
if (this.secondRevisionsRepository === null) {
|
||||
return Result.fail('Secondary revision repository is not set')
|
||||
}
|
||||
@@ -30,6 +33,10 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
||||
return Result.ok()
|
||||
}
|
||||
|
||||
if (await this.hasAlreadyDataInSecondaryDatabase(userUuid)) {
|
||||
return Result.fail(`Revisions for user ${userUuid.value} already exist in secondary database`)
|
||||
}
|
||||
|
||||
const migrationTimeStart = this.timer.getTimestampInMicroseconds()
|
||||
|
||||
this.logger.debug(`Transitioning revisions for user ${userUuid.value}`)
|
||||
@@ -83,13 +90,12 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
||||
try {
|
||||
const totalRevisionsCountForUser = await this.primaryRevisionsRepository.countByUserUuid(userUuid)
|
||||
let totalRevisionsCountTransitionedToSecondary = 0
|
||||
const pageSize = 1
|
||||
const totalPages = Math.ceil(totalRevisionsCountForUser / pageSize)
|
||||
const totalPages = Math.ceil(totalRevisionsCountForUser / this.pageSize)
|
||||
for (let currentPage = 1; currentPage <= totalPages; currentPage++) {
|
||||
const query = {
|
||||
userUuid: userUuid,
|
||||
offset: (currentPage - 1) * pageSize,
|
||||
limit: pageSize,
|
||||
offset: (currentPage - 1) * this.pageSize,
|
||||
limit: this.pageSize,
|
||||
}
|
||||
|
||||
const revisions = await this.primaryRevisionsRepository.findByUserUuid(query)
|
||||
@@ -143,6 +149,14 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
||||
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 isAlreadyMigrated(userUuid: Uuid): Promise<boolean> {
|
||||
const totalRevisionsCountForUserInPrimary = await this.primaryRevisionsRepository.countByUserUuid(userUuid)
|
||||
|
||||
@@ -153,13 +167,12 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
||||
try {
|
||||
const totalRevisionsCountForUserInPrimary = await this.primaryRevisionsRepository.countByUserUuid(userUuid)
|
||||
|
||||
const pageSize = 1
|
||||
const totalPages = Math.ceil(totalRevisionsCountForUserInPrimary / pageSize)
|
||||
const totalPages = Math.ceil(totalRevisionsCountForUserInPrimary / this.pageSize)
|
||||
for (let currentPage = 1; currentPage <= totalPages; currentPage++) {
|
||||
const query = {
|
||||
userUuid: userUuid,
|
||||
offset: (currentPage - 1) * pageSize,
|
||||
limit: pageSize,
|
||||
offset: (currentPage - 1) * this.pageSize,
|
||||
limit: this.pageSize,
|
||||
}
|
||||
|
||||
const revisions = await this.primaryRevisionsRepository.findByUserUuid(query)
|
||||
@@ -180,7 +193,11 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
||||
}
|
||||
|
||||
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)}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ export class MongoDBRevisionRepository implements RevisionRepositoryInterface {
|
||||
let persistence = []
|
||||
if (sharedVaultUuids.length > 0) {
|
||||
persistence = await this.mongoRepository.find({
|
||||
select: ['_id', 'contentType', 'createdAt', 'updatedAt'],
|
||||
select: ['_id', 'contentType', 'createdAt', 'updatedAt', 'sharedVaultUuid', 'itemUuid'],
|
||||
where: {
|
||||
$and: [
|
||||
{ itemUuid: { $eq: itemUuid.value } },
|
||||
@@ -144,7 +144,7 @@ export class MongoDBRevisionRepository implements RevisionRepositoryInterface {
|
||||
})
|
||||
} else {
|
||||
persistence = await this.mongoRepository.find({
|
||||
select: ['_id', 'contentType', 'createdAt', 'updatedAt'],
|
||||
select: ['_id', 'contentType', 'createdAt', 'updatedAt', 'sharedVaultUuid', 'itemUuid'],
|
||||
where: {
|
||||
$and: [{ itemUuid: { $eq: itemUuid.value } }, { userUuid: { $eq: userUuid.value } }],
|
||||
},
|
||||
|
||||
@@ -129,6 +129,7 @@ export class SQLLegacyRevisionRepository implements RevisionRepositoryInterface
|
||||
.addSelect('content_type', 'contentType')
|
||||
.addSelect('created_at', 'createdAt')
|
||||
.addSelect('updated_at', 'updatedAt')
|
||||
.addSelect('item_uuid', 'itemUuid')
|
||||
.where('item_uuid = :itemUuid', { itemUuid: itemUuid.value })
|
||||
.andWhere('user_uuid = :userUuid', { userUuid: userUuid.value })
|
||||
.orderBy('created_at', 'DESC')
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Column, Entity, Index } from 'typeorm'
|
||||
|
||||
import { SQLLegacyRevision } from './SQLLegacyRevision'
|
||||
|
||||
@Entity({ name: 'revisions' })
|
||||
@Entity({ name: 'revisions_revisions' })
|
||||
export class SQLRevision extends SQLLegacyRevision {
|
||||
@Column({
|
||||
type: 'varchar',
|
||||
|
||||
@@ -17,6 +17,27 @@ export class SQLRevisionRepository extends SQLLegacyRevisionRepository {
|
||||
super(ormRepository, revisionMetadataMapper, revisionMapper, logger)
|
||||
}
|
||||
|
||||
override async removeByUserUuid(userUuid: Uuid): Promise<void> {
|
||||
await this.ormRepository
|
||||
.createQueryBuilder()
|
||||
.delete()
|
||||
.from('revisions_revisions')
|
||||
.where('user_uuid = :userUuid', { userUuid: userUuid.value })
|
||||
.execute()
|
||||
}
|
||||
|
||||
override async removeOneByUuid(revisionUuid: Uuid, userUuid: Uuid): Promise<void> {
|
||||
await this.ormRepository
|
||||
.createQueryBuilder()
|
||||
.delete()
|
||||
.from('revisions_revisions')
|
||||
.where('uuid = :revisionUuid AND user_uuid = :userUuid', {
|
||||
userUuid: userUuid.value,
|
||||
revisionUuid: revisionUuid.value,
|
||||
})
|
||||
.execute()
|
||||
}
|
||||
|
||||
override async findOneByUuid(revisionUuid: Uuid, userUuid: Uuid, sharedVaultUuids: Uuid[]): Promise<Revision | null> {
|
||||
const queryBuilder = this.ormRepository.createQueryBuilder()
|
||||
|
||||
@@ -71,6 +92,8 @@ export class SQLRevisionRepository extends SQLLegacyRevisionRepository {
|
||||
.addSelect('content_type', 'contentType')
|
||||
.addSelect('created_at', 'createdAt')
|
||||
.addSelect('updated_at', 'updatedAt')
|
||||
.addSelect('shared_vault_uuid', 'sharedVaultUuid')
|
||||
.addSelect('item_uuid', 'itemUuid')
|
||||
.orderBy('created_at', 'DESC')
|
||||
|
||||
if (sharedVaultUuids.length > 0) {
|
||||
|
||||
@@ -15,10 +15,12 @@ export class RevisionMetadataHttpMapper
|
||||
toProjection(domain: RevisionMetadata): RevisionMetadataHttpRepresentation {
|
||||
return {
|
||||
uuid: domain.id.toString(),
|
||||
item_uuid: domain.props.itemUuid.value,
|
||||
content_type: domain.props.contentType.value as string,
|
||||
created_at: domain.props.dates.createdAt.toISOString(),
|
||||
updated_at: domain.props.dates.updatedAt.toISOString(),
|
||||
required_role: this.getRequiredRoleToViewRevision.execute({ createdAt: domain.props.dates.createdAt }).getValue(),
|
||||
shared_vault_uuid: domain.props.sharedVaultUuid ? domain.props.sharedVaultUuid.value : null,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
export interface RevisionMetadataHttpRepresentation {
|
||||
uuid: string
|
||||
item_uuid: string
|
||||
content_type: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
required_role: string
|
||||
shared_vault_uuid: string | null
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { MapperInterface, Dates, UniqueEntityId, ContentType } from '@standardnotes/domain-core'
|
||||
import { MapperInterface, Dates, UniqueEntityId, ContentType, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { RevisionMetadata } from '../../../Domain/Revision/RevisionMetadata'
|
||||
import { MongoDBRevision } from '../../../Infra/TypeORM/MongoDB/MongoDBRevision'
|
||||
@@ -20,10 +20,27 @@ export class MongoDBRevisionMetadataPersistenceMapper implements MapperInterface
|
||||
}
|
||||
const dates = datesOrError.getValue()
|
||||
|
||||
let sharedVaultUuid = null
|
||||
if (projection.sharedVaultUuid) {
|
||||
const sharedVaultUuidOrError = Uuid.create(projection.sharedVaultUuid)
|
||||
if (sharedVaultUuidOrError.isFailed()) {
|
||||
throw new Error(`Could not create shared vault uuid: ${sharedVaultUuidOrError.getError()}`)
|
||||
}
|
||||
sharedVaultUuid = sharedVaultUuidOrError.getValue()
|
||||
}
|
||||
|
||||
const itemUuidOrError = Uuid.create(projection.itemUuid)
|
||||
if (itemUuidOrError.isFailed()) {
|
||||
throw new Error(`Could not create item uuid: ${itemUuidOrError.getError()}`)
|
||||
}
|
||||
const itemUuid = itemUuidOrError.getValue()
|
||||
|
||||
const revisionMetadataOrError = RevisionMetadata.create(
|
||||
{
|
||||
contentType,
|
||||
dates,
|
||||
sharedVaultUuid,
|
||||
itemUuid,
|
||||
},
|
||||
new UniqueEntityId(projection._id.toHexString()),
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { MapperInterface, Dates, UniqueEntityId, ContentType } from '@standardnotes/domain-core'
|
||||
import { MapperInterface, Dates, UniqueEntityId, ContentType, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { RevisionMetadata } from '../../../Domain/Revision/RevisionMetadata'
|
||||
import { SQLLegacyRevision } from '../../../Infra/TypeORM/SQL/SQLLegacyRevision'
|
||||
@@ -22,10 +22,18 @@ export class SQLLegacyRevisionMetadataPersistenceMapper
|
||||
}
|
||||
const dates = datesOrError.getValue()
|
||||
|
||||
const itemUuidOrError = Uuid.create(projection.itemUuid)
|
||||
if (itemUuidOrError.isFailed()) {
|
||||
throw new Error(`Could not create item uuid: ${itemUuidOrError.getError()}`)
|
||||
}
|
||||
const itemUuid = itemUuidOrError.getValue()
|
||||
|
||||
const revisionMetadataOrError = RevisionMetadata.create(
|
||||
{
|
||||
contentType,
|
||||
dates,
|
||||
sharedVaultUuid: null,
|
||||
itemUuid,
|
||||
},
|
||||
new UniqueEntityId(projection.uuid),
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { MapperInterface, Dates, UniqueEntityId, ContentType } from '@standardnotes/domain-core'
|
||||
import { MapperInterface, Dates, UniqueEntityId, ContentType, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { RevisionMetadata } from '../../../Domain/Revision/RevisionMetadata'
|
||||
import { SQLRevision } from '../../../Infra/TypeORM/SQL/SQLRevision'
|
||||
@@ -20,10 +20,27 @@ export class SQLRevisionMetadataPersistenceMapper implements MapperInterface<Rev
|
||||
}
|
||||
const dates = datesOrError.getValue()
|
||||
|
||||
let sharedVaultUuid = null
|
||||
if (projection.sharedVaultUuid) {
|
||||
const sharedVaultUuidOrError = Uuid.create(projection.sharedVaultUuid)
|
||||
if (sharedVaultUuidOrError.isFailed()) {
|
||||
throw new Error(`Could not create shared vault uuid: ${sharedVaultUuidOrError.getError()}`)
|
||||
}
|
||||
sharedVaultUuid = sharedVaultUuidOrError.getValue()
|
||||
}
|
||||
|
||||
const itemUuidOrError = Uuid.create(projection.itemUuid)
|
||||
if (itemUuidOrError.isFailed()) {
|
||||
throw new Error(`Could not create item uuid: ${itemUuidOrError.getError()}`)
|
||||
}
|
||||
const itemUuid = itemUuidOrError.getValue()
|
||||
|
||||
const revisionMetadataOrError = RevisionMetadata.create(
|
||||
{
|
||||
contentType,
|
||||
dates,
|
||||
sharedVaultUuid,
|
||||
itemUuid,
|
||||
},
|
||||
new UniqueEntityId(projection.uuid),
|
||||
)
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/scheduler-server",
|
||||
"version": "1.20.39",
|
||||
"version": "1.20.41",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.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)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/security",
|
||||
"version": "1.13.0",
|
||||
"version": "1.13.1",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -25,4 +25,5 @@ export type CrossServiceTokenData = {
|
||||
}
|
||||
extensionKey?: string
|
||||
ongoing_transition?: boolean
|
||||
ongoing_revisions_transition?: boolean
|
||||
}
|
||||
|
||||
@@ -3,6 +3,40 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** remove transitioning upon sign out ([#819](https://github.com/standardnotes/syncing-server-js/issues/819)) ([330bff0](https://github.com/standardnotes/syncing-server-js/commit/330bff0124f5f49c3441304d166ea43c21fea7bc))
|
||||
|
||||
## [1.95.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.95.0...@standardnotes/syncing-server@1.95.1) (2023-09-11)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* disable running migrations in worker mode of a given service ([a82b9a0](https://github.com/standardnotes/syncing-server-js/commit/a82b9a0c8a023ba8a450ff9e34bcd62f928fcab3))
|
||||
|
||||
# [1.95.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.94.0...@standardnotes/syncing-server@1.95.0) (2023-09-08)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -24,7 +24,7 @@ const requestTransition = async (
|
||||
return
|
||||
}
|
||||
|
||||
const container = new ContainerConfigLoader()
|
||||
const container = new ContainerConfigLoader('worker')
|
||||
void container.load().then((container) => {
|
||||
const env: Env = new Env()
|
||||
env.load()
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Env } from '../src/Bootstrap/Env'
|
||||
import { DomainEventSubscriberFactoryInterface } from '@standardnotes/domain-events'
|
||||
import { ContainerConfigLoader } from '../src/Bootstrap/Container'
|
||||
|
||||
const container = new ContainerConfigLoader()
|
||||
const container = new ContainerConfigLoader('worker')
|
||||
void container.load().then((container) => {
|
||||
const env: Env = new Env()
|
||||
env.load()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/syncing-server",
|
||||
"version": "1.95.0",
|
||||
"version": "1.95.6",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -173,6 +173,8 @@ export class ContainerConfigLoader {
|
||||
private readonly DEFAULT_MAX_ITEMS_LIMIT = 300
|
||||
private readonly DEFAULT_FILE_UPLOAD_PATH = `${__dirname}/../../uploads`
|
||||
|
||||
constructor(private mode: 'server' | 'worker' = 'server') {}
|
||||
|
||||
async load(configuration?: {
|
||||
controllerConatiner?: ControllerContainerInterface
|
||||
directCallDomainEventPublisher?: DirectCallDomainEventPublisher
|
||||
@@ -211,7 +213,7 @@ export class ContainerConfigLoader {
|
||||
}
|
||||
container.bind<winston.Logger>(TYPES.Sync_Logger).toConstantValue(logger)
|
||||
|
||||
const appDataSource = new AppDataSource(env)
|
||||
const appDataSource = new AppDataSource({ env, runMigrations: this.mode === 'server' })
|
||||
await appDataSource.initialize()
|
||||
|
||||
logger.debug('Database initialized')
|
||||
@@ -831,6 +833,7 @@ export class ContainerConfigLoader {
|
||||
isSecondaryDatabaseEnabled ? container.get<ItemRepositoryInterface>(TYPES.Sync_MongoDBItemRepository) : null,
|
||||
container.get<TimerInterface>(TYPES.Sync_Timer),
|
||||
container.get<Logger>(TYPES.Sync_Logger),
|
||||
env.get('MIGRATION_BATCH_SIZE', true) ? +env.get('MIGRATION_BATCH_SIZE', true) : 100,
|
||||
),
|
||||
)
|
||||
container
|
||||
|
||||
@@ -15,7 +15,12 @@ export class AppDataSource {
|
||||
private _dataSource: DataSource | undefined
|
||||
private _secondaryDataSource: DataSource | undefined
|
||||
|
||||
constructor(private env: Env) {}
|
||||
constructor(
|
||||
private configuration: {
|
||||
env: Env
|
||||
runMigrations: boolean
|
||||
},
|
||||
) {}
|
||||
|
||||
getRepository<Entity extends ObjectLiteral>(target: EntityTarget<Entity>): Repository<Entity> {
|
||||
if (!this._dataSource) {
|
||||
@@ -42,20 +47,20 @@ export class AppDataSource {
|
||||
}
|
||||
|
||||
get secondaryDataSource(): DataSource | undefined {
|
||||
this.env.load()
|
||||
this.configuration.env.load()
|
||||
|
||||
if (this.env.get('SECONDARY_DB_ENABLED', true) !== 'true') {
|
||||
if (this.configuration.env.get('SECONDARY_DB_ENABLED', true) !== 'true') {
|
||||
return undefined
|
||||
}
|
||||
|
||||
this._secondaryDataSource = new DataSource({
|
||||
type: 'mongodb',
|
||||
host: this.env.get('MONGO_HOST'),
|
||||
host: this.configuration.env.get('MONGO_HOST'),
|
||||
authSource: 'admin',
|
||||
port: parseInt(this.env.get('MONGO_PORT')),
|
||||
username: this.env.get('MONGO_USERNAME'),
|
||||
password: this.env.get('MONGO_PASSWORD', true),
|
||||
database: this.env.get('MONGO_DATABASE'),
|
||||
port: parseInt(this.configuration.env.get('MONGO_PORT')),
|
||||
username: this.configuration.env.get('MONGO_USERNAME'),
|
||||
password: this.configuration.env.get('MONGO_PASSWORD', true),
|
||||
database: this.configuration.env.get('MONGO_DATABASE'),
|
||||
entities: [MongoDBItem],
|
||||
retryWrites: false,
|
||||
synchronize: true,
|
||||
@@ -65,14 +70,15 @@ export class AppDataSource {
|
||||
}
|
||||
|
||||
get dataSource(): DataSource {
|
||||
this.env.load()
|
||||
this.configuration.env.load()
|
||||
|
||||
const isConfiguredForMySQL = this.env.get('DB_TYPE') === 'mysql'
|
||||
const isConfiguredForMySQL = this.configuration.env.get('DB_TYPE') === 'mysql'
|
||||
const isConfiguredForHomeServerOrSelfHosting =
|
||||
this.env.get('MODE', true) === 'home-server' || this.env.get('MODE', true) === 'self-hosted'
|
||||
this.configuration.env.get('MODE', true) === 'home-server' ||
|
||||
this.configuration.env.get('MODE', true) === 'self-hosted'
|
||||
|
||||
const maxQueryExecutionTime = this.env.get('DB_MAX_QUERY_EXECUTION_TIME', true)
|
||||
? +this.env.get('DB_MAX_QUERY_EXECUTION_TIME', true)
|
||||
const maxQueryExecutionTime = this.configuration.env.get('DB_MAX_QUERY_EXECUTION_TIME', true)
|
||||
? +this.configuration.env.get('DB_MAX_QUERY_EXECUTION_TIME', true)
|
||||
: 45_000
|
||||
|
||||
const migrationsSourceDirectoryName = isConfiguredForMySQL
|
||||
@@ -92,28 +98,28 @@ export class AppDataSource {
|
||||
TypeORMMessage,
|
||||
],
|
||||
migrations: [`${__dirname}/../../migrations/${migrationsSourceDirectoryName}/*.js`],
|
||||
migrationsRun: true,
|
||||
logging: <LoggerOptions>this.env.get('DB_DEBUG_LEVEL', true) ?? 'info',
|
||||
migrationsRun: this.configuration.runMigrations,
|
||||
logging: <LoggerOptions>this.configuration.env.get('DB_DEBUG_LEVEL', true) ?? 'info',
|
||||
}
|
||||
|
||||
if (isConfiguredForMySQL) {
|
||||
const inReplicaMode = this.env.get('DB_REPLICA_HOST', true) ? true : false
|
||||
const inReplicaMode = this.configuration.env.get('DB_REPLICA_HOST', true) ? true : false
|
||||
|
||||
const replicationConfig = {
|
||||
master: {
|
||||
host: this.env.get('DB_HOST'),
|
||||
port: parseInt(this.env.get('DB_PORT')),
|
||||
username: this.env.get('DB_USERNAME'),
|
||||
password: this.env.get('DB_PASSWORD'),
|
||||
database: this.env.get('DB_DATABASE'),
|
||||
host: this.configuration.env.get('DB_HOST'),
|
||||
port: parseInt(this.configuration.env.get('DB_PORT')),
|
||||
username: this.configuration.env.get('DB_USERNAME'),
|
||||
password: this.configuration.env.get('DB_PASSWORD'),
|
||||
database: this.configuration.env.get('DB_DATABASE'),
|
||||
},
|
||||
slaves: [
|
||||
{
|
||||
host: this.env.get('DB_REPLICA_HOST', true),
|
||||
port: parseInt(this.env.get('DB_PORT')),
|
||||
username: this.env.get('DB_USERNAME'),
|
||||
password: this.env.get('DB_PASSWORD'),
|
||||
database: this.env.get('DB_DATABASE'),
|
||||
host: this.configuration.env.get('DB_REPLICA_HOST', true),
|
||||
port: parseInt(this.configuration.env.get('DB_PORT')),
|
||||
username: this.configuration.env.get('DB_USERNAME'),
|
||||
password: this.configuration.env.get('DB_PASSWORD'),
|
||||
database: this.configuration.env.get('DB_DATABASE'),
|
||||
},
|
||||
],
|
||||
removeNodeErrorCount: 10,
|
||||
@@ -127,11 +133,11 @@ export class AppDataSource {
|
||||
supportBigNumbers: true,
|
||||
bigNumberStrings: false,
|
||||
replication: inReplicaMode ? replicationConfig : undefined,
|
||||
host: inReplicaMode ? undefined : this.env.get('DB_HOST'),
|
||||
port: inReplicaMode ? undefined : parseInt(this.env.get('DB_PORT')),
|
||||
username: inReplicaMode ? undefined : this.env.get('DB_USERNAME'),
|
||||
password: inReplicaMode ? undefined : this.env.get('DB_PASSWORD'),
|
||||
database: inReplicaMode ? undefined : this.env.get('DB_DATABASE'),
|
||||
host: inReplicaMode ? undefined : this.configuration.env.get('DB_HOST'),
|
||||
port: inReplicaMode ? undefined : parseInt(this.configuration.env.get('DB_PORT')),
|
||||
username: inReplicaMode ? undefined : this.configuration.env.get('DB_USERNAME'),
|
||||
password: inReplicaMode ? undefined : this.configuration.env.get('DB_PASSWORD'),
|
||||
database: inReplicaMode ? undefined : this.configuration.env.get('DB_DATABASE'),
|
||||
}
|
||||
|
||||
this._dataSource = new DataSource(mySQLDataSourceOptions)
|
||||
@@ -139,7 +145,7 @@ export class AppDataSource {
|
||||
const sqliteDataSourceOptions: SqliteConnectionOptions = {
|
||||
...commonDataSourceOptions,
|
||||
type: 'sqlite',
|
||||
database: this.env.get('DB_SQLITE_DATABASE_PATH'),
|
||||
database: this.configuration.env.get('DB_SQLITE_DATABASE_PATH'),
|
||||
enableWAL: true,
|
||||
busyErrorRetry: 2000,
|
||||
}
|
||||
|
||||
@@ -4,4 +4,4 @@ import { Env } from './Env'
|
||||
const env: Env = new Env()
|
||||
env.load()
|
||||
|
||||
export const MigrationsDataSource = new AppDataSource(env).dataSource
|
||||
export const MigrationsDataSource = new AppDataSource({ env, runMigrations: true }).dataSource
|
||||
|
||||
@@ -52,7 +52,7 @@ export interface DomainEventFactoryInterface {
|
||||
createTransitionStatusUpdatedEvent(dto: {
|
||||
userUuid: string
|
||||
transitionType: 'items' | 'revisions'
|
||||
status: 'STARTED' | 'FAILED' | 'FINISHED'
|
||||
status: 'STARTED' | 'IN_PROGRESS' | 'FAILED' | 'FINISHED'
|
||||
}): TransitionStatusUpdatedEvent
|
||||
createEmailRequestedEvent(dto: {
|
||||
userEmail: string
|
||||
|
||||
@@ -17,6 +17,14 @@ export class TransitionStatusUpdatedEventHandler implements DomainEventHandlerIn
|
||||
|
||||
async handle(event: TransitionStatusUpdatedEvent): Promise<void> {
|
||||
if (event.payload.status === 'STARTED' && event.payload.transitionType === 'items') {
|
||||
await this.domainEventPublisher.publish(
|
||||
this.domainEventFactory.createTransitionStatusUpdatedEvent({
|
||||
userUuid: event.payload.userUuid,
|
||||
status: 'IN_PROGRESS',
|
||||
transitionType: 'items',
|
||||
}),
|
||||
)
|
||||
|
||||
const result = await this.transitionItemsFromPrimaryToSecondaryDatabaseForUser.execute({
|
||||
userUuid: event.payload.userUuid,
|
||||
})
|
||||
|
||||
@@ -84,6 +84,7 @@ describe('SaveItems', () => {
|
||||
const result = await useCase.execute({
|
||||
itemHashes: [itemHash1],
|
||||
userUuid: 'user-uuid',
|
||||
onGoingRevisionsTransition: false,
|
||||
apiVersion: '1',
|
||||
readOnlyAccess: false,
|
||||
sessionUuid: 'session-uuid',
|
||||
@@ -96,6 +97,7 @@ describe('SaveItems', () => {
|
||||
expect(saveNewItem.execute).toHaveBeenCalledWith({
|
||||
itemHash: itemHash1,
|
||||
userUuid: 'user-uuid',
|
||||
onGoingRevisionsTransition: false,
|
||||
sessionUuid: 'session-uuid',
|
||||
roleNames: ['CORE_USER'],
|
||||
})
|
||||
@@ -109,6 +111,7 @@ describe('SaveItems', () => {
|
||||
const result = await useCase.execute({
|
||||
itemHashes: [itemHash1],
|
||||
userUuid: 'user-uuid',
|
||||
onGoingRevisionsTransition: false,
|
||||
apiVersion: '1',
|
||||
readOnlyAccess: false,
|
||||
sessionUuid: 'session-uuid',
|
||||
@@ -133,6 +136,7 @@ describe('SaveItems', () => {
|
||||
const result = await useCase.execute({
|
||||
itemHashes: [itemHash1],
|
||||
userUuid: 'user-uuid',
|
||||
onGoingRevisionsTransition: false,
|
||||
apiVersion: '1',
|
||||
readOnlyAccess: false,
|
||||
sessionUuid: 'session-uuid',
|
||||
@@ -155,6 +159,7 @@ describe('SaveItems', () => {
|
||||
const result = await useCase.execute({
|
||||
itemHashes: [itemHash1],
|
||||
userUuid: 'user-uuid',
|
||||
onGoingRevisionsTransition: false,
|
||||
apiVersion: '1',
|
||||
readOnlyAccess: true,
|
||||
sessionUuid: 'session-uuid',
|
||||
@@ -178,6 +183,7 @@ describe('SaveItems', () => {
|
||||
const result = await useCase.execute({
|
||||
itemHashes: [itemHash1],
|
||||
userUuid: 'user-uuid',
|
||||
onGoingRevisionsTransition: false,
|
||||
apiVersion: '1',
|
||||
readOnlyAccess: false,
|
||||
sessionUuid: 'session-uuid',
|
||||
@@ -197,6 +203,7 @@ describe('SaveItems', () => {
|
||||
const result = await useCase.execute({
|
||||
itemHashes: [itemHash1],
|
||||
userUuid: 'user-uuid',
|
||||
onGoingRevisionsTransition: false,
|
||||
apiVersion: '1',
|
||||
readOnlyAccess: false,
|
||||
sessionUuid: 'session-uuid',
|
||||
@@ -215,6 +222,7 @@ describe('SaveItems', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
itemHashes: [itemHash1],
|
||||
onGoingRevisionsTransition: false,
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
apiVersion: '1',
|
||||
readOnlyAccess: false,
|
||||
@@ -229,6 +237,7 @@ describe('SaveItems', () => {
|
||||
existingItem: savedItem,
|
||||
sessionUuid: 'session-uuid',
|
||||
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||
onGoingRevisionsTransition: false,
|
||||
roleNames: ['CORE_USER'],
|
||||
})
|
||||
})
|
||||
@@ -242,6 +251,7 @@ describe('SaveItems', () => {
|
||||
const result = await useCase.execute({
|
||||
itemHashes: [itemHash1],
|
||||
userUuid: 'user-uuid',
|
||||
onGoingRevisionsTransition: false,
|
||||
apiVersion: '1',
|
||||
readOnlyAccess: false,
|
||||
sessionUuid: 'session-uuid',
|
||||
@@ -267,6 +277,7 @@ describe('SaveItems', () => {
|
||||
const result = await useCase.execute({
|
||||
itemHashes: [ItemHash.create({ ...itemHash1.props, uuid: 'invalid-uuid' }).getValue()],
|
||||
userUuid: 'user-uuid',
|
||||
onGoingRevisionsTransition: false,
|
||||
apiVersion: '1',
|
||||
readOnlyAccess: false,
|
||||
sessionUuid: 'session-uuid',
|
||||
@@ -313,6 +324,7 @@ describe('SaveItems', () => {
|
||||
ItemHash.create({ ...itemHash1.props, uuid: '00000000-0000-0000-0000-000000000003' }).getValue(),
|
||||
],
|
||||
userUuid: 'user-uuid',
|
||||
onGoingRevisionsTransition: false,
|
||||
apiVersion: '2',
|
||||
readOnlyAccess: false,
|
||||
sessionUuid: 'session-uuid',
|
||||
@@ -330,6 +342,7 @@ describe('SaveItems', () => {
|
||||
const result = await useCase.execute({
|
||||
itemHashes: [itemHash1],
|
||||
userUuid: 'user-uuid',
|
||||
onGoingRevisionsTransition: false,
|
||||
apiVersion: '2',
|
||||
readOnlyAccess: false,
|
||||
sessionUuid: 'session-uuid',
|
||||
|
||||
@@ -86,6 +86,7 @@ export class SaveItems implements UseCaseInterface<SaveItemsResult> {
|
||||
sessionUuid: dto.sessionUuid,
|
||||
performingUserUuid: dto.userUuid,
|
||||
roleNames: dto.roleNames,
|
||||
onGoingRevisionsTransition: dto.onGoingRevisionsTransition,
|
||||
})
|
||||
if (udpatedItemOrError.isFailed()) {
|
||||
this.logger.error(
|
||||
@@ -109,6 +110,7 @@ export class SaveItems implements UseCaseInterface<SaveItemsResult> {
|
||||
itemHash,
|
||||
sessionUuid: dto.sessionUuid,
|
||||
roleNames: dto.roleNames,
|
||||
onGoingRevisionsTransition: dto.onGoingRevisionsTransition,
|
||||
})
|
||||
if (newItemOrError.isFailed()) {
|
||||
this.logger.error(
|
||||
|
||||
@@ -8,4 +8,5 @@ export interface SaveItemsDTO {
|
||||
sessionUuid: string | null
|
||||
snjsVersion: string
|
||||
roleNames: string[]
|
||||
onGoingRevisionsTransition: boolean
|
||||
}
|
||||
|
||||
@@ -90,6 +90,7 @@ describe('SaveNewItem', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
onGoingRevisionsTransition: false,
|
||||
roleNames: [RoleName.NAMES.CoreUser],
|
||||
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
||||
itemHash: itemHash1,
|
||||
@@ -112,6 +113,7 @@ describe('SaveNewItem', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
onGoingRevisionsTransition: true,
|
||||
roleNames: [RoleName.NAMES.CoreUser],
|
||||
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
||||
itemHash: itemHash1,
|
||||
@@ -132,6 +134,7 @@ describe('SaveNewItem', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
onGoingRevisionsTransition: false,
|
||||
roleNames: [RoleName.NAMES.CoreUser],
|
||||
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
||||
itemHash: itemHash1,
|
||||
@@ -150,6 +153,7 @@ describe('SaveNewItem', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
onGoingRevisionsTransition: false,
|
||||
roleNames: [RoleName.NAMES.CoreUser],
|
||||
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
||||
itemHash: itemHash1,
|
||||
@@ -170,6 +174,7 @@ describe('SaveNewItem', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
onGoingRevisionsTransition: false,
|
||||
roleNames: [RoleName.NAMES.CoreUser],
|
||||
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
||||
itemHash: itemHash1,
|
||||
@@ -190,6 +195,7 @@ describe('SaveNewItem', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
onGoingRevisionsTransition: false,
|
||||
roleNames: [RoleName.NAMES.CoreUser],
|
||||
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
||||
itemHash: itemHash1,
|
||||
@@ -208,6 +214,7 @@ describe('SaveNewItem', () => {
|
||||
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
||||
itemHash: itemHash1,
|
||||
roleNames: [RoleName.NAMES.CoreUser],
|
||||
onGoingRevisionsTransition: false,
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
@@ -218,6 +225,7 @@ describe('SaveNewItem', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
onGoingRevisionsTransition: false,
|
||||
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
||||
itemHash: itemHash1,
|
||||
roleNames: ['invalid'],
|
||||
@@ -231,6 +239,7 @@ describe('SaveNewItem', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
onGoingRevisionsTransition: false,
|
||||
roleNames: [RoleName.NAMES.CoreUser],
|
||||
sessionUuid: '00000000-0000-0000-0000-00000000000',
|
||||
itemHash: itemHash1,
|
||||
@@ -249,6 +258,7 @@ describe('SaveNewItem', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
onGoingRevisionsTransition: false,
|
||||
roleNames: [RoleName.NAMES.CoreUser],
|
||||
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
||||
itemHash: itemHash1,
|
||||
@@ -267,6 +277,7 @@ describe('SaveNewItem', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
onGoingRevisionsTransition: false,
|
||||
roleNames: [RoleName.NAMES.CoreUser],
|
||||
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
||||
itemHash: itemHash1,
|
||||
@@ -285,6 +296,7 @@ describe('SaveNewItem', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
onGoingRevisionsTransition: false,
|
||||
roleNames: [RoleName.NAMES.CoreUser],
|
||||
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
||||
itemHash: itemHash1,
|
||||
@@ -305,6 +317,7 @@ describe('SaveNewItem', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
onGoingRevisionsTransition: false,
|
||||
roleNames: [RoleName.NAMES.CoreUser],
|
||||
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
||||
itemHash: itemHash1,
|
||||
@@ -328,6 +341,7 @@ describe('SaveNewItem', () => {
|
||||
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
||||
itemHash: itemHash1,
|
||||
roleNames: [RoleName.NAMES.CoreUser],
|
||||
onGoingRevisionsTransition: false,
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
@@ -344,6 +358,7 @@ describe('SaveNewItem', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
onGoingRevisionsTransition: false,
|
||||
roleNames: [RoleName.NAMES.CoreUser],
|
||||
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
||||
itemHash: itemHash1,
|
||||
@@ -371,6 +386,7 @@ describe('SaveNewItem', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
onGoingRevisionsTransition: false,
|
||||
roleNames: [RoleName.NAMES.CoreUser],
|
||||
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
||||
itemHash: itemHash1,
|
||||
@@ -393,6 +409,7 @@ describe('SaveNewItem', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
onGoingRevisionsTransition: false,
|
||||
roleNames: [RoleName.NAMES.CoreUser],
|
||||
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
||||
itemHash: itemHash1,
|
||||
@@ -412,6 +429,7 @@ describe('SaveNewItem', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
onGoingRevisionsTransition: false,
|
||||
roleNames: [RoleName.NAMES.CoreUser],
|
||||
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
||||
itemHash: itemHash1,
|
||||
@@ -435,6 +453,7 @@ describe('SaveNewItem', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
onGoingRevisionsTransition: false,
|
||||
roleNames: [RoleName.NAMES.CoreUser],
|
||||
sessionUuid: '00000000-0000-0000-0000-000000000001',
|
||||
itemHash: itemHash1,
|
||||
|
||||
@@ -145,13 +145,15 @@ export class SaveNewItem implements UseCaseInterface<Item> {
|
||||
await itemRepository.save(newItem)
|
||||
|
||||
if (contentType.value !== null && [ContentType.TYPES.Note, ContentType.TYPES.File].includes(contentType.value)) {
|
||||
await this.domainEventPublisher.publish(
|
||||
this.domainEventFactory.createItemRevisionCreationRequested({
|
||||
itemUuid: newItem.id.toString(),
|
||||
userUuid: newItem.props.userUuid.value,
|
||||
roleNames: dto.roleNames,
|
||||
}),
|
||||
)
|
||||
if (!dto.onGoingRevisionsTransition) {
|
||||
await this.domainEventPublisher.publish(
|
||||
this.domainEventFactory.createItemRevisionCreationRequested({
|
||||
itemUuid: newItem.id.toString(),
|
||||
userUuid: newItem.props.userUuid.value,
|
||||
roleNames: dto.roleNames,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (duplicateOf) {
|
||||
|
||||
@@ -5,4 +5,5 @@ export interface SaveNewItemDTO {
|
||||
roleNames: string[]
|
||||
itemHash: ItemHash
|
||||
sessionUuid: string | null
|
||||
onGoingRevisionsTransition: boolean
|
||||
}
|
||||
|
||||
@@ -143,6 +143,7 @@ describe('SyncItems', () => {
|
||||
it('should sync items', async () => {
|
||||
const result = await createUseCase().execute({
|
||||
userUuid: '1-2-3',
|
||||
onGoingRevisionsTransition: false,
|
||||
itemHashes: [itemHash],
|
||||
computeIntegrityHash: false,
|
||||
syncToken: 'foo',
|
||||
@@ -178,6 +179,7 @@ describe('SyncItems', () => {
|
||||
expect(saveItemsUseCase.execute).toHaveBeenCalledWith({
|
||||
itemHashes: [itemHash],
|
||||
userUuid: '1-2-3',
|
||||
onGoingRevisionsTransition: false,
|
||||
apiVersion: '20200115',
|
||||
snjsVersion: '1.2.3',
|
||||
roleNames: [RoleName.NAMES.CoreUser],
|
||||
@@ -189,6 +191,7 @@ describe('SyncItems', () => {
|
||||
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({
|
||||
userUuid: '1-2-3',
|
||||
onGoingRevisionsTransition: false,
|
||||
itemHashes: [itemHash],
|
||||
computeIntegrityHash: false,
|
||||
limit: 10,
|
||||
@@ -215,6 +218,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 () => {
|
||||
const result = await createUseCase().execute({
|
||||
userUuid: '1-2-3',
|
||||
onGoingRevisionsTransition: false,
|
||||
itemHashes: [itemHash],
|
||||
computeIntegrityHash: false,
|
||||
limit: 10,
|
||||
@@ -266,6 +270,7 @@ describe('SyncItems', () => {
|
||||
|
||||
const result = await createUseCase().execute({
|
||||
userUuid: '1-2-3',
|
||||
onGoingRevisionsTransition: false,
|
||||
itemHashes: [itemHash],
|
||||
computeIntegrityHash: false,
|
||||
syncToken: 'foo',
|
||||
@@ -306,6 +311,7 @@ describe('SyncItems', () => {
|
||||
|
||||
const result = await createUseCase().execute({
|
||||
userUuid: '1-2-3',
|
||||
onGoingRevisionsTransition: false,
|
||||
itemHashes: [itemHash],
|
||||
computeIntegrityHash: false,
|
||||
syncToken: 'foo',
|
||||
@@ -327,6 +333,7 @@ describe('SyncItems', () => {
|
||||
|
||||
const result = await createUseCase().execute({
|
||||
userUuid: '1-2-3',
|
||||
onGoingRevisionsTransition: false,
|
||||
itemHashes: [itemHash],
|
||||
computeIntegrityHash: false,
|
||||
syncToken: 'foo',
|
||||
@@ -346,6 +353,7 @@ describe('SyncItems', () => {
|
||||
it('should return error if role names are invalid', async () => {
|
||||
const result = await createUseCase().execute({
|
||||
userUuid: '1-2-3',
|
||||
onGoingRevisionsTransition: false,
|
||||
itemHashes: [itemHash],
|
||||
computeIntegrityHash: false,
|
||||
limit: 10,
|
||||
@@ -365,6 +373,7 @@ describe('SyncItems', () => {
|
||||
|
||||
const result = await createUseCase().execute({
|
||||
userUuid: '1-2-3',
|
||||
onGoingRevisionsTransition: false,
|
||||
itemHashes: [itemHash],
|
||||
computeIntegrityHash: false,
|
||||
syncToken: 'foo',
|
||||
@@ -386,6 +395,7 @@ describe('SyncItems', () => {
|
||||
|
||||
const result = await createUseCase().execute({
|
||||
userUuid: '1-2-3',
|
||||
onGoingRevisionsTransition: false,
|
||||
itemHashes: [itemHash],
|
||||
computeIntegrityHash: false,
|
||||
syncToken: 'foo',
|
||||
@@ -407,6 +417,7 @@ describe('SyncItems', () => {
|
||||
|
||||
const result = await createUseCase().execute({
|
||||
userUuid: '1-2-3',
|
||||
onGoingRevisionsTransition: false,
|
||||
itemHashes: [itemHash],
|
||||
computeIntegrityHash: false,
|
||||
syncToken: 'foo',
|
||||
@@ -428,6 +439,7 @@ describe('SyncItems', () => {
|
||||
|
||||
const result = await createUseCase().execute({
|
||||
userUuid: '1-2-3',
|
||||
onGoingRevisionsTransition: false,
|
||||
itemHashes: [itemHash],
|
||||
computeIntegrityHash: false,
|
||||
syncToken: 'foo',
|
||||
|
||||
@@ -52,6 +52,7 @@ export class SyncItems implements UseCaseInterface<SyncItemsResponse> {
|
||||
sessionUuid: dto.sessionUuid,
|
||||
snjsVersion: dto.snjsVersion,
|
||||
roleNames: dto.roleNames,
|
||||
onGoingRevisionsTransition: dto.onGoingRevisionsTransition,
|
||||
})
|
||||
if (saveItemsResultOrError.isFailed()) {
|
||||
return Result.fail(saveItemsResultOrError.getError())
|
||||
|
||||
@@ -14,4 +14,5 @@ export type SyncItemsDTO = {
|
||||
snjsVersion: string
|
||||
readOnlyAccess: boolean
|
||||
sessionUuid: string | null
|
||||
onGoingRevisionsTransition: boolean
|
||||
}
|
||||
|
||||
@@ -140,6 +140,23 @@ describe('UpdateExistingItem', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
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,
|
||||
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
||||
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||
@@ -155,6 +172,7 @@ describe('UpdateExistingItem', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
existingItem: item1,
|
||||
onGoingRevisionsTransition: false,
|
||||
itemHash: itemHash1,
|
||||
sessionUuid: 'invalid-uuid',
|
||||
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||
@@ -169,6 +187,7 @@ describe('UpdateExistingItem', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
existingItem: item1,
|
||||
onGoingRevisionsTransition: false,
|
||||
itemHash: itemHash1,
|
||||
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
||||
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||
@@ -183,6 +202,7 @@ describe('UpdateExistingItem', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
existingItem: item1,
|
||||
onGoingRevisionsTransition: false,
|
||||
itemHash: ItemHash.create({
|
||||
...itemHash1.props,
|
||||
content_type: 'invalid',
|
||||
@@ -200,6 +220,7 @@ describe('UpdateExistingItem', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
existingItem: item1,
|
||||
onGoingRevisionsTransition: false,
|
||||
itemHash: ItemHash.create({
|
||||
...itemHash1.props,
|
||||
deleted: true,
|
||||
@@ -224,6 +245,7 @@ describe('UpdateExistingItem', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
existingItem: item1,
|
||||
onGoingRevisionsTransition: false,
|
||||
itemHash: ItemHash.create({
|
||||
...itemHash1.props,
|
||||
duplicate_of: '00000000-0000-0000-0000-000000000001',
|
||||
@@ -243,6 +265,7 @@ describe('UpdateExistingItem', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
existingItem: item1,
|
||||
onGoingRevisionsTransition: false,
|
||||
itemHash: ItemHash.create({
|
||||
...itemHash1.props,
|
||||
duplicate_of: 'invalid-uuid',
|
||||
@@ -260,6 +283,7 @@ describe('UpdateExistingItem', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
existingItem: item1,
|
||||
onGoingRevisionsTransition: false,
|
||||
itemHash: ItemHash.create({
|
||||
...itemHash1.props,
|
||||
updated_at_timestamp: 123,
|
||||
@@ -279,6 +303,7 @@ describe('UpdateExistingItem', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
existingItem: item1,
|
||||
onGoingRevisionsTransition: false,
|
||||
itemHash: ItemHash.create({
|
||||
...itemHash1.props,
|
||||
created_at: undefined,
|
||||
@@ -302,6 +327,7 @@ describe('UpdateExistingItem', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
existingItem: item1,
|
||||
onGoingRevisionsTransition: false,
|
||||
itemHash: ItemHash.create({
|
||||
...itemHash1.props,
|
||||
created_at_timestamp: 123,
|
||||
@@ -327,6 +353,7 @@ describe('UpdateExistingItem', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
existingItem: item1,
|
||||
onGoingRevisionsTransition: false,
|
||||
itemHash: ItemHash.create({
|
||||
...itemHash1.props,
|
||||
created_at_timestamp: 123,
|
||||
@@ -346,6 +373,7 @@ describe('UpdateExistingItem', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
existingItem: item1,
|
||||
onGoingRevisionsTransition: false,
|
||||
itemHash: itemHash1,
|
||||
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
||||
performingUserUuid: 'invalid-uuid',
|
||||
@@ -365,6 +393,7 @@ describe('UpdateExistingItem', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
existingItem: item1,
|
||||
onGoingRevisionsTransition: false,
|
||||
itemHash,
|
||||
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
||||
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||
@@ -392,6 +421,7 @@ describe('UpdateExistingItem', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
existingItem: item1,
|
||||
onGoingRevisionsTransition: false,
|
||||
itemHash,
|
||||
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
||||
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||
@@ -430,6 +460,7 @@ describe('UpdateExistingItem', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
existingItem: item1,
|
||||
onGoingRevisionsTransition: false,
|
||||
itemHash,
|
||||
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
||||
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||
@@ -459,6 +490,7 @@ describe('UpdateExistingItem', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
existingItem: item1,
|
||||
onGoingRevisionsTransition: false,
|
||||
itemHash,
|
||||
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
||||
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||
@@ -480,6 +512,7 @@ describe('UpdateExistingItem', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
existingItem: item1,
|
||||
onGoingRevisionsTransition: false,
|
||||
itemHash,
|
||||
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
||||
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||
@@ -512,6 +545,7 @@ describe('UpdateExistingItem', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
existingItem: item1,
|
||||
onGoingRevisionsTransition: false,
|
||||
itemHash,
|
||||
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
||||
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||
@@ -547,6 +581,7 @@ describe('UpdateExistingItem', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
existingItem: item1,
|
||||
onGoingRevisionsTransition: false,
|
||||
itemHash,
|
||||
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
||||
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||
@@ -569,6 +604,7 @@ describe('UpdateExistingItem', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
existingItem: item1,
|
||||
onGoingRevisionsTransition: false,
|
||||
itemHash,
|
||||
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
||||
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||
@@ -589,6 +625,7 @@ describe('UpdateExistingItem', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
existingItem: item1,
|
||||
onGoingRevisionsTransition: false,
|
||||
itemHash,
|
||||
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
||||
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||
@@ -611,6 +648,7 @@ describe('UpdateExistingItem', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
existingItem: item1,
|
||||
onGoingRevisionsTransition: false,
|
||||
itemHash,
|
||||
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
||||
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||
@@ -632,6 +670,7 @@ describe('UpdateExistingItem', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
existingItem: item1,
|
||||
onGoingRevisionsTransition: false,
|
||||
itemHash,
|
||||
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
||||
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||
@@ -655,6 +694,7 @@ describe('UpdateExistingItem', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
existingItem: item1,
|
||||
onGoingRevisionsTransition: false,
|
||||
itemHash,
|
||||
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
||||
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||
|
||||
@@ -191,13 +191,15 @@ export class UpdateExistingItem implements UseCaseInterface<Item> {
|
||||
dto.existingItem.props.contentType.value !== null &&
|
||||
[ContentType.TYPES.Note, ContentType.TYPES.File].includes(dto.existingItem.props.contentType.value)
|
||||
) {
|
||||
await this.domainEventPublisher.publish(
|
||||
this.domainEventFactory.createItemRevisionCreationRequested({
|
||||
itemUuid: dto.existingItem.id.toString(),
|
||||
userUuid: dto.existingItem.props.userUuid.value,
|
||||
roleNames: dto.roleNames,
|
||||
}),
|
||||
)
|
||||
if (!dto.onGoingRevisionsTransition) {
|
||||
await this.domainEventPublisher.publish(
|
||||
this.domainEventFactory.createItemRevisionCreationRequested({
|
||||
itemUuid: dto.existingItem.id.toString(),
|
||||
userUuid: dto.existingItem.props.userUuid.value,
|
||||
roleNames: dto.roleNames,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,4 +7,5 @@ export interface UpdateExistingItemDTO {
|
||||
sessionUuid: string | null
|
||||
performingUserUuid: string
|
||||
roleNames: string[]
|
||||
onGoingRevisionsTransition: boolean
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ describe('TransitionItemsFromPrimaryToSecondaryDatabaseForUser', () => {
|
||||
secondaryItemRepository,
|
||||
timer,
|
||||
logger,
|
||||
1,
|
||||
)
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -106,7 +107,7 @@ describe('TransitionItemsFromPrimaryToSecondaryDatabaseForUser', () => {
|
||||
secondaryItemRepository = {} as jest.Mocked<ItemRepositoryInterface>
|
||||
secondaryItemRepository.save = 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
|
||||
.fn()
|
||||
.mockResolvedValueOnce(secondaryItem1)
|
||||
@@ -205,9 +206,6 @@ describe('TransitionItemsFromPrimaryToSecondaryDatabaseForUser', () => {
|
||||
})
|
||||
|
||||
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(primaryItemRepository.deleteByUserUuid).not.toHaveBeenCalled()
|
||||
@@ -316,7 +314,10 @@ describe('TransitionItemsFromPrimaryToSecondaryDatabaseForUser', () => {
|
||||
})
|
||||
|
||||
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()
|
||||
|
||||
@@ -331,7 +332,7 @@ describe('TransitionItemsFromPrimaryToSecondaryDatabaseForUser', () => {
|
||||
|
||||
expect(primaryItemRepository.countAll).toHaveBeenCalledTimes(3)
|
||||
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((secondaryItemRepository as ItemRepositoryInterface).deleteByUserUuid).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
@@ -353,13 +354,16 @@ describe('TransitionItemsFromPrimaryToSecondaryDatabaseForUser', () => {
|
||||
|
||||
expect(primaryItemRepository.countAll).toHaveBeenCalledTimes(3)
|
||||
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((secondaryItemRepository as ItemRepositoryInterface).deleteByUserUuid).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
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()
|
||||
|
||||
@@ -393,4 +397,21 @@ describe('TransitionItemsFromPrimaryToSecondaryDatabaseForUser', () => {
|
||||
expect(primaryItemRepository.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()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -12,9 +12,12 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
|
||||
private secondaryItemRepository: ItemRepositoryInterface | null,
|
||||
private timer: TimerInterface,
|
||||
private logger: Logger,
|
||||
private pageSize: number,
|
||||
) {}
|
||||
|
||||
async execute(dto: TransitionItemsFromPrimaryToSecondaryDatabaseForUserDTO): Promise<Result<void>> {
|
||||
this.logger.info(`Transitioning items for user ${dto.userUuid}`)
|
||||
|
||||
if (this.secondaryItemRepository === null) {
|
||||
return Result.fail('Secondary item repository is not set')
|
||||
}
|
||||
@@ -31,6 +34,10 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
|
||||
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 migrationResult = await this.migrateItemsForUser(userUuid)
|
||||
@@ -78,6 +85,14 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
|
||||
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> {
|
||||
const totalItemsCountForUser = await this.primaryItemRepository.countAll({ userUuid: userUuid.value })
|
||||
|
||||
@@ -92,14 +107,12 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
|
||||
private async migrateItemsForUser(userUuid: Uuid): Promise<Result<void>> {
|
||||
try {
|
||||
const totalItemsCountForUser = await this.primaryItemRepository.countAll({ userUuid: userUuid.value })
|
||||
const pageSize = 1
|
||||
const totalPages = totalItemsCountForUser
|
||||
let currentPage = 1
|
||||
for (currentPage; currentPage <= totalPages; currentPage++) {
|
||||
const totalPages = Math.ceil(totalItemsCountForUser / this.pageSize)
|
||||
for (let currentPage = 1; currentPage <= totalPages; currentPage++) {
|
||||
const query: ItemQuery = {
|
||||
userUuid: userUuid.value,
|
||||
offset: currentPage - 1,
|
||||
limit: pageSize,
|
||||
offset: (currentPage - 1) * this.pageSize,
|
||||
limit: this.pageSize,
|
||||
}
|
||||
|
||||
const items = await this.primaryItemRepository.findAll(query)
|
||||
@@ -140,14 +153,12 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
|
||||
)
|
||||
}
|
||||
|
||||
const pageSize = 1
|
||||
const totalPages = totalItemsCountForUserInPrimary
|
||||
let currentPage = 1
|
||||
for (currentPage; currentPage <= totalPages; currentPage++) {
|
||||
const totalPages = Math.ceil(totalItemsCountForUserInPrimary / this.pageSize)
|
||||
for (let currentPage = 1; currentPage <= totalPages; currentPage++) {
|
||||
const query: ItemQuery = {
|
||||
userUuid: userUuid.value,
|
||||
offset: currentPage - 1,
|
||||
limit: pageSize,
|
||||
offset: (currentPage - 1) * this.pageSize,
|
||||
limit: this.pageSize,
|
||||
}
|
||||
|
||||
const items = await this.primaryItemRepository.findAll(query)
|
||||
@@ -159,7 +170,13 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
|
||||
}
|
||||
|
||||
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)}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ export class BaseItemsController extends BaseHttpController {
|
||||
|
||||
async sync(request: Request, response: Response): Promise<results.JsonResult> {
|
||||
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[] = []
|
||||
@@ -79,6 +79,7 @@ export class BaseItemsController extends BaseHttpController {
|
||||
readOnlyAccess: response.locals.readOnlyAccess,
|
||||
sessionUuid: response.locals.session ? response.locals.session.uuid : null,
|
||||
sharedVaultUuids,
|
||||
onGoingRevisionsTransition: response.locals.onGoingRevisionsTransition,
|
||||
})
|
||||
if (syncResult.isFailed()) {
|
||||
return this.json({ error: { message: syncResult.getError() } }, HttpStatusCode.BadRequest)
|
||||
|
||||
@@ -30,6 +30,7 @@ export class InversifyExpressAuthMiddleware extends BaseMiddleware {
|
||||
response.locals.readOnlyAccess = decodedToken.session?.readonly_access ?? false
|
||||
response.locals.sharedVaultOwnerContext = decodedToken.shared_vault_owner_context
|
||||
response.locals.ongoingTransition = decodedToken.ongoing_transition
|
||||
response.locals.onGoingRevisionsTransition = decodedToken.ongoing_revisions_transition
|
||||
|
||||
return next()
|
||||
} catch (error) {
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/websockets-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/websockets-server",
|
||||
"version": "1.10.36",
|
||||
"version": "1.10.38",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user