feat(revisions): add MongoDB support (#715)

* feat(revisions): add MongoDB support

* fix: add missing mongodb from revisions

* fix: mongodb bson imports
This commit is contained in:
Karol Sójko 2023-08-29 12:19:55 +02:00 committed by GitHub
parent 28e058c6e8
commit 2646b756a9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 472 additions and 185 deletions

152
.pnp.cjs generated
View file

@ -3602,6 +3602,16 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\
}]\
]],\
["@mongodb-js/saslprep", [\
["npm:1.1.0", {\
"packageLocation": "./.yarn/cache/@mongodb-js-saslprep-npm-1.1.0-3906c025b8-2cf6d124d4.zip/node_modules/@mongodb-js/saslprep/",\
"packageDependencies": [\
["@mongodb-js/saslprep", "npm:1.1.0"],\
["sparse-bitfield", "npm:3.0.3"]\
],\
"linkType": "HARD"\
}]\
]],\
["@mrleebo/prisma-ast", [\
["npm:0.5.2", {\
"packageLocation": "./.yarn/cache/@mrleebo-prisma-ast-npm-0.5.2-538c9d793e-69a7f3c188.zip/node_modules/@mrleebo/prisma-ast/",\
@ -4997,6 +5007,7 @@ const RAW_RUNTIME_STATE =
["inversify", "npm:6.0.1"],\
["inversify-express-utils", "npm:6.4.3"],\
["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.5.0"],\
["mongodb", "virtual:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:6.0.0"],\
["mysql2", "npm:3.3.3"],\
["newrelic", "npm:10.1.2"],\
["npm-check-updates", "npm:16.10.12"],\
@ -5191,7 +5202,7 @@ const RAW_RUNTIME_STATE =
["inversify-express-utils", "npm:6.4.3"],\
["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.5.0"],\
["jsonwebtoken", "npm:9.0.0"],\
["mongodb", "virtual:67ad3a1ca34e24ce4821cc48979e98af0c3e5dd7aabc7ad0b5d22d1d977d6f943f81c9f141a420105ebdc61ef777e508a96c7946081decd98f8c30543d468b33#npm:5.7.0"],\
["mongodb", "virtual:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:6.0.0"],\
["mysql2", "npm:3.3.3"],\
["newrelic", "npm:10.1.2"],\
["nodemon", "npm:2.0.22"],\
@ -5202,7 +5213,7 @@ const RAW_RUNTIME_STATE =
["semver", "npm:7.5.1"],\
["sqlite3", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:5.1.6"],\
["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.0"],\
["typeorm", "virtual:67ad3a1ca34e24ce4821cc48979e98af0c3e5dd7aabc7ad0b5d22d1d977d6f943f81c9f141a420105ebdc61ef777e508a96c7946081decd98f8c30543d468b33#npm:0.3.16"],\
["typeorm", "virtual:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:0.3.16"],\
["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"],\
@ -7096,10 +7107,10 @@ const RAW_RUNTIME_STATE =
}]\
]],\
["bson", [\
["npm:5.4.0", {\
"packageLocation": "./.yarn/cache/bson-npm-5.4.0-2f854c8216-2c913a45c0.zip/node_modules/bson/",\
["npm:6.0.0", {\
"packageLocation": "./.yarn/cache/bson-npm-6.0.0-7b3cba060e-7290998ee8.zip/node_modules/bson/",\
"packageDependencies": [\
["bson", "npm:5.4.0"]\
["bson", "npm:6.0.0"]\
],\
"linkType": "HARD"\
}]\
@ -12330,43 +12341,50 @@ const RAW_RUNTIME_STATE =
}]\
]],\
["mongodb", [\
["npm:5.7.0", {\
"packageLocation": "./.yarn/cache/mongodb-npm-5.7.0-c5e415a2e7-23a291ffe7.zip/node_modules/mongodb/",\
["npm:6.0.0", {\
"packageLocation": "./.yarn/cache/mongodb-npm-6.0.0-7c1e74de91-daec6dc9dc.zip/node_modules/mongodb/",\
"packageDependencies": [\
["mongodb", "npm:5.7.0"]\
["mongodb", "npm:6.0.0"]\
],\
"linkType": "SOFT"\
}],\
["virtual:67ad3a1ca34e24ce4821cc48979e98af0c3e5dd7aabc7ad0b5d22d1d977d6f943f81c9f141a420105ebdc61ef777e508a96c7946081decd98f8c30543d468b33#npm:5.7.0", {\
"packageLocation": "./.yarn/__virtual__/mongodb-virtual-eb0cd47e23/0/cache/mongodb-npm-5.7.0-c5e415a2e7-23a291ffe7.zip/node_modules/mongodb/",\
["virtual:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:6.0.0", {\
"packageLocation": "./.yarn/__virtual__/mongodb-virtual-789f2eaaac/0/cache/mongodb-npm-6.0.0-7c1e74de91-daec6dc9dc.zip/node_modules/mongodb/",\
"packageDependencies": [\
["mongodb", "virtual:67ad3a1ca34e24ce4821cc48979e98af0c3e5dd7aabc7ad0b5d22d1d977d6f943f81c9f141a420105ebdc61ef777e508a96c7946081decd98f8c30543d468b33#npm:5.7.0"],\
["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],\
["bson", "npm:5.4.0"],\
["@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"],\
["saslprep", "npm:1.0.3"],\
["snappy", null],\
["socks", "npm:2.7.1"]\
["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"\
"snappy",\
"socks"\
],\
"linkType": "HARD"\
}]\
@ -14341,16 +14359,6 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\
}]\
]],\
["saslprep", [\
["npm:1.0.3", {\
"packageLocation": "./.yarn/cache/saslprep-npm-1.0.3-8db649c346-23ebcda091.zip/node_modules/saslprep/",\
"packageDependencies": [\
["saslprep", "npm:1.0.3"],\
["sparse-bitfield", "npm:3.0.3"]\
],\
"linkType": "HARD"\
}]\
]],\
["schema-utils", [\
["npm:3.1.2", {\
"packageLocation": "./.yarn/cache/schema-utils-npm-3.1.2-d97c6dc247-11d35f997e.zip/node_modules/schema-utils/",\
@ -15821,99 +15829,7 @@ const RAW_RUNTIME_STATE =
["hdb-pool", null],\
["ioredis", null],\
["mkdirp", "npm:2.1.6"],\
["mongodb", null],\
["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:67ad3a1ca34e24ce4821cc48979e98af0c3e5dd7aabc7ad0b5d22d1d977d6f943f81c9f141a420105ebdc61ef777e508a96c7946081decd98f8c30543d468b33#npm:0.3.16", {\
"packageLocation": "./.yarn/__virtual__/typeorm-virtual-13b6364fde/0/cache/typeorm-npm-0.3.16-5ac12a7afc-19803f935e.zip/node_modules/typeorm/",\
"packageDependencies": [\
["typeorm", "virtual:67ad3a1ca34e24ce4821cc48979e98af0c3e5dd7aabc7ad0b5d22d1d977d6f943f81c9f141a420105ebdc61ef777e508a96c7946081decd98f8c30543d468b33#npm:0.3.16"],\
["@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", null],\
["@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", null],\
["mkdirp", "npm:2.1.6"],\
["mongodb", "virtual:67ad3a1ca34e24ce4821cc48979e98af0c3e5dd7aabc7ad0b5d22d1d977d6f943f81c9f141a420105ebdc61ef777e508a96c7946081decd98f8c30543d468b33#npm:5.7.0"],\
["mongodb", "virtual:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:6.0.0"],\
["mssql", null],\
["mysql2", "npm:3.3.3"],\
["oracledb", null],\

Binary file not shown.

View file

@ -33,3 +33,11 @@ NEW_RELIC_NO_CONFIG_FILE=true
NEW_RELIC_DISTRIBUTED_TRACING_ENABLED=false
NEW_RELIC_LOG_ENABLED=false
NEW_RELIC_LOG_LEVEL=info
# (Optional) Mongo Setup
SECONDARY_DB_ENABLED=false
MONGO_HOST=
MONGO_PORT=
MONGO_USERNAME=
MONGO_PASSWORD=
MONGO_DATABASE=

View file

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

View file

@ -1,15 +1,13 @@
import { ControllerContainer, ControllerContainerInterface, MapperInterface } from '@standardnotes/domain-core'
import { Container, interfaces } from 'inversify'
import { Repository } from 'typeorm'
import { MongoRepository, Repository } from 'typeorm'
import * as winston from 'winston'
import { Revision } from '../Domain/Revision/Revision'
import { RevisionMetadata } from '../Domain/Revision/RevisionMetadata'
import { RevisionRepositoryInterface } from '../Domain/Revision/RevisionRepositoryInterface'
import { TypeORMRevisionRepository } from '../Infra/TypeORM/TypeORMRevisionRepository'
import { TypeORMRevision } from '../Infra/TypeORM/TypeORMRevision'
import { RevisionMetadataPersistenceMapper } from '../Mapping/RevisionMetadataPersistenceMapper'
import { RevisionPersistenceMapper } from '../Mapping/RevisionPersistenceMapper'
import { TypeORMRevisionRepository } from '../Infra/TypeORM/SQLRevisionRepository'
import { TypeORMRevision } from '../Infra/TypeORM/SQLRevision'
import { AppDataSource } from './DataSource'
import { Env } from './Env'
import TYPES from './Types'
@ -21,8 +19,7 @@ import { DeleteRevision } from '../Domain/UseCase/DeleteRevision/DeleteRevision'
import { GetRequiredRoleToViewRevision } from '../Domain/UseCase/GetRequiredRoleToViewRevision/GetRequiredRoleToViewRevision'
import { GetRevision } from '../Domain/UseCase/GetRevision/GetRevision'
import { GetRevisionsMetada } from '../Domain/UseCase/GetRevisionsMetada/GetRevisionsMetada'
import { RevisionHttpMapper } from '../Mapping/RevisionHttpMapper'
import { RevisionMetadataHttpMapper } from '../Mapping/RevisionMetadataHttpMapper'
import { RevisionMetadataHttpMapper } from '../Mapping/Http/RevisionMetadataHttpMapper'
import { S3Client } from '@aws-sdk/client-s3'
import { SQSClient, SQSClientConfig } from '@aws-sdk/client-sqs'
import {
@ -44,9 +41,16 @@ import { RevisionsCopyRequestedEventHandler } from '../Domain/Handler/RevisionsC
import { CopyRevisions } from '../Domain/UseCase/CopyRevisions/CopyRevisions'
import { FSDumpRepository } from '../Infra/FS/FSDumpRepository'
import { S3DumpRepository } from '../Infra/S3/S3ItemDumpRepository'
import { RevisionItemStringMapper } from '../Mapping/RevisionItemStringMapper'
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 { SQLRevisionMetadataPersistenceMapper } from '../Mapping/Persistence/SQL/SQLRevisionMetadataPersistenceMapper'
import { SQLRevisionPersistenceMapper } from '../Mapping/Persistence/SQL/SQLRevisionPersistenceMapper'
import { MongoDBRevisionMetadataPersistenceMapper } from '../Mapping/Persistence/MongoDB/MongoDBRevisionMetadataPersistenceMapper'
import { MongoDBRevisionPersistenceMapper } from '../Mapping/Persistence/MongoDB/MongoDBRevisionPersistenceMapper'
import { RevisionHttpMapper } from '../Mapping/Http/RevisionHttpMapper'
export class ContainerConfigLoader {
async load(configuration?: {
@ -62,6 +66,7 @@ export class ContainerConfigLoader {
env.load()
const isConfiguredForHomeServer = env.get('MODE', true) === 'home-server'
const isSecondaryDatabaseEnabled = env.get('SECONDARY_DB_ENABLED', true) === 'true'
const container = new Container({
defaultScope: 'Singleton',
@ -101,11 +106,19 @@ export class ContainerConfigLoader {
// Map
container
.bind<MapperInterface<RevisionMetadata, TypeORMRevision>>(TYPES.Revisions_RevisionMetadataPersistenceMapper)
.toDynamicValue(() => new RevisionMetadataPersistenceMapper())
.bind<MapperInterface<RevisionMetadata, TypeORMRevision>>(TYPES.Revisions_SQLRevisionMetadataPersistenceMapper)
.toConstantValue(new SQLRevisionMetadataPersistenceMapper())
container
.bind<MapperInterface<Revision, TypeORMRevision>>(TYPES.Revisions_RevisionPersistenceMapper)
.toDynamicValue(() => new RevisionPersistenceMapper())
.bind<MapperInterface<Revision, TypeORMRevision>>(TYPES.Revisions_SQLRevisionPersistenceMapper)
.toConstantValue(new SQLRevisionPersistenceMapper())
container
.bind<MapperInterface<RevisionMetadata, MongoDBRevision>>(
TYPES.Revisions_MongoDBRevisionMetadataPersistenceMapper,
)
.toConstantValue(new MongoDBRevisionMetadataPersistenceMapper())
container
.bind<MapperInterface<Revision, MongoDBRevision>>(TYPES.Revisions_MongoDBRevisionPersistenceMapper)
.toConstantValue(new MongoDBRevisionPersistenceMapper())
// ORM
container
@ -115,14 +128,35 @@ export class ContainerConfigLoader {
// Repositories
container
.bind<RevisionRepositoryInterface>(TYPES.Revisions_RevisionRepository)
.toDynamicValue((context: interfaces.Context) => {
return new TypeORMRevisionRepository(
context.container.get(TYPES.Revisions_ORMRevisionRepository),
context.container.get(TYPES.Revisions_RevisionMetadataPersistenceMapper),
context.container.get(TYPES.Revisions_RevisionPersistenceMapper),
context.container.get(TYPES.Revisions_Logger),
.toConstantValue(
new TypeORMRevisionRepository(
container.get<Repository<TypeORMRevision>>(TYPES.Revisions_ORMRevisionRepository),
container.get<MapperInterface<RevisionMetadata, TypeORMRevision>>(
TYPES.Revisions_SQLRevisionMetadataPersistenceMapper,
),
container.get<MapperInterface<Revision, TypeORMRevision>>(TYPES.Revisions_SQLRevisionPersistenceMapper),
container.get<winston.Logger>(TYPES.Revisions_Logger),
),
)
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<TimerInterface>(TYPES.Revisions_Timer).toDynamicValue(() => new Timer())

View file

@ -1,25 +1,66 @@
import { DataSource, EntityTarget, LoggerOptions, ObjectLiteral, Repository } from 'typeorm'
import { DataSource, EntityTarget, LoggerOptions, MongoRepository, ObjectLiteral, Repository } from 'typeorm'
import { MysqlConnectionOptions } from 'typeorm/driver/mysql/MysqlConnectionOptions'
import { TypeORMRevision } from '../Infra/TypeORM/TypeORMRevision'
import { TypeORMRevision } from '../Infra/TypeORM/SQLRevision'
import { Env } from './Env'
import { SqliteConnectionOptions } from 'typeorm/driver/sqlite/SqliteConnectionOptions'
import { MongoDBRevision } from '../Infra/TypeORM/MongoDB/MongoDBRevision'
export class AppDataSource {
private dataSource: DataSource | undefined
private _dataSource: DataSource | undefined
private _secondaryDataSource: DataSource | undefined
constructor(private env: Env) {}
getRepository<Entity extends ObjectLiteral>(target: EntityTarget<Entity>): Repository<Entity> {
if (!this.dataSource) {
if (!this._dataSource) {
throw new Error('DataSource not initialized')
}
return this.dataSource.getRepository(target)
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.env.load()
if (this.env.get('SECONDARY_DB_ENABLED', true) !== 'true') {
return undefined
}
this._secondaryDataSource = new DataSource({
type: 'mongodb',
host: this.env.get('MONGO_HOST'),
authSource: 'admin',
port: parseInt(this.env.get('MONGO_PORT')),
username: this.env.get('MONGO_USERNAME'),
password: this.env.get('MONGO_PASSWORD', true),
database: this.env.get('MONGO_DATABASE'),
entities: [MongoDBRevision],
retryWrites: false,
synchronize: true,
})
return this._secondaryDataSource
}
get dataSource(): DataSource {
this.env.load()
const isConfiguredForMySQL = this.env.get('DB_TYPE') === 'mysql'
@ -74,7 +115,7 @@ export class AppDataSource {
database: inReplicaMode ? undefined : this.env.get('DB_DATABASE'),
}
this.dataSource = new DataSource(mySQLDataSourceOptions)
this._dataSource = new DataSource(mySQLDataSourceOptions)
} else {
const sqliteDataSourceOptions: SqliteConnectionOptions = {
...commonDataSourceOptions,
@ -84,9 +125,9 @@ export class AppDataSource {
busyErrorRetry: 2000,
}
this.dataSource = new DataSource(sqliteDataSourceOptions)
this._dataSource = new DataSource(sqliteDataSourceOptions)
}
await this.dataSource.initialize()
return this._dataSource
}
}

View file

@ -5,15 +5,20 @@ const TYPES = {
Revisions_S3: Symbol.for('Revisions_S3'),
Revisions_Env: Symbol.for('Revisions_Env'),
// Map
Revisions_RevisionMetadataPersistenceMapper: Symbol.for('Revisions_RevisionMetadataPersistenceMapper'),
Revisions_RevisionPersistenceMapper: Symbol.for('Revisions_RevisionPersistenceMapper'),
Revisions_SQLRevisionMetadataPersistenceMapper: Symbol.for('Revisions_SQLRevisionMetadataPersistenceMapper'),
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'),
// Mongo
Revisions_ORMMongoRevisionRepository: Symbol.for('Revisions_ORMMongoRevisionRepository'),
// Repositories
Revisions_RevisionRepository: Symbol.for('Revisions_RevisionRepository'),
Revisions_MongoDBRevisionRepository: Symbol.for('Revisions_MongoDBRevisionRepository'),
Revisions_DumpRepository: Symbol.for('Revisions_DumpRepository'),
// env vars
Revisions_AUTH_JWT_SECRET: Symbol.for('Revisions_AUTH_JWT_SECRET'),

View file

@ -0,0 +1,40 @@
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
}

View file

@ -0,0 +1,123 @@
import { MapperInterface, Uuid } from '@standardnotes/domain-core'
import { MongoRepository } 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 removeByUserUuid(userUuid: Uuid): Promise<void> {
await this.mongoRepository.deleteMany({ where: { userUuid: { $eq: userUuid.value } } })
}
async removeOneByUuid(revisionUuid: Uuid, userUuid: Uuid): Promise<void> {
await this.mongoRepository.deleteOne({
where: {
$and: [
{ _id: { $eq: BSON.UUID.createFromHexString(revisionUuid.value) } },
{ userUuid: { $eq: userUuid.value } },
],
},
})
}
async findOneByUuid(revisionUuid: Uuid, userUuid: Uuid): Promise<Revision | null> {
const 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): Promise<RevisionMetadata[]> {
const persistence = await this.mongoRepository.find({
select: ['_id', 'contentType', 'createdAt', 'updatedAt'],
where: {
$and: [{ itemUuid: { $eq: itemUuid.value } }, { userUuid: { $eq: userUuid.value } }],
},
order: {
createdAt: '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 save(revision: Revision): Promise<Revision> {
const persistence = this.revisionMapper.toProjection(revision)
const { _id, ...rest } = persistence
await this.mongoRepository.updateOne(
{ _id: { $eq: _id } },
{
$set: rest,
},
{ upsert: true },
)
return revision
}
}

View file

@ -5,7 +5,7 @@ import { Logger } from 'winston'
import { Revision } from '../../Domain/Revision/Revision'
import { RevisionMetadata } from '../../Domain/Revision/RevisionMetadata'
import { RevisionRepositoryInterface } from '../../Domain/Revision/RevisionRepositoryInterface'
import { TypeORMRevision } from './TypeORMRevision'
import { TypeORMRevision } from './SQLRevision'
export class TypeORMRevisionRepository implements RevisionRepositoryInterface {
constructor(

View file

@ -1,6 +1,6 @@
import { MapperInterface, Dates, Uuid, ContentType } from '@standardnotes/domain-core'
import { Revision } from '../Domain/Revision/Revision'
import { Revision } from '../../Domain/Revision/Revision'
export class RevisionItemStringMapper implements MapperInterface<Revision, string> {
toDomain(projection: string): Revision {

View file

@ -1,6 +1,6 @@
import { MapperInterface } from '@standardnotes/domain-core'
import { Revision } from '../Domain/Revision/Revision'
import { Revision } from '../../Domain/Revision/Revision'
export class RevisionHttpMapper
implements

View file

@ -1,6 +1,6 @@
import { MapperInterface, SyncUseCaseInterface } from '@standardnotes/domain-core'
import { RevisionMetadata } from '../Domain/Revision/RevisionMetadata'
import { RevisionMetadata } from '../../Domain/Revision/RevisionMetadata'
export class RevisionMetadataHttpMapper
implements

View file

@ -0,0 +1,41 @@
import { MapperInterface, Dates, UniqueEntityId, ContentType } 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()
const revisionMetadataOrError = RevisionMetadata.create(
{
contentType,
dates,
},
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

@ -0,0 +1,74 @@
import { MapperInterface, Dates, UniqueEntityId, Uuid, ContentType } from '@standardnotes/domain-core'
import { MongoDBRevision } from '../../../Infra/TypeORM/MongoDB/MongoDBRevision'
import { Revision } from '../../../Domain/Revision/Revision'
import { BSON } from 'mongodb'
export class MongoDBRevisionPersistenceMapper implements MapperInterface<Revision, MongoDBRevision> {
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()
}
const revisionOrError = Revision.create(
{
authHash: projection.authHash,
content: projection.content,
contentType,
creationDate: projection.creationDate,
encItemKey: projection.encItemKey,
itemsKeyId: projection.itemsKeyId,
itemUuid,
userUuid,
dates,
},
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 = domain.props.creationDate
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._id = BSON.UUID.createFromHexString(domain.id.toString())
return mongoDBRevision
}
}

View file

@ -1,9 +1,9 @@
import { MapperInterface, Dates, UniqueEntityId, ContentType } from '@standardnotes/domain-core'
import { RevisionMetadata } from '../Domain/Revision/RevisionMetadata'
import { TypeORMRevision } from '../Infra/TypeORM/TypeORMRevision'
import { RevisionMetadata } from '../../../Domain/Revision/RevisionMetadata'
import { TypeORMRevision } from '../../../Infra/TypeORM/SQLRevision'
export class RevisionMetadataPersistenceMapper implements MapperInterface<RevisionMetadata, TypeORMRevision> {
export class SQLRevisionMetadataPersistenceMapper implements MapperInterface<RevisionMetadata, TypeORMRevision> {
toDomain(projection: TypeORMRevision): RevisionMetadata {
const contentTypeOrError = ContentType.create(projection.contentType)
if (contentTypeOrError.isFailed()) {

View file

@ -1,8 +1,9 @@
import { MapperInterface, Dates, UniqueEntityId, Uuid, ContentType } from '@standardnotes/domain-core'
import { Revision } from '../Domain/Revision/Revision'
import { TypeORMRevision } from '../Infra/TypeORM/TypeORMRevision'
export class RevisionPersistenceMapper implements MapperInterface<Revision, TypeORMRevision> {
import { Revision } from '../../../Domain/Revision/Revision'
import { TypeORMRevision } from '../../../Infra/TypeORM/SQLRevision'
export class SQLRevisionPersistenceMapper implements MapperInterface<Revision, TypeORMRevision> {
toDomain(projection: TypeORMRevision): Revision {
const contentTypeOrError = ContentType.create(projection.contentType)
if (contentTypeOrError.isFailed()) {

View file

@ -49,7 +49,7 @@
"inversify": "^6.0.1",
"inversify-express-utils": "^6.4.3",
"jsonwebtoken": "^9.0.0",
"mongodb": "^5.7.0",
"mongodb": "^6.0.0",
"mysql2": "^3.0.1",
"nodemon": "^2.0.19",
"prettyjson": "^1.2.5",

View file

@ -2666,6 +2666,15 @@ __metadata:
languageName: node
linkType: hard
"@mongodb-js/saslprep@npm:^1.1.0":
version: 1.1.0
resolution: "@mongodb-js/saslprep@npm:1.1.0"
dependencies:
sparse-bitfield: "npm:^3.0.3"
checksum: 2cf6d124d48d517716eb3a18a09de27bd9b190863692234494954bc7d80cf69e65f6c3165f7d4bbf399c3e70a7e195ac8fb93fbc720f01250d7d987f681d8708
languageName: node
linkType: hard
"@mrleebo/prisma-ast@npm:^0.5.2":
version: 0.5.2
resolution: "@mrleebo/prisma-ast@npm:0.5.2"
@ -3901,6 +3910,7 @@ __metadata:
inversify: "npm:^6.0.1"
inversify-express-utils: "npm:^6.4.3"
jest: "npm:^29.5.0"
mongodb: "npm:^6.0.0"
mysql2: "npm:^3.0.1"
newrelic: "npm:^10.1.2"
npm-check-updates: "npm:^16.0.1"
@ -4101,7 +4111,7 @@ __metadata:
inversify-express-utils: "npm:^6.4.3"
jest: "npm:^29.5.0"
jsonwebtoken: "npm:^9.0.0"
mongodb: "npm:^5.7.0"
mongodb: "npm:^6.0.0"
mysql2: "npm:^3.0.1"
newrelic: "npm:^10.1.2"
nodemon: "npm:^2.0.19"
@ -5646,10 +5656,10 @@ __metadata:
languageName: node
linkType: hard
"bson@npm:^5.4.0":
version: 5.4.0
resolution: "bson@npm:5.4.0"
checksum: 2c913a45c05bf8f1f8120c05e0e4ac9a864928853193c4794634b0c941a7d64397b9cbfe9fa9aba7249eb89d075911c5953efbb1be6b4e0848a0760660dca628
"bson@npm:^6.0.0":
version: 6.0.0
resolution: "bson@npm:6.0.0"
checksum: 7290998ee8eb7d105f9168e5940a6a04743001fe39674d897d802da31c8b326a2934b9e782ba1650906264513fdd27777a802451e69193ba10d6163032214d0a
languageName: node
linkType: hard
@ -10243,35 +10253,37 @@ __metadata:
languageName: node
linkType: hard
"mongodb@npm:^5.7.0":
version: 5.7.0
resolution: "mongodb@npm:5.7.0"
"mongodb@npm:^6.0.0":
version: 6.0.0
resolution: "mongodb@npm:6.0.0"
dependencies:
bson: "npm:^5.4.0"
"@mongodb-js/saslprep": "npm:^1.1.0"
bson: "npm:^6.0.0"
mongodb-connection-string-url: "npm:^2.6.0"
saslprep: "npm:^1.0.3"
socks: "npm:^2.7.1"
peerDependencies:
"@aws-sdk/credential-providers": ^3.201.0
"@aws-sdk/credential-providers": ^3.188.0
"@mongodb-js/zstd": ^1.1.0
gcp-metadata: ^5.2.0
kerberos: ^2.0.1
mongodb-client-encryption: ">=2.3.0 <3"
mongodb-client-encryption: ">=6.0.0 <7"
snappy: ^7.2.2
dependenciesMeta:
saslprep:
optional: true
socks: ^2.7.1
peerDependenciesMeta:
"@aws-sdk/credential-providers":
optional: true
"@mongodb-js/zstd":
optional: true
gcp-metadata:
optional: true
kerberos:
optional: true
mongodb-client-encryption:
optional: true
snappy:
optional: true
checksum: 23a291ffe7e990f25b527f2d4bd1a848b866211596cc30a36cbe86d773f3bcd74d688aa0a7158b35e24271264d15c35832fcced639b81df4cab7303cdd8442c0
socks:
optional: true
checksum: daec6dc9dc937a9a38c1b1605ef93088928821615499d8f297cf41fcea774682ab1e6a645c43fb34b584b3a5077c74e650d6d9fbf474010a20f5f93279f492d5
languageName: node
linkType: hard
@ -12077,15 +12089,6 @@ __metadata:
languageName: node
linkType: hard
"saslprep@npm:^1.0.3":
version: 1.0.3
resolution: "saslprep@npm:1.0.3"
dependencies:
sparse-bitfield: "npm:^3.0.3"
checksum: 23ebcda091621541fb9db9635ff36b9be81dc35a79a2adbf2a8309e162bcc9607513488aa3a9da757f11e856592ab8a727ac45c98c6084ff93d627509a882b84
languageName: node
linkType: hard
"schema-utils@npm:^3.1.1, schema-utils@npm:^3.1.2":
version: 3.1.2
resolution: "schema-utils@npm:3.1.2"
@ -12375,7 +12378,7 @@ __metadata:
languageName: node
linkType: hard
"socks@npm:^2.6.2, socks@npm:^2.7.1":
"socks@npm:^2.6.2":
version: 2.7.1
resolution: "socks@npm:2.7.1"
dependencies: