feat: remove transition state (#882)

* fix: Dockerfile build deps

* feat: remove transition state
This commit is contained in:
Karol Sójko 2023-10-19 11:38:16 +02:00 committed by GitHub
parent 3ef8e9ea24
commit a2c1ebe675
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
145 changed files with 152 additions and 4228 deletions

240
.pnp.cjs generated
View file

@ -3065,16 +3065,6 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\
}]\
]],\
["@mongodb-js/saslprep", [\
["npm:1.1.0", {\
"packageLocation": "./.yarn/cache/@mongodb-js-saslprep-npm-1.1.0-3906c025b8-1a631b92d2.zip/node_modules/@mongodb-js/saslprep/",\
"packageDependencies": [\
["@mongodb-js/saslprep", "npm:1.1.0"],\
["sparse-bitfield", "npm:3.0.3"]\
],\
"linkType": "HARD"\
}]\
]],\
["@nodelib/fs.scandir", [\
["npm:2.1.5", {\
"packageLocation": "./.yarn/cache/@nodelib-fs.scandir-npm-2.1.5-89c67370dd-6ab2a9b8a1.zip/node_modules/@nodelib/fs.scandir/",\
@ -5897,13 +5887,12 @@ const RAW_RUNTIME_STATE =
["inversify-express-utils", "npm:6.4.3"],\
["ioredis", "npm:5.3.2"],\
["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.5.0"],\
["mongodb", "virtual:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:6.0.0"],\
["mysql2", "npm:3.3.3"],\
["prettier", "npm:3.0.3"],\
["reflect-metadata", "npm:0.1.13"],\
["sqlite3", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:5.1.6"],\
["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.0"],\
["typeorm", "virtual:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:0.3.17"],\
["typeorm", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:0.3.17"],\
["typescript", "patch:typescript@npm%3A5.0.4#optional!builtin<compat/typescript>::version=5.0.4&hash=b5f058"],\
["winston", "npm:3.9.0"]\
],\
@ -6083,7 +6072,6 @@ const RAW_RUNTIME_STATE =
["ioredis", "npm:5.3.2"],\
["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.5.0"],\
["jsonwebtoken", "npm:9.0.0"],\
["mongodb", "virtual:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:6.0.0"],\
["mysql2", "npm:3.3.3"],\
["prettier", "npm:3.0.3"],\
["prettyjson", "npm:1.2.5"],\
@ -6091,7 +6079,7 @@ const RAW_RUNTIME_STATE =
["semver", "npm:7.5.4"],\
["sqlite3", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:5.1.6"],\
["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.0"],\
["typeorm", "virtual:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:0.3.17"],\
["typeorm", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:0.3.17"],\
["typescript", "patch:typescript@npm%3A5.0.4#optional!builtin<compat/typescript>::version=5.0.4&hash=b5f058"],\
["ua-parser-js", "npm:1.0.35"],\
["uuid", "npm:9.0.0"],\
@ -6729,26 +6717,6 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\
}]\
]],\
["@types/webidl-conversions", [\
["npm:7.0.0", {\
"packageLocation": "./.yarn/cache/@types-webidl-conversions-npm-7.0.0-0903313151-60142c7ddd.zip/node_modules/@types/webidl-conversions/",\
"packageDependencies": [\
["@types/webidl-conversions", "npm:7.0.0"]\
],\
"linkType": "HARD"\
}]\
]],\
["@types/whatwg-url", [\
["npm:8.2.2", {\
"packageLocation": "./.yarn/cache/@types-whatwg-url-npm-8.2.2-54c5c24e6c-5dc5afe078.zip/node_modules/@types/whatwg-url/",\
"packageDependencies": [\
["@types/whatwg-url", "npm:8.2.2"],\
["@types/node", "npm:20.2.5"],\
["@types/webidl-conversions", "npm:7.0.0"]\
],\
"linkType": "HARD"\
}]\
]],\
["@types/yargs", [\
["npm:17.0.24", {\
"packageLocation": "./.yarn/cache/@types-yargs-npm-17.0.24-b034cf1d8b-03d9a985cb.zip/node_modules/@types/yargs/",\
@ -7949,15 +7917,6 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\
}]\
]],\
["bson", [\
["npm:6.0.0", {\
"packageLocation": "./.yarn/cache/bson-npm-6.0.0-7b3cba060e-e7614bdc53.zip/node_modules/bson/",\
"packageDependencies": [\
["bson", "npm:6.0.0"]\
],\
"linkType": "HARD"\
}]\
]],\
["buffer", [\
["npm:5.7.1", {\
"packageLocation": "./.yarn/cache/buffer-npm-5.7.1-513ef8259e-997434d3c6.zip/node_modules/buffer/",\
@ -12561,15 +12520,6 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\
}]\
]],\
["memory-pager", [\
["npm:1.5.0", {\
"packageLocation": "./.yarn/cache/memory-pager-npm-1.5.0-46e20e6c81-ffe3461b6a.zip/node_modules/memory-pager/",\
"packageDependencies": [\
["memory-pager", "npm:1.5.0"]\
],\
"linkType": "HARD"\
}]\
]],\
["meow", [\
["npm:8.1.2", {\
"packageLocation": "./.yarn/cache/meow-npm-8.1.2-bcfe48d4f3-d4770f9013.zip/node_modules/meow/",\
@ -12914,66 +12864,6 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\
}]\
]],\
["mongodb", [\
["npm:6.0.0", {\
"packageLocation": "./.yarn/cache/mongodb-npm-6.0.0-7c1e74de91-501feaecb7.zip/node_modules/mongodb/",\
"packageDependencies": [\
["mongodb", "npm:6.0.0"]\
],\
"linkType": "SOFT"\
}],\
["virtual:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:6.0.0", {\
"packageLocation": "./.yarn/__virtual__/mongodb-virtual-789f2eaaac/0/cache/mongodb-npm-6.0.0-7c1e74de91-501feaecb7.zip/node_modules/mongodb/",\
"packageDependencies": [\
["mongodb", "virtual:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:6.0.0"],\
["@aws-sdk/credential-providers", null],\
["@mongodb-js/saslprep", "npm:1.1.0"],\
["@mongodb-js/zstd", null],\
["@types/aws-sdk__credential-providers", null],\
["@types/gcp-metadata", null],\
["@types/kerberos", null],\
["@types/mongodb-client-encryption", null],\
["@types/mongodb-js__zstd", null],\
["@types/snappy", null],\
["@types/socks", null],\
["bson", "npm:6.0.0"],\
["gcp-metadata", null],\
["kerberos", null],\
["mongodb-client-encryption", null],\
["mongodb-connection-string-url", "npm:2.6.0"],\
["snappy", null],\
["socks", null]\
],\
"packagePeers": [\
"@aws-sdk/credential-providers",\
"@mongodb-js/zstd",\
"@types/aws-sdk__credential-providers",\
"@types/gcp-metadata",\
"@types/kerberos",\
"@types/mongodb-client-encryption",\
"@types/mongodb-js__zstd",\
"@types/snappy",\
"@types/socks",\
"gcp-metadata",\
"kerberos",\
"mongodb-client-encryption",\
"snappy",\
"socks"\
],\
"linkType": "HARD"\
}]\
]],\
["mongodb-connection-string-url", [\
["npm:2.6.0", {\
"packageLocation": "./.yarn/cache/mongodb-connection-string-url-npm-2.6.0-af011ba17f-d0903b9824.zip/node_modules/mongodb-connection-string-url/",\
"packageDependencies": [\
["mongodb-connection-string-url", "npm:2.6.0"],\
["@types/whatwg-url", "npm:8.2.2"],\
["whatwg-url", "npm:11.0.0"]\
],\
"linkType": "HARD"\
}]\
]],\
["ms", [\
["npm:2.0.0", {\
"packageLocation": "./.yarn/cache/ms-npm-2.0.0-9e1101a471-0e6a22b8b7.zip/node_modules/ms/",\
@ -15055,16 +14945,6 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\
}]\
]],\
["sparse-bitfield", [\
["npm:3.0.3", {\
"packageLocation": "./.yarn/cache/sparse-bitfield-npm-3.0.3-cb80d0c89f-174da88dbb.zip/node_modules/sparse-bitfield/",\
"packageDependencies": [\
["sparse-bitfield", "npm:3.0.3"],\
["memory-pager", "npm:1.5.0"]\
],\
"linkType": "HARD"\
}]\
]],\
["spdx-correct", [\
["npm:3.2.0", {\
"packageLocation": "./.yarn/cache/spdx-correct-npm-3.2.0-ffae008484-cc2e4dbef8.zip/node_modules/spdx-correct/",\
@ -15714,14 +15594,6 @@ const RAW_RUNTIME_STATE =
["tr46", "npm:0.0.3"]\
],\
"linkType": "HARD"\
}],\
["npm:3.0.0", {\
"packageLocation": "./.yarn/cache/tr46-npm-3.0.0-e1ae1ea7c9-b09a15886c.zip/node_modules/tr46/",\
"packageDependencies": [\
["tr46", "npm:3.0.0"],\
["punycode", "npm:2.3.0"]\
],\
"linkType": "HARD"\
}]\
]],\
["treeverse", [\
@ -16175,98 +16047,6 @@ const RAW_RUNTIME_STATE =
],\
"linkType": "HARD"\
}],\
["virtual:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:0.3.17", {\
"packageLocation": "./.yarn/__virtual__/typeorm-virtual-bfb7ebf128/0/cache/typeorm-npm-0.3.17-f8c2578e7f-3a7fe2a5e9.zip/node_modules/typeorm/",\
"packageDependencies": [\
["typeorm", "virtual:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:0.3.17"],\
["@google-cloud/spanner", null],\
["@sap/hana-client", null],\
["@sqltools/formatter", "npm:1.2.5"],\
["@types/better-sqlite3", null],\
["@types/google-cloud__spanner", null],\
["@types/hdb-pool", null],\
["@types/ioredis", "npm:5.0.0"],\
["@types/mongodb", null],\
["@types/mssql", null],\
["@types/mysql2", null],\
["@types/oracledb", null],\
["@types/pg", null],\
["@types/pg-native", null],\
["@types/pg-query-stream", null],\
["@types/redis", null],\
["@types/sap__hana-client", null],\
["@types/sql.js", null],\
["@types/sqlite3", null],\
["@types/ts-node", null],\
["@types/typeorm-aurora-data-api-driver", null],\
["app-root-path", "npm:3.1.0"],\
["better-sqlite3", null],\
["buffer", "npm:6.0.3"],\
["chalk", "npm:4.1.2"],\
["cli-highlight", "npm:2.1.11"],\
["date-fns", "npm:2.30.0"],\
["debug", "virtual:ac3d8e680759ce54399273724d44e041d6c9b73454d191d411a8c44bb27e22f02aaf6ed9d3ad0ac1c298eac4833cff369c9c7b84c573016112c4f84be2cd8543#npm:4.3.4"],\
["dotenv", "npm:16.1.3"],\
["glob", "npm:8.1.0"],\
["hdb-pool", null],\
["ioredis", "npm:5.3.2"],\
["mkdirp", "npm:2.1.6"],\
["mongodb", "virtual:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:6.0.0"],\
["mssql", null],\
["mysql2", "npm:3.3.3"],\
["oracledb", null],\
["pg", null],\
["pg-native", null],\
["pg-query-stream", null],\
["redis", null],\
["reflect-metadata", "npm:0.1.13"],\
["sha.js", "npm:2.4.11"],\
["sql.js", null],\
["sqlite3", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:5.1.6"],\
["ts-node", null],\
["tslib", "npm:2.5.2"],\
["typeorm-aurora-data-api-driver", null],\
["uuid", "npm:9.0.0"],\
["yargs", "npm:17.7.2"]\
],\
"packagePeers": [\
"@google-cloud/spanner",\
"@sap/hana-client",\
"@types/better-sqlite3",\
"@types/google-cloud__spanner",\
"@types/hdb-pool",\
"@types/ioredis",\
"@types/mongodb",\
"@types/mssql",\
"@types/mysql2",\
"@types/oracledb",\
"@types/pg-native",\
"@types/pg-query-stream",\
"@types/pg",\
"@types/redis",\
"@types/sap__hana-client",\
"@types/sql.js",\
"@types/sqlite3",\
"@types/ts-node",\
"@types/typeorm-aurora-data-api-driver",\
"better-sqlite3",\
"hdb-pool",\
"ioredis",\
"mongodb",\
"mssql",\
"mysql2",\
"oracledb",\
"pg-native",\
"pg-query-stream",\
"pg",\
"redis",\
"sql.js",\
"sqlite3",\
"ts-node",\
"typeorm-aurora-data-api-driver"\
],\
"linkType": "HARD"\
}],\
["virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:0.3.17", {\
"packageLocation": "./.yarn/__virtual__/typeorm-virtual-bfa664706d/0/cache/typeorm-npm-0.3.17-f8c2578e7f-3a7fe2a5e9.zip/node_modules/typeorm/",\
"packageDependencies": [\
@ -16659,13 +16439,6 @@ const RAW_RUNTIME_STATE =
["webidl-conversions", "npm:3.0.1"]\
],\
"linkType": "HARD"\
}],\
["npm:7.0.0", {\
"packageLocation": "./.yarn/cache/webidl-conversions-npm-7.0.0-e8c8e30c68-4c4f65472c.zip/node_modules/webidl-conversions/",\
"packageDependencies": [\
["webidl-conversions", "npm:7.0.0"]\
],\
"linkType": "HARD"\
}]\
]],\
["webpack", [\
@ -16724,15 +16497,6 @@ const RAW_RUNTIME_STATE =
}]\
]],\
["whatwg-url", [\
["npm:11.0.0", {\
"packageLocation": "./.yarn/cache/whatwg-url-npm-11.0.0-073529d93a-dfcd51c6f4.zip/node_modules/whatwg-url/",\
"packageDependencies": [\
["whatwg-url", "npm:11.0.0"],\
["tr46", "npm:3.0.0"],\
["webidl-conversions", "npm:7.0.0"]\
],\
"linkType": "HARD"\
}],\
["npm:5.0.0", {\
"packageLocation": "./.yarn/cache/whatwg-url-npm-5.0.0-374fb45e60-f95adbc1e8.zip/node_modules/whatwg-url/",\
"packageDependencies": [\

View file

@ -3,6 +3,8 @@ FROM node:20.6.1-alpine
ENV NODE_ENV production
RUN apk add --update --no-cache \
g++ \
make \
openssl \
curl \
bash \

View file

@ -57,9 +57,6 @@ fi
if [ -z "$CACHE_TYPE" ]; then
export CACHE_TYPE="redis"
fi
if [ -z "$SECONDARY_DB_ENABLED" ]; then
export SECONDARY_DB_ENABLED=false
fi
export DB_MIGRATIONS_PATH="dist/migrations/*.js"
#########

View file

@ -39,7 +39,7 @@ export abstract class AuthMiddleware extends BaseMiddleware {
crossServiceToken = await this.crossServiceTokenCache.get(cacheKey)
}
if (this.crossServiceTokenIsEmptyOrRequiresRevalidation(crossServiceToken)) {
if (crossServiceToken === null) {
const authResponse = await this.serviceProxy.validateSession({
authorization: authHeaderValue,
sharedVaultOwnerContext: sharedVaultOwnerContextHeaderValue,
@ -129,14 +129,4 @@ export abstract class AuthMiddleware extends BaseMiddleware {
return Math.min(crossServiceTokenDefaultCacheExpiration, sessionAccessExpiration, sessionRefreshExpiration)
}
private crossServiceTokenIsEmptyOrRequiresRevalidation(crossServiceToken: string | null) {
if (crossServiceToken === null) {
return true
}
const decodedToken = <CrossServiceTokenData>verify(crossServiceToken, this.jwtSecret, { algorithms: ['HS256'] })
return decodedToken.ongoing_transition === true
}
}

View file

@ -34,16 +34,6 @@ export class ItemsController extends BaseHttpController {
)
}
@httpPost('/transition')
async transition(request: Request, response: Response): Promise<void> {
await this.serviceProxy.callSyncingServer(
request,
response,
this.endpointResolver.resolveEndpointOrMethodIdentifier('POST', 'items/transition'),
request.body,
)
}
@httpGet('/:uuid')
async getItem(request: Request, response: Response): Promise<void> {
await this.serviceProxy.callSyncingServer(

View file

@ -1,6 +1,6 @@
import { Request, Response } from 'express'
import { inject } from 'inversify'
import { BaseHttpController, controller, httpDelete, httpGet, httpPost } from 'inversify-express-utils'
import { BaseHttpController, controller, httpDelete, httpGet } from 'inversify-express-utils'
import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface'
@ -55,14 +55,4 @@ export class RevisionsControllerV2 extends BaseHttpController {
),
)
}
@httpPost('/revisions/transition')
async transition(request: Request, response: Response): Promise<void> {
await this.serviceProxy.callRevisionsServer(
request,
response,
this.endpointResolver.resolveEndpointOrMethodIdentifier('POST', 'revisions/transition'),
request.body,
)
}
}

View file

@ -1,170 +0,0 @@
import 'reflect-metadata'
import { OpenTelemetrySDK, OpenTelemetryTracer } from '@standardnotes/domain-events-infra'
import { ServiceIdentifier, RoleName, TransitionStatus } from '@standardnotes/domain-core'
const sdk = new OpenTelemetrySDK({ serviceName: ServiceIdentifier.NAMES.AuthScheduledTask })
sdk.start()
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 { TimerInterface } from '@standardnotes/time'
import { TransitionStatusRepositoryInterface } from '../src/Domain/Transition/TransitionStatusRepositoryInterface'
const inputArgs = process.argv.slice(2)
const startDateString = inputArgs[0]
const endDateString = inputArgs[1]
const forceRunParam = inputArgs[2]
const requestTransition = async (
transitionStatusRepository: TransitionStatusRepositoryInterface,
userRepository: UserRepositoryInterface,
logger: Logger,
domainEventFactory: DomainEventFactoryInterface,
domainEventPublisher: DomainEventPublisherInterface,
timer: TimerInterface,
): Promise<void> => {
const startDate = new Date(startDateString)
const endDate = new Date(endDateString)
const usersCount = await userRepository.countAllCreatedBetween(startDate, endDate)
const timestamp = timer.getTimestampInMicroseconds()
logger.info(
`[TRANSITION ${timestamp}] Found ${usersCount} users created between ${startDateString} and ${endDateString}`,
)
let itemTransitionsTriggered = 0
let revisionTransitionsTriggered = 0
const forceRun = forceRunParam === 'true'
const pageLimit = 100
const totalPages = Math.ceil(usersCount / pageLimit)
for (let currentPage = 1; currentPage <= totalPages; currentPage++) {
const users = await userRepository.findAllCreatedBetween({
start: startDate,
end: endDate,
offset: (currentPage - 1) * pageLimit,
limit: pageLimit,
})
for (const user of users) {
const itemsTransitionStatus = await transitionStatusRepository.getStatus(user.uuid, 'items')
const revisionsTransitionStatus = await transitionStatusRepository.getStatus(user.uuid, 'revisions')
const userRoles = await user.roles
const userHasTransitionRole = userRoles.some((role) => role.name === RoleName.NAMES.TransitionUser)
const bothTransitionStatusesAreVerified =
itemsTransitionStatus?.value === TransitionStatus.STATUSES.Verified &&
revisionsTransitionStatus?.value === TransitionStatus.STATUSES.Verified
if (!userHasTransitionRole && bothTransitionStatusesAreVerified) {
continue
}
logger.info(
`[TRANSITION ${timestamp}] Transition status for user ${user.uuid} - items status: ${itemsTransitionStatus?.value}, revisions status: ${revisionsTransitionStatus?.value}, has transition role: ${userHasTransitionRole}`,
)
if (
itemsTransitionStatus === null ||
itemsTransitionStatus.value === TransitionStatus.STATUSES.Failed ||
(itemsTransitionStatus.value === TransitionStatus.STATUSES.InProgress && forceRun)
) {
await transitionStatusRepository.remove(user.uuid, 'items')
await domainEventPublisher.publish(
domainEventFactory.createTransitionRequestedEvent({
userUuid: user.uuid,
type: 'items',
timestamp,
}),
)
itemTransitionsTriggered++
}
if (
revisionsTransitionStatus === null ||
revisionsTransitionStatus.value === TransitionStatus.STATUSES.Failed ||
(revisionsTransitionStatus.value === TransitionStatus.STATUSES.InProgress && forceRun)
) {
await transitionStatusRepository.remove(user.uuid, 'revisions')
await domainEventPublisher.publish(
domainEventFactory.createTransitionRequestedEvent({
userUuid: user.uuid,
type: 'revisions',
timestamp,
}),
)
revisionTransitionsTriggered++
}
}
}
logger.info(
`[TRANSITION ${timestamp}] Triggered ${itemTransitionsTriggered} item transitions and ${revisionTransitionsTriggered} revision transitions for 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)
const timer = container.get<TimerInterface>(TYPES.Auth_Timer)
const transitionStatusRepository = container.get<TransitionStatusRepositoryInterface>(
TYPES.Auth_TransitionStatusRepository,
)
const tracer = new OpenTelemetryTracer()
tracer.startSpan(ServiceIdentifier.NAMES.AuthScheduledTask, 'transition')
Promise.resolve(
requestTransition(
transitionStatusRepository,
userRepository,
logger,
domainEventFactory,
domainEventPublisher,
timer,
),
)
.then(() => {
logger.info(`Finished transition request for users created between ${startDateString} and ${endDateString}`)
tracer.stopSpan()
process.exit(0)
})
.catch((error) => {
logger.error(
`Error while requesting transition for users created between ${startDateString} and ${endDateString}: ${error}`,
)
tracer.stopSpanWithError(error)
process.exit(1)
})
})

View file

@ -1,11 +0,0 @@
'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

View file

@ -55,13 +55,6 @@ 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"
;;

View file

@ -0,0 +1,11 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class RemoveTransitionRole1697704066569 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DELETE FROM `roles` WHERE name = "TRANSITION_USER"')
}
public async down(): Promise<void> {
return
}
}

View file

@ -258,11 +258,6 @@ import { UpdateStorageQuotaUsedForUser } from '../Domain/UseCase/UpdateStorageQu
import { SharedVaultFileUploadedEventHandler } from '../Domain/Handler/SharedVaultFileUploadedEventHandler'
import { SharedVaultFileRemovedEventHandler } from '../Domain/Handler/SharedVaultFileRemovedEventHandler'
import { SharedVaultFileMovedEventHandler } from '../Domain/Handler/SharedVaultFileMovedEventHandler'
import { TransitionStatusRepositoryInterface } from '../Domain/Transition/TransitionStatusRepositoryInterface'
import { RedisTransitionStatusRepository } from '../Infra/Redis/RedisTransitionStatusRepository'
import { InMemoryTransitionStatusRepository } from '../Infra/InMemory/InMemoryTransitionStatusRepository'
import { TransitionStatusUpdatedEventHandler } from '../Domain/Handler/TransitionStatusUpdatedEventHandler'
import { UpdateTransitionStatus } from '../Domain/UseCase/UpdateTransitionStatus/UpdateTransitionStatus'
import { TypeORMSharedVaultUser } from '../Infra/TypeORM/TypeORMSharedVaultUser'
import { SharedVaultUserPersistenceMapper } from '../Mapping/SharedVaultUserPersistenceMapper'
import { SharedVaultUserRepositoryInterface } from '../Domain/SharedVault/SharedVaultUserRepositoryInterface'
@ -645,9 +640,6 @@ export class ContainerConfigLoader {
container.get(TYPES.Auth_Timer),
),
)
container
.bind<TransitionStatusRepositoryInterface>(TYPES.Auth_TransitionStatusRepository)
.toConstantValue(new InMemoryTransitionStatusRepository())
} else {
container.bind<PKCERepositoryInterface>(TYPES.Auth_PKCERepository).to(RedisPKCERepository)
container.bind<LockRepositoryInterface>(TYPES.Auth_LockRepository).to(LockRepository)
@ -660,9 +652,6 @@ export class ContainerConfigLoader {
container
.bind<SubscriptionTokenRepositoryInterface>(TYPES.Auth_SubscriptionTokenRepository)
.to(RedisSubscriptionTokenRepository)
container
.bind<TransitionStatusRepositoryInterface>(TYPES.Auth_TransitionStatusRepository)
.toConstantValue(new RedisTransitionStatusRepository(container.get<Redis>(TYPES.Auth_Redis)))
}
// Services
@ -985,15 +974,6 @@ export class ContainerConfigLoader {
container.get(TYPES.Auth_SubscriptionSettingService),
),
)
container
.bind<UpdateTransitionStatus>(TYPES.Auth_UpdateTransitionStatus)
.toConstantValue(
new UpdateTransitionStatus(
container.get<TransitionStatusRepositoryInterface>(TYPES.Auth_TransitionStatusRepository),
container.get<RoleServiceInterface>(TYPES.Auth_RoleService),
container.get<winston.Logger>(TYPES.Auth_Logger),
),
)
container
.bind<AddSharedVaultUser>(TYPES.Auth_AddSharedVaultUser)
.toConstantValue(
@ -1174,14 +1154,6 @@ export class ContainerConfigLoader {
container.get(TYPES.Auth_Logger),
),
)
container
.bind<TransitionStatusUpdatedEventHandler>(TYPES.Auth_TransitionStatusUpdatedEventHandler)
.toConstantValue(
new TransitionStatusUpdatedEventHandler(
container.get<UpdateTransitionStatus>(TYPES.Auth_UpdateTransitionStatus),
container.get<winston.Logger>(TYPES.Auth_Logger),
),
)
container
.bind<UserAddedToSharedVaultEventHandler>(TYPES.Auth_UserAddedToSharedVaultEventHandler)
.toConstantValue(
@ -1239,7 +1211,6 @@ export class ContainerConfigLoader {
['PREDICATE_VERIFICATION_REQUESTED', container.get(TYPES.Auth_PredicateVerificationRequestedEventHandler)],
['EMAIL_SUBSCRIPTION_UNSUBSCRIBED', container.get(TYPES.Auth_EmailSubscriptionUnsubscribedEventHandler)],
['PAYMENTS_ACCOUNT_DELETED', container.get(TYPES.Auth_PaymentsAccountDeletedEventHandler)],
['TRANSITION_STATUS_UPDATED', container.get(TYPES.Auth_TransitionStatusUpdatedEventHandler)],
['USER_ADDED_TO_SHARED_VAULT', container.get(TYPES.Auth_UserAddedToSharedVaultEventHandler)],
['USER_REMOVED_FROM_SHARED_VAULT', container.get(TYPES.Auth_UserRemovedFromSharedVaultEventHandler)],
[

View file

@ -36,7 +36,6 @@ const TYPES = {
Auth_AuthenticatorRepository: Symbol.for('Auth_AuthenticatorRepository'),
Auth_AuthenticatorChallengeRepository: Symbol.for('Auth_AuthenticatorChallengeRepository'),
Auth_CacheEntryRepository: Symbol.for('Auth_CacheEntryRepository'),
Auth_TransitionStatusRepository: Symbol.for('Auth_TransitionStatusRepository'),
Auth_SharedVaultUserRepository: Symbol.for('Auth_SharedVaultUserRepository'),
// ORM
Auth_ORMOfflineSettingRepository: Symbol.for('Auth_ORMOfflineSettingRepository'),
@ -157,7 +156,6 @@ const TYPES = {
Auth_SignInWithRecoveryCodes: Symbol.for('Auth_SignInWithRecoveryCodes'),
Auth_GetUserKeyParamsRecovery: Symbol.for('Auth_GetUserKeyParamsRecovery'),
Auth_UpdateStorageQuotaUsedForUser: Symbol.for('Auth_UpdateStorageQuotaUsedForUser'),
Auth_UpdateTransitionStatus: Symbol.for('Auth_UpdateTransitionStatus'),
Auth_AddSharedVaultUser: Symbol.for('Auth_AddSharedVaultUser'),
Auth_RemoveSharedVaultUser: Symbol.for('Auth_RemoveSharedVaultUser'),
Auth_DesignateSurvivor: Symbol.for('Auth_DesignateSurvivor'),
@ -190,7 +188,6 @@ const TYPES = {
Auth_PredicateVerificationRequestedEventHandler: Symbol.for('Auth_PredicateVerificationRequestedEventHandler'),
Auth_EmailSubscriptionUnsubscribedEventHandler: Symbol.for('Auth_EmailSubscriptionUnsubscribedEventHandler'),
Auth_PaymentsAccountDeletedEventHandler: Symbol.for('Auth_PaymentsAccountDeletedEventHandler'),
Auth_TransitionStatusUpdatedEventHandler: Symbol.for('Auth_TransitionStatusUpdatedEventHandler'),
Auth_UserAddedToSharedVaultEventHandler: Symbol.for('Auth_UserAddedToSharedVaultEventHandler'),
Auth_UserRemovedFromSharedVaultEventHandler: Symbol.for('Auth_UserRemovedFromSharedVaultEventHandler'),
Auth_UserDesignatedAsSurvivorInSharedVaultEventHandler: Symbol.for(

View file

@ -20,7 +20,6 @@ import {
StatisticPersistenceRequestedEvent,
SessionCreatedEvent,
SessionRefreshedEvent,
TransitionRequestedEvent,
} from '@standardnotes/domain-events'
import { Predicate, PredicateVerificationResult } from '@standardnotes/predicates'
import { TimerInterface } from '@standardnotes/time'
@ -34,25 +33,6 @@ import { KeyParamsData } from '@standardnotes/responses'
export class DomainEventFactory implements DomainEventFactoryInterface {
constructor(@inject(TYPES.Auth_Timer) private timer: TimerInterface) {}
createTransitionRequestedEvent(dto: {
userUuid: string
type: 'items' | 'revisions'
timestamp: number
}): TransitionRequestedEvent {
return {
type: 'TRANSITION_REQUESTED',
createdAt: this.timer.getUTCDate(),
meta: {
correlation: {
userIdentifier: dto.userUuid,
userIdentifierType: 'uuid',
},
origin: DomainEventService.Auth,
},
payload: dto,
}
}
createSessionCreatedEvent(dto: { userUuid: string }): SessionCreatedEvent {
return {
type: 'SESSION_CREATED',

View file

@ -18,7 +18,6 @@ import {
StatisticPersistenceRequestedEvent,
SessionCreatedEvent,
SessionRefreshedEvent,
TransitionRequestedEvent,
} from '@standardnotes/domain-events'
import { InviteeIdentifierType } from '../SharedSubscription/InviteeIdentifierType'
import { KeyParamsData } from '@standardnotes/responses'
@ -92,9 +91,4 @@ export interface DomainEventFactoryInterface {
}): StatisticPersistenceRequestedEvent
createSessionCreatedEvent(dto: { userUuid: string }): SessionCreatedEvent
createSessionRefreshedEvent(dto: { userUuid: string }): SessionRefreshedEvent
createTransitionRequestedEvent(dto: {
userUuid: string
type: 'items' | 'revisions'
timestamp: number
}): TransitionRequestedEvent
}

View file

@ -1,23 +0,0 @@
import { DomainEventHandlerInterface, TransitionStatusUpdatedEvent } from '@standardnotes/domain-events'
import { UpdateTransitionStatus } from '../UseCase/UpdateTransitionStatus/UpdateTransitionStatus'
import { Logger } from 'winston'
export class TransitionStatusUpdatedEventHandler implements DomainEventHandlerInterface {
constructor(
private updateTransitionStatusUseCase: UpdateTransitionStatus,
private logger: Logger,
) {}
async handle(event: TransitionStatusUpdatedEvent): Promise<void> {
const result = await this.updateTransitionStatusUseCase.execute({
status: event.payload.status,
userUuid: event.payload.userUuid,
transitionType: event.payload.transitionType,
transitionTimestamp: event.payload.transitionTimestamp,
})
if (result.isFailed()) {
this.logger.error(`Failed to update transition status for user ${event.payload.userUuid}`)
}
}
}

View file

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

View file

@ -9,15 +9,7 @@ import { UserRepositoryInterface } from '../../User/UserRepositoryInterface'
import { CreateCrossServiceToken } from './CreateCrossServiceToken'
import { GetSetting } from '../GetSetting/GetSetting'
import {
Result,
SharedVaultUser,
SharedVaultUserPermission,
Timestamps,
TransitionStatus,
Uuid,
} from '@standardnotes/domain-core'
import { TransitionStatusRepositoryInterface } from '../../Transition/TransitionStatusRepositoryInterface'
import { Result, SharedVaultUser, SharedVaultUserPermission, Timestamps, Uuid } from '@standardnotes/domain-core'
import { SharedVaultUserRepositoryInterface } from '../../SharedVault/SharedVaultUserRepositoryInterface'
describe('CreateCrossServiceToken', () => {
@ -27,7 +19,6 @@ describe('CreateCrossServiceToken', () => {
let tokenEncoder: TokenEncoderInterface<CrossServiceTokenData>
let userRepository: UserRepositoryInterface
let getSettingUseCase: GetSetting
let transitionStatusRepository: TransitionStatusRepositoryInterface
let sharedVaultUserRepository: SharedVaultUserRepositoryInterface
const jwtTTL = 60
@ -44,7 +35,6 @@ describe('CreateCrossServiceToken', () => {
userRepository,
jwtTTL,
getSettingUseCase,
transitionStatusRepository,
sharedVaultUserRepository,
)
@ -78,11 +68,6 @@ describe('CreateCrossServiceToken', () => {
getSettingUseCase = {} as jest.Mocked<GetSetting>
getSettingUseCase.execute = jest.fn().mockReturnValue(Result.ok({ setting: { value: '100' } }))
transitionStatusRepository = {} as jest.Mocked<TransitionStatusRepositoryInterface>
transitionStatusRepository.getStatus = jest
.fn()
.mockReturnValue(TransitionStatus.create(TransitionStatus.STATUSES.Verified).getValue())
sharedVaultUserRepository = {} as jest.Mocked<SharedVaultUserRepositoryInterface>
sharedVaultUserRepository.findByUserUuid = jest.fn().mockReturnValue([
SharedVaultUser.create({
@ -122,46 +107,6 @@ describe('CreateCrossServiceToken', () => {
email: 'test@test.te',
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(TransitionStatus.create(TransitionStatus.STATUSES.InProgress).getValue())
await createUseCase().execute({
user,
session,
})
expect(tokenEncoder.encodeExpirableToken).toHaveBeenCalledWith(
{
roles: [
{
name: 'role1',
uuid: '1-3-4',
},
],
belongs_to_shared_vaults: [
{
shared_vault_uuid: '00000000-0000-0000-0000-000000000000',
permission: 'read',
},
],
session: {
test: 'test',
},
user: {
email: 'test@test.te',
uuid: '00000000-0000-0000-0000-000000000000',
},
ongoing_transition: true,
ongoing_revisions_transition: true,
},
60,
)
@ -190,8 +135,6 @@ describe('CreateCrossServiceToken', () => {
email: 'test@test.te',
uuid: '00000000-0000-0000-0000-000000000000',
},
ongoing_transition: false,
ongoing_revisions_transition: false,
},
60,
)
@ -220,8 +163,6 @@ describe('CreateCrossServiceToken', () => {
email: 'test@test.te',
uuid: '00000000-0000-0000-0000-000000000000',
},
ongoing_transition: false,
ongoing_revisions_transition: false,
},
60,
)
@ -277,8 +218,6 @@ describe('CreateCrossServiceToken', () => {
email: 'test@test.te',
uuid: '00000000-0000-0000-0000-000000000000',
},
ongoing_revisions_transition: false,
ongoing_transition: false,
},
60,
)

View file

@ -1,6 +1,6 @@
import { TokenEncoderInterface, CrossServiceTokenData } from '@standardnotes/security'
import { inject, injectable } from 'inversify'
import { Result, TransitionStatus, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import TYPES from '../../../Bootstrap/Types'
import { ProjectorInterface } from '../../../Projection/ProjectorInterface'
@ -12,7 +12,6 @@ import { UserRepositoryInterface } from '../../User/UserRepositoryInterface'
import { CreateCrossServiceTokenDTO } from './CreateCrossServiceTokenDTO'
import { GetSetting } from '../GetSetting/GetSetting'
import { SettingName } from '@standardnotes/settings'
import { TransitionStatusRepositoryInterface } from '../../Transition/TransitionStatusRepositoryInterface'
import { SharedVaultUserRepositoryInterface } from '../../SharedVault/SharedVaultUserRepositoryInterface'
@injectable()
@ -26,8 +25,6 @@ export class CreateCrossServiceToken implements UseCaseInterface<string> {
@inject(TYPES.Auth_AUTH_JWT_TTL) private jwtTTL: number,
@inject(TYPES.Auth_GetSetting)
private getSettingUseCase: GetSetting,
@inject(TYPES.Auth_TransitionStatusRepository)
private transitionStatusRepository: TransitionStatusRepositoryInterface,
@inject(TYPES.Auth_SharedVaultUserRepository) private sharedVaultUserRepository: SharedVaultUserRepositoryInterface,
) {}
@ -47,9 +44,6 @@ export class CreateCrossServiceToken implements UseCaseInterface<string> {
return Result.fail(`Could not find user with uuid ${dto.userUuid}`)
}
const transitionStatus = await this.transitionStatusRepository.getStatus(user.uuid, 'items')
const revisionsTransitionStatus = await this.transitionStatusRepository.getStatus(user.uuid, 'revisions')
const roles = await user.roles
const sharedVaultAssociations = await this.sharedVaultUserRepository.findByUserUuid(
@ -60,8 +54,6 @@ export class CreateCrossServiceToken implements UseCaseInterface<string> {
user: this.projectUser(user),
roles: this.projectRoles(roles),
shared_vault_owner_context: undefined,
ongoing_transition: transitionStatus?.value === TransitionStatus.STATUSES.InProgress,
ongoing_revisions_transition: revisionsTransitionStatus?.value === TransitionStatus.STATUSES.InProgress,
belongs_to_shared_vaults: sharedVaultAssociations.map((association) => ({
shared_vault_uuid: association.props.sharedVaultUuid.value,
permission: association.props.permission.value,

View file

@ -1,6 +1,5 @@
import 'reflect-metadata'
import { TransitionStatus } from '@standardnotes/domain-core'
import { TimerInterface } from '@standardnotes/time'
import { TokenEncoderInterface, ValetTokenData, ValetTokenOperation } from '@standardnotes/security'
@ -11,7 +10,6 @@ import { User } from '../../User/User'
import { UserSubscriptionType } from '../../Subscription/UserSubscriptionType'
import { SubscriptionSettingsAssociationServiceInterface } from '../../Setting/SubscriptionSettingsAssociationServiceInterface'
import { UserSubscriptionServiceInterface } from '../../Subscription/UserSubscriptionServiceInterface'
import { TransitionStatusRepositoryInterface } from '../../Transition/TransitionStatusRepositoryInterface'
describe('CreateValetToken', () => {
let tokenEncoder: TokenEncoderInterface<ValetTokenData>
@ -23,7 +21,6 @@ describe('CreateValetToken', () => {
let regularSubscription: UserSubscription
let sharedSubscription: UserSubscription
let user: User
let transitionStatusRepository: TransitionStatusRepositoryInterface
const createUseCase = () =>
new CreateValetToken(
@ -33,7 +30,6 @@ describe('CreateValetToken', () => {
userSubscriptionService,
timer,
valetTokenTTL,
transitionStatusRepository,
)
beforeEach(() => {
@ -71,11 +67,6 @@ describe('CreateValetToken', () => {
timer = {} as jest.Mocked<TimerInterface>
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(100)
transitionStatusRepository = {} as jest.Mocked<TransitionStatusRepositoryInterface>
transitionStatusRepository.getStatus = jest
.fn()
.mockReturnValue(TransitionStatus.create(TransitionStatus.STATUSES.Verified).getValue())
})
it('should create a read valet token', async () => {
@ -176,7 +167,6 @@ describe('CreateValetToken', () => {
{
sharedSubscriptionUuid: undefined,
regularSubscriptionUuid: '1-2-3',
ongoingTransition: false,
permittedOperation: 'write',
permittedResources: [
{
@ -217,7 +207,6 @@ describe('CreateValetToken', () => {
{
sharedSubscriptionUuid: '2-3-4',
regularSubscriptionUuid: '1-2-3',
ongoingTransition: false,
permittedOperation: 'write',
permittedResources: [
{
@ -278,7 +267,6 @@ describe('CreateValetToken', () => {
{
sharedSubscriptionUuid: undefined,
regularSubscriptionUuid: '1-2-3',
ongoingTransition: false,
permittedOperation: 'write',
permittedResources: [
{

View file

@ -13,8 +13,6 @@ import { CreateValetTokenDTO } from './CreateValetTokenDTO'
import { SubscriptionSettingsAssociationServiceInterface } from '../../Setting/SubscriptionSettingsAssociationServiceInterface'
import { UserSubscriptionServiceInterface } from '../../Subscription/UserSubscriptionServiceInterface'
import { CreateValetTokenPayload } from '../../ValetToken/CreateValetTokenPayload'
import { TransitionStatusRepositoryInterface } from '../../Transition/TransitionStatusRepositoryInterface'
import { TransitionStatus } from '@standardnotes/domain-core'
@injectable()
export class CreateValetToken implements UseCaseInterface {
@ -27,8 +25,6 @@ export class CreateValetToken implements UseCaseInterface {
@inject(TYPES.Auth_UserSubscriptionService) private userSubscriptionService: UserSubscriptionServiceInterface,
@inject(TYPES.Auth_Timer) private timer: TimerInterface,
@inject(TYPES.Auth_VALET_TOKEN_TTL) private valetTokenTTL: number,
@inject(TYPES.Auth_TransitionStatusRepository)
private transitionStatusRepository: TransitionStatusRepositoryInterface,
) {}
async execute(dto: CreateValetTokenDTO): Promise<CreateValetTokenResponseData> {
@ -87,8 +83,6 @@ export class CreateValetToken implements UseCaseInterface {
sharedSubscriptionUuid = sharedSubscription.uuid
}
const transitionStatus = await this.transitionStatusRepository.getStatus(userUuid, 'items')
const tokenData: ValetTokenData = {
userUuid: dto.userUuid,
permittedOperation: dto.operation,
@ -97,7 +91,6 @@ export class CreateValetToken implements UseCaseInterface {
uploadBytesLimit,
sharedSubscriptionUuid,
regularSubscriptionUuid: regularSubscription.uuid,
ongoingTransition: transitionStatus?.value === TransitionStatus.STATUSES.InProgress,
}
const valetToken = this.tokenEncoder.encodeExpirableToken(tokenData, this.valetTokenTTL)

View file

@ -1,107 +0,0 @@
import { RoleName, TransitionStatus, Uuid } from '@standardnotes/domain-core'
import { RoleServiceInterface } from '../../Role/RoleServiceInterface'
import { TransitionStatusRepositoryInterface } from '../../Transition/TransitionStatusRepositoryInterface'
import { UpdateTransitionStatus } from './UpdateTransitionStatus'
import { Logger } from 'winston'
describe('UpdateTransitionStatus', () => {
let transitionStatusRepository: TransitionStatusRepositoryInterface
let roleService: RoleServiceInterface
let logger: Logger
const createUseCase = () => new UpdateTransitionStatus(transitionStatusRepository, roleService, logger)
beforeEach(() => {
logger = {} as jest.Mocked<Logger>
logger.info = jest.fn()
transitionStatusRepository = {} as jest.Mocked<TransitionStatusRepositoryInterface>
transitionStatusRepository.updateStatus = jest.fn()
transitionStatusRepository.getStatus = jest.fn().mockResolvedValue(null)
roleService = {} as jest.Mocked<RoleServiceInterface>
roleService.removeRoleFromUser = jest.fn()
})
it('should add TRANSITION_USER role', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
status: 'VERIFIED',
transitionType: 'items',
transitionTimestamp: 123,
})
expect(result.isFailed()).toBeFalsy()
expect(roleService.removeRoleFromUser).toHaveBeenCalledWith(
Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
RoleName.create(RoleName.NAMES.TransitionUser).getValue(),
)
})
it('should update transition status', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
status: TransitionStatus.STATUSES.InProgress,
transitionType: 'items',
transitionTimestamp: 123,
})
expect(result.isFailed()).toBeFalsy()
expect(transitionStatusRepository.updateStatus).toHaveBeenCalled()
})
it('should return error when user uuid is invalid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: 'invalid',
status: 'STARTED',
transitionType: 'items',
transitionTimestamp: 123,
})
expect(result.isFailed()).toBeTruthy()
expect(result.getError()).toEqual('Given value is not a valid uuid: invalid')
})
it('should not update status if transition is already verified', async () => {
transitionStatusRepository.getStatus = jest
.fn()
.mockResolvedValue(TransitionStatus.create(TransitionStatus.STATUSES.Verified).getValue())
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
status: TransitionStatus.STATUSES.InProgress,
transitionType: 'items',
transitionTimestamp: 123,
})
expect(result.isFailed()).toBeFalsy()
expect(transitionStatusRepository.updateStatus).not.toHaveBeenCalled()
})
it('should not update status if transition is already failed', async () => {
transitionStatusRepository.getStatus = jest
.fn()
.mockResolvedValue(TransitionStatus.create(TransitionStatus.STATUSES.Failed).getValue())
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
status: TransitionStatus.STATUSES.InProgress,
transitionType: 'items',
transitionTimestamp: 123,
})
expect(result.isFailed()).toBeFalsy()
expect(transitionStatusRepository.updateStatus).not.toHaveBeenCalled()
})
})

View file

@ -1,40 +0,0 @@
import { Result, RoleName, TransitionStatus, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import { TransitionStatusRepositoryInterface } from '../../Transition/TransitionStatusRepositoryInterface'
import { UpdateTransitionStatusDTO } from './UpdateTransitionStatusDTO'
import { RoleServiceInterface } from '../../Role/RoleServiceInterface'
import { Logger } from 'winston'
export class UpdateTransitionStatus implements UseCaseInterface<void> {
constructor(
private transitionStatusRepository: TransitionStatusRepositoryInterface,
private roleService: RoleServiceInterface,
private logger: Logger,
) {}
async execute(dto: UpdateTransitionStatusDTO): Promise<Result<void>> {
const userUuidOrError = Uuid.create(dto.userUuid)
if (userUuidOrError.isFailed()) {
return Result.fail(userUuidOrError.getError())
}
const userUuid = userUuidOrError.getValue()
const currentStatus = await this.transitionStatusRepository.getStatus(dto.userUuid, dto.transitionType)
if (
[TransitionStatus.STATUSES.Verified, TransitionStatus.STATUSES.Failed].includes(currentStatus?.value as string)
) {
this.logger.info(`User ${dto.userUuid} transition already finished.`)
return Result.ok()
}
const transitionStatus = TransitionStatus.create(dto.status).getValue()
await this.transitionStatusRepository.updateStatus(dto.userUuid, dto.transitionType, transitionStatus)
if (dto.transitionType === 'items' && transitionStatus.value === TransitionStatus.STATUSES.Verified) {
await this.roleService.removeRoleFromUser(userUuid, RoleName.create(RoleName.NAMES.TransitionUser).getValue())
}
return Result.ok()
}
}

View file

@ -1,6 +0,0 @@
export interface UpdateTransitionStatusDTO {
userUuid: string
transitionType: 'items' | 'revisions'
transitionTimestamp: number
status: string
}

View file

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

View file

@ -1,43 +0,0 @@
import * as IORedis from 'ioredis'
import { TransitionStatusRepositoryInterface } from '../../Domain/Transition/TransitionStatusRepositoryInterface'
import { TransitionStatus } from '@standardnotes/domain-core'
export class RedisTransitionStatusRepository implements TransitionStatusRepositoryInterface {
private readonly PREFIX = 'transition-back'
constructor(private redisClient: IORedis.Redis) {}
async remove(userUuid: string, transitionType: 'items' | 'revisions'): Promise<void> {
await this.redisClient.del(`${this.PREFIX}:${transitionType}:${userUuid}`)
}
async updateStatus(userUuid: string, transitionType: 'items' | 'revisions', status: TransitionStatus): Promise<void> {
switch (status.value) {
case TransitionStatus.STATUSES.Failed:
case TransitionStatus.STATUSES.Verified:
await this.redisClient.set(`${this.PREFIX}:${transitionType}:${userUuid}`, status.value)
break
case TransitionStatus.STATUSES.InProgress: {
const ttl24Hours = 86_400
await this.redisClient.setex(`${this.PREFIX}:${transitionType}:${userUuid}`, ttl24Hours, status.value)
break
}
}
}
async getStatus(userUuid: string, transitionType: 'items' | 'revisions'): Promise<TransitionStatus | null> {
const status = await this.redisClient.get(`${this.PREFIX}:${transitionType}:${userUuid}`)
if (status === null) {
return null
}
const transitionStatusOrError = TransitionStatus.create(status)
if (transitionStatusOrError.isFailed()) {
return null
}
return transitionStatusOrError.getValue()
}
}

View file

@ -21,36 +21,25 @@ describe('RoleName', () => {
const plusUserRole = RoleName.create(RoleName.NAMES.PlusUser).getValue()
const coreUser = RoleName.create(RoleName.NAMES.CoreUser).getValue()
const internalTeamUser = RoleName.create(RoleName.NAMES.InternalTeamUser).getValue()
const transitionUser = RoleName.create(RoleName.NAMES.TransitionUser).getValue()
expect(internalTeamUser.hasMoreOrEqualPowerTo(proUserRole)).toBeTruthy()
expect(internalTeamUser.hasMoreOrEqualPowerTo(proUserRole)).toBeTruthy()
expect(internalTeamUser.hasMoreOrEqualPowerTo(plusUserRole)).toBeTruthy()
expect(internalTeamUser.hasMoreOrEqualPowerTo(coreUser)).toBeTruthy()
expect(internalTeamUser.hasMoreOrEqualPowerTo(transitionUser)).toBeTruthy()
expect(proUserRole.hasMoreOrEqualPowerTo(internalTeamUser)).toBeFalsy()
expect(proUserRole.hasMoreOrEqualPowerTo(proUserRole)).toBeTruthy()
expect(proUserRole.hasMoreOrEqualPowerTo(plusUserRole)).toBeTruthy()
expect(proUserRole.hasMoreOrEqualPowerTo(coreUser)).toBeTruthy()
expect(proUserRole.hasMoreOrEqualPowerTo(transitionUser)).toBeTruthy()
expect(plusUserRole.hasMoreOrEqualPowerTo(internalTeamUser)).toBeFalsy()
expect(plusUserRole.hasMoreOrEqualPowerTo(proUserRole)).toBeFalsy()
expect(plusUserRole.hasMoreOrEqualPowerTo(plusUserRole)).toBeTruthy()
expect(plusUserRole.hasMoreOrEqualPowerTo(coreUser)).toBeTruthy()
expect(plusUserRole.hasMoreOrEqualPowerTo(transitionUser)).toBeTruthy()
expect(coreUser.hasMoreOrEqualPowerTo(internalTeamUser)).toBeFalsy()
expect(coreUser.hasMoreOrEqualPowerTo(proUserRole)).toBeFalsy()
expect(coreUser.hasMoreOrEqualPowerTo(plusUserRole)).toBeFalsy()
expect(coreUser.hasMoreOrEqualPowerTo(coreUser)).toBeTruthy()
expect(coreUser.hasMoreOrEqualPowerTo(transitionUser)).toBeTruthy()
expect(transitionUser.hasMoreOrEqualPowerTo(internalTeamUser)).toBeFalsy()
expect(transitionUser.hasMoreOrEqualPowerTo(proUserRole)).toBeFalsy()
expect(transitionUser.hasMoreOrEqualPowerTo(plusUserRole)).toBeFalsy()
expect(transitionUser.hasMoreOrEqualPowerTo(coreUser)).toBeTruthy()
expect(transitionUser.hasMoreOrEqualPowerTo(transitionUser)).toBeTruthy()
})
})

View file

@ -8,7 +8,6 @@ export class RoleName extends ValueObject<RoleNameProps> {
PlusUser: 'PLUS_USER',
ProUser: 'PRO_USER',
InternalTeamUser: 'INTERNAL_TEAM_USER',
TransitionUser: 'TRANSITION_USER',
VaultsUser: 'VAULTS_USER',
}
@ -21,20 +20,12 @@ export class RoleName extends ValueObject<RoleNameProps> {
case RoleName.NAMES.InternalTeamUser:
return true
case RoleName.NAMES.ProUser:
return [
RoleName.NAMES.CoreUser,
RoleName.NAMES.PlusUser,
RoleName.NAMES.ProUser,
RoleName.NAMES.TransitionUser,
].includes(roleName.value)
return [RoleName.NAMES.CoreUser, RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser].includes(roleName.value)
case RoleName.NAMES.PlusUser:
return [RoleName.NAMES.CoreUser, RoleName.NAMES.PlusUser, RoleName.NAMES.TransitionUser].includes(
roleName.value,
)
return [RoleName.NAMES.CoreUser, RoleName.NAMES.PlusUser].includes(roleName.value)
case RoleName.NAMES.CoreUser:
case RoleName.NAMES.TransitionUser:
case RoleName.NAMES.VaultsUser:
return [RoleName.NAMES.CoreUser, RoleName.NAMES.TransitionUser].includes(roleName.value)
return [RoleName.NAMES.CoreUser].includes(roleName.value)
/*istanbul ignore next*/
default:
throw new Error(`Invalid role name: ${this.value}`)

View file

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

View file

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

View file

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

View file

@ -1,6 +1,5 @@
export interface AccountDeletionRequestedEventPayload {
userUuid: string
roleNames: string[]
userCreatedAtTimestamp: number
regularSubscriptionUuid: string | undefined
}

View file

@ -1,5 +1,4 @@
export interface DuplicateItemSyncedEventPayload {
itemUuid: string
userUuid: string
roleNames: string[]
}

View file

@ -1,4 +1,3 @@
export interface ItemDumpedEventPayload {
fileDumpPath: string
roleNames: string[]
}

View file

@ -1,4 +1,3 @@
export interface ItemRevisionCreationRequestedEventPayload {
itemUuid: string
roleNames: string[]
}

View file

@ -1,5 +1,4 @@
export interface RevisionsCopyRequestedEventPayload {
newItemUuid: string
originalItemUuid: string
roleNames: string[]
}

View file

@ -1,8 +0,0 @@
import { DomainEventInterface } from './DomainEventInterface'
import { TransitionRequestedEventPayload } from './TransitionRequestedEventPayload'
export interface TransitionRequestedEvent extends DomainEventInterface {
type: 'TRANSITION_REQUESTED'
payload: TransitionRequestedEventPayload
}

View file

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

View file

@ -1,8 +0,0 @@
import { DomainEventInterface } from './DomainEventInterface'
import { TransitionStatusUpdatedEventPayload } from './TransitionStatusUpdatedEventPayload'
export interface TransitionStatusUpdatedEvent extends DomainEventInterface {
type: 'TRANSITION_STATUS_UPDATED'
payload: TransitionStatusUpdatedEventPayload
}

View file

@ -1,7 +0,0 @@
export interface TransitionStatusUpdatedEventPayload {
userUuid: string
transitionType: 'items' | 'revisions'
transitionTimestamp: number
status: string
page?: number
}

View file

@ -98,10 +98,6 @@ export * from './Event/SubscriptionRevertRequestedEvent'
export * from './Event/SubscriptionRevertRequestedEventPayload'
export * from './Event/SubscriptionSyncRequestedEvent'
export * from './Event/SubscriptionSyncRequestedEventPayload'
export * from './Event/TransitionRequestedEvent'
export * from './Event/TransitionRequestedEventPayload'
export * from './Event/TransitionStatusUpdatedEvent'
export * from './Event/TransitionStatusUpdatedEventPayload'
export * from './Event/UserAddedToSharedVaultEvent'
export * from './Event/UserAddedToSharedVaultEventPayload'
export * from './Event/UserDesignatedAsSurvivorInSharedVaultEvent'

View file

@ -223,27 +223,4 @@ describe('ValetTokenAuthMiddleware', () => {
expect(next).toHaveBeenCalledWith(error)
})
it('should throw an error if the valet token indicates an ongoing transition', async () => {
request.headers['x-valet-token'] = 'valet-token'
tokenDecoder.decodeToken = jest.fn().mockReturnValue({
userUuid: '1-2-3',
permittedResources: [
{
remoteIdentifier: '00000000-0000-0000-0000-000000000000',
unencryptedFileSize: 30,
},
],
permittedOperation: 'write',
uploadBytesLimit: -1,
uploadBytesUsed: 80,
ongoingTransition: true,
})
await createMiddleware().handler(request, response, next)
expect(response.status).toHaveBeenCalledWith(500)
expect(next).not.toHaveBeenCalled()
})
})

View file

@ -46,19 +46,6 @@ export class ValetTokenAuthMiddleware extends BaseMiddleware {
return
}
if (valetTokenData.ongoingTransition === true) {
this.logger.error(`Cannot perform file operations for user ${valetTokenData.userUuid} during transition`)
response.status(500).send({
error: {
tag: 'ongoing-transition',
message: 'Cannot perform file operations during transition',
},
})
return
}
for (const resource of valetTokenData.permittedResources) {
const resourceUuidOrError = Uuid.create(resource.remoteIdentifier)
if (resourceUuidOrError.isFailed()) {

View file

@ -9,10 +9,3 @@ PSEUDO_KEY_PARAMS_KEY=
VALET_TOKEN_SECRET=
FILES_SERVER_URL=
SECONDARY_DB_ENABLED=false
MONGO_HOST=localhost
MONGO_PORT=27017
MONGO_USERNAME=standardnotes
MONGO_PASSWORD=standardnotes
MONGO_DATABASE=standardnotes

View file

@ -17,20 +17,7 @@ DB_DEBUG_LEVEL=all # "all" | "query" | "schema" | "error" | "warn" | "info" | "l
DB_MIGRATIONS_PATH=dist/migrations/*.js
DB_TYPE=mysql
REDIS_URL=redis://cache
CACHE_TYPE=redis
SNS_TOPIC_ARN=
SNS_AWS_REGION=
SQS_QUEUE_URL=
SQS_AWS_REGION=
S3_AWS_REGION=
S3_BACKUP_BUCKET_NAME=
# (Optional) Mongo Setup
SECONDARY_DB_ENABLED=false
MONGO_HOST=
MONGO_PORT=
MONGO_USERNAME=
MONGO_PASSWORD=
MONGO_DATABASE=

View file

@ -42,7 +42,6 @@
"inversify": "^6.0.1",
"inversify-express-utils": "^6.4.3",
"ioredis": "^5.3.2",
"mongodb": "^6.0.0",
"mysql2": "^3.0.1",
"reflect-metadata": "0.1.13",
"sqlite3": "^5.1.6",

View file

@ -4,11 +4,9 @@ import {
MapperInterface,
ServiceIdentifier,
} from '@standardnotes/domain-core'
import Redis from 'ioredis'
import { Container, interfaces } from 'inversify'
import { MongoRepository, Repository } from 'typeorm'
import { Repository } from 'typeorm'
import * as winston from 'winston'
import { SNSClient, SNSClientConfig } from '@aws-sdk/client-sns'
import { Revision } from '../Domain/Revision/Revision'
import { RevisionMetadata } from '../Domain/Revision/RevisionMetadata'
@ -38,7 +36,6 @@ import {
SQSEventMessageHandler,
DirectCallEventMessageHandler,
DirectCallDomainEventPublisher,
SNSOpenTelemetryDomainEventPublisher,
SQSOpenTelemetryDomainEventSubscriber,
} from '@standardnotes/domain-events-infra'
import { DumpRepositoryInterface } from '../Domain/Dump/DumpRepositoryInterface'
@ -51,30 +48,18 @@ import { S3DumpRepository } from '../Infra/S3/S3ItemDumpRepository'
import { RevisionItemStringMapper } from '../Mapping/Backup/RevisionItemStringMapper'
import { BaseRevisionsController } from '../Infra/InversifyExpress/Base/BaseRevisionsController'
import { Transform } from 'stream'
import { MongoDBRevision } from '../Infra/TypeORM/MongoDB/MongoDBRevision'
import { MongoDBRevisionRepository } from '../Infra/TypeORM/MongoDB/MongoDBRevisionRepository'
import { SQLLegacyRevisionMetadataPersistenceMapper } from '../Mapping/Persistence/SQL/SQLLegacyRevisionMetadataPersistenceMapper'
import { SQLLegacyRevisionPersistenceMapper } from '../Mapping/Persistence/SQL/SQLLegacyRevisionPersistenceMapper'
import { MongoDBRevisionMetadataPersistenceMapper } from '../Mapping/Persistence/MongoDB/MongoDBRevisionMetadataPersistenceMapper'
import { MongoDBRevisionPersistenceMapper } from '../Mapping/Persistence/MongoDB/MongoDBRevisionPersistenceMapper'
import { RevisionHttpMapper } from '../Mapping/Http/RevisionHttpMapper'
import { RevisionRepositoryResolverInterface } from '../Domain/Revision/RevisionRepositoryResolverInterface'
import { TypeORMRevisionRepositoryResolver } from '../Infra/TypeORM/TypeORMRevisionRepositoryResolver'
import { RevisionMetadataHttpRepresentation } from '../Mapping/Http/RevisionMetadataHttpRepresentation'
import { RevisionHttpRepresentation } from '../Mapping/Http/RevisionHttpRepresentation'
import { TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser } from '../Domain/UseCase/Transition/TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser/TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser'
import { DomainEventFactoryInterface } from '../Domain/Event/DomainEventFactoryInterface'
import { DomainEventFactory } from '../Domain/Event/DomainEventFactory'
import { SQLRevision } from '../Infra/TypeORM/SQL/SQLRevision'
import { SQLRevisionRepository } from '../Infra/TypeORM/SQL/SQLRevisionRepository'
import { SQLRevisionMetadataPersistenceMapper } from '../Mapping/Persistence/SQL/SQLRevisionMetadataPersistenceMapper'
import { SQLRevisionPersistenceMapper } from '../Mapping/Persistence/SQL/SQLRevisionPersistenceMapper'
import { RemoveRevisionsFromSharedVault } from '../Domain/UseCase/RemoveRevisionsFromSharedVault/RemoveRevisionsFromSharedVault'
import { ItemRemovedFromSharedVaultEventHandler } from '../Domain/Handler/ItemRemovedFromSharedVaultEventHandler'
import { TransitionRequestedEventHandler } from '../Domain/Handler/TransitionRequestedEventHandler'
import { SharedVaultRemovedEventHandler } from '../Domain/Handler/SharedVaultRemovedEventHandler'
import { TransitionRepositoryInterface } from '../Domain/Transition/TransitionRepositoryInterface'
import { RedisTransitionRepository } from '../Infra/Redis/RedisTransitionRepository'
import { CreateRevisionFromDump } from '../Domain/UseCase/CreateRevisionFromDump/CreateRevisionFromDump'
export class ContainerConfigLoader {
@ -95,8 +80,6 @@ export class ContainerConfigLoader {
const isConfiguredForHomeServer = env.get('MODE', true) === 'home-server'
const isConfiguredForSelfHosting = env.get('MODE', true) === 'self-hosted'
const isConfiguredForHomeServerOrSelfHosting = isConfiguredForHomeServer || isConfiguredForSelfHosting
const isSecondaryDatabaseEnabled = env.get('SECONDARY_DB_ENABLED', true) === 'true'
const isConfiguredForInMemoryCache = env.get('CACHE_TYPE', true) === 'memory'
const container = new Container({
defaultScope: 'Singleton',
@ -106,22 +89,6 @@ export class ContainerConfigLoader {
.bind<boolean>(TYPES.Revisions_IS_CONFIGURED_FOR_HOME_SERVER_OR_SELF_HOSTING)
.toConstantValue(isConfiguredForHomeServerOrSelfHosting)
if (!isConfiguredForInMemoryCache) {
const redisUrl = env.get('REDIS_URL')
const isRedisInClusterMode = redisUrl.indexOf(',') > 0
let redis
if (isRedisInClusterMode) {
redis = new Redis.Cluster(redisUrl.split(','))
} else {
redis = new Redis(redisUrl)
}
container.bind(TYPES.Revisions_Redis).toConstantValue(redis)
container
.bind<TransitionRepositoryInterface>(TYPES.Revisions_TransitionStatusRepository)
.toConstantValue(new RedisTransitionRepository(container.get<Redis>(TYPES.Revisions_Redis)))
}
let logger: winston.Logger
if (configuration?.logger) {
logger = configuration.logger as winston.Logger
@ -150,41 +117,10 @@ export class ContainerConfigLoader {
if (!isConfiguredForHomeServer) {
// env vars
container.bind(TYPES.Revisions_SNS_TOPIC_ARN).toConstantValue(env.get('SNS_TOPIC_ARN'))
container.bind(TYPES.Revisions_SNS_AWS_REGION).toConstantValue(env.get('SNS_AWS_REGION', true))
container.bind(TYPES.Revisions_SQS_QUEUE_URL).toConstantValue(env.get('SQS_QUEUE_URL'))
container.bind(TYPES.Revisions_S3_AWS_REGION).toConstantValue(env.get('S3_AWS_REGION', true))
container.bind(TYPES.Revisions_S3_BACKUP_BUCKET_NAME).toConstantValue(env.get('S3_BACKUP_BUCKET_NAME', true))
container.bind<SNSClient>(TYPES.Revisions_SNS).toDynamicValue((context: interfaces.Context) => {
const env: Env = context.container.get(TYPES.Revisions_Env)
const snsConfig: SNSClientConfig = {
apiVersion: 'latest',
region: env.get('SNS_AWS_REGION', true),
}
if (env.get('SNS_ENDPOINT', true)) {
snsConfig.endpoint = env.get('SNS_ENDPOINT', true)
}
if (env.get('SNS_ACCESS_KEY_ID', true) && env.get('SNS_SECRET_ACCESS_KEY', true)) {
snsConfig.credentials = {
accessKeyId: env.get('SNS_ACCESS_KEY_ID', true),
secretAccessKey: env.get('SNS_SECRET_ACCESS_KEY', true),
}
}
return new SNSClient(snsConfig)
})
container
.bind<DomainEventPublisherInterface>(TYPES.Revisions_DomainEventPublisher)
.toDynamicValue((context: interfaces.Context) => {
return new SNSOpenTelemetryDomainEventPublisher(
context.container.get(TYPES.Revisions_SNS),
context.container.get(TYPES.Revisions_SNS_TOPIC_ARN),
)
})
container.bind<SQSClient>(TYPES.Revisions_SQS).toDynamicValue((context: interfaces.Context) => {
const env: Env = context.container.get(TYPES.Revisions_Env)
@ -223,10 +159,6 @@ export class ContainerConfigLoader {
.toConstantValue(directCallDomainEventPublisher)
}
container
.bind<DomainEventFactoryInterface>(TYPES.Revisions_DomainEventFactory)
.toConstantValue(new DomainEventFactory(container.get(TYPES.Revisions_Timer)))
// Map
container
.bind<MapperInterface<RevisionMetadata, SQLLegacyRevision>>(
@ -242,14 +174,6 @@ export class ContainerConfigLoader {
container
.bind<MapperInterface<Revision, SQLRevision>>(TYPES.Revisions_SQLRevisionPersistenceMapper)
.toConstantValue(new SQLRevisionPersistenceMapper(container.get<TimerInterface>(TYPES.Revisions_Timer)))
container
.bind<MapperInterface<RevisionMetadata, MongoDBRevision>>(
TYPES.Revisions_MongoDBRevisionMetadataPersistenceMapper,
)
.toConstantValue(new MongoDBRevisionMetadataPersistenceMapper())
container
.bind<MapperInterface<Revision, MongoDBRevision>>(TYPES.Revisions_MongoDBRevisionPersistenceMapper)
.toConstantValue(new MongoDBRevisionPersistenceMapper(container.get<TimerInterface>(TYPES.Revisions_Timer)))
// ORM
container
@ -284,36 +208,6 @@ export class ContainerConfigLoader {
),
)
if (isSecondaryDatabaseEnabled) {
container
.bind<MongoRepository<MongoDBRevision>>(TYPES.Revisions_ORMMongoRevisionRepository)
.toConstantValue(appDataSource.getMongoRepository(MongoDBRevision))
container
.bind<RevisionRepositoryInterface>(TYPES.Revisions_MongoDBRevisionRepository)
.toConstantValue(
new MongoDBRevisionRepository(
container.get<MongoRepository<MongoDBRevision>>(TYPES.Revisions_ORMMongoRevisionRepository),
container.get<MapperInterface<RevisionMetadata, MongoDBRevision>>(
TYPES.Revisions_MongoDBRevisionMetadataPersistenceMapper,
),
container.get<MapperInterface<Revision, MongoDBRevision>>(TYPES.Revisions_MongoDBRevisionPersistenceMapper),
container.get<winston.Logger>(TYPES.Revisions_Logger),
),
)
}
container
.bind<RevisionRepositoryResolverInterface>(TYPES.Revisions_RevisionRepositoryResolver)
.toConstantValue(
new TypeORMRevisionRepositoryResolver(
container.get<RevisionRepositoryInterface>(TYPES.Revisions_SQLRevisionRepository),
isSecondaryDatabaseEnabled
? container.get<RevisionRepositoryInterface>(TYPES.Revisions_MongoDBRevisionRepository)
: null,
),
)
container
.bind<GetRequiredRoleToViewRevision>(TYPES.Revisions_GetRequiredRoleToViewRevision)
.toDynamicValue((context: interfaces.Context) => {
@ -351,56 +245,28 @@ export class ContainerConfigLoader {
container
.bind<GetRevisionsMetada>(TYPES.Revisions_GetRevisionsMetada)
.toConstantValue(
new GetRevisionsMetada(
container.get<RevisionRepositoryResolverInterface>(TYPES.Revisions_RevisionRepositoryResolver),
),
new GetRevisionsMetada(container.get<RevisionRepositoryInterface>(TYPES.Revisions_SQLRevisionRepository)),
)
container
.bind<GetRevision>(TYPES.Revisions_GetRevision)
.toConstantValue(
new GetRevision(container.get<RevisionRepositoryResolverInterface>(TYPES.Revisions_RevisionRepositoryResolver)),
new GetRevision(container.get<RevisionRepositoryInterface>(TYPES.Revisions_SQLRevisionRepository)),
)
container
.bind<DeleteRevision>(TYPES.Revisions_DeleteRevision)
.toConstantValue(
new DeleteRevision(
container.get<RevisionRepositoryResolverInterface>(TYPES.Revisions_RevisionRepositoryResolver),
),
new DeleteRevision(container.get<RevisionRepositoryInterface>(TYPES.Revisions_SQLRevisionRepository)),
)
container
.bind<CopyRevisions>(TYPES.Revisions_CopyRevisions)
.toConstantValue(
new CopyRevisions(
container.get<RevisionRepositoryResolverInterface>(TYPES.Revisions_RevisionRepositoryResolver),
),
)
container
.bind<TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser>(
TYPES.Revisions_TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser,
)
.toConstantValue(
new TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser(
container.get<RevisionRepositoryInterface>(TYPES.Revisions_SQLRevisionRepository),
isSecondaryDatabaseEnabled
? container.get<RevisionRepositoryInterface>(TYPES.Revisions_MongoDBRevisionRepository)
: null,
isConfiguredForInMemoryCache
? null
: container.get<TransitionRepositoryInterface>(TYPES.Revisions_TransitionStatusRepository),
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.get<DomainEventPublisherInterface>(TYPES.Revisions_DomainEventPublisher),
container.get<DomainEventFactoryInterface>(TYPES.Revisions_DomainEventFactory),
),
new CopyRevisions(container.get<RevisionRepositoryInterface>(TYPES.Revisions_SQLRevisionRepository)),
)
container
.bind<RemoveRevisionsFromSharedVault>(TYPES.Revisions_RemoveRevisionsFromSharedVault)
.toConstantValue(
new RemoveRevisionsFromSharedVault(
isSecondaryDatabaseEnabled
? container.get<RevisionRepositoryInterface>(TYPES.Revisions_MongoDBRevisionRepository)
: container.get<RevisionRepositoryInterface>(TYPES.Revisions_SQLRevisionRepository),
container.get<RevisionRepositoryInterface>(TYPES.Revisions_SQLRevisionRepository),
),
)
container
@ -408,7 +274,7 @@ export class ContainerConfigLoader {
.toConstantValue(
new CreateRevisionFromDump(
container.get<DumpRepositoryInterface>(TYPES.Revisions_DumpRepository),
container.get<RevisionRepositoryResolverInterface>(TYPES.Revisions_RevisionRepositoryResolver),
container.get<RevisionRepositoryInterface>(TYPES.Revisions_SQLRevisionRepository),
),
)
@ -448,7 +314,7 @@ export class ContainerConfigLoader {
.bind<AccountDeletionRequestedEventHandler>(TYPES.Revisions_AccountDeletionRequestedEventHandler)
.toConstantValue(
new AccountDeletionRequestedEventHandler(
container.get<RevisionRepositoryResolverInterface>(TYPES.Revisions_RevisionRepositoryResolver),
container.get<RevisionRepositoryInterface>(TYPES.Revisions_SQLRevisionRepository),
container.get<winston.Logger>(TYPES.Revisions_Logger),
),
)
@ -468,17 +334,6 @@ export class ContainerConfigLoader {
container.get<winston.Logger>(TYPES.Revisions_Logger),
),
)
container
.bind<TransitionRequestedEventHandler>(TYPES.Revisions_TransitionRequestedEventHandler)
.toConstantValue(
new TransitionRequestedEventHandler(
false,
container.get<TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser>(
TYPES.Revisions_TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser,
),
container.get<winston.Logger>(TYPES.Revisions_Logger),
),
)
container
.bind<SharedVaultRemovedEventHandler>(TYPES.Revisions_SharedVaultRemovedEventHandler)
.toConstantValue(
@ -493,7 +348,6 @@ export class ContainerConfigLoader {
['ACCOUNT_DELETION_REQUESTED', container.get(TYPES.Revisions_AccountDeletionRequestedEventHandler)],
['REVISIONS_COPY_REQUESTED', container.get(TYPES.Revisions_RevisionsCopyRequestedEventHandler)],
['ITEM_REMOVED_FROM_SHARED_VAULT', container.get(TYPES.Revisions_ItemRemovedFromSharedVaultEventHandler)],
['TRANSITION_REQUESTED', container.get(TYPES.Revisions_TransitionRequestedEventHandler)],
['SHARED_VAULT_REMOVED', container.get(TYPES.Revisions_SharedVaultRemovedEventHandler)],
])

View file

@ -1,16 +1,14 @@
import { DataSource, EntityTarget, LoggerOptions, MongoRepository, ObjectLiteral, Repository } from 'typeorm'
import { DataSource, EntityTarget, LoggerOptions, ObjectLiteral, Repository } from 'typeorm'
import { MysqlConnectionOptions } from 'typeorm/driver/mysql/MysqlConnectionOptions'
import { SQLLegacyRevision } from '../Infra/TypeORM/SQL/SQLLegacyRevision'
import { Env } from './Env'
import { SqliteConnectionOptions } from 'typeorm/driver/sqlite/SqliteConnectionOptions'
import { MongoDBRevision } from '../Infra/TypeORM/MongoDB/MongoDBRevision'
import { SQLRevision } from '../Infra/TypeORM/SQL/SQLRevision'
export class AppDataSource {
private _dataSource: DataSource | undefined
private _secondaryDataSource: DataSource | undefined
constructor(
private configuration: {
@ -27,43 +25,8 @@ export class AppDataSource {
return this._dataSource.getRepository(target)
}
getMongoRepository<Entity extends ObjectLiteral>(target: EntityTarget<Entity>): MongoRepository<Entity> {
if (!this._secondaryDataSource) {
throw new Error('Secondary DataSource not initialized')
}
return this._secondaryDataSource.getMongoRepository(target)
}
async initialize(): Promise<void> {
await this.dataSource.initialize()
const secondaryDataSource = this.secondaryDataSource
if (secondaryDataSource) {
await secondaryDataSource.initialize()
}
}
get secondaryDataSource(): DataSource | undefined {
this.configuration.env.load()
if (this.configuration.env.get('SECONDARY_DB_ENABLED', true) !== 'true') {
return undefined
}
this._secondaryDataSource = new DataSource({
type: 'mongodb',
host: this.configuration.env.get('MONGO_HOST'),
authSource: 'admin',
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,
})
return this._secondaryDataSource
}
get dataSource(): DataSource {

View file

@ -13,22 +13,15 @@ const TYPES = {
Revisions_SQLRevisionMetadataPersistenceMapper: Symbol.for('Revisions_SQLRevisionMetadataPersistenceMapper'),
Revisions_SQLLegacyRevisionPersistenceMapper: Symbol.for('Revisions_SQLLegacyRevisionPersistenceMapper'),
Revisions_SQLRevisionPersistenceMapper: Symbol.for('Revisions_SQLRevisionPersistenceMapper'),
Revisions_MongoDBRevisionMetadataPersistenceMapper: Symbol.for('Revisions_MongoDBRevisionMetadataPersistenceMapper'),
Revisions_MongoDBRevisionPersistenceMapper: Symbol.for('Revisions_MongoDBRevisionPersistenceMapper'),
Revisions_RevisionItemStringMapper: Symbol.for('Revisions_RevisionItemStringMapper'),
Revisions_RevisionHttpMapper: Symbol.for('Revisions_RevisionHttpMapper'),
Revisions_RevisionMetadataHttpMapper: Symbol.for('Revisions_RevisionMetadataHttpMapper'),
// ORM
Revisions_ORMRevisionRepository: Symbol.for('Revisions_ORMRevisionRepository'),
Revisions_ORMLegacyRevisionRepository: Symbol.for('Revisions_ORMLegacyRevisionRepository'),
// Mongo
Revisions_ORMMongoRevisionRepository: Symbol.for('Revisions_ORMMongoRevisionRepository'),
// Repositories
Revisions_SQLRevisionRepository: Symbol.for('Revisions_SQLRevisionRepository'),
Revisions_MongoDBRevisionRepository: Symbol.for('Revisions_MongoDBRevisionRepository'),
Revisions_DumpRepository: Symbol.for('Revisions_DumpRepository'),
Revisions_RevisionRepositoryResolver: Symbol.for('Revisions_RevisionRepositoryResolver'),
Revisions_TransitionStatusRepository: Symbol.for('Revisions_TransitionStatusRepository'),
// env vars
Revisions_AUTH_JWT_SECRET: Symbol.for('Revisions_AUTH_JWT_SECRET'),
Revisions_SQS_QUEUE_URL: Symbol.for('Revisions_SQS_QUEUE_URL'),
@ -47,9 +40,6 @@ const TYPES = {
Revisions_DeleteRevision: Symbol.for('Revisions_DeleteRevision'),
Revisions_CopyRevisions: Symbol.for('Revisions_CopyRevisions'),
Revisions_GetRequiredRoleToViewRevision: Symbol.for('Revisions_GetRequiredRoleToViewRevision'),
Revisions_TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser: Symbol.for(
'Revisions_TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser',
),
Revisions_RemoveRevisionsFromSharedVault: Symbol.for('Revisions_RemoveRevisionsFromSharedVault'),
Revisions_CreateRevisionFromDump: Symbol.for('Revisions_CreateRevisionFromDump'),
// Controller
@ -61,7 +51,6 @@ const TYPES = {
Revisions_AccountDeletionRequestedEventHandler: Symbol.for('Revisions_AccountDeletionRequestedEventHandler'),
Revisions_RevisionsCopyRequestedEventHandler: Symbol.for('Revisions_RevisionsCopyRequestedEventHandler'),
Revisions_ItemRemovedFromSharedVaultEventHandler: Symbol.for('Revisions_ItemRemovedFromSharedVaultEventHandler'),
Revisions_TransitionRequestedEventHandler: Symbol.for('Revisions_TransitionRequestedEventHandler'),
Revisions_SharedVaultRemovedEventHandler: Symbol.for('Revisions_SharedVaultRemovedEventHandler'),
// Services
Revisions_CrossServiceTokenDecoder: Symbol.for('Revisions_CrossServiceTokenDecoder'),

View file

@ -1,28 +0,0 @@
/* istanbul ignore file */
import { DomainEventService, TransitionStatusUpdatedEvent } from '@standardnotes/domain-events'
import { TimerInterface } from '@standardnotes/time'
import { DomainEventFactoryInterface } from './DomainEventFactoryInterface'
export class DomainEventFactory implements DomainEventFactoryInterface {
constructor(private timer: TimerInterface) {}
createTransitionStatusUpdatedEvent(dto: {
userUuid: string
transitionType: 'items' | 'revisions'
transitionTimestamp: number
status: string
}): TransitionStatusUpdatedEvent {
return {
type: 'TRANSITION_STATUS_UPDATED',
createdAt: this.timer.getUTCDate(),
meta: {
correlation: {
userIdentifier: dto.userUuid,
userIdentifierType: 'uuid',
},
origin: DomainEventService.Revisions,
},
payload: dto,
}
}
}

View file

@ -1,10 +0,0 @@
import { TransitionStatusUpdatedEvent } from '@standardnotes/domain-events'
export interface DomainEventFactoryInterface {
createTransitionStatusUpdatedEvent(dto: {
userUuid: string
transitionType: 'items' | 'revisions'
transitionTimestamp: number
status: string
}): TransitionStatusUpdatedEvent
}

View file

@ -4,23 +4,18 @@ import { AccountDeletionRequestedEvent } from '@standardnotes/domain-events'
import { Logger } from 'winston'
import { AccountDeletionRequestedEventHandler } from './AccountDeletionRequestedEventHandler'
import { RevisionRepositoryInterface } from '../Revision/RevisionRepositoryInterface'
import { RevisionRepositoryResolverInterface } from '../Revision/RevisionRepositoryResolverInterface'
describe('AccountDeletionRequestedEventHandler', () => {
let revisionRepository: RevisionRepositoryInterface
let revisionRepositoryResolver: RevisionRepositoryResolverInterface
let logger: Logger
let event: AccountDeletionRequestedEvent
const createHandler = () => new AccountDeletionRequestedEventHandler(revisionRepositoryResolver, logger)
const createHandler = () => new AccountDeletionRequestedEventHandler(revisionRepository, logger)
beforeEach(() => {
revisionRepository = {} as jest.Mocked<RevisionRepositoryInterface>
revisionRepository.removeByUserUuid = jest.fn()
revisionRepositoryResolver = {} as jest.Mocked<RevisionRepositoryResolverInterface>
revisionRepositoryResolver.resolve = jest.fn().mockReturnValue(revisionRepository)
logger = {} as jest.Mocked<Logger>
logger.info = jest.fn()
logger.warn = jest.fn()
@ -32,7 +27,6 @@ describe('AccountDeletionRequestedEventHandler', () => {
userUuid: '2-3-4',
userCreatedAtTimestamp: 1,
regularSubscriptionUuid: '1-2-3',
roleNames: ['CORE_USER'],
}
})
@ -49,13 +43,4 @@ describe('AccountDeletionRequestedEventHandler', () => {
expect(revisionRepository.removeByUserUuid).not.toHaveBeenCalled()
})
it('should do nothing if role names are not valid', async () => {
event.payload.userUuid = '84c0f8e8-544a-4c7e-9adf-26209303bc1d'
event.payload.roleNames = ['INVALID_ROLE_NAME']
await createHandler().handle(event)
expect(revisionRepository.removeByUserUuid).not.toHaveBeenCalled()
})
})

View file

@ -1,12 +1,11 @@
import { RoleNameCollection, Uuid } from '@standardnotes/domain-core'
import { Uuid } from '@standardnotes/domain-core'
import { AccountDeletionRequestedEvent, DomainEventHandlerInterface } from '@standardnotes/domain-events'
import { Logger } from 'winston'
import { RevisionRepositoryResolverInterface } from '../Revision/RevisionRepositoryResolverInterface'
import { RevisionRepositoryInterface } from '../Revision/RevisionRepositoryInterface'
export class AccountDeletionRequestedEventHandler implements DomainEventHandlerInterface {
constructor(
private revisionRepositoryResolver: RevisionRepositoryResolverInterface,
private revisionRepository: RevisionRepositoryInterface,
private logger: Logger,
) {}
@ -19,17 +18,7 @@ export class AccountDeletionRequestedEventHandler implements DomainEventHandlerI
}
const userUuid = userUuidOrError.getValue()
const roleNamesOrError = RoleNameCollection.create(event.payload.roleNames)
if (roleNamesOrError.isFailed()) {
this.logger.error(`Failed account cleanup: ${roleNamesOrError.getError()}`)
return
}
const roleNames = roleNamesOrError.getValue()
const revisionRepository = this.revisionRepositoryResolver.resolve(roleNames)
await revisionRepository.removeByUserUuid(userUuid)
await this.revisionRepository.removeByUserUuid(userUuid)
this.logger.info(`Finished account cleanup for user: ${event.payload.userUuid}`)
}

View file

@ -12,7 +12,6 @@ export class ItemDumpedEventHandler implements DomainEventHandlerInterface {
async handle(event: ItemDumpedEvent): Promise<void> {
const result = await this.createRevisionFromDump.execute({
filePath: event.payload.fileDumpPath,
roleNames: event.payload.roleNames,
})
if (result.isFailed()) {

View file

@ -23,7 +23,6 @@ describe('RevisionsCopyRequestedEventHandler', () => {
event.payload = {
newItemUuid: '1-2-3',
originalItemUuid: '2-3-4',
roleNames: ['CORE_USER'],
}
})

View file

@ -12,7 +12,6 @@ export class RevisionsCopyRequestedEventHandler implements DomainEventHandlerInt
const result = await this.copyRevisions.execute({
newItemUuid: event.payload.newItemUuid,
originalItemUuid: event.payload.originalItemUuid,
roleNames: event.payload.roleNames,
})
if (result.isFailed()) {

View file

@ -1,30 +0,0 @@
import { DomainEventHandlerInterface, TransitionRequestedEvent } from '@standardnotes/domain-events'
import { Logger } from 'winston'
import { TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser } from '../UseCase/Transition/TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser/TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser'
export class TransitionRequestedEventHandler implements DomainEventHandlerInterface {
constructor(
private disabled: boolean,
private transitionRevisionsFromPrimaryToSecondaryDatabaseForUser: TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser,
private logger: Logger,
) {}
async handle(event: TransitionRequestedEvent): Promise<void> {
if (this.disabled) {
return
}
if (event.payload.type !== 'revisions') {
return
}
const result = await this.transitionRevisionsFromPrimaryToSecondaryDatabaseForUser.execute({
userUuid: event.payload.userUuid,
timestamp: event.payload.timestamp,
})
if (result.isFailed()) {
this.logger.error(`[${event.payload.userUuid}] Failed to transition: ${result.getError()}`)
}
}
}

View file

@ -1,7 +0,0 @@
import { RoleNameCollection } from '@standardnotes/domain-core'
import { RevisionRepositoryInterface } from './RevisionRepositoryInterface'
export interface RevisionRepositoryResolverInterface {
resolve(roleNames: RoleNameCollection): RevisionRepositoryInterface
}

View file

@ -1,6 +0,0 @@
export interface TransitionRepositoryInterface {
getPagingProgress(userUuid: string): Promise<number>
setPagingProgress(userUuid: string, progress: number): Promise<void>
getIntegrityProgress(userUuid: string): Promise<number>
setIntegrityProgress(userUuid: string, progress: number): Promise<void>
}

View file

@ -2,21 +2,16 @@ import { Result } from '@standardnotes/domain-core'
import { Revision } from '../../Revision/Revision'
import { RevisionRepositoryInterface } from '../../Revision/RevisionRepositoryInterface'
import { CopyRevisions } from './CopyRevisions'
import { RevisionRepositoryResolverInterface } from '../../Revision/RevisionRepositoryResolverInterface'
describe('CopyRevisions', () => {
let revisionRepository: RevisionRepositoryInterface
let revisionRepositoryResolver: RevisionRepositoryResolverInterface
const createUseCase = () => new CopyRevisions(revisionRepositoryResolver)
const createUseCase = () => new CopyRevisions(revisionRepository)
beforeEach(() => {
revisionRepository = {} as jest.Mocked<RevisionRepositoryInterface>
revisionRepository.findByItemUuid = jest.fn().mockReturnValue([{} as jest.Mocked<Revision>])
revisionRepository.insert = jest.fn()
revisionRepositoryResolver = {} as jest.Mocked<RevisionRepositoryResolverInterface>
revisionRepositoryResolver.resolve = jest.fn().mockReturnValue(revisionRepository)
})
it('should not copy revisions to new item if revision creation fails', async () => {
@ -26,7 +21,6 @@ describe('CopyRevisions', () => {
const result = await createUseCase().execute({
originalItemUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
newItemUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
roleNames: ['CORE_USER'],
})
expect(result.isFailed()).toBeTruthy()
@ -34,21 +28,10 @@ describe('CopyRevisions', () => {
revisionMock.mockRestore()
})
it('should do nothing if the role names are not valid', async () => {
const result = await createUseCase().execute({
originalItemUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
newItemUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
roleNames: ['INVALID_ROLE_NAME'],
})
expect(result.isFailed()).toBeTruthy()
})
it('should copy revisions to new item', async () => {
const result = await createUseCase().execute({
originalItemUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
newItemUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
roleNames: ['CORE_USER'],
})
expect(result.isFailed()).toBeFalsy()
@ -60,7 +43,6 @@ describe('CopyRevisions', () => {
const result = await createUseCase().execute({
originalItemUuid: '1-2-3',
newItemUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
roleNames: ['CORE_USER'],
})
expect(result.isFailed()).toBeTruthy()
@ -70,7 +52,6 @@ describe('CopyRevisions', () => {
const result = await createUseCase().execute({
newItemUuid: '1-2-3',
originalItemUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
roleNames: ['CORE_USER'],
})
expect(result.isFailed()).toBeTruthy()

View file

@ -1,12 +1,12 @@
import { Result, RoleNameCollection, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import { Revision } from '../../Revision/Revision'
import { CopyRevisionsDTO } from './CopyRevisionsDTO'
import { RevisionRepositoryResolverInterface } from '../../Revision/RevisionRepositoryResolverInterface'
import { RevisionRepositoryInterface } from '../../Revision/RevisionRepositoryInterface'
export class CopyRevisions implements UseCaseInterface<string> {
constructor(private revisionRepositoryResolver: RevisionRepositoryResolverInterface) {}
constructor(private revisionRepository: RevisionRepositoryInterface) {}
async execute(dto: CopyRevisionsDTO): Promise<Result<string>> {
const orignalItemUuidOrError = Uuid.create(dto.originalItemUuid)
@ -21,15 +21,7 @@ export class CopyRevisions implements UseCaseInterface<string> {
}
const newItemUuid = newItemUuidOrError.getValue()
const roleNamesOrError = RoleNameCollection.create(dto.roleNames)
if (roleNamesOrError.isFailed()) {
return Result.fail(roleNamesOrError.getError())
}
const roleNames = roleNamesOrError.getValue()
const revisionRepository = this.revisionRepositoryResolver.resolve(roleNames)
const revisions = await revisionRepository.findByItemUuid(originalItemUuid)
const revisions = await this.revisionRepository.findByItemUuid(originalItemUuid)
for (const existingRevision of revisions) {
const revisionCopyOrError = Revision.create({
@ -43,7 +35,7 @@ export class CopyRevisions implements UseCaseInterface<string> {
const revisionCopy = revisionCopyOrError.getValue()
await revisionRepository.insert(revisionCopy)
await this.revisionRepository.insert(revisionCopy)
}
return Result.ok<string>('Revisions copied')

View file

@ -1,5 +1,4 @@
export interface CopyRevisionsDTO {
originalItemUuid: string
newItemUuid: string
roleNames: string[]
}

View file

@ -3,16 +3,14 @@ import { Uuid, ContentType, Dates, Result } from '@standardnotes/domain-core'
import { DumpRepositoryInterface } from '../../Dump/DumpRepositoryInterface'
import { Revision } from '../../Revision/Revision'
import { RevisionRepositoryInterface } from '../../Revision/RevisionRepositoryInterface'
import { RevisionRepositoryResolverInterface } from '../../Revision/RevisionRepositoryResolverInterface'
import { CreateRevisionFromDump } from './CreateRevisionFromDump'
describe('CreateRevisionFromDump', () => {
let revisionRepository: RevisionRepositoryInterface
let revision: Revision
let dumpRepository: DumpRepositoryInterface
let revisionRepositoryResolver: RevisionRepositoryResolverInterface
const createUseCase = () => new CreateRevisionFromDump(dumpRepository, revisionRepositoryResolver)
const createUseCase = () => new CreateRevisionFromDump(dumpRepository, revisionRepository)
beforeEach(() => {
revision = Revision.create({
@ -33,15 +31,11 @@ describe('CreateRevisionFromDump', () => {
revisionRepository = {} as jest.Mocked<RevisionRepositoryInterface>
revisionRepository.insert = jest.fn().mockReturnValue(true)
revisionRepositoryResolver = {} as jest.Mocked<RevisionRepositoryResolverInterface>
revisionRepositoryResolver.resolve = jest.fn().mockReturnValue(revisionRepository)
})
it('should create a revision from file dump', async () => {
const result = await createUseCase().execute({
filePath: 'foobar',
roleNames: ['CORE_USER'],
})
expect(result.isFailed()).toBeFalsy()
@ -52,7 +46,6 @@ describe('CreateRevisionFromDump', () => {
it('should fail if file path is empty', async () => {
const result = await createUseCase().execute({
filePath: '',
roleNames: ['CORE_USER'],
})
expect(result.isFailed()).toBeTruthy()
@ -60,23 +53,11 @@ describe('CreateRevisionFromDump', () => {
expect(dumpRepository.removeDump).not.toHaveBeenCalled()
})
it('should fail if role name is invalid', async () => {
const result = await createUseCase().execute({
filePath: 'foobar',
roleNames: ['INVALID_ROLE_NAME'],
})
expect(result.isFailed()).toBeTruthy()
expect(revisionRepository.insert).not.toHaveBeenCalled()
expect(dumpRepository.removeDump).toHaveBeenCalled()
})
it('should fail if revision cannot be found', async () => {
dumpRepository.getRevisionFromDumpPath = jest.fn().mockReturnValue(Result.fail('Oops'))
const result = await createUseCase().execute({
filePath: 'foobar',
roleNames: ['CORE_USER'],
})
expect(result.isFailed()).toBeTruthy()
@ -89,7 +70,6 @@ describe('CreateRevisionFromDump', () => {
const result = await createUseCase().execute({
filePath: 'foobar',
roleNames: ['CORE_USER'],
})
expect(result.isFailed()).toBeTruthy()

View file

@ -1,12 +1,12 @@
import { Result, RoleNameCollection, UseCaseInterface, Validator } from '@standardnotes/domain-core'
import { Result, UseCaseInterface, Validator } from '@standardnotes/domain-core'
import { DumpRepositoryInterface } from '../../Dump/DumpRepositoryInterface'
import { RevisionRepositoryResolverInterface } from '../../Revision/RevisionRepositoryResolverInterface'
import { CreateRevisionFromDumpDTO } from './CreateRevisionFromDumpDTO'
import { RevisionRepositoryInterface } from '../../Revision/RevisionRepositoryInterface'
export class CreateRevisionFromDump implements UseCaseInterface<void> {
constructor(
private dumpRepository: DumpRepositoryInterface,
private revisionRepositoryResolver: RevisionRepositoryResolverInterface,
private revisionRepository: RevisionRepositoryInterface,
) {}
async execute(dto: CreateRevisionFromDumpDTO): Promise<Result<void>> {
@ -23,17 +23,7 @@ export class CreateRevisionFromDump implements UseCaseInterface<void> {
}
const revision = revisionOrError.getValue()
const roleNamesOrError = RoleNameCollection.create(dto.roleNames)
if (roleNamesOrError.isFailed()) {
await this.dumpRepository.removeDump(dto.filePath)
return Result.fail(`Could not create revision from dump: ${roleNamesOrError.getError()}`)
}
const roleNames = roleNamesOrError.getValue()
const revisionRepository = this.revisionRepositoryResolver.resolve(roleNames)
const successfullyInserted = await revisionRepository.insert(revision)
const successfullyInserted = await this.revisionRepository.insert(revision)
if (!successfullyInserted) {
await this.dumpRepository.removeDump(dto.filePath)

View file

@ -1,4 +1,3 @@
export interface CreateRevisionFromDumpDTO {
filePath: string
roleNames: string[]
}

View file

@ -1,47 +1,30 @@
import { RevisionRepositoryInterface } from '../../Revision/RevisionRepositoryInterface'
import { RevisionRepositoryResolverInterface } from '../../Revision/RevisionRepositoryResolverInterface'
import { DeleteRevision } from './DeleteRevision'
describe('DeleteRevision', () => {
let revisionRepository: RevisionRepositoryInterface
let revisionRepositoryResolver: RevisionRepositoryResolverInterface
const createUseCase = () => new DeleteRevision(revisionRepositoryResolver)
const createUseCase = () => new DeleteRevision(revisionRepository)
beforeEach(() => {
revisionRepository = {} as jest.Mocked<RevisionRepositoryInterface>
revisionRepository.removeOneByUuid = jest.fn()
revisionRepositoryResolver = {} as jest.Mocked<RevisionRepositoryResolverInterface>
revisionRepositoryResolver.resolve = jest.fn().mockReturnValue(revisionRepository)
})
it('should delete revision', async () => {
const result = await createUseCase().execute({
revisionUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
userUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
roleNames: ['CORE_USER'],
})
expect(result.isFailed()).toBeFalsy()
expect(result.getValue()).toEqual('Revision removed')
})
it('should do nothing if role names are not valid', async () => {
const result = await createUseCase().execute({
revisionUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
userUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
roleNames: ['INVALID_ROLE_NAME'],
})
expect(result.isFailed()).toBeTruthy()
})
it('should not delete revision for an invalid item uuid', async () => {
const result = await createUseCase().execute({
revisionUuid: '1-2-3',
userUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
roleNames: ['CORE_USER'],
})
expect(result.isFailed()).toBeTruthy()
@ -51,7 +34,6 @@ describe('DeleteRevision', () => {
const result = await createUseCase().execute({
userUuid: '1-2-3',
revisionUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
roleNames: ['CORE_USER'],
})
expect(result.isFailed()).toBeTruthy()

View file

@ -1,10 +1,10 @@
import { Result, RoleNameCollection, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import { DeleteRevisionDTO } from './DeleteRevisionDTO'
import { RevisionRepositoryResolverInterface } from '../../Revision/RevisionRepositoryResolverInterface'
import { RevisionRepositoryInterface } from '../../Revision/RevisionRepositoryInterface'
export class DeleteRevision implements UseCaseInterface<string> {
constructor(private revisionRepositoryResolver: RevisionRepositoryResolverInterface) {}
constructor(private revisionRepository: RevisionRepositoryInterface) {}
async execute(dto: DeleteRevisionDTO): Promise<Result<string>> {
const revisionUuidOrError = Uuid.create(dto.revisionUuid)
@ -19,15 +19,7 @@ export class DeleteRevision implements UseCaseInterface<string> {
}
const userUuid = userUuidOrError.getValue()
const roleNamesOrError = RoleNameCollection.create(dto.roleNames)
if (roleNamesOrError.isFailed()) {
return Result.fail(roleNamesOrError.getError())
}
const roleNames = roleNamesOrError.getValue()
const revisionRepository = this.revisionRepositoryResolver.resolve(roleNames)
await revisionRepository.removeOneByUuid(revisionUuid, userUuid)
await this.revisionRepository.removeOneByUuid(revisionUuid, userUuid)
return Result.ok<string>('Revision removed')
}

View file

@ -1,5 +1,4 @@
export interface DeleteRevisionDTO {
userUuid: string
revisionUuid: string
roleNames: string[]
}

View file

@ -1,27 +1,21 @@
import { Revision } from '../../Revision/Revision'
import { RevisionRepositoryInterface } from '../../Revision/RevisionRepositoryInterface'
import { RevisionRepositoryResolverInterface } from '../../Revision/RevisionRepositoryResolverInterface'
import { GetRevision } from './GetRevision'
describe('GetRevision', () => {
let revisionRepository: RevisionRepositoryInterface
let revisionRepositoryResolver: RevisionRepositoryResolverInterface
const createUseCase = () => new GetRevision(revisionRepositoryResolver)
const createUseCase = () => new GetRevision(revisionRepository)
beforeEach(() => {
revisionRepository = {} as jest.Mocked<RevisionRepositoryInterface>
revisionRepository.findOneByUuid = jest.fn().mockReturnValue({} as jest.Mocked<Revision>)
revisionRepositoryResolver = {} as jest.Mocked<RevisionRepositoryResolverInterface>
revisionRepositoryResolver.resolve = jest.fn().mockReturnValue(revisionRepository)
})
it('should return revision for a given item', async () => {
const result = await createUseCase().execute({
revisionUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
userUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
roleNames: ['CORE_USER'],
sharedVaultUuids: ['84c0f8e8-544a-4c7e-9adf-26209303bc1d'],
})
@ -33,31 +27,18 @@ describe('GetRevision', () => {
const result = await createUseCase().execute({
revisionUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
userUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
roleNames: ['CORE_USER'],
sharedVaultUuids: ['INVALID_SHARED_VAULT_UUID'],
})
expect(result.isFailed()).toBeTruthy()
})
it('should do nothing if role names are not valid', async () => {
const result = await createUseCase().execute({
revisionUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
userUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
roleNames: ['INVALID_ROLE_NAME'],
sharedVaultUuids: ['84c0f8e8-544a-4c7e-9adf-26209303bc1d'],
})
expect(result.isFailed()).toBeTruthy()
})
it('should not return revision for a given item if not found', async () => {
revisionRepository.findOneByUuid = jest.fn().mockReturnValue(null)
const result = await createUseCase().execute({
revisionUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
userUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
roleNames: ['CORE_USER'],
sharedVaultUuids: ['84c0f8e8-544a-4c7e-9adf-26209303bc1d'],
})
@ -68,7 +49,6 @@ describe('GetRevision', () => {
const result = await createUseCase().execute({
revisionUuid: '1-2-3',
userUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
roleNames: ['CORE_USER'],
sharedVaultUuids: ['84c0f8e8-544a-4c7e-9adf-26209303bc1d'],
})
@ -79,7 +59,6 @@ describe('GetRevision', () => {
const result = await createUseCase().execute({
userUuid: '1-2-3',
revisionUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
roleNames: ['CORE_USER'],
sharedVaultUuids: ['84c0f8e8-544a-4c7e-9adf-26209303bc1d'],
})

View file

@ -1,11 +1,11 @@
import { Result, RoleNameCollection, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import { Revision } from '../../Revision/Revision'
import { GetRevisionDTO } from './GetRevisionDTO'
import { RevisionRepositoryResolverInterface } from '../../Revision/RevisionRepositoryResolverInterface'
import { RevisionRepositoryInterface } from '../../Revision/RevisionRepositoryInterface'
export class GetRevision implements UseCaseInterface<Revision> {
constructor(private revisionRepositoryResolver: RevisionRepositoryResolverInterface) {}
constructor(private revisionRepository: RevisionRepositoryInterface) {}
async execute(dto: GetRevisionDTO): Promise<Result<Revision>> {
const revisionUuidOrError = Uuid.create(dto.revisionUuid)
@ -20,12 +20,6 @@ export class GetRevision implements UseCaseInterface<Revision> {
}
const userUuid = userUuidOrError.getValue()
const roleNamesOrError = RoleNameCollection.create(dto.roleNames)
if (roleNamesOrError.isFailed()) {
return Result.fail(roleNamesOrError.getError())
}
const roleNames = roleNamesOrError.getValue()
const sharedVaultUuids = []
for (const sharedVaultUuid of dto.sharedVaultUuids) {
const sharedVaultUuidOrError = Uuid.create(sharedVaultUuid)
@ -35,9 +29,7 @@ export class GetRevision implements UseCaseInterface<Revision> {
sharedVaultUuids.push(sharedVaultUuidOrError.getValue())
}
const revisionRepository = this.revisionRepositoryResolver.resolve(roleNames)
const revision = await revisionRepository.findOneByUuid(revisionUuid, userUuid, sharedVaultUuids)
const revision = await this.revisionRepository.findOneByUuid(revisionUuid, userUuid, sharedVaultUuids)
if (revision === null) {
return Result.fail<Revision>(`Could not find revision with uuid: ${revisionUuid.value}`)

View file

@ -1,6 +1,5 @@
export interface GetRevisionDTO {
userUuid: string
revisionUuid: string
roleNames: string[]
sharedVaultUuids: string[]
}

View file

@ -1,27 +1,21 @@
import { RevisionMetadata } from '../../Revision/RevisionMetadata'
import { RevisionRepositoryInterface } from '../../Revision/RevisionRepositoryInterface'
import { RevisionRepositoryResolverInterface } from '../../Revision/RevisionRepositoryResolverInterface'
import { GetRevisionsMetada } from './GetRevisionsMetada'
describe('GetRevisionsMetada', () => {
let revisionRepository: RevisionRepositoryInterface
let revisionRepositoryResolver: RevisionRepositoryResolverInterface
const createUseCase = () => new GetRevisionsMetada(revisionRepositoryResolver)
const createUseCase = () => new GetRevisionsMetada(revisionRepository)
beforeEach(() => {
revisionRepository = {} as jest.Mocked<RevisionRepositoryInterface>
revisionRepository.findMetadataByItemId = jest.fn().mockReturnValue([{} as jest.Mocked<RevisionMetadata>])
revisionRepositoryResolver = {} as jest.Mocked<RevisionRepositoryResolverInterface>
revisionRepositoryResolver.resolve = jest.fn().mockReturnValue(revisionRepository)
})
it('should return revisions metadata for a given item', async () => {
const result = await createUseCase().execute({
itemUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
userUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
roleNames: ['CORE_USER'],
sharedVaultUuids: ['84c0f8e8-544a-4c7e-9adf-26209303bc1d'],
})
@ -33,7 +27,6 @@ describe('GetRevisionsMetada', () => {
const result = await createUseCase().execute({
itemUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
userUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
roleNames: ['CORE_USER'],
sharedVaultUuids: ['1-2-3'],
})
@ -44,7 +37,6 @@ describe('GetRevisionsMetada', () => {
const result = await createUseCase().execute({
itemUuid: '1-2-3',
userUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
roleNames: ['CORE_USER'],
sharedVaultUuids: [],
})
@ -55,18 +47,6 @@ describe('GetRevisionsMetada', () => {
const result = await createUseCase().execute({
userUuid: '1-2-3',
itemUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
roleNames: ['CORE_USER'],
sharedVaultUuids: [],
})
expect(result.isFailed()).toBeTruthy()
})
it('should do nothing if role names are not valid', async () => {
const result = await createUseCase().execute({
itemUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
userUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
roleNames: ['INVALID_ROLE_NAME'],
sharedVaultUuids: [],
})

View file

@ -1,12 +1,12 @@
import { Result, RoleNameCollection, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import { RevisionMetadata } from '../../Revision/RevisionMetadata'
import { GetRevisionsMetadaDTO } from './GetRevisionsMetadaDTO'
import { RevisionRepositoryResolverInterface } from '../../Revision/RevisionRepositoryResolverInterface'
import { RevisionRepositoryInterface } from '../../Revision/RevisionRepositoryInterface'
export class GetRevisionsMetada implements UseCaseInterface<RevisionMetadata[]> {
constructor(private revisionRepositoryResolver: RevisionRepositoryResolverInterface) {}
constructor(private revisionRepository: RevisionRepositoryInterface) {}
async execute(dto: GetRevisionsMetadaDTO): Promise<Result<RevisionMetadata[]>> {
const itemUuidOrError = Uuid.create(dto.itemUuid)
@ -28,15 +28,7 @@ export class GetRevisionsMetada implements UseCaseInterface<RevisionMetadata[]>
sharedVaultUuids.push(sharedVaultUuidOrError.getValue())
}
const roleNamesOrError = RoleNameCollection.create(dto.roleNames)
if (roleNamesOrError.isFailed()) {
return Result.fail(roleNamesOrError.getError())
}
const roleNames = roleNamesOrError.getValue()
const revisionRepository = this.revisionRepositoryResolver.resolve(roleNames)
const revisionsMetdata = await revisionRepository.findMetadataByItemId(
const revisionsMetdata = await this.revisionRepository.findMetadataByItemId(
itemUuidOrError.getValue(),
userUuidOrError.getValue(),
sharedVaultUuids,

View file

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

View file

@ -1,325 +0,0 @@
/* istanbul ignore file */
import { Result, TransitionStatus, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import { TimerInterface } from '@standardnotes/time'
import { Logger } from 'winston'
import { TransitionRevisionsFromPrimaryToSecondaryDatabaseForUserDTO } from './TransitionRevisionsFromPrimaryToSecondaryDatabaseForUserDTO'
import { RevisionRepositoryInterface } from '../../../Revision/RevisionRepositoryInterface'
import { TransitionRepositoryInterface } from '../../../Transition/TransitionRepositoryInterface'
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements UseCaseInterface<void> {
constructor(
private primaryRevisionsRepository: RevisionRepositoryInterface,
private secondRevisionsRepository: RevisionRepositoryInterface | null,
private transitionStatusRepository: TransitionRepositoryInterface | null,
private timer: TimerInterface,
private logger: Logger,
private pageSize: number,
private domainEventPublisher: DomainEventPublisherInterface,
private domainEventFactory: DomainEventFactoryInterface,
) {}
async execute(dto: TransitionRevisionsFromPrimaryToSecondaryDatabaseForUserDTO): Promise<Result<void>> {
this.logger.info(`[TRANSITION][${dto.userUuid}] Transitioning revisions for user`)
if (this.secondRevisionsRepository === null) {
return Result.fail('Secondary revision repository is not set')
}
if (this.transitionStatusRepository === null) {
return Result.fail('Transition status repository is not set')
}
const userUuidOrError = Uuid.create(dto.userUuid)
if (userUuidOrError.isFailed()) {
return Result.fail(userUuidOrError.getError())
}
const userUuid = userUuidOrError.getValue()
if (await this.isAlreadyMigrated(userUuid)) {
this.logger.info(`[TRANSITION][${userUuid.value}] User already migrated.`)
await this.updateTransitionStatus(userUuid, TransitionStatus.STATUSES.Verified, dto.timestamp)
return Result.ok()
}
await this.updateTransitionStatus(userUuid, TransitionStatus.STATUSES.InProgress, dto.timestamp)
const migrationTimeStart = this.timer.getTimestampInMicroseconds()
this.logger.info(`[TRANSITION][${dto.userUuid}] Migrating revisions`)
const migrationResult = await this.migrateRevisionsForUser(userUuid, dto.timestamp)
if (migrationResult.isFailed()) {
await this.updateTransitionStatus(userUuid, TransitionStatus.STATUSES.Failed, dto.timestamp)
return Result.fail(migrationResult.getError())
}
this.logger.info(`[TRANSITION][${dto.userUuid}] Revisions migrated`)
await this.allowForPrimaryDatabaseToCatchUp()
this.logger.info(`[TRANSITION][${dto.userUuid}] Checking integrity between primary and secondary database`)
const integrityCheckResult = await this.checkIntegrityBetweenPrimaryAndSecondaryDatabase(userUuid)
if (integrityCheckResult.isFailed()) {
await (this.transitionStatusRepository as TransitionRepositoryInterface).setPagingProgress(userUuid.value, 1)
await (this.transitionStatusRepository as TransitionRepositoryInterface).setIntegrityProgress(userUuid.value, 1)
await this.updateTransitionStatus(userUuid, TransitionStatus.STATUSES.Failed, dto.timestamp)
return Result.fail(integrityCheckResult.getError())
}
const cleanupResult = await this.deleteRevisionsForUser(
userUuid,
this.secondRevisionsRepository as RevisionRepositoryInterface,
)
if (cleanupResult.isFailed()) {
await this.updateTransitionStatus(userUuid, TransitionStatus.STATUSES.Failed, dto.timestamp)
this.logger.error(
`[TRANSITION][${dto.userUuid}] Failed to clean up secondary database revisions: ${cleanupResult.getError()}`,
)
}
const migrationTimeEnd = this.timer.getTimestampInMicroseconds()
const migrationDuration = migrationTimeEnd - migrationTimeStart
const migrationDurationTimeStructure = this.timer.convertMicrosecondsToTimeStructure(migrationDuration)
this.logger.info(
`[TRANSITION][${dto.userUuid}] Transitioned revisions in ${migrationDurationTimeStructure.hours}h ${migrationDurationTimeStructure.minutes}m ${migrationDurationTimeStructure.seconds}s ${migrationDurationTimeStructure.milliseconds}ms`,
)
await this.updateTransitionStatus(userUuid, TransitionStatus.STATUSES.Verified, dto.timestamp)
return Result.ok()
}
private async migrateRevisionsForUser(userUuid: Uuid, timestamp: number): Promise<Result<void>> {
try {
const initialPage = await (this.transitionStatusRepository as TransitionRepositoryInterface).getPagingProgress(
userUuid.value,
)
this.logger.info(`[TRANSITION][${userUuid.value}] Migrating from page ${initialPage}`)
const totalRevisionsCountForUser = await (
this.secondRevisionsRepository as RevisionRepositoryInterface
).countByUserUuid(userUuid)
this.logger.info(`[TRANSITION][${userUuid.value}] Total revisions count for user: ${totalRevisionsCountForUser}`)
const totalPages = Math.ceil(totalRevisionsCountForUser / this.pageSize)
this.logger.info(`[TRANSITION][${userUuid.value}] Total pages: ${totalPages}`)
let insertedCount = 0
let newerCount = 0
let identicalCount = 0
let updatedCount = 0
for (let currentPage = initialPage; currentPage <= totalPages; currentPage++) {
const isPageInEvery10Percent = currentPage % Math.ceil(totalPages / 10) === 0
if (isPageInEvery10Percent) {
this.logger.info(
`[TRANSITION][${userUuid.value}] Migrating revisions for user: ${Math.round(
(currentPage / totalPages) * 100,
)}% completed`,
)
this.logger.info(
`[TRANSITION][${userUuid.value}] Inserted ${insertedCount} revisions so far. Skipped ${newerCount} revisions because they were newer in primary database. Skipped ${identicalCount} revisions because they were identical in primary and secondary database. Updated ${updatedCount} revisions because they were older in primary database.`,
)
await this.updateTransitionStatus(userUuid, TransitionStatus.STATUSES.InProgress, timestamp)
}
await (this.transitionStatusRepository as TransitionRepositoryInterface).setPagingProgress(
userUuid.value,
currentPage,
)
const query = {
userUuid: userUuid,
offset: (currentPage - 1) * this.pageSize,
limit: this.pageSize,
}
const revisions = await (this.secondRevisionsRepository as RevisionRepositoryInterface).findByUserUuid(query)
for (const revision of revisions) {
try {
const revisionInPrimary = await this.primaryRevisionsRepository.findOneByUuid(
Uuid.create(revision.id.toString()).getValue(),
revision.props.userUuid as Uuid,
[],
)
if (!revisionInPrimary) {
await this.primaryRevisionsRepository.insert(revision)
insertedCount++
} else {
if (revisionInPrimary.props.dates.updatedAt > revision.props.dates.updatedAt) {
this.logger.info(
`[TRANSITION][${
userUuid.value
}] Revision ${revision.id.toString()} is older in secondary than revision in primary database`,
)
newerCount++
continue
}
if (revisionInPrimary.isIdenticalTo(revision)) {
identicalCount++
continue
}
await this.primaryRevisionsRepository.update(revision)
updatedCount++
}
} catch (error) {
this.logger.error(
`[TRANSITION][${
userUuid.value
}] Errored when saving revision ${revision.id.toString()} to primary database: ${
(error as Error).message
}`,
)
}
}
}
this.logger.info(
`[TRANSITION][${userUuid.value}] Inserted ${insertedCount} revisions. Skipped ${newerCount} revisions because they were newer in primary database. Skipped ${identicalCount} revisions because they were identical in primary and secondary database. Updated ${updatedCount} revisions because they were older in primary database.`,
)
return Result.ok()
} catch (error) {
return Result.fail(`Errored when migrating revisions for user ${userUuid.value}: ${(error as Error).message}`)
}
}
private async deleteRevisionsForUser(
userUuid: Uuid,
revisionRepository: RevisionRepositoryInterface,
): Promise<Result<void>> {
try {
this.logger.info(`[TRANSITION][${userUuid.value}] Deleting all revisions from secondary database`)
await revisionRepository.removeByUserUuid(userUuid)
return Result.ok()
} catch (error) {
return Result.fail(`Errored when deleting revisions for user ${userUuid.value}: ${(error as Error).message}`)
}
}
private async allowForPrimaryDatabaseToCatchUp(): Promise<void> {
const delay = 1_000
await this.timer.sleep(delay)
}
private async checkIntegrityBetweenPrimaryAndSecondaryDatabase(userUuid: Uuid): Promise<Result<boolean>> {
try {
const initialPage = await (this.transitionStatusRepository as TransitionRepositoryInterface).getIntegrityProgress(
userUuid.value,
)
this.logger.info(`[TRANSITION][${userUuid.value}] Checking integrity from page ${initialPage}`)
const totalRevisionsCountForUserInSecondary = await (
this.secondRevisionsRepository as RevisionRepositoryInterface
).countByUserUuid(userUuid)
const totalRevisionsCountForUserInPrimary = await this.primaryRevisionsRepository.countByUserUuid(userUuid)
if (totalRevisionsCountForUserInPrimary < totalRevisionsCountForUserInSecondary) {
return Result.fail(
`Total revisions count for user ${userUuid.value} in primary database (${totalRevisionsCountForUserInPrimary}) does not match total revisions count in secondary database (${totalRevisionsCountForUserInSecondary})`,
)
}
const totalPages = Math.ceil(totalRevisionsCountForUserInPrimary / this.pageSize)
for (let currentPage = initialPage; currentPage <= totalPages; currentPage++) {
await (this.transitionStatusRepository as TransitionRepositoryInterface).setIntegrityProgress(
userUuid.value,
currentPage,
)
const query = {
userUuid: userUuid,
offset: (currentPage - 1) * this.pageSize,
limit: this.pageSize,
}
const revisions = await (this.secondRevisionsRepository as RevisionRepositoryInterface).findByUserUuid(query)
for (const revision of revisions) {
const revisionUuidOrError = Uuid.create(revision.id.toString())
/* istanbul ignore if */
if (revisionUuidOrError.isFailed()) {
return Result.fail(revisionUuidOrError.getError())
}
const revisionUuid = revisionUuidOrError.getValue()
const revisionInPrimary = await this.primaryRevisionsRepository.findOneByUuid(revisionUuid, userUuid, [])
if (!revisionInPrimary) {
return Result.fail(`Revision ${revision.id.toString()} not found in primary database`)
}
if (revisionInPrimary.props.dates.updatedAt > revision.props.dates.updatedAt) {
this.logger.info(
`[TRANSITION][${
userUuid.value
}] Integrity check of revision ${revision.id.toString()} - is older in secondary than revision in primary database`,
)
continue
}
if (revision.isIdenticalTo(revisionInPrimary)) {
continue
}
return Result.fail(
`Revision ${revision.id.toString()} is not identical in primary and secondary database. Revision in primary database: ${JSON.stringify(
revisionInPrimary,
)}, revision in secondary database: ${JSON.stringify(revision)}`,
)
}
}
return Result.ok()
} catch (error) {
return Result.fail(
`Errored when checking integrity between primary and secondary database: ${(error as Error).message}`,
)
}
}
private async updateTransitionStatus(userUuid: Uuid, status: string, timestamp: number): Promise<void> {
await this.domainEventPublisher.publish(
this.domainEventFactory.createTransitionStatusUpdatedEvent({
userUuid: userUuid.value,
status,
transitionType: 'revisions',
transitionTimestamp: timestamp,
}),
)
}
private async isAlreadyMigrated(userUuid: Uuid): Promise<boolean> {
const totalRevisionsCountForUserInSecondary = await (
this.secondRevisionsRepository as RevisionRepositoryInterface
).countByUserUuid(userUuid)
if (totalRevisionsCountForUserInSecondary > 0) {
this.logger.info(
`[TRANSITION][${userUuid.value}] User has ${totalRevisionsCountForUserInSecondary} revisions in secondary database.`,
)
}
return totalRevisionsCountForUserInSecondary === 0
}
}

View file

@ -1,4 +0,0 @@
export interface TransitionRevisionsFromPrimaryToSecondaryDatabaseForUserDTO {
userUuid: string
timestamp: number
}

View file

@ -1,5 +1,4 @@
import { HttpStatusCode } from '@standardnotes/responses'
import { Role } from '@standardnotes/security'
import { BaseHttpController, results } from 'inversify-express-utils'
import { Request, Response } from 'express'
import { ControllerContainerInterface, MapperInterface } from '@standardnotes/domain-core'
@ -34,7 +33,6 @@ export class BaseRevisionsController extends BaseHttpController {
const revisionMetadataOrError = await this.getRevisionsMetadata.execute({
itemUuid: request.params.itemUuid,
userUuid: response.locals.user.uuid,
roleNames: response.locals.roles.map((role: Role) => role.name),
sharedVaultUuids: response.locals.belongsToSharedVaults.map(
(association: { shared_vault_uuid: string; permission: string }) => association.shared_vault_uuid,
),
@ -61,7 +59,6 @@ export class BaseRevisionsController extends BaseHttpController {
const revisionOrError = await this.doGetRevision.execute({
revisionUuid: request.params.uuid,
userUuid: response.locals.user.uuid,
roleNames: response.locals.roles.map((role: Role) => role.name),
sharedVaultUuids: response.locals.belongsToSharedVaults.map(
(association: { shared_vault_uuid: string; permission: string }) => association.shared_vault_uuid,
),
@ -87,7 +84,6 @@ export class BaseRevisionsController extends BaseHttpController {
const revisionOrError = await this.doDeleteRevision.execute({
revisionUuid: request.params.uuid,
userUuid: response.locals.user.uuid,
roleNames: response.locals.roles.map((role: Role) => role.name),
})
if (revisionOrError.isFailed()) {

View file

@ -1,38 +0,0 @@
import * as IORedis from 'ioredis'
import { TransitionRepositoryInterface } from '../../Domain/Transition/TransitionRepositoryInterface'
export class RedisTransitionRepository implements TransitionRepositoryInterface {
private readonly PREFIX = 'transition-revisions-migration-progress'
private readonly INTEGRITY_PREFIX = 'transition-revisions-integrity-progress'
constructor(private redisClient: IORedis.Redis) {}
async getIntegrityProgress(userUuid: string): Promise<number> {
const progress = await this.redisClient.get(`${this.INTEGRITY_PREFIX}:${userUuid}`)
if (progress === null) {
return 1
}
return parseInt(progress)
}
async setIntegrityProgress(userUuid: string, progress: number): Promise<void> {
await this.redisClient.setex(`${this.INTEGRITY_PREFIX}:${userUuid}`, 172_800, progress.toString())
}
async getPagingProgress(userUuid: string): Promise<number> {
const progress = await this.redisClient.get(`${this.PREFIX}:${userUuid}`)
if (progress === null) {
return 1
}
return parseInt(progress)
}
async setPagingProgress(userUuid: string, progress: number): Promise<void> {
await this.redisClient.setex(`${this.PREFIX}:${userUuid}`, 172_800, progress.toString())
}
}

View file

@ -1,50 +0,0 @@
import { BSON } from 'mongodb'
import { Column, Entity, Index, ObjectIdColumn } from 'typeorm'
@Entity({ name: 'revisions' })
export class MongoDBRevision {
@ObjectIdColumn()
declare _id: BSON.UUID
@Column()
@Index('item_uuid_on_revisions')
declare itemUuid: string
@Column()
@Index('user_uuid_on_revisions')
declare userUuid: string | null
@Column()
declare content: string | null
@Column()
declare contentType: string | null
@Column()
declare itemsKeyId: string | null
@Column()
declare encItemKey: string | null
@Column()
declare authHash: string | null
@Column()
declare creationDate: Date
@Column()
declare createdAt: Date
@Column()
declare updatedAt: Date
@Column()
declare editedBy: string | null
@Column()
@Index('index_revisions_on_shared_vault_uuid')
declare sharedVaultUuid: string | null
@Column()
declare keySystemIdentifier: string | null
}

View file

@ -1,209 +0,0 @@
import { MapperInterface, Uuid } from '@standardnotes/domain-core'
import { MongoRepository, ObjectLiteral } from 'typeorm'
import { BSON } from 'mongodb'
import { Logger } from 'winston'
import { MongoDBRevision } from './MongoDBRevision'
import { Revision } from '../../../Domain/Revision/Revision'
import { RevisionMetadata } from '../../../Domain/Revision/RevisionMetadata'
import { RevisionRepositoryInterface } from '../../../Domain/Revision/RevisionRepositoryInterface'
export class MongoDBRevisionRepository implements RevisionRepositoryInterface {
constructor(
private mongoRepository: MongoRepository<MongoDBRevision>,
private revisionMetadataMapper: MapperInterface<RevisionMetadata, MongoDBRevision>,
private revisionMapper: MapperInterface<Revision, MongoDBRevision>,
private logger: Logger,
) {}
async clearSharedVaultAndKeySystemAssociations(dto: { itemUuid?: Uuid; sharedVaultUuid: Uuid }): Promise<void> {
let query: ObjectLiteral
if (dto.itemUuid !== undefined) {
query = {
itemUuid: { $eq: dto.itemUuid.value },
sharedVaultUuid: { $eq: dto.sharedVaultUuid.value },
}
} else {
query = {
sharedVaultUuid: { $eq: dto.sharedVaultUuid.value },
}
}
await this.mongoRepository.updateMany(query, {
$set: {
sharedVaultUuid: null,
keySystemIdentifier: null,
},
})
}
async countByUserUuid(userUuid: Uuid): Promise<number> {
return this.mongoRepository.count({ userUuid: { $eq: userUuid.value } })
}
async findByUserUuid(dto: { userUuid: Uuid; offset?: number; limit?: number }): Promise<Revision[]> {
const mongoRevisions = await this.mongoRepository.find({
where: { userUuid: { $eq: dto.userUuid.value } },
order: {
createdAt: 'ASC',
_id: 'ASC',
},
skip: dto.offset,
take: dto.limit,
})
const revisions = []
for (const mongoRevision of mongoRevisions) {
revisions.push(this.revisionMapper.toDomain(mongoRevision))
}
return revisions
}
async removeByUserUuid(userUuid: Uuid): Promise<void> {
await this.mongoRepository.deleteMany({ userUuid: userUuid.value })
}
async removeOneByUuid(revisionUuid: Uuid, userUuid: Uuid): Promise<void> {
await this.mongoRepository.deleteOne({
_id: { $eq: BSON.UUID.createFromHexString(revisionUuid.value) },
userUuid: { $eq: userUuid.value },
})
}
async findOneByUuid(revisionUuid: Uuid, userUuid: Uuid, sharedVaultUuids: Uuid[]): Promise<Revision | null> {
let persistence = null
if (sharedVaultUuids.length > 0) {
persistence = await this.mongoRepository.findOne({
where: {
$and: [
{ _id: { $eq: BSON.UUID.createFromHexString(revisionUuid.value) } },
{
$or: [
{ sharedVaultUuid: { $in: sharedVaultUuids.map((uuid) => uuid.value) } },
{ userUuid: { $eq: userUuid.value } },
],
},
],
},
})
} else {
persistence = await this.mongoRepository.findOne({
where: {
$and: [
{ _id: { $eq: BSON.UUID.createFromHexString(revisionUuid.value) } },
{ userUuid: { $eq: userUuid.value } },
],
},
})
}
if (persistence === null) {
return null
}
return this.revisionMapper.toDomain(persistence)
}
async findByItemUuid(itemUuid: Uuid): Promise<Revision[]> {
const persistence = await this.mongoRepository.find({
where: {
itemUuid: { $eq: itemUuid.value },
},
})
const revisions: Revision[] = []
for (const revision of persistence) {
try {
revisions.push(this.revisionMapper.toDomain(revision))
} catch (error) {
this.logger.error(`Failed to map revision ${revision._id.toHexString()} to domain: ${(error as Error).message}`)
}
}
return revisions
}
async findMetadataByItemId(
itemUuid: Uuid,
userUuid: Uuid,
sharedVaultUuids: Uuid[],
): Promise<Array<RevisionMetadata>> {
let persistence = []
if (sharedVaultUuids.length > 0) {
persistence = await this.mongoRepository.find({
select: ['_id', 'contentType', 'createdAt', 'updatedAt', 'sharedVaultUuid', 'itemUuid'],
where: {
$and: [
{ itemUuid: { $eq: itemUuid.value } },
{
$or: [
{ sharedVaultUuid: { $in: sharedVaultUuids.map((uuid) => uuid.value) } },
{ userUuid: { $eq: userUuid.value } },
],
},
],
},
order: {
createdAt: 'DESC',
_id: 'DESC',
},
})
} else {
persistence = await this.mongoRepository.find({
select: ['_id', 'contentType', 'createdAt', 'updatedAt', 'sharedVaultUuid', 'itemUuid'],
where: {
$and: [{ itemUuid: { $eq: itemUuid.value } }, { userUuid: { $eq: userUuid.value } }],
},
order: {
createdAt: 'DESC',
_id: 'DESC',
},
})
}
const revisions: RevisionMetadata[] = []
for (const revision of persistence) {
try {
revisions.push(this.revisionMetadataMapper.toDomain(revision))
} catch (error) {
this.logger.error(`Failed to map revision ${revision._id.toHexString()} to domain: ${(error as Error).message}`)
}
}
return revisions
}
async updateUserUuid(itemUuid: Uuid, userUuid: Uuid): Promise<void> {
await this.mongoRepository.updateMany(
{
itemUuid: { $eq: itemUuid.value },
},
{
$set: {
userUuid: userUuid.value,
},
},
)
}
async insert(revision: Revision): Promise<boolean> {
const persistence = this.revisionMapper.toProjection(revision)
const insertResult = await this.mongoRepository.insertOne(persistence)
return insertResult.acknowledged
}
async update(revision: Revision): Promise<boolean> {
const persistence = this.revisionMapper.toProjection(revision)
const { _id, ...rest } = persistence
const updateResult = await this.mongoRepository.updateOne({ _id: _id }, { $set: rest })
return updateResult.acknowledged
}
}

View file

@ -1,25 +0,0 @@
import { RoleName, RoleNameCollection } from '@standardnotes/domain-core'
import { RevisionRepositoryResolverInterface } from '../../Domain/Revision/RevisionRepositoryResolverInterface'
import { RevisionRepositoryInterface } from '../../Domain/Revision/RevisionRepositoryInterface'
export class TypeORMRevisionRepositoryResolver implements RevisionRepositoryResolverInterface {
constructor(
private sqlRevisionRepository: RevisionRepositoryInterface,
private mongoDbRevisionRepository: RevisionRepositoryInterface | null,
) {}
resolve(roleNames: RoleNameCollection): RevisionRepositoryInterface {
if (!this.mongoDbRevisionRepository) {
return this.sqlRevisionRepository
}
const transitionRoleName = RoleName.create(RoleName.NAMES.TransitionUser).getValue()
if (roleNames.includes(transitionRoleName)) {
return this.mongoDbRevisionRepository
}
return this.sqlRevisionRepository
}
}

View file

@ -1,58 +0,0 @@
import { MapperInterface, Dates, UniqueEntityId, ContentType, Uuid } from '@standardnotes/domain-core'
import { RevisionMetadata } from '../../../Domain/Revision/RevisionMetadata'
import { MongoDBRevision } from '../../../Infra/TypeORM/MongoDB/MongoDBRevision'
export class MongoDBRevisionMetadataPersistenceMapper implements MapperInterface<RevisionMetadata, MongoDBRevision> {
toDomain(projection: MongoDBRevision): RevisionMetadata {
const contentTypeOrError = ContentType.create(projection.contentType)
if (contentTypeOrError.isFailed()) {
throw new Error(`Could not create content type: ${contentTypeOrError.getError()}`)
}
const contentType = contentTypeOrError.getValue()
const createdAt = projection.createdAt instanceof Date ? projection.createdAt : new Date(projection.createdAt)
const updatedAt = projection.updatedAt instanceof Date ? projection.updatedAt : new Date(projection.updatedAt)
const datesOrError = Dates.create(createdAt, updatedAt)
if (datesOrError.isFailed()) {
throw new Error(`Could not create dates: ${datesOrError.getError()}`)
}
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()),
)
if (revisionMetadataOrError.isFailed()) {
throw new Error(`Could not create revision metdata: ${revisionMetadataOrError.getError()}`)
}
return revisionMetadataOrError.getValue()
}
toProjection(_domain: RevisionMetadata): MongoDBRevision {
throw new Error('Method not implemented.')
}
}

View file

@ -1,125 +0,0 @@
import { MapperInterface, Dates, UniqueEntityId, Uuid, ContentType } from '@standardnotes/domain-core'
import { BSON } from 'mongodb'
import { MongoDBRevision } from '../../../Infra/TypeORM/MongoDB/MongoDBRevision'
import { Revision } from '../../../Domain/Revision/Revision'
import { SharedVaultAssociation } from '../../../Domain/SharedVault/SharedVaultAssociation'
import { KeySystemAssociation } from '../../../Domain/KeySystem/KeySystemAssociation'
import { TimerInterface } from '@standardnotes/time'
export class MongoDBRevisionPersistenceMapper implements MapperInterface<Revision, MongoDBRevision> {
constructor(private timer: TimerInterface) {}
toDomain(projection: MongoDBRevision): Revision {
const contentTypeOrError = ContentType.create(projection.contentType)
if (contentTypeOrError.isFailed()) {
throw new Error(`Could not map typeorm revision to domain revision: ${contentTypeOrError.getError()}`)
}
const contentType = contentTypeOrError.getValue()
const datesOrError = Dates.create(projection.createdAt, projection.updatedAt)
if (datesOrError.isFailed()) {
throw new Error(`Could not map typeorm revision to domain revision: ${datesOrError.getError()}`)
}
const dates = datesOrError.getValue()
const itemUuidOrError = Uuid.create(projection.itemUuid)
if (itemUuidOrError.isFailed()) {
throw new Error(`Could not map typeorm revision to domain revision: ${itemUuidOrError.getError()}`)
}
const itemUuid = itemUuidOrError.getValue()
let userUuid = null
if (projection.userUuid !== null) {
const userUuidOrError = Uuid.create(projection.userUuid)
if (userUuidOrError.isFailed()) {
throw new Error(`Could not map typeorm revision to domain revision: ${userUuidOrError.getError()}`)
}
userUuid = userUuidOrError.getValue()
}
let sharedVaultAssociation: SharedVaultAssociation | undefined = undefined
if (projection.sharedVaultUuid && projection.editedBy) {
const sharedVaultUuidOrError = Uuid.create(projection.sharedVaultUuid)
if (sharedVaultUuidOrError.isFailed()) {
throw new Error(`Failed to create revision from projection: ${sharedVaultUuidOrError.getError()}`)
}
const sharedVaultUuid = sharedVaultUuidOrError.getValue()
const lastEditedByOrError = Uuid.create(projection.editedBy)
if (lastEditedByOrError.isFailed()) {
throw new Error(`Failed to create revision from projection: ${lastEditedByOrError.getError()}`)
}
const lastEditedBy = lastEditedByOrError.getValue()
const sharedVaultAssociationOrError = SharedVaultAssociation.create({
sharedVaultUuid,
editedBy: lastEditedBy,
})
if (sharedVaultAssociationOrError.isFailed()) {
throw new Error(`Failed to create revision from projection: ${sharedVaultAssociationOrError.getError()}`)
}
sharedVaultAssociation = sharedVaultAssociationOrError.getValue()
}
let keySystemAssociation: KeySystemAssociation | undefined = undefined
if (projection.keySystemIdentifier) {
const keySystemAssociationOrError = KeySystemAssociation.create(projection.keySystemIdentifier)
if (keySystemAssociationOrError.isFailed()) {
throw new Error(`Failed to create revision from projection: ${keySystemAssociationOrError.getError()}`)
}
keySystemAssociation = keySystemAssociationOrError.getValue()
}
const revisionOrError = Revision.create(
{
authHash: projection.authHash,
content: projection.content,
contentType,
creationDate: new Date(this.timer.convertDateToFormattedString(projection.creationDate, 'YYYY-MM-DD')),
encItemKey: projection.encItemKey,
itemsKeyId: projection.itemsKeyId,
itemUuid,
userUuid,
dates,
sharedVaultAssociation,
keySystemAssociation,
},
new UniqueEntityId(projection._id.toHexString()),
)
if (revisionOrError.isFailed()) {
throw new Error(`Could not map typeorm revision to domain revision: ${revisionOrError.getError()}`)
}
return revisionOrError.getValue()
}
toProjection(domain: Revision): MongoDBRevision {
const mongoDBRevision = new MongoDBRevision()
mongoDBRevision.authHash = domain.props.authHash
mongoDBRevision.content = domain.props.content
mongoDBRevision.contentType = domain.props.contentType.value
mongoDBRevision.createdAt = domain.props.dates.createdAt
mongoDBRevision.updatedAt = domain.props.dates.updatedAt
mongoDBRevision.creationDate = new Date(
this.timer.convertDateToFormattedString(domain.props.creationDate, 'YYYY-MM-DD'),
)
mongoDBRevision.encItemKey = domain.props.encItemKey
mongoDBRevision.itemUuid = domain.props.itemUuid.value
mongoDBRevision.itemsKeyId = domain.props.itemsKeyId
mongoDBRevision.userUuid = domain.props.userUuid ? domain.props.userUuid.value : null
mongoDBRevision.sharedVaultUuid = domain.props.sharedVaultAssociation
? domain.props.sharedVaultAssociation.props.sharedVaultUuid.value
: null
mongoDBRevision.editedBy = domain.props.sharedVaultAssociation
? domain.props.sharedVaultAssociation.props.editedBy.value
: null
mongoDBRevision.keySystemIdentifier = domain.props.keySystemAssociation
? domain.props.keySystemAssociation.props.keySystemIdentifier
: null
mongoDBRevision._id = BSON.UUID.createFromHexString(domain.id.toString())
return mongoDBRevision
}
}

View file

@ -24,6 +24,4 @@ export type CrossServiceTokenData = {
refresh_expiration: string
}
extensionKey?: string
ongoing_transition?: boolean
ongoing_revisions_transition?: boolean
}

View file

@ -11,5 +11,4 @@ export type ValetTokenData = {
}>
uploadBytesUsed: number
uploadBytesLimit: number
ongoingTransition?: boolean
}

View file

@ -17,9 +17,6 @@ DB_DEBUG_LEVEL=all # "all" | "query" | "schema" | "error" | "warn" | "info" | "l
DB_MIGRATIONS_PATH=dist/migrations/*.js
DB_TYPE=mysql
REDIS_URL=redis://cache
CACHE_TYPE=redis
VALET_TOKEN_SECRET=change-me-!
VALET_TOKEN_TTL=1000
@ -43,11 +40,3 @@ FILE_UPLOAD_PATH=
VALET_TOKEN_SECRET=change-me-!
VALET_TOKEN_TTL=7200
# (Optional) Mongo Setup
SECONDARY_DB_ENABLED=false
MONGO_HOST=
MONGO_PORT=
MONGO_USERNAME=
MONGO_PASSWORD=
MONGO_DATABASE=

View file

@ -49,7 +49,6 @@
"inversify-express-utils": "^6.4.3",
"ioredis": "^5.3.2",
"jsonwebtoken": "^9.0.0",
"mongodb": "^6.0.0",
"mysql2": "^3.0.1",
"prettyjson": "^1.2.5",
"reflect-metadata": "0.1.13",

View file

@ -1,5 +1,4 @@
import * as winston from 'winston'
import Redis from 'ioredis'
import { Container, interfaces } from 'inversify'
import { Env } from './Env'
@ -8,7 +7,7 @@ import { AppDataSource } from './DataSource'
import { SNSClient, SNSClientConfig } from '@aws-sdk/client-sns'
import { ItemRepositoryInterface } from '../Domain/Item/ItemRepositoryInterface'
import { SQLLegacyItemRepository } from '../Infra/TypeORM/SQLLegacyItemRepository'
import { MongoRepository, Repository } from 'typeorm'
import { Repository } from 'typeorm'
import { Item } from '../Domain/Item/Item'
import {
DirectCallDomainEventPublisher,
@ -151,27 +150,18 @@ import { UpdateStorageQuotaUsedInSharedVault } from '../Domain/UseCase/SharedVau
import { SharedVaultFileUploadedEventHandler } from '../Domain/Handler/SharedVaultFileUploadedEventHandler'
import { SharedVaultFileRemovedEventHandler } from '../Domain/Handler/SharedVaultFileRemovedEventHandler'
import { AddNotificationsForUsers } from '../Domain/UseCase/Messaging/AddNotificationsForUsers/AddNotificationsForUsers'
import { MongoDBItem } from '../Infra/TypeORM/MongoDBItem'
import { MongoDBItemRepository } from '../Infra/TypeORM/MongoDBItemRepository'
import { MongoDBItemPersistenceMapper } from '../Mapping/Persistence/MongoDB/MongoDBItemPersistenceMapper'
import { Logger } from 'winston'
import { ItemRepositoryResolverInterface } from '../Domain/Item/ItemRepositoryResolverInterface'
import { TypeORMItemRepositoryResolver } from '../Infra/TypeORM/TypeORMItemRepositoryResolver'
import { TransitionItemsFromPrimaryToSecondaryDatabaseForUser } from '../Domain/UseCase/Transition/TransitionItemsFromPrimaryToSecondaryDatabaseForUser/TransitionItemsFromPrimaryToSecondaryDatabaseForUser'
import { SharedVaultFileMovedEventHandler } from '../Domain/Handler/SharedVaultFileMovedEventHandler'
import { SQLItem } from '../Infra/TypeORM/SQLItem'
import { SQLItemPersistenceMapper } from '../Mapping/Persistence/SQLItemPersistenceMapper'
import { SQLItemRepository } from '../Infra/TypeORM/SQLItemRepository'
import { SendEventToClient } from '../Domain/UseCase/Syncing/SendEventToClient/SendEventToClient'
import { TransitionRequestedEventHandler } from '../Domain/Handler/TransitionRequestedEventHandler'
import { DeleteSharedVaults } from '../Domain/UseCase/SharedVaults/DeleteSharedVaults/DeleteSharedVaults'
import { RemoveItemsFromSharedVault } from '../Domain/UseCase/SharedVaults/RemoveItemsFromSharedVault/RemoveItemsFromSharedVault'
import { SharedVaultRemovedEventHandler } from '../Domain/Handler/SharedVaultRemovedEventHandler'
import { DesignateSurvivor } from '../Domain/UseCase/SharedVaults/DesignateSurvivor/DesignateSurvivor'
import { RemoveUserFromSharedVaults } from '../Domain/UseCase/SharedVaults/RemoveUserFromSharedVaults/RemoveUserFromSharedVaults'
import { TransferSharedVault } from '../Domain/UseCase/SharedVaults/TransferSharedVault/TransferSharedVault'
import { TransitionRepositoryInterface } from '../Domain/Transition/TransitionRepositoryInterface'
import { RedisTransitionRepository } from '../Infra/Redis/RedisTransitionRepository'
import { TransferSharedVaultItems } from '../Domain/UseCase/SharedVaults/TransferSharedVaultItems/TransferSharedVaultItems'
import { DumpItem } from '../Domain/UseCase/Syncing/DumpItem/DumpItem'
@ -223,29 +213,11 @@ export class ContainerConfigLoader {
const isConfiguredForHomeServer = env.get('MODE', true) === 'home-server'
const isConfiguredForSelfHosting = env.get('MODE', true) === 'self-hosted'
const isConfiguredForHomeServerOrSelfHosting = isConfiguredForHomeServer || isConfiguredForSelfHosting
const isSecondaryDatabaseEnabled = env.get('SECONDARY_DB_ENABLED', true) === 'true'
const isConfiguredForInMemoryCache = env.get('CACHE_TYPE', true) === 'memory'
container
.bind<boolean>(TYPES.Sync_IS_CONFIGURED_FOR_HOME_SERVER_OR_SELF_HOSTING)
.toConstantValue(isConfiguredForHomeServerOrSelfHosting)
if (!isConfiguredForInMemoryCache) {
const redisUrl = env.get('REDIS_URL')
const isRedisInClusterMode = redisUrl.indexOf(',') > 0
let redis
if (isRedisInClusterMode) {
redis = new Redis.Cluster(redisUrl.split(','))
} else {
redis = new Redis(redisUrl)
}
container.bind(TYPES.Sync_Redis).toConstantValue(redis)
container
.bind<TransitionRepositoryInterface>(TYPES.Sync_TransitionStatusRepository)
.toConstantValue(new RedisTransitionRepository(container.get<Redis>(TYPES.Sync_Redis)))
}
container.bind<Env>(TYPES.Sync_Env).toConstantValue(env)
if (isConfiguredForHomeServer) {
@ -413,27 +385,6 @@ export class ContainerConfigLoader {
.bind<Repository<TypeORMMessage>>(TYPES.Sync_ORMMessageRepository)
.toConstantValue(appDataSource.getRepository(TypeORMMessage))
// Mongo
if (isSecondaryDatabaseEnabled) {
container
.bind<MapperInterface<Item, MongoDBItem>>(TYPES.Sync_MongoDBItemPersistenceMapper)
.toConstantValue(new MongoDBItemPersistenceMapper())
container
.bind<MongoRepository<MongoDBItem>>(TYPES.Sync_ORMMongoItemRepository)
.toConstantValue(appDataSource.getMongoRepository(MongoDBItem))
container
.bind<ItemRepositoryInterface>(TYPES.Sync_MongoDBItemRepository)
.toConstantValue(
new MongoDBItemRepository(
container.get<MongoRepository<MongoDBItem>>(TYPES.Sync_ORMMongoItemRepository),
container.get<MapperInterface<Item, MongoDBItem>>(TYPES.Sync_MongoDBItemPersistenceMapper),
container.get<Logger>(TYPES.Sync_Logger),
),
)
}
// Repositories
container
.bind<ItemRepositoryInterface>(TYPES.Sync_SQLItemRepository)
@ -450,14 +401,6 @@ export class ContainerConfigLoader {
container.get<Logger>(TYPES.Sync_Logger),
),
)
container
.bind<ItemRepositoryResolverInterface>(TYPES.Sync_ItemRepositoryResolver)
.toConstantValue(
new TypeORMItemRepositoryResolver(
container.get<ItemRepositoryInterface>(TYPES.Sync_SQLItemRepository),
isSecondaryDatabaseEnabled ? container.get<ItemRepositoryInterface>(TYPES.Sync_MongoDBItemRepository) : null,
),
)
container
.bind<SharedVaultRepositoryInterface>(TYPES.Sync_SharedVaultRepository)
.toConstantValue(
@ -603,7 +546,7 @@ export class ContainerConfigLoader {
.bind<GetItems>(TYPES.Sync_GetItems)
.toConstantValue(
new GetItems(
container.get(TYPES.Sync_ItemRepositoryResolver),
container.get(TYPES.Sync_SQLItemRepository),
container.get(TYPES.Sync_SharedVaultUserRepository),
container.get(TYPES.Sync_CONTENT_SIZE_TRANSFER_LIMIT),
container.get(TYPES.Sync_ItemTransferCalculator),
@ -615,7 +558,7 @@ export class ContainerConfigLoader {
.bind<SaveNewItem>(TYPES.Sync_SaveNewItem)
.toConstantValue(
new SaveNewItem(
container.get(TYPES.Sync_ItemRepositoryResolver),
container.get(TYPES.Sync_SQLItemRepository),
container.get(TYPES.Sync_Timer),
container.get(TYPES.Sync_DomainEventPublisher),
container.get(TYPES.Sync_DomainEventFactory),
@ -655,7 +598,7 @@ export class ContainerConfigLoader {
.bind<UpdateExistingItem>(TYPES.Sync_UpdateExistingItem)
.toConstantValue(
new UpdateExistingItem(
container.get<ItemRepositoryResolverInterface>(TYPES.Sync_ItemRepositoryResolver),
container.get<ItemRepositoryInterface>(TYPES.Sync_SQLItemRepository),
container.get<TimerInterface>(TYPES.Sync_Timer),
container.get<DomainEventPublisherInterface>(TYPES.Sync_DomainEventPublisher),
container.get<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory),
@ -670,7 +613,7 @@ export class ContainerConfigLoader {
.toConstantValue(
new SaveItems(
container.get(TYPES.Sync_ItemSaveValidator),
container.get(TYPES.Sync_ItemRepositoryResolver),
container.get(TYPES.Sync_SQLItemRepository),
container.get(TYPES.Sync_Timer),
container.get(TYPES.Sync_SaveNewItem),
container.get(TYPES.Sync_UpdateExistingItem),
@ -699,7 +642,7 @@ export class ContainerConfigLoader {
.bind<SyncItems>(TYPES.Sync_SyncItems)
.toConstantValue(
new SyncItems(
container.get<ItemRepositoryResolverInterface>(TYPES.Sync_ItemRepositoryResolver),
container.get<ItemRepositoryInterface>(TYPES.Sync_SQLItemRepository),
container.get<GetItems>(TYPES.Sync_GetItems),
container.get<SaveItems>(TYPES.Sync_SaveItems),
container.get<GetSharedVaults>(TYPES.Sync_GetSharedVaults),
@ -710,10 +653,10 @@ export class ContainerConfigLoader {
),
)
container.bind<CheckIntegrity>(TYPES.Sync_CheckIntegrity).toDynamicValue((context: interfaces.Context) => {
return new CheckIntegrity(context.container.get(TYPES.Sync_ItemRepositoryResolver))
return new CheckIntegrity(context.container.get(TYPES.Sync_SQLItemRepository))
})
container.bind<GetItem>(TYPES.Sync_GetItem).toDynamicValue((context: interfaces.Context) => {
return new GetItem(context.container.get(TYPES.Sync_ItemRepositoryResolver))
return new GetItem(context.container.get(TYPES.Sync_SQLItemRepository))
})
container
.bind<InviteUserToSharedVault>(TYPES.Sync_InviteUserToSharedVault)
@ -854,32 +797,10 @@ export class ContainerConfigLoader {
container.get<SharedVaultRepositoryInterface>(TYPES.Sync_SharedVaultRepository),
),
)
container
.bind<TransitionItemsFromPrimaryToSecondaryDatabaseForUser>(
TYPES.Sync_TransitionItemsFromPrimaryToSecondaryDatabaseForUser,
)
.toConstantValue(
new TransitionItemsFromPrimaryToSecondaryDatabaseForUser(
container.get<ItemRepositoryInterface>(TYPES.Sync_SQLItemRepository),
isSecondaryDatabaseEnabled ? container.get<ItemRepositoryInterface>(TYPES.Sync_MongoDBItemRepository) : null,
isConfiguredForInMemoryCache
? null
: container.get<TransitionRepositoryInterface>(TYPES.Sync_TransitionStatusRepository),
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.get<DomainEventPublisherInterface>(TYPES.Sync_DomainEventPublisher),
container.get<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory),
),
)
container
.bind<RemoveItemsFromSharedVault>(TYPES.Sync_RemoveItemsFromSharedVault)
.toConstantValue(
new RemoveItemsFromSharedVault(
isSecondaryDatabaseEnabled
? container.get<ItemRepositoryInterface>(TYPES.Sync_MongoDBItemRepository)
: container.get<ItemRepositoryInterface>(TYPES.Sync_SQLItemRepository),
),
new RemoveItemsFromSharedVault(container.get<ItemRepositoryInterface>(TYPES.Sync_SQLItemRepository)),
)
container
.bind<DesignateSurvivor>(TYPES.Sync_DesignateSurvivor)
@ -905,11 +826,7 @@ export class ContainerConfigLoader {
container
.bind<TransferSharedVaultItems>(TYPES.Sync_TransferSharedVaultItems)
.toConstantValue(
new TransferSharedVaultItems(
isSecondaryDatabaseEnabled
? container.get<ItemRepositoryInterface>(TYPES.Sync_MongoDBItemRepository)
: container.get<ItemRepositoryInterface>(TYPES.Sync_SQLItemRepository),
),
new TransferSharedVaultItems(container.get<ItemRepositoryInterface>(TYPES.Sync_SQLItemRepository)),
)
container
.bind<TransferSharedVault>(TYPES.Sync_TransferSharedVault)
@ -947,7 +864,7 @@ export class ContainerConfigLoader {
.bind<DumpItem>(TYPES.Sync_DumpItem)
.toConstantValue(
new DumpItem(
container.get<ItemRepositoryResolverInterface>(TYPES.Sync_ItemRepositoryResolver),
container.get<ItemRepositoryInterface>(TYPES.Sync_SQLItemRepository),
container.get<ItemBackupServiceInterface>(TYPES.Sync_ItemBackupService),
container.get<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory),
container.get<DomainEventPublisherInterface>(TYPES.Sync_DomainEventPublisher),
@ -985,7 +902,7 @@ export class ContainerConfigLoader {
.bind<DuplicateItemSyncedEventHandler>(TYPES.Sync_DuplicateItemSyncedEventHandler)
.toConstantValue(
new DuplicateItemSyncedEventHandler(
container.get<ItemRepositoryResolverInterface>(TYPES.Sync_ItemRepositoryResolver),
container.get<ItemRepositoryInterface>(TYPES.Sync_SQLItemRepository),
container.get<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory),
container.get<DomainEventPublisherInterface>(TYPES.Sync_DomainEventPublisher),
container.get<Logger>(TYPES.Sync_Logger),
@ -995,7 +912,7 @@ export class ContainerConfigLoader {
.bind<AccountDeletionRequestedEventHandler>(TYPES.Sync_AccountDeletionRequestedEventHandler)
.toConstantValue(
new AccountDeletionRequestedEventHandler(
container.get<ItemRepositoryResolverInterface>(TYPES.Sync_ItemRepositoryResolver),
container.get<ItemRepositoryInterface>(TYPES.Sync_SQLItemRepository),
container.get<DeleteSharedVaults>(TYPES.Sync_DeleteSharedVaults),
container.get<RemoveUserFromSharedVaults>(TYPES.Sync_RemoveUserFromSharedVaults),
container.get<Logger>(TYPES.Sync_Logger),
@ -1036,17 +953,6 @@ export class ContainerConfigLoader {
container.get<winston.Logger>(TYPES.Sync_Logger),
),
)
container
.bind<TransitionRequestedEventHandler>(TYPES.Sync_TransitionRequestedEventHandler)
.toConstantValue(
new TransitionRequestedEventHandler(
false,
container.get<TransitionItemsFromPrimaryToSecondaryDatabaseForUser>(
TYPES.Sync_TransitionItemsFromPrimaryToSecondaryDatabaseForUser,
),
container.get<Logger>(TYPES.Sync_Logger),
),
)
container
.bind<SharedVaultRemovedEventHandler>(TYPES.Sync_SharedVaultRemovedEventHandler)
.toConstantValue(
@ -1065,7 +971,6 @@ export class ContainerConfigLoader {
new ExtensionsHttpService(
container.get<AxiosInstance>(TYPES.Sync_HTTPClient),
container.get<ItemRepositoryInterface>(TYPES.Sync_SQLItemRepository),
isSecondaryDatabaseEnabled ? container.get<ItemRepositoryInterface>(TYPES.Sync_MongoDBItemRepository) : null,
container.get<ContentDecoderInterface>(TYPES.Sync_ContentDecoder),
container.get<DomainEventPublisherInterface>(TYPES.Sync_DomainEventPublisher),
container.get<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory),
@ -1089,10 +994,6 @@ export class ContainerConfigLoader {
'SHARED_VAULT_FILE_MOVED',
container.get<SharedVaultFileMovedEventHandler>(TYPES.Sync_SharedVaultFileMovedEventHandler),
],
[
'TRANSITION_REQUESTED',
container.get<TransitionRequestedEventHandler>(TYPES.Sync_TransitionRequestedEventHandler),
],
[
'SHARED_VAULT_REMOVED',
container.get<SharedVaultRemovedEventHandler>(TYPES.Sync_SharedVaultRemovedEventHandler),
@ -1104,9 +1005,6 @@ export class ContainerConfigLoader {
.toConstantValue(
new EmailBackupRequestedEventHandler(
container.get<ItemRepositoryInterface>(TYPES.Sync_SQLItemRepository),
isSecondaryDatabaseEnabled
? container.get<ItemRepositoryInterface>(TYPES.Sync_MongoDBItemRepository)
: null,
container.get<ItemBackupServiceInterface>(TYPES.Sync_ItemBackupService),
container.get<DomainEventPublisherInterface>(TYPES.Sync_DomainEventPublisher),
container.get<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory),

View file

@ -1,4 +1,4 @@
import { DataSource, EntityTarget, LoggerOptions, MongoRepository, ObjectLiteral, Repository } from 'typeorm'
import { DataSource, EntityTarget, LoggerOptions, ObjectLiteral, Repository } from 'typeorm'
import { MysqlConnectionOptions } from 'typeorm/driver/mysql/MysqlConnectionOptions'
import { Env } from './Env'
import { SqliteConnectionOptions } from 'typeorm/driver/sqlite/SqliteConnectionOptions'
@ -8,12 +8,10 @@ import { TypeORMSharedVault } from '../Infra/TypeORM/TypeORMSharedVault'
import { TypeORMSharedVaultUser } from '../Infra/TypeORM/TypeORMSharedVaultUser'
import { TypeORMSharedVaultInvite } from '../Infra/TypeORM/TypeORMSharedVaultInvite'
import { TypeORMMessage } from '../Infra/TypeORM/TypeORMMessage'
import { MongoDBItem } from '../Infra/TypeORM/MongoDBItem'
import { SQLItem } from '../Infra/TypeORM/SQLItem'
export class AppDataSource {
private _dataSource: DataSource | undefined
private _secondaryDataSource: DataSource | undefined
constructor(
private configuration: {
@ -30,43 +28,8 @@ export class AppDataSource {
return this._dataSource.getRepository(target)
}
getMongoRepository<Entity extends ObjectLiteral>(target: EntityTarget<Entity>): MongoRepository<Entity> {
if (!this._secondaryDataSource) {
throw new Error('Secondary DataSource not initialized')
}
return this._secondaryDataSource.getMongoRepository(target)
}
async initialize(): Promise<void> {
await this.dataSource.initialize()
const secondaryDataSource = this.secondaryDataSource
if (secondaryDataSource) {
await secondaryDataSource.initialize()
}
}
get secondaryDataSource(): DataSource | undefined {
this.configuration.env.load()
if (this.configuration.env.get('SECONDARY_DB_ENABLED', true) !== 'true') {
return undefined
}
this._secondaryDataSource = new DataSource({
type: 'mongodb',
host: this.configuration.env.get('MONGO_HOST'),
authSource: 'admin',
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,
})
return this._secondaryDataSource
}
get dataSource(): DataSource {

View file

@ -7,15 +7,12 @@ const TYPES = {
Sync_S3: Symbol.for('Sync_S3'),
Sync_Env: Symbol.for('Sync_Env'),
// Repositories
Sync_ItemRepositoryResolver: Symbol.for('Sync_ItemRepositoryResolver'),
Sync_SQLItemRepository: Symbol.for('Sync_SQLItemRepository'),
Sync_MongoDBItemRepository: Symbol.for('Sync_MongoDBItemRepository'),
Sync_SharedVaultRepository: Symbol.for('Sync_SharedVaultRepository'),
Sync_SharedVaultInviteRepository: Symbol.for('Sync_SharedVaultInviteRepository'),
Sync_SharedVaultUserRepository: Symbol.for('Sync_SharedVaultUserRepository'),
Sync_NotificationRepository: Symbol.for('Sync_NotificationRepository'),
Sync_MessageRepository: Symbol.for('Sync_MessageRepository'),
Sync_TransitionStatusRepository: Symbol.for('Sync_TransitionStatusRepository'),
// ORM
Sync_ORMItemRepository: Symbol.for('Sync_ORMItemRepository'),
Sync_ORMLegacyItemRepository: Symbol.for('Sync_ORMLegacyItemRepository'),
@ -24,8 +21,6 @@ const TYPES = {
Sync_ORMSharedVaultUserRepository: Symbol.for('Sync_ORMSharedVaultUserRepository'),
Sync_ORMNotificationRepository: Symbol.for('Sync_ORMNotificationRepository'),
Sync_ORMMessageRepository: Symbol.for('Sync_ORMMessageRepository'),
// Mongo
Sync_ORMMongoItemRepository: Symbol.for('Sync_ORMMongoItemRepository'),
// Middleware
Sync_AuthMiddleware: Symbol.for('Sync_AuthMiddleware'),
// env vars
@ -82,9 +77,6 @@ const TYPES = {
Sync_DetermineSharedVaultOperationOnItem: Symbol.for('Sync_DetermineSharedVaultOperationOnItem'),
Sync_UpdateStorageQuotaUsedInSharedVault: Symbol.for('Sync_UpdateStorageQuotaUsedInSharedVault'),
Sync_AddNotificationsForUsers: Symbol.for('Sync_AddNotificationsForUsers'),
Sync_TransitionItemsFromPrimaryToSecondaryDatabaseForUser: Symbol.for(
'Sync_TransitionItemsFromPrimaryToSecondaryDatabaseForUser',
),
Sync_SendEventToClient: Symbol.for('Sync_SendEventToClient'),
Sync_RemoveItemsFromSharedVault: Symbol.for('Sync_RemoveItemsFromSharedVault'),
Sync_DesignateSurvivor: Symbol.for('Sync_DesignateSurvivor'),
@ -100,7 +92,6 @@ const TYPES = {
Sync_SharedVaultFileRemovedEventHandler: Symbol.for('Sync_SharedVaultFileRemovedEventHandler'),
Sync_SharedVaultFileUploadedEventHandler: Symbol.for('Sync_SharedVaultFileUploadedEventHandler'),
Sync_SharedVaultFileMovedEventHandler: Symbol.for('Sync_SharedVaultFileMovedEventHandler'),
Sync_TransitionRequestedEventHandler: Symbol.for('Sync_TransitionRequestedEventHandler'),
Sync_SharedVaultRemovedEventHandler: Symbol.for('Sync_SharedVaultRemovedEventHandler'),
// Services
Sync_ContentDecoder: Symbol.for('Sync_ContentDecoder'),
@ -139,7 +130,6 @@ const TYPES = {
Sync_NotificationHttpMapper: Symbol.for('Sync_NotificationHttpMapper'),
Sync_SQLLegacyItemPersistenceMapper: Symbol.for('Sync_SQLLegacyItemPersistenceMapper'),
Sync_SQLItemPersistenceMapper: Symbol.for('Sync_SQLItemPersistenceMapper'),
Sync_MongoDBItemPersistenceMapper: Symbol.for('Sync_MongoDBItemPersistenceMapper'),
Sync_ItemHttpMapper: Symbol.for('Sync_ItemHttpMapper'),
Sync_ItemHashHttpMapper: Symbol.for('Sync_ItemHashHttpMapper'),
Sync_SavedItemHttpMapper: Symbol.for('Sync_SavedItemHttpMapper'),

Some files were not shown because too many files have changed in this diff Show more